Skip to content
This repository has been archived by the owner on Sep 21, 2020. It is now read-only.

Commit

Permalink
retrieve apk sign v2 block info
Browse files Browse the repository at this point in the history
  • Loading branch information
hsiafan committed Jan 18, 2018
1 parent a83b55e commit 9431415
Show file tree
Hide file tree
Showing 20 changed files with 445 additions and 214 deletions.
144 changes: 70 additions & 74 deletions src/main/java/net/dongliu/apk/parser/AbstractApkFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -38,7 +40,8 @@ public abstract class AbstractApkFile implements Closeable {
private ApkMeta apkMeta;
private List<IconPath> iconPaths;

private List<ApkCertificateFileInfo> apkCertificateFileInfos;
private List<ApkSigner> apkSigners;
private List<ApkV2Signer> apkV2Signers;

private static final Locale DEFAULT_LOCALE = Locale.US;

Expand Down Expand Up @@ -79,44 +82,82 @@ public Set<Locale> 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<CertificateMeta> getCertificateMetaList() throws IOException,
CertificateException {
if (apkCertificateFileInfos == null) {
@Deprecated
public List<CertificateMeta> 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<String, List<CertificateMeta>> getAllCertificateMetas() throws IOException, CertificateException {
List<ApkCertificateFileInfo> certificateInfos = getCertificateInfos();
List<ApkSigner> apkSigners = getApkSingers();
Map<String, List<CertificateMeta>> 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<ApkCertificateFileInfo> getCertificateInfos() throws IOException, CertificateException {
if (apkCertificateFileInfos == null) {
public List<ApkSigner> 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<CertificateMeta> 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<ApkV2Signer> getApkV2Singers() throws IOException, CertificateException {
if (apkV2Signers == null) {
parseApkSigningBlock();
}
return this.apkV2Signers;
}

private void parseApkSigningBlock() throws IOException, CertificateException {
List<ApkV2Signer> list = new ArrayList<>();
ByteBuffer apkSignBlockBuf = findApkSignBlock();
if (apkSignBlockBuf != null) {
ApkSignBlockParser parser = new ApkSignBlockParser(apkSignBlockBuf);
ApkSigningBlock apkSigningBlock = parser.parse();
for (SignerBlock signerBlock : apkSigningBlock.getSignerBlocks()) {
List<X509Certificate> certificates = signerBlock.getCertificates();
List<CertificateMeta> certificateMetas = CertificateMetas.from(certificates);
ApkV2Signer apkV2Signer = new ApkV2Signer(certificateMetas);
list.add(apkV2Signer);
}
}
this.apkV2Signers = list;
}


Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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();

Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}

}
5 changes: 3 additions & 2 deletions src/main/java/net/dongliu/apk/parser/ApkFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ protected List<CertificateFile> 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;
Expand All @@ -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
Expand Down Expand Up @@ -107,4 +107,5 @@ public void close() throws IOException {
super.close();
zf.close();
}

}
4 changes: 2 additions & 2 deletions src/main/java/net/dongliu/apk/parser/ByteArrayApkFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected List<CertificateFile> 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)));
}
}
}
Expand All @@ -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);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/dongliu/apk/parser/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,27 @@
/**
* ApkSignV1 certificate file.
*/
public class ApkCertificateFileInfo {
public class ApkSigner {
/**
* The cert file path in apk file
*/
private String path;
/**
* The meta info of certificate contained in this cert file.
*/
private List<CertificateMeta> certificateMetaList;
private List<CertificateMeta> certificateMetas;

public ApkCertificateFileInfo(String path, List<CertificateMeta> certificateMetaList) {
public ApkSigner(String path, List<CertificateMeta> certificateMetas) {
this.path = path;
this.certificateMetaList = certificateMetaList;
this.certificateMetas = certificateMetas;
}

public String getPath() {
return path;
}

public List<CertificateMeta> getCertificateMetaList() {
return certificateMetaList;
public List<CertificateMeta> getCertificateMetas() {
return certificateMetas;
}

}
22 changes: 22 additions & 0 deletions src/main/java/net/dongliu/apk/parser/bean/ApkV2Signer.java
Original file line number Diff line number Diff line change
@@ -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<CertificateMeta> certificateMetas;

public ApkV2Signer(List<CertificateMeta> certificateMetas) {
this.certificateMetas = certificateMetas;
}

public List<CertificateMeta> getCertificateMetas() {
return certificateMetas;
}

}
Loading

0 comments on commit 9431415

Please sign in to comment.