implement basic structure of APK signature block

This commit is contained in:
REAndroid 2023-04-10 17:30:05 +02:00
parent 2b6da361dc
commit 32a82f9527
26 changed files with 1058 additions and 334 deletions

View File

@ -15,13 +15,10 @@
*/ */
package com.reandroid.archive2; package com.reandroid.archive2;
import com.reandroid.archive2.block.CentralEntryHeader; import com.reandroid.archive2.block.*;
import com.reandroid.archive2.block.EndRecord;
import com.reandroid.archive2.block.LocalFileHeader;
import com.reandroid.archive2.io.ArchiveFile; import com.reandroid.archive2.io.ArchiveFile;
import com.reandroid.archive2.io.ArchiveUtil; import com.reandroid.archive2.io.ArchiveUtil;
import com.reandroid.archive2.io.ZipSource; import com.reandroid.archive2.io.ZipSource;
import com.reandroid.archive2.model.ApkSigBlock;
import com.reandroid.archive2.model.LocalFileDirectory; import com.reandroid.archive2.model.LocalFileDirectory;
import java.io.File; import java.io.File;
@ -38,7 +35,7 @@ public class Archive {
private final ZipSource zipSource; private final ZipSource zipSource;
private final List<ArchiveEntry> entryList; private final List<ArchiveEntry> entryList;
private final EndRecord endRecord; private final EndRecord endRecord;
private final ApkSigBlock apkSigBlock; private final ApkSignatureBlock apkSignatureBlock;
public Archive(ZipSource zipSource) throws IOException { public Archive(ZipSource zipSource) throws IOException {
this.zipSource = zipSource; this.zipSource = zipSource;
LocalFileDirectory lfd = new LocalFileDirectory(); LocalFileDirectory lfd = new LocalFileDirectory();
@ -54,7 +51,7 @@ public class Archive {
} }
this.entryList = entryList; this.entryList = entryList;
this.endRecord = lfd.getCentralFileDirectory().getEndRecord(); this.endRecord = lfd.getCentralFileDirectory().getEndRecord();
this.apkSigBlock = lfd.getApkSigBlock(); this.apkSignatureBlock = lfd.getApkSigBlock();
} }
public Archive(File file) throws IOException { public Archive(File file) throws IOException {
this(new ArchiveFile(file)); this(new ArchiveFile(file));
@ -74,8 +71,8 @@ public class Archive {
return entryList; return entryList;
} }
public ApkSigBlock getApkSigBlock() { public ApkSignatureBlock getApkSigBlock() {
return apkSigBlock; return apkSignatureBlock;
} }
public EndRecord getEndRecord() { public EndRecord getEndRecord() {
return endRecord; return endRecord;
@ -104,4 +101,12 @@ public class Archive {
String name = archiveEntry.getName().replace('/', File.separatorChar); String name = archiveEntry.getName().replace('/', File.separatorChar);
return new File(dir, name); return new File(dir, name);
} }
// for test
public void writeSignatureData(File dir) throws IOException{
ApkSignatureBlock apkSignatureBlock = getApkSigBlock();
if(apkSignatureBlock == null){
throw new IOException("Does not have signature block");
}
apkSignatureBlock.writeSignatureData(dir);
}
} }

View File

