diff --git a/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java b/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java index 561dd9a..0e0f366 100644 --- a/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java +++ b/src/main/java/net/dongliu/apk/parser/AbstractApkFile.java @@ -4,9 +4,10 @@ import net.dongliu.apk.parser.exception.ParserException; import net.dongliu.apk.parser.parser.*; import net.dongliu.apk.parser.struct.AndroidConstants; -import net.dongliu.apk.parser.struct.ApkSigningBlock; import net.dongliu.apk.parser.struct.resource.Densities; import net.dongliu.apk.parser.struct.resource.ResourceTable; +import net.dongliu.apk.parser.struct.signingv2.ApkSigningBlock; +import net.dongliu.apk.parser.struct.signingv2.SignerBlock; import net.dongliu.apk.parser.struct.zip.EOCD; import net.dongliu.apk.parser.utils.Buffers; import net.dongliu.apk.parser.utils.Unsigned; @@ -16,6 +17,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.*; import static java.lang.System.arraycopy; @@ -38,7 +40,8 @@ public abstract class AbstractApkFile implements Closeable { private ApkMeta apkMeta; private List iconPaths; - private List apkCertificateFileInfos; + private List apkSigners; + private List apkV2Signers; private static final Locale DEFAULT_LOCALE = Locale.US; @@ -79,44 +82,82 @@ public Set getLocales() throws IOException { } /** - * Get the apk's certificate meta. If have multi signature, return the certificate the first signature used. + * Get the apk's certificate meta. If have multi signer, return the certificate the first signer used. + * + * @deprecated use {{@link #getApkSingers()}} instead */ - public List getCertificateMetaList() throws IOException, - CertificateException { - if (apkCertificateFileInfos == null) { + @Deprecated + public List getCertificateMetaList() throws IOException, CertificateException { + if (apkSigners == null) { parseCertificates(); } - if (apkCertificateFileInfos.isEmpty()) { + if (apkSigners.isEmpty()) { throw new ParserException("ApkFile certificate not found"); } - return apkCertificateFileInfos.get(0).getCertificateMetaList(); + return apkSigners.get(0).getCertificateMetas(); } /** * Get the apk's all certificates. * For each entry, the key is certificate file path in apk file, the value is the certificates info of the certificate file. * - * @deprecated use {{@link #getCertificateInfos()}} instead + * @deprecated use {{@link #getApkSingers()}} instead */ @Deprecated public Map> getAllCertificateMetas() throws IOException, CertificateException { - List certificateInfos = getCertificateInfos(); + List apkSigners = getApkSingers(); Map> map = new LinkedHashMap<>(); - for (ApkCertificateFileInfo certificateInfo : certificateInfos) { - map.put(certificateInfo.getPath(), certificateInfo.getCertificateMetaList()); + for (ApkSigner apkSigner : apkSigners) { + map.put(apkSigner.getPath(), apkSigner.getCertificateMetas()); } return map; } /** - * Get the apk's all certificates of all cert file. - * For each item, the key is cert file path in apk file, the value is the certificates info of the cert file. + * Get the apk's all cert file info, of apk v1 signing. + * If cert faile not exist, return empty list. */ - public List getCertificateInfos() throws IOException, CertificateException { - if (apkCertificateFileInfos == null) { + public List getApkSingers() throws IOException, CertificateException { + if (apkSigners == null) { parseCertificates(); } - return this.apkCertificateFileInfos; + return this.apkSigners; + } + + private void parseCertificates() throws IOException, CertificateException { + this.apkSigners = new ArrayList<>(); + for (CertificateFile file : getAllCertificateData()) { + CertificateParser parser = new CertificateParser(file.getData()); + List certificateMetas = parser.parse(); + apkSigners.add(new ApkSigner(file.getPath(), certificateMetas)); + } + } + + /** + * Get the apk's all signer in apk sign block, using apk singing v2 scheme. + * If apk v2 signing block not exists, return empty list. + */ + public List getApkV2Singers() throws IOException, CertificateException { + if (apkV2Signers == null) { + parseApkSigningBlock(); + } + return this.apkV2Signers; + } + + private void parseApkSigningBlock() throws IOException, CertificateException { + List list = new ArrayList<>(); + ByteBuffer apkSignBlockBuf = findApkSignBlock(); + if (apkSignBlockBuf != null) { + ApkSignBlockParser parser = new ApkSignBlockParser(apkSignBlockBuf); + ApkSigningBlock apkSigningBlock = parser.parse(); + for (SignerBlock signerBlock : apkSigningBlock.getSignerBlocks()) { + List certificates = signerBlock.getCertificates(); + List certificateMetas = CertificateMetas.from(certificates); + ApkV2Signer apkV2Signer = new ApkV2Signer(certificateMetas); + list.add(apkV2Signer); + } + } + this.apkV2Signers = list; } @@ -140,15 +181,6 @@ public byte[] getData() { } } - private void parseCertificates() throws IOException, CertificateException { - this.apkCertificateFileInfos = new ArrayList<>(); - for (CertificateFile file : getAllCertificateData()) { - CertificateParser parser = new CertificateParser(file.getData()); - parser.parse(); - apkCertificateFileInfos.add(new ApkCertificateFileInfo(file.getPath(), parser.getCertificateMetas())); - } - } - private void parseManifest() throws IOException { if (manifestParsed) { return; @@ -267,8 +299,7 @@ private DexClass[] parseDexFile(String path) throws IOException { } ByteBuffer buffer = ByteBuffer.wrap(data); DexParser dexParser = new DexParser(buffer); - dexParser.parse(); - return dexParser.getDexClasses(); + return dexParser.parse(); } private void parseDexFiles() throws IOException { @@ -315,7 +346,7 @@ private void parseResourceTable() throws IOException { @Override public void close() throws IOException { - this.apkCertificateFileInfos = null; + this.apkSigners = null; this.resourceTable = null; this.iconPaths = null; } @@ -337,7 +368,12 @@ public void setPreferredLocale(Locale preferredLocale) { } } - private ApkSigningBlock findApkSignBlock() throws IOException { + /** + * Create ApkSignBlockParser for this apk file. + * + * @return null if do not have sign block + */ + protected ByteBuffer findApkSignBlock() throws IOException { ByteBuffer buffer = fileData().order(ByteOrder.LITTLE_ENDIAN); int len = buffer.limit(); @@ -367,10 +403,11 @@ private ApkSigningBlock findApkSignBlock() throws IOException { return null; } + int magicStrLen = 16; long cdStart = eocd.getCdStart(); // find apk sign block - Buffers.position(buffer, cdStart - 16); - String magic = Buffers.readAsciiString(buffer, 16); + Buffers.position(buffer, cdStart - magicStrLen); + String magic = Buffers.readAsciiString(buffer, magicStrLen); if (!magic.equals(ApkSigningBlock.MAGIC)) { return null; } @@ -382,48 +419,7 @@ private ApkSigningBlock findApkSignBlock() throws IOException { return null; } // now at the start of signing block - ByteBuffer signingBuffer = Buffers.slice(buffer, blockSize); - return readSigningBlock(signingBuffer); - } - - private ApkSigningBlock readSigningBlock(ByteBuffer buffer) { - ApkSigningBlock block = new ApkSigningBlock(); - block.setSize(buffer.limit()); - // sign block found, read pairs - while (buffer.hasRemaining()) { - int id = buffer.getInt(); - int size = Unsigned.ensureUInt(buffer.getInt()); - if (id == ApkSigningBlock.SIGNING_V2_ID) { - ByteBuffer signingV2Buffer = Buffers.slice(buffer, size); - // now only care about apk signing v2 entry - readSigningV2(signingV2Buffer); - - Buffers.position(buffer, buffer.position() + size); - } else { - Buffers.position(buffer, buffer.position() + size); - } - } - return block; - } - - private void readSigningV2(ByteBuffer buffer) { - int singersLen = Unsigned.ensureUInt(buffer.getInt()); - int singersEnd = buffer.position() + singersLen; - - while (buffer.position() < singersEnd) { - int singerLen = Unsigned.ensureUInt(buffer.getInt()); - int singerBegin = buffer.position(); - int singerDataLen = Unsigned.ensureUInt(buffer.getInt()); - Buffers.skip(buffer, singerDataLen); - - singerLen = singerLen - singerDataLen - 4; - if (singerLen > 0) { - int signaturesLen = Unsigned.ensureUInt(buffer.getInt()); - Buffers.skip(buffer, signaturesLen); - int publicKeyLen = Unsigned.ensureUInt(buffer.getInt()); - Buffers.skip(buffer, publicKeyLen); - } - } + return Buffers.sliceAndSkip(buffer, blockSize - magicStrLen); } } diff --git a/src/main/java/net/dongliu/apk/parser/ApkFile.java b/src/main/java/net/dongliu/apk/parser/ApkFile.java index 6415081..98a200f 100644 --- a/src/main/java/net/dongliu/apk/parser/ApkFile.java +++ b/src/main/java/net/dongliu/apk/parser/ApkFile.java @@ -47,7 +47,7 @@ protected List getAllCertificateData() throws IOException { } String name = ne.getName().toUpperCase(); if (name.endsWith(".RSA") || name.endsWith(".DSA")) { - list.add(new CertificateFile(name, Inputs.toByteArray(zf.getInputStream(ne)))); + list.add(new CertificateFile(name, Inputs.readAll(zf.getInputStream(ne)))); } } return list; @@ -61,7 +61,7 @@ public byte[] getFileData(String path) throws IOException { } InputStream inputStream = zf.getInputStream(entry); - return Inputs.toByteArray(inputStream); + return Inputs.readAll(inputStream); } @Override @@ -107,4 +107,5 @@ public void close() throws IOException { super.close(); zf.close(); } + } diff --git a/src/main/java/net/dongliu/apk/parser/ByteArrayApkFile.java b/src/main/java/net/dongliu/apk/parser/ByteArrayApkFile.java index a0ed3e2..ff24fe3 100644 --- a/src/main/java/net/dongliu/apk/parser/ByteArrayApkFile.java +++ b/src/main/java/net/dongliu/apk/parser/ByteArrayApkFile.java @@ -36,7 +36,7 @@ protected List getAllCertificateData() throws IOException { while ((entry = zis.getNextEntry()) != null) { String name = entry.getName(); if (name.toUpperCase().endsWith(".RSA") || name.toUpperCase().endsWith(".DSA")) { - list.add(new CertificateFile(name, Inputs.toByteArray(zis))); + list.add(new CertificateFile(name, Inputs.readAll(zis))); } } } @@ -50,7 +50,7 @@ public byte[] getFileData(String path) throws IOException { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { if (path.equals(entry.getName())) { - return Inputs.toByteArray(zis); + return Inputs.readAll(zis); } } } diff --git a/src/main/java/net/dongliu/apk/parser/Main.java b/src/main/java/net/dongliu/apk/parser/Main.java index 6949dcd..3c180c8 100644 --- a/src/main/java/net/dongliu/apk/parser/Main.java +++ b/src/main/java/net/dongliu/apk/parser/Main.java @@ -11,7 +11,7 @@ public class Main { public static void main(String[] args) throws IOException, CertificateException { try (ApkFile apkFile = new ApkFile(args[0])) { - System.out.println(apkFile.getApkMeta()); + System.out.println(apkFile.getApkV2Singers().get(0).getCertificateMetas()); } } } diff --git a/src/main/java/net/dongliu/apk/parser/bean/ApkCertificateFileInfo.java b/src/main/java/net/dongliu/apk/parser/bean/ApkSigner.java similarity index 51% rename from src/main/java/net/dongliu/apk/parser/bean/ApkCertificateFileInfo.java rename to src/main/java/net/dongliu/apk/parser/bean/ApkSigner.java index 68d3bfb..6794907 100644 --- a/src/main/java/net/dongliu/apk/parser/bean/ApkCertificateFileInfo.java +++ b/src/main/java/net/dongliu/apk/parser/bean/ApkSigner.java @@ -5,7 +5,7 @@ /** * ApkSignV1 certificate file. */ -public class ApkCertificateFileInfo { +public class ApkSigner { /** * The cert file path in apk file */ @@ -13,19 +13,19 @@ public class ApkCertificateFileInfo { /** * The meta info of certificate contained in this cert file. */ - private List certificateMetaList; + private List certificateMetas; - public ApkCertificateFileInfo(String path, List certificateMetaList) { + public ApkSigner(String path, List certificateMetas) { this.path = path; - this.certificateMetaList = certificateMetaList; + this.certificateMetas = certificateMetas; } public String getPath() { return path; } - public List getCertificateMetaList() { - return certificateMetaList; + public List getCertificateMetas() { + return certificateMetas; } } diff --git a/src/main/java/net/dongliu/apk/parser/bean/ApkV2Signer.java b/src/main/java/net/dongliu/apk/parser/bean/ApkV2Signer.java new file mode 100644 index 0000000..527d97a --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/bean/ApkV2Signer.java @@ -0,0 +1,22 @@ +package net.dongliu.apk.parser.bean; + +import java.util.List; + +/** + * ApkSignV1 certificate file. + */ +public class ApkV2Signer { + /** + * The meta info of certificate contained in this cert file. + */ + private List certificateMetas; + + public ApkV2Signer(List certificateMetas) { + this.certificateMetas = certificateMetas; + } + + public List getCertificateMetas() { + return certificateMetas; + } + +} diff --git a/src/main/java/net/dongliu/apk/parser/parser/ApkSignBlockParser.java b/src/main/java/net/dongliu/apk/parser/parser/ApkSignBlockParser.java new file mode 100644 index 0000000..9e73902 --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/parser/ApkSignBlockParser.java @@ -0,0 +1,135 @@ +package net.dongliu.apk.parser.parser; + +import net.dongliu.apk.parser.struct.signingv2.ApkSigningBlock; +import net.dongliu.apk.parser.struct.signingv2.Digest; +import net.dongliu.apk.parser.struct.signingv2.Signature; +import net.dongliu.apk.parser.struct.signingv2.SignerBlock; +import net.dongliu.apk.parser.utils.Buffers; +import net.dongliu.apk.parser.utils.Unsigned; + +import java.io.ByteArrayInputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +// see https://source.android.com/security/apksigning/v2 + +/** + * The Apk Sign Block V2 Parser. + */ +public class ApkSignBlockParser { + private ByteBuffer data; + + public ApkSignBlockParser(ByteBuffer data) { + this.data = data.order(ByteOrder.LITTLE_ENDIAN); + } + + public ApkSigningBlock parse() throws CertificateException { + // sign block found, read pairs + List signerBlocks = new ArrayList<>(); + while (data.hasRemaining()) { + int id = data.getInt(); + int size = Unsigned.ensureUInt(data.getInt()); + if (id == ApkSigningBlock.SIGNING_V2_ID) { + ByteBuffer signingV2Buffer = Buffers.sliceAndSkip(data, size); + // now only care about apk signing v2 entry + while (signingV2Buffer.hasRemaining()) { + SignerBlock signerBlock = readSigningV2(signingV2Buffer); + signerBlocks.add(signerBlock); + } + } else { + // just ignore now + Buffers.position(data, data.position() + size); + } + } + return new ApkSigningBlock(signerBlocks); + } + + private SignerBlock readSigningV2(ByteBuffer buffer) throws CertificateException { + buffer = readLenPrefixData(buffer); + + ByteBuffer signedData = readLenPrefixData(buffer); + ByteBuffer digestsData = readLenPrefixData(signedData); + List digests = readDigests(digestsData); + ByteBuffer certificateData = readLenPrefixData(signedData); + List certificates = readCertificates(certificateData); + ByteBuffer attributesData = readLenPrefixData(signedData); + readAttributes(attributesData); + + ByteBuffer signaturesData = readLenPrefixData(buffer); + List signatures = readSignatures(signaturesData); + + ByteBuffer publicKeyData = readLenPrefixData(buffer); + return new SignerBlock(digests, certificates, signatures); + } + + private List readDigests(ByteBuffer buffer) { + List list = new ArrayList<>(); + while (buffer.hasRemaining()) { + ByteBuffer digestData = readLenPrefixData(buffer); + int algorithmID = digestData.getInt(); + byte[] digest = Buffers.readBytes(digestData); + list.add(new Digest(algorithmID, digest)); + } + return list; + } + + private List readCertificates(ByteBuffer buffer) throws CertificateException { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + List certificates = new ArrayList<>(); + while (buffer.hasRemaining()) { + ByteBuffer certificateData = readLenPrefixData(buffer); + Certificate certificate = certificateFactory.generateCertificate( + new ByteArrayInputStream(Buffers.readBytes(certificateData))); + certificates.add((X509Certificate) certificate); + } + return certificates; + } + + private void readAttributes(ByteBuffer buffer) { + while (buffer.hasRemaining()) { + ByteBuffer attributeData = readLenPrefixData(buffer); + int id = attributeData.getInt(); +// byte[] value = Buffers.readBytes(attributeData); + } + } + + private List readSignatures(ByteBuffer buffer) { + List signatures = new ArrayList<>(); + while (buffer.hasRemaining()) { + ByteBuffer signatureData = readLenPrefixData(buffer); + int algorithmID = signatureData.getInt(); + int signatureDataLen = Unsigned.ensureUInt(signatureData.getInt()); + byte[] signature = Buffers.readBytes(signatureData, signatureDataLen); + signatures.add(new Signature(algorithmID, signature)); + } + return signatures; + } + + + private ByteBuffer readLenPrefixData(ByteBuffer buffer) { + int len = Unsigned.ensureUInt(buffer.getInt()); + return Buffers.sliceAndSkip(buffer, len); + } + + // 0x0101—RSASSA-PSS with SHA2-256 digest, SHA2-256 MGF1, 32 bytes of salt, trailer: 0xbc + private static final int PSS_SHA_256 = 0x0101; + // 0x0102—RSASSA-PSS with SHA2-512 digest, SHA2-512 MGF1, 64 bytes of salt, trailer: 0xbc + private static final int PSS_SHA_512 = 0x0102; + // 0x0103—RSASSA-PKCS1-v1_5 with SHA2-256 digest. This is for build systems which require deterministic signatures. + private static final int PKCS1_SHA_256 = 0x0103; + // 0x0104—RSASSA-PKCS1-v1_5 with SHA2-512 digest. This is for build systems which require deterministic signatures. + private static final int PKCS1_SHA_512 = 0x0104; + // 0x0201—ECDSA with SHA2-256 digest + private static final int ECDSA_SHA_256 = 0x0201; + // 0x0202—ECDSA with SHA2-512 digest + private static final int ECDSA_SHA_512 = 0x0202; + // 0x0301—DSA with SHA2-256 digest + private static final int DSA_SHA_256 = 0x0301; + +} diff --git a/src/main/java/net/dongliu/apk/parser/parser/CertificateMetas.java b/src/main/java/net/dongliu/apk/parser/parser/CertificateMetas.java new file mode 100644 index 0000000..a37b2c3 --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/parser/CertificateMetas.java @@ -0,0 +1,78 @@ +package net.dongliu.apk.parser.parser; + +import net.dongliu.apk.parser.bean.CertificateMeta; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +public class CertificateMetas { + + public static List from(List certificates) throws CertificateEncodingException { + List certificateMetas = new ArrayList<>(certificates.size()); + for (X509Certificate certificate : certificates) { + CertificateMeta certificateMeta = CertificateMetas.from(certificate); + certificateMetas.add(certificateMeta); + } + return certificateMetas; + } + + public static CertificateMeta from(X509Certificate certificate) throws CertificateEncodingException { + CertificateMeta certificateMeta = new CertificateMeta(); + byte[] bytes = certificate.getEncoded(); + String certMd5 = md5Digest(bytes); + String publicKeyString = byteToHexString(bytes); + String certBase64Md5 = md5Digest(publicKeyString); + certificateMeta.setData(bytes); + certificateMeta.setCertBase64Md5(certBase64Md5); + certificateMeta.setCertMd5(certMd5); + certificateMeta.setStartDate(certificate.getNotBefore()); + certificateMeta.setEndDate(certificate.getNotAfter()); + certificateMeta.setSignAlgorithm(certificate.getSigAlgName()); + certificateMeta.setSignAlgorithmOID(certificate.getSigAlgOID()); + return certificateMeta; + } + + private static String md5Digest(byte[] input) { + MessageDigest digest = getDigest("md5"); + digest.update(input); + return getHexString(digest.digest()); + } + + private static String md5Digest(String input) { + MessageDigest digest = getDigest("md5"); + digest.update(input.getBytes(StandardCharsets.UTF_8)); + return getHexString(digest.digest()); + } + + private static String byteToHexString(byte[] bArray) { + StringBuilder sb = new StringBuilder(bArray.length); + String sTemp; + for (byte aBArray : bArray) { + sTemp = Integer.toHexString(0xFF & (char) aBArray); + if (sTemp.length() < 2) { + sb.append(0); + } + sb.append(sTemp.toUpperCase()); + } + return sb.toString(); + } + + private static String getHexString(byte[] digest) { + BigInteger bi = new BigInteger(1, digest); + return String.format("%032x", bi); + } + + private static MessageDigest getDigest(String algorithm) { + try { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e.getMessage()); + } + } +} diff --git a/src/main/java/net/dongliu/apk/parser/parser/CertificateParser.java b/src/main/java/net/dongliu/apk/parser/parser/CertificateParser.java index 905448a..a8e4dbf 100644 --- a/src/main/java/net/dongliu/apk/parser/parser/CertificateParser.java +++ b/src/main/java/net/dongliu/apk/parser/parser/CertificateParser.java @@ -10,13 +10,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.Store; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.Security; -import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -33,8 +27,6 @@ public class CertificateParser { private final byte[] data; - private List certificateMetas; - static { Security.addProvider(new BouncyCastleProvider()); } @@ -45,11 +37,8 @@ public CertificateParser(byte[] data) { /** * get certificate info - * - * @throws IOException - * @throws CertificateEncodingException */ - public void parse() throws IOException, CertificateException { + public List parse() throws CertificateException { CMSSignedData cmsSignedData; @@ -68,65 +57,7 @@ public void parse() throws IOException, CertificateException { certificates.add(new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder)); } } - certificateMetas = new ArrayList<>(); - for (X509Certificate certificate : certificates) { - CertificateMeta certificateMeta = new CertificateMeta(); - certificateMetas.add(certificateMeta); - - byte[] bytes = certificate.getEncoded(); - String certMd5 = md5Digest(bytes); - String publicKeyString = byteToHexString(bytes); - String certBase64Md5 = md5Digest(publicKeyString); - certificateMeta.setData(bytes); - certificateMeta.setCertBase64Md5(certBase64Md5); - certificateMeta.setCertMd5(certMd5); - certificateMeta.setStartDate(certificate.getNotBefore()); - certificateMeta.setEndDate(certificate.getNotAfter()); - certificateMeta.setSignAlgorithm(certificate.getSigAlgName()); - certificateMeta.setSignAlgorithmOID(certificate.getSigAlgOID()); - } - } - - - private String md5Digest(byte[] input) throws IOException { - MessageDigest digest = getDigest("md5"); - digest.update(input); - return getHexString(digest.digest()); - } - - private String md5Digest(String input) throws IOException { - MessageDigest digest = getDigest("md5"); - digest.update(input.getBytes(StandardCharsets.UTF_8)); - return getHexString(digest.digest()); + return CertificateMetas.from(certificates); } - private String byteToHexString(byte[] bArray) { - StringBuilder sb = new StringBuilder(bArray.length); - String sTemp; - for (byte aBArray : bArray) { - sTemp = Integer.toHexString(0xFF & (char) aBArray); - if (sTemp.length() < 2) { - sb.append(0); - } - sb.append(sTemp.toUpperCase()); - } - return sb.toString(); - } - - private String getHexString(byte[] digest) { - BigInteger bi = new BigInteger(1, digest); - return String.format("%032x", bi); - } - - private MessageDigest getDigest(String algorithm) { - try { - return MessageDigest.getInstance(algorithm); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e.getMessage()); - } - } - - public List getCertificateMetas() { - return certificateMetas; - } } diff --git a/src/main/java/net/dongliu/apk/parser/parser/DexParser.java b/src/main/java/net/dongliu/apk/parser/parser/DexParser.java index 2a968b3..32cde79 100644 --- a/src/main/java/net/dongliu/apk/parser/parser/DexParser.java +++ b/src/main/java/net/dongliu/apk/parser/parser/DexParser.java @@ -23,22 +23,19 @@ public class DexParser { private ByteBuffer buffer; - private ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; private static final int NO_INDEX = 0xffffffff; - private DexClass[] dexClasses; - public DexParser(ByteBuffer buffer) { this.buffer = buffer.duplicate(); - this.buffer.order(byteOrder); + this.buffer.order(ByteOrder.LITTLE_ENDIAN); } - public void parse() { + public DexClass[] parse() { // read magic String magic = new String(Buffers.readBytes(buffer, 8)); if (!magic.startsWith("dex\n")) { - return; + return new DexClass[0]; } int version = Integer.parseInt(magic.substring(4, 7)); // now the version is 035 @@ -69,7 +66,7 @@ public void parse() { types[i] = stringpool.get(typeIds[i]); } - dexClasses = new DexClass[dexClassStructs.length]; + DexClass[] dexClasses = new DexClass[dexClassStructs.length]; for (int i = 0; i < dexClasses.length; i++) { dexClasses[i] = new DexClass(); } @@ -82,6 +79,7 @@ public void parse() { } dexClass.setAccessFlags(dexClassStruct.getAccessFlags()); } + return dexClasses; } /** @@ -283,9 +281,5 @@ private DexHeader readDexHeader() { return header; } - public DexClass[] getDexClasses() { - return dexClasses; - } - } diff --git a/src/main/java/net/dongliu/apk/parser/struct/ApkSignatureV2.java b/src/main/java/net/dongliu/apk/parser/struct/ApkSignatureV2.java deleted file mode 100644 index dcfbf3b..0000000 --- a/src/main/java/net/dongliu/apk/parser/struct/ApkSignatureV2.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.dongliu.apk.parser.struct; - -/** - * Apk signature v2 block - * - * @see apksigning v2 scheme - */ -public class ApkSigningBlock { - public static final int SIGNING_V2_ID = 0x7109871a; - - public static final String MAGIC = "APK Sig Block 42"; - - // uint64, the size of this block - public long size; - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = Unsigned.ensureUInt(size); - } - - public static class Entry { - // uint32 the entry id - private int id; - // the len of this pair - private int size; - } - - -} diff --git a/src/main/java/net/dongliu/apk/parser/struct/signingv2/ApkSigningBlock.java b/src/main/java/net/dongliu/apk/parser/struct/signingv2/ApkSigningBlock.java new file mode 100644 index 0000000..5796f5b --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/struct/signingv2/ApkSigningBlock.java @@ -0,0 +1,24 @@ +package net.dongliu.apk.parser.struct.signingv2; + +import java.util.List; + +/** + * For read apk signing block + * + * @see apksigning v2 scheme + */ +public class ApkSigningBlock { + public static final int SIGNING_V2_ID = 0x7109871a; + + public static final String MAGIC = "APK Sig Block 42"; + + private List signerBlocks; + + public ApkSigningBlock(List signerBlocks) { + this.signerBlocks = signerBlocks; + } + + public List getSignerBlocks() { + return signerBlocks; + } +} diff --git a/src/main/java/net/dongliu/apk/parser/struct/signingv2/Digest.java b/src/main/java/net/dongliu/apk/parser/struct/signingv2/Digest.java new file mode 100644 index 0000000..5276269 --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/struct/signingv2/Digest.java @@ -0,0 +1,19 @@ +package net.dongliu.apk.parser.struct.signingv2; + +public class Digest { + private int algorithmID; + private byte[] value; + + public Digest(int algorithmID, byte[] value) { + this.algorithmID = algorithmID; + this.value = value; + } + + public int getAlgorithmID() { + return algorithmID; + } + + public byte[] getValue() { + return value; + } +} diff --git a/src/main/java/net/dongliu/apk/parser/struct/signingv2/Signature.java b/src/main/java/net/dongliu/apk/parser/struct/signingv2/Signature.java new file mode 100644 index 0000000..05ed0db --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/struct/signingv2/Signature.java @@ -0,0 +1,19 @@ +package net.dongliu.apk.parser.struct.signingv2; + +public class Signature { + private int algorithmID; + private byte[] data; + + public Signature(int algorithmID, byte[] data) { + this.algorithmID = algorithmID; + this.data = data; + } + + public int getAlgorithmID() { + return algorithmID; + } + + public byte[] getData() { + return data; + } +} diff --git a/src/main/java/net/dongliu/apk/parser/struct/signingv2/SignerBlock.java b/src/main/java/net/dongliu/apk/parser/struct/signingv2/SignerBlock.java new file mode 100644 index 0000000..a1b8e79 --- /dev/null +++ b/src/main/java/net/dongliu/apk/parser/struct/signingv2/SignerBlock.java @@ -0,0 +1,28 @@ +package net.dongliu.apk.parser.struct.signingv2; + +import java.security.cert.X509Certificate; +import java.util.List; + +public class SignerBlock { + private List digests; + private List certificates; + private List signatures; + + public SignerBlock(List digests, List certificates, List signatures) { + this.digests = digests; + this.certificates = certificates; + this.signatures = signatures; + } + + public List getDigests() { + return digests; + } + + public List getCertificates() { + return certificates; + } + + public List getSignatures() { + return signatures; + } +} diff --git a/src/main/java/net/dongliu/apk/parser/utils/Buffers.java b/src/main/java/net/dongliu/apk/parser/utils/Buffers.java index f10f232..4e28b80 100644 --- a/src/main/java/net/dongliu/apk/parser/utils/Buffers.java +++ b/src/main/java/net/dongliu/apk/parser/utils/Buffers.java @@ -44,6 +44,13 @@ public static byte[] readBytes(ByteBuffer buffer, int size) { return bytes; } + /** + * get all bytes remains + */ + public static byte[] readBytes(ByteBuffer buffer) { + return readBytes(buffer, buffer.remaining()); + } + /** * Read ascii string ,by len */ @@ -112,7 +119,7 @@ public static void position(ByteBuffer buffer, long position) { * Return one new ByteBuffer from current position, with size, the byte order of new buffer will be set to little endian; * And advance the original buffer with size. */ - public static ByteBuffer slice(ByteBuffer buffer, int size) { + public static ByteBuffer sliceAndSkip(ByteBuffer buffer, int size) { ByteBuffer buf = buffer.slice().order(ByteOrder.LITTLE_ENDIAN); ByteBuffer slice = (ByteBuffer) ((Buffer) buf).limit(buf.position() + size); skip(buffer, size); diff --git a/src/main/java/net/dongliu/apk/parser/utils/Inputs.java b/src/main/java/net/dongliu/apk/parser/utils/Inputs.java index 11ce57e..c3cd355 100644 --- a/src/main/java/net/dongliu/apk/parser/utils/Inputs.java +++ b/src/main/java/net/dongliu/apk/parser/utils/Inputs.java @@ -6,7 +6,7 @@ public class Inputs { - public static byte[] toByteArray(InputStream in) throws IOException { + public static byte[] readAll(InputStream in) throws IOException { try { byte[] buf = new byte[1024 * 8]; try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { diff --git a/src/main/java/net/dongliu/apk/parser/utils/ResourceFetcher.java b/src/main/java/net/dongliu/apk/parser/utils/ResourceFetcher.java index 8605194..9527016 100644 --- a/src/main/java/net/dongliu/apk/parser/utils/ResourceFetcher.java +++ b/src/main/java/net/dongliu/apk/parser/utils/ResourceFetcher.java @@ -106,7 +106,7 @@ private String getUrl(String url) throws IOException { conn.setRequestMethod("GET"); conn.setReadTimeout(10000); conn.setConnectTimeout(10000); - byte[] bytes = Inputs.toByteArray(conn.getInputStream()); + byte[] bytes = Inputs.readAll(conn.getInputStream()); return new String(bytes, StandardCharsets.UTF_8); } finally { conn.disconnect(); diff --git a/src/test/java/net/dongliu/apk/parser/parser/ApkSignBlockParserTest.java b/src/test/java/net/dongliu/apk/parser/parser/ApkSignBlockParserTest.java new file mode 100644 index 0000000..76307b9 --- /dev/null +++ b/src/test/java/net/dongliu/apk/parser/parser/ApkSignBlockParserTest.java @@ -0,0 +1,21 @@ +package net.dongliu.apk.parser.parser; + +import net.dongliu.apk.parser.utils.Inputs; +import org.junit.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.security.cert.CertificateException; + +import static org.junit.Assert.*; + +public class ApkSignBlockParserTest { + + @Test + public void parse() throws IOException, CertificateException { + byte[] bytes = Inputs.readAll(getClass().getResourceAsStream("/sign/gmail_sign_block")); + ApkSignBlockParser parser = new ApkSignBlockParser(ByteBuffer.wrap(bytes)); + parser.parse(); + } +} \ No newline at end of file