@ -1,202 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.decoder.ValueDecoder;
import java.io.*;
import java.util.Objects;
public class ApkSignature extends ZipBlock{
public ApkSignature() {
super(MIN_SIZE);
}
public boolean isValid(){
return getSigSize() > MIN_SIZE && getId() != null;
}
@Override
public int readBytes(InputStream inputStream) throws IOException {
setBytesLength(8, false);
byte[] bytes = getBytesInternal();
int read = inputStream.read(bytes, 0, bytes.length);
if(read != bytes.length){
return read;
}
int offset = bytes.length;
int length = (int) getSigSize();
setSigSize(length);
bytes = getBytesInternal();
int dataRead = inputStream.read(bytes, offset, length);
if(dataRead != length){
setSigSize(0);
}
return read + dataRead;
}
public long getSigSize(){
return getLong(OFFSET_size);
}
public void setSigSize(long size){
int length = (int) (OFFSET_id + size);
setBytesLength(length, false);
putLong(OFFSET_size, size);
}
public int getIdValue(){
return getInteger(OFFSET_id);
}
public Id getId(){
return Id.valueOf(getIdValue());
}
public void setId(int id){
putInteger(OFFSET_id, id);
}
public void setId(Id signatureId){
setId(signatureId == null? 0 : signatureId.getId());
}
public byte[] getData(){
return getBytes(OFFSET_data, (int) getSigSize() - 4, true);
}
public void setData(byte[] data){
if(data == null){
data = new byte[0];
}
setData(data, 0, data.length);
}
public void setData(byte[] data, int offset, int length){
setSigSize(length);
putBytes(data, offset, OFFSET_data, data.length);
}
public void writeData(OutputStream outputStream) throws IOException{
byte[] bytes = getBytesInternal();
outputStream.write(bytes, OFFSET_data, (int) (getSigSize() - 4));
}
public void writeData(File file) throws IOException{
File dir = file.getParentFile();
if(dir != null && !dir.exists()){
dir.mkdirs();
}
FileOutputStream outputStream = new FileOutputStream(file);
writeData(outputStream);
outputStream.close();
}
public File writeToDir(File dir) throws IOException{
File file = new File(dir, getId().toFileName());
writeData(file);
return file;
}
@Override
public String toString() {
return getId() + ", size=" + getSigSize();
}
public static class Id {
private final String name;
private final int id;
private Id(String name, int id) {
this.name = name;
this.id = id;
}
public String name() {
return name;
}
public int getId() {
return id;
}
public String toFileName(){
if(this.name != null){
return name + FILE_EXTENSION;
}
return String.format("0x%08x", id) + FILE_EXTENSION;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Id that = (Id) obj;
return id == that.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString(){
String name = this.name;
if(name != null){
return name;
}
return "UNKNOWN("+String.format("0x%08x", id)+")";
}
public static Id valueOf(String name){
if(name == null){
return null;
}
String ext = FILE_EXTENSION;
if(name.endsWith(ext)){
name = name.substring(0, name.length()-ext.length());
}
for(Id signatureId:VALUES){
if(name.equalsIgnoreCase(signatureId.name())){
return signatureId;
}
}
if(ValueDecoder.isHex(name)){
return new Id(null, ValueDecoder.parseHex(name));
}
return null;
}
public static Id valueOf(int id){
if(id == 0){
return null;
}
for(Id signatureId:VALUES){
if(id == signatureId.getId()){
return signatureId;
}
}
return new Id(null, id);
}
public static Id[] values() {
return VALUES.clone();
}
public static final Id V2 = new Id("V2", 0x7109871A);
public static final Id V3 = new Id("V3",0xF05368C0);
public static final Id V31 = new Id("V31",0x1B93AD61);
public static final Id STAMP_V1 = new Id("STAMP_V1", 0x2B09189E);
public static final Id STAMP_V2 = new Id("STAMP_V2", 0x6DFF800D);
public static final Id PADDING = new Id("PADDING", 0x42726577);
private static final Id[] VALUES = new Id[]{
V2, V3, V31, STAMP_V1, STAMP_V2, PADDING
};
private static final String FILE_EXTENSION = ".bin";
}
public static final int MIN_SIZE = 12;
private static final int OFFSET_size = 0;
private static final int OFFSET_id = 8;
private static final int OFFSET_data = 12;
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class ApkSignatureBlock extends LengthPrefixedList<SignatureInfo> {
public ApkSignatureBlock(SignatureFooter signatureFooter){
super(true);
setBottomBlock(signatureFooter);
}
public List<SignatureInfo> getSignatures(){
return super.getElements();
}
public int countSignatures(){
return super.getElementsCount();
}
public SignatureInfo getSignature(SignatureId signatureId){
for(SignatureInfo signatureInfo:getSignatures()){
if(signatureInfo.getId().equals(signatureId)){
return signatureInfo;
}
}
return null;
}
public SignatureFooter getSignatureFooter(){
return (SignatureFooter) getBottomBlock();
}
@Override
public SignatureInfo newInstance() {
return new SignatureInfo();
}
@Override
protected void onRefreshed(){
SignatureFooter footer = getSignatureFooter();
footer.updateMagic();
super.onRefreshed();
footer.setSignatureSize(getDataSize());
}
// for test
public void writeSignatureData(File dir) throws IOException{
for(SignatureInfo signatureInfo:getElements()){
signatureInfo.writeToDir(dir);
}
}
private static final long CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
private static final byte[] APK_SIGNING_BLOCK_MAGIC = new byte[] {
0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
};
public static final int VERSION_SOURCE_STAMP = 0;
public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
public static final int VERSION_APK_SIGNATURE_SCHEME_V31 = 31;
public static final int VERSION_APK_SIGNATURE_SCHEME_V4 = 4;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.container.BlockList;
import com.reandroid.arsc.io.BlockReader;
import java.io.IOException;
// General purpose block to consume the remaining bytes of BlockReader
public class BottomBlock extends BlockList<LengthPrefixedBytes> {
public BottomBlock(){
super();
}
@Override
public void onReadBytes(BlockReader reader) throws IOException {
while (reader.isAvailable()){
LengthPrefixedBytes prefixedBytes = new LengthPrefixedBytes(false);
prefixedBytes.readBytes(reader);
this.add(prefixedBytes);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import java.io.ByteArrayInputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class CertificateBlock extends LengthPrefixedBytes{
public CertificateBlock() {
super(false);
}
public X509Certificate getCertificate(){
return generateCertificate(getByteArray().toArray());
}
public static X509Certificate generateCertificate(byte[] encodedForm){
CertificateFactory factory = getCertFactory();
if(factory == null){
return null;
}
try{
// TODO: cert bytes could be in DER format ?
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedForm));
}catch (CertificateException ignored){
return null;
}
}
private static CertificateFactory getCertFactory() {
if (sCertFactory == null) {
try {
sCertFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException ignored) {
}
}
return sCertFactory;
}
private static CertificateFactory sCertFactory = null;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
public class CertificateBlockList extends LengthPrefixedList<CertificateBlock>{
public CertificateBlockList() {
super(false);
}
@Override
public CertificateBlock newInstance() {
return new CertificateBlock();
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.base.Block;
import com.reandroid.arsc.container.ExpandableBlockContainer;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.IntegerItem;
import com.reandroid.arsc.item.LongItem;
import java.io.IOException;
public class LengthPrefixedBlock extends ExpandableBlockContainer{
private final Block numberBlock;
public LengthPrefixedBlock(int childesCount, boolean is_long) {
super(1 + childesCount);
Block numberBlock;
if(is_long){
numberBlock = new LongItem();
}else {
numberBlock = new IntegerItem();
}
this.numberBlock = numberBlock;
addChild(this.numberBlock);
}
public long getDataSize(){
Block numberBlock = this.numberBlock;
if(numberBlock instanceof LongItem){
return ((LongItem)numberBlock).get();
}
return ((IntegerItem)numberBlock).get();
}
public void setDataSize(long dataSize){
Block numberBlock = this.numberBlock;
if(numberBlock instanceof LongItem){
((LongItem)numberBlock).set(dataSize);
}else {
((IntegerItem)numberBlock).set((int) dataSize);
}
}
@Override
public void onReadBytes(BlockReader reader) throws IOException {
if(!reader.isAvailable()){
return;
}
Block numberBlock = this.numberBlock;
numberBlock.readBytes(reader);
int dataSize = (int) getDataSize();
if(dataSize <= 0){
onSizeLoaded(0);
return;
}
onSizeLoaded(dataSize);
BlockReader chunkReader = reader.create(dataSize);
Block[] childes = getChildes();
for(int i=0;i<childes.length;i++){
Block child = childes[i];
if(child == numberBlock){
continue;
}
child.readBytes(chunkReader);
}
reader.offset(dataSize);
}
protected void onSizeLoaded(int dataSize){
}
@Override
protected void onRefreshed(){
int size = countBytes() - numberBlock.countBytes();
setDataSize(size);
}
@Override
public String toString(){
return "size=" + numberBlock;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.item.ByteArray;
public class LengthPrefixedBytes extends LengthPrefixedBlock{
private final ByteArray byteArray;
public LengthPrefixedBytes(boolean is_long) {
super(1, is_long);
this.byteArray = new ByteArray();
addChild(this.byteArray);
}
public ByteArray getByteArray() {
return byteArray;
}
@Override
protected void onSizeLoaded(int dataSize){
this.byteArray.setSize(dataSize);
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.base.Block;
import com.reandroid.arsc.base.BlockCreator;
import com.reandroid.arsc.container.BlockList;
import com.reandroid.arsc.container.FixedBlockContainer;
import com.reandroid.arsc.container.SingleBlockContainer;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.IntegerItem;
import com.reandroid.arsc.item.LongItem;
import java.io.IOException;
import java.util.List;
public abstract class LengthPrefixedList<T extends Block> extends FixedBlockContainer
implements BlockCreator<T> {
private final Block numberBlock;
private final BlockList<T> elements;
private final SingleBlockContainer<Block> bottomContainer;
public LengthPrefixedList(boolean is_long){
super(3);
Block numberBlock;
if(is_long){
numberBlock = new LongItem();
}else {
numberBlock = new IntegerItem();
}
this.numberBlock = numberBlock;
this.elements = new BlockList<>();
this.bottomContainer = new SingleBlockContainer<>();
addChild(0, this.numberBlock);
addChild(1, this.elements);
addChild(2, this.bottomContainer);
}
public long getDataSize(){
Block numberBlock = this.numberBlock;
if(numberBlock instanceof LongItem){
return ((LongItem)numberBlock).get();
}
return ((IntegerItem)numberBlock).get();
}
public void setDataSize(long dataSize){
Block numberBlock = this.numberBlock;
if(numberBlock instanceof LongItem){
((LongItem)numberBlock).set(dataSize);
}else {
((IntegerItem)numberBlock).set((int) dataSize);
}
}
public int getElementsCount(){
return getElements().size();
}
public List<T> getElements() {
return elements.getChildes();
}
public T add(T element){
this.elements.add(element);
return element;
}
public boolean remove(T element){
return this.elements.remove(element);
}
@Override
public void onReadBytes(BlockReader reader) throws IOException{
if(!reader.isAvailable()){
return;
}
numberBlock.readBytes(reader);
int totalSize = (int) getDataSize();
if(totalSize <= 0){
return;
}
BlockReader chunkReader = reader.create(totalSize);
readElements(chunkReader);
if(chunkReader.isAvailable()){
String junk = "";
}
bottomContainer.readBytes(chunkReader);
reader.offset(totalSize);
}
private void readElements(BlockReader reader) throws IOException{
int preserve = bottomContainer.countBytes() + 4;
while (reader.available() > preserve){
int position = reader.getPosition();
T element = newInstance();
element = add(element);
element.readBytes(reader);
if(position == reader.getPosition()){
break;
}
}
}
@Override
protected void onRefreshed(){
int size = countBytes() - numberBlock.countBytes();
setDataSize(size);
}
public Block getBottomBlock(){
return bottomContainer.getItem();
}
public void setBottomBlock(Block block){
bottomContainer.setItem(block);
}
@Override
public String toString(){
return "size=" + numberBlock + ", count=" + getElementsCount();
}
}

View File

@ -31,25 +31,37 @@ public class SignatureFooter extends ZipBlock{
byte[] bytes = getBytesInternal(); byte[] bytes = getBytesInternal();
return inputStream.read(bytes, 0, bytes.length); return inputStream.read(bytes, 0, bytes.length);
} }
public long getSigBlockSizeInFooter(){ public long getSignatureSize(){
return getLong(OFFSET_size); return getLong(OFFSET_size);
} }
public void setSigBlockSizeInFooter(long size){ public void setSignatureSize(long size){
int minLength = MIN_SIZE;
if(countBytes() < minLength){
setBytesLength(minLength, false);
}
putLong(OFFSET_size, size); putLong(OFFSET_size, size);
} }
public byte[] getMagic() { public byte[] getMagic() {
return getBytes(OFFSET_magic, APK_SIG_BLOCK_MAGIC.length, false); return getBytes(OFFSET_magic, APK_SIG_BLOCK_MAGIC.length, false);
} }
public void setMagic(byte[] magic){ public void setMagic(byte[] magic){
if(magic == null){
magic = new byte[0];
}
int length = OFFSET_magic + magic.length;
setBytesLength(length, false);
putBytes(magic, 0, OFFSET_magic, magic.length); putBytes(magic, 0, OFFSET_magic, magic.length);
} }
public boolean isValid(){ public boolean isValid(){
return getSigBlockSizeInFooter() > MIN_SIZE return getSignatureSize() > MIN_SIZE
&& ByteArray.equals(APK_SIG_BLOCK_MAGIC, getMagic()); && ByteArray.equals(APK_SIG_BLOCK_MAGIC, getMagic());
} }
public void updateMagic(){
setMagic(APK_SIG_BLOCK_MAGIC);
}
@Override @Override
public String toString(){ public String toString(){
return getSigBlockSizeInFooter() + " ["+new String(getMagic())+"]"; return getSignatureSize() + " ["+new String(getMagic())+"]";
} }
public static final int MIN_SIZE = 24; public static final int MIN_SIZE = 24;

View File

@ -0,0 +1,109 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.decoder.ValueDecoder;
import java.util.Objects;
public class SignatureId {
private final String name;
private final int id;
private SignatureId(String name, int id) {
this.name = name;
this.id = id;
}
public String name() {
return name;
}
public int getId() {
return id;
}
public String toFileName() {
if (this.name != null) {
return name + FILE_EXTENSION;
}
return String.format("0x%08x", id) + FILE_EXTENSION;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SignatureId that = (SignatureId) obj;
return id == that.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
String name = this.name;
if (name != null) {
return name;
}
return "UNKNOWN(" + String.format("0x%08x", id) + ")";
}
public static SignatureId valueOf(String name) {
if (name == null) {
return null;
}
String ext = FILE_EXTENSION;
if (name.endsWith(ext)) {
name = name.substring(0, name.length() - ext.length());
}
for (SignatureId signatureId : VALUES) {
if (name.equalsIgnoreCase(signatureId.name())) {
return signatureId;
}
}
if (ValueDecoder.isHex(name)) {
return new SignatureId(null, ValueDecoder.parseHex(name));
}
return null;
}
public static SignatureId valueOf(int id) {
for (SignatureId signatureId : VALUES) {
if (id == signatureId.getId()) {
return signatureId;
}
}
return new SignatureId(null, id);
}
public static SignatureId[] values() {
return VALUES.clone();
}
public static final SignatureId V2 = new SignatureId("V2", 0x7109871A);
public static final SignatureId V3 = new SignatureId("V3", 0xF05368C0);
public static final SignatureId V31 = new SignatureId("V31", 0x1B93AD61);
public static final SignatureId STAMP_V1 = new SignatureId("STAMP_V1", 0x2B09189E);
public static final SignatureId STAMP_V2 = new SignatureId("STAMP_V2", 0x6DFF800D);
public static final SignatureId PADDING = new SignatureId("PADDING", 0x42726577);
public static final SignatureId NULL = new SignatureId("NULL", 0x0);
private static final SignatureId[] VALUES = new SignatureId[]{
V2, V3, V31, STAMP_V1, STAMP_V2, PADDING, NULL
};
private static final String FILE_EXTENSION = ".bin";
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.archive2.block.pad.SchemePadding;
import com.reandroid.archive2.block.stamp.SchemeStampV1;
import com.reandroid.archive2.block.stamp.SchemeStampV2;
import com.reandroid.archive2.block.v2.SchemeV2;
import com.reandroid.archive2.block.v3.SchemeV3;
import com.reandroid.archive2.block.v3.SchemeV31;
import com.reandroid.arsc.base.Block;
import com.reandroid.arsc.container.SingleBlockContainer;
import com.reandroid.arsc.io.BlockLoad;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.IntegerItem;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class SignatureInfo extends LengthPrefixedBlock implements BlockLoad {
private final IntegerItem idItem;
private final SingleBlockContainer<SignatureScheme> schemeContainer;
public SignatureInfo() {
super(2, true);
this.idItem = new IntegerItem();
this.schemeContainer = new SingleBlockContainer<>();
addChild(this.idItem);
addChild(this.schemeContainer);
this.idItem.setBlockLoad(this);
}
public int getIdValue(){
return idItem.get();
}
public SignatureId getId(){
return SignatureId.valueOf(getIdValue());
}
public void setId(int id){
idItem.set(id);
}
public void setId(SignatureId signatureId){
setId(signatureId == null? 0 : signatureId.getId());
}
public SignatureScheme getSignatureScheme(){
return schemeContainer.getItem();
}
public void setSignatureScheme(SignatureScheme signatureScheme){
schemeContainer.setItem(signatureScheme);
}
@Override
public void onBlockLoaded(BlockReader reader, Block sender) throws IOException {
if(sender == this.idItem){
onIdLoaded();
}
}
private void onIdLoaded(){
SignatureId signatureId = getId();
SignatureScheme scheme;
if(signatureId == SignatureId.V2){
scheme = new SchemeV2();
}else if(signatureId == SignatureId.V3){
scheme = new SchemeV3();
}else if(signatureId == SignatureId.V31){
scheme = new SchemeV31();
}else if(signatureId == SignatureId.STAMP_V1){
scheme = new SchemeStampV1();
}else if(signatureId == SignatureId.STAMP_V2){
scheme = new SchemeStampV2();
}else if(signatureId == SignatureId.PADDING){
scheme = new SchemePadding();
}else {
scheme = new UnknownScheme(signatureId);
}
schemeContainer.setItem(scheme);
}
// for test
public void writeData(File file) throws IOException{
File dir = file.getParentFile();
if(dir != null && !dir.exists()){
dir.mkdirs();
}
FileOutputStream outputStream = new FileOutputStream(file);
writeBytes(outputStream);
outputStream.close();
}
// for test
public File writeToDir(File dir) throws IOException{
File file = new File(dir, getId().toFileName());
writeData(file);
return file;
}
@Override
public String toString() {
return getId() + ", scheme: " + getSignatureScheme();
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.ByteArray;
import java.io.IOException;
// General purpose block to consume the specified bytes of BlockReader
// TODO: No class should override this, implement all like SchemeV2
public class UnknownScheme extends SignatureScheme{
private final ByteArray byteArray;
public UnknownScheme(SignatureId signatureId) {
super(1, signatureId);
this.byteArray = new ByteArray();
addChild(byteArray);
}
@Override
public void onReadBytes(BlockReader reader) throws IOException {
SignatureInfo signatureInfo = getSignatureInfo();
int size = (int) signatureInfo.getDataSize() - 4;
byteArray.setSize(size);
super.onReadBytes(reader);
}
}

View File

@ -98,29 +98,4 @@ public abstract class ZipBlock extends BlockItem {
putShort(getBytesInternal(), offset, (short) value); putShort(getBytesInternal(), offset, (short) value);
} }
public static long getLong(byte[] bytes, int offset){
if((offset + 8)>bytes.length){
return 0;
}
long result = 0;
int index = offset + 7;
while (index>=offset){
result = result << 8;
result |= (bytes[index] & 0xff);
index --;
}
return result;
}
public static void putLong(byte[] bytes, int offset, long value){
if((offset + 8) > bytes.length){
return;
}
int index = offset;
offset = index + 8;
while (index<offset){
bytes[index] = (byte) (value & 0xff);
value = value >>> 8;
index++;
}
}
} }

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.pad;
import com.reandroid.archive2.block.SignatureId;
import com.reandroid.archive2.block.SignatureInfo;
import com.reandroid.archive2.block.SignatureScheme;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.ByteArray;
import java.io.IOException;
public class SchemePadding extends SignatureScheme {
private final ByteArray byteArray;
public SchemePadding() {
super(1, SignatureId.PADDING);
this.byteArray = new ByteArray();
addChild(this.byteArray);
}
@Override
public void onReadBytes(BlockReader reader) throws IOException{
SignatureInfo signatureInfo = getSignatureInfo();
int size = (int) signatureInfo.getDataSize() - 4;
byteArray.setSize(size);
super.onReadBytes(reader);
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.stamp;
import com.reandroid.archive2.block.SignatureId;
import com.reandroid.archive2.block.UnknownScheme;
// TODO: implement structure
public class SchemeStampV1 extends UnknownScheme {
public SchemeStampV1() {
super(SignatureId.STAMP_V1);
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.stamp;
import com.reandroid.archive2.block.SignatureId;
import com.reandroid.archive2.block.UnknownScheme;
// TODO: implement structure
public class SchemeStampV2 extends UnknownScheme {
public SchemeStampV2() {
super(SignatureId.STAMP_V2);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v2;
import com.reandroid.archive2.block.SignatureId;
import com.reandroid.archive2.block.SignatureScheme;
public class SchemeV2 extends SignatureScheme {
private final V2SignedDataList signedDataList;
public SchemeV2(){
super(1, SignatureId.V2);
this.signedDataList = new V2SignedDataList();
addChild(this.signedDataList);
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v2;
import com.reandroid.archive2.block.LengthPrefixedBytes;
public class V2Signature extends LengthPrefixedBytes {
public V2Signature() {
super(false);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v2;
import com.reandroid.archive2.block.BottomBlock;
import com.reandroid.archive2.block.LengthPrefixedBlock;
import com.reandroid.arsc.io.BlockReader;
import java.io.IOException;
public class V2SignedData extends LengthPrefixedBlock {
private final V2Signer signer;
private final BottomBlock unknown;
public V2SignedData() {
super(2, false);
this.signer = new V2Signer();
this.unknown = new BottomBlock();
addChild(this.signer);
addChild(this.unknown);
}
public void onReadBytes(BlockReader reader) throws IOException {
super.onReadBytes(reader);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v2;
import com.reandroid.archive2.block.LengthPrefixedList;
public class V2SignedDataList extends LengthPrefixedList<V2SignedData> {
public V2SignedDataList() {
super(false);
}
@Override
public V2SignedData newInstance() {
return new V2SignedData();
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v2;
import com.reandroid.archive2.block.BottomBlock;
import com.reandroid.archive2.block.CertificateBlock;
import com.reandroid.archive2.block.CertificateBlockList;
import com.reandroid.archive2.block.LengthPrefixedBlock;
import java.util.List;
public class V2Signer extends LengthPrefixedBlock {
private final V2Signature v2Signature;
private final CertificateBlockList certificateBlockList;
private final BottomBlock unknown;
public V2Signer() {
super(3, false);
this.v2Signature = new V2Signature();
this.certificateBlockList = new CertificateBlockList();
this.unknown = new BottomBlock();
addChild(this.v2Signature);
addChild(this.certificateBlockList);
addChild(this.unknown);
}
public List<CertificateBlock> getCertificateBlockList(){
return certificateBlockList.getElements();
}
public void addCertificateBlock(CertificateBlock certificateBlock){
certificateBlockList.add(certificateBlock);
}
public void removeCertificateBlock(CertificateBlock certificateBlock){
certificateBlockList.remove(certificateBlock);
}
@Override
public String toString(){
return super.toString()+", sig="+v2Signature+", certs="+certificateBlockList;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v3;
import com.reandroid.archive2.block.SignatureId;
import com.reandroid.archive2.block.UnknownScheme;
// TODO: implement structure
public class SchemeV3 extends UnknownScheme {
public SchemeV3() {
super(SignatureId.V3);
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.block.v3;
import com.reandroid.archive2.block.SignatureId;
import com.reandroid.archive2.block.UnknownScheme;
// TODO: implement structure
public class SchemeV31 extends UnknownScheme {
public SchemeV31() {
super(SignatureId.V31);
}
}

View File

@ -1,86 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.archive2.model;
import com.reandroid.archive2.block.ApkSignature;
import com.reandroid.archive2.block.LongBlock;
import com.reandroid.archive2.block.SignatureFooter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class ApkSigBlock {
private final SignatureFooter signatureFooter;
private final LongBlock mSize;
private final List<ApkSignature> mSignatures;
public ApkSigBlock(SignatureFooter signatureFooter){
this.signatureFooter = signatureFooter;
this.mSize = new LongBlock();
this.mSignatures = new ArrayList<>();
}
public LongBlock getTotalSize(){
return mSize;
}
public List<ApkSignature> getSignatures() {
return mSignatures;
}
public void readBytes(InputStream inputStream) throws IOException {
mSize.readBytes(inputStream);
ApkSignature apkSignature;
while ((apkSignature = readNext(inputStream))!=null){
mSignatures.add(apkSignature);
}
}
private ApkSignature readNext(InputStream inputStream) throws IOException {
ApkSignature apkSignature = new ApkSignature();
apkSignature.readBytes(inputStream);
if(apkSignature.isValid()){
return apkSignature;
}
return null;
}
public void writeSigData(File dir) throws IOException{
for(ApkSignature apkSignature:mSignatures){
apkSignature.writeToDir(dir);
}
}
@Override
public String toString(){
return "size=" + getTotalSize() + ", count=" + getSignatures().size();
}
private static final long CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
private static final byte[] APK_SIGNING_BLOCK_MAGIC = new byte[] {
0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
};
public static final int VERITY_PADDING_BLOCK_ID = 0x42726577;
public static final int VERSION_SOURCE_STAMP = 0;
public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
public static final int VERSION_APK_SIGNATURE_SCHEME_V31 = 31;
public static final int VERSION_APK_SIGNATURE_SCHEME_V4 = 4;
}

View File

@ -16,7 +16,9 @@
package com.reandroid.archive2.model; package com.reandroid.archive2.model;
import com.reandroid.archive2.block.*; import com.reandroid.archive2.block.*;
import com.reandroid.archive2.block.ApkSignatureBlock;
import com.reandroid.archive2.io.ZipSource; import com.reandroid.archive2.io.ZipSource;
import com.reandroid.arsc.io.BlockReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -26,7 +28,7 @@ import java.util.List;
public class LocalFileDirectory { public class LocalFileDirectory {
private final CentralFileDirectory centralFileDirectory; private final CentralFileDirectory centralFileDirectory;
private final List<LocalFileHeader> headerList; private final List<LocalFileHeader> headerList;
private ApkSigBlock apkSigBlock; private ApkSignatureBlock apkSignatureBlock;
private long mTotalDataLength; private long mTotalDataLength;
public LocalFileDirectory(CentralFileDirectory centralFileDirectory){ public LocalFileDirectory(CentralFileDirectory centralFileDirectory){
this.centralFileDirectory = centralFileDirectory; this.centralFileDirectory = centralFileDirectory;
@ -86,15 +88,14 @@ public class LocalFileDirectory {
return; return;
} }
EndRecord endRecord = cfd.getEndRecord(); EndRecord endRecord = cfd.getEndRecord();
long length = footer.getSigBlockSizeInFooter() + 8; long length = footer.getSignatureSize() + 8;
long offset = endRecord.getOffsetOfCentralDirectory() long offset = endRecord.getOffsetOfCentralDirectory() - length;
- length; ApkSignatureBlock apkSignatureBlock = new ApkSignatureBlock(footer);
ApkSigBlock apkSigBlock = new ApkSigBlock(footer); apkSignatureBlock.readBytes(new BlockReader(zipSource.getInputStream(offset, length)));
apkSigBlock.readBytes(zipSource.getInputStream(offset, length)); this.apkSignatureBlock = apkSignatureBlock;
this.apkSigBlock = apkSigBlock;
} }
public ApkSigBlock getApkSigBlock() { public ApkSignatureBlock getApkSigBlock() {
return apkSigBlock; return apkSignatureBlock;
} }
public CentralFileDirectory getCentralFileDirectory() { public CentralFileDirectory getCentralFileDirectory() {
return centralFileDirectory; return centralFileDirectory;