Merge "Add proof-of-rotation information to PackageParser.SigningDetails"
This commit is contained in:
committed by
Android (Google) Code Review
commit
4ec3efce54
@@ -5684,23 +5684,74 @@ public class PackageParser {
|
||||
@Nullable
|
||||
public final ArraySet<PublicKey> publicKeys;
|
||||
|
||||
/**
|
||||
* Collection of {@code Signature} objects, each of which is formed from a former signing
|
||||
* certificate of this APK before it was changed by signing certificate rotation.
|
||||
*/
|
||||
@Nullable
|
||||
public final Signature[] pastSigningCertificates;
|
||||
|
||||
/**
|
||||
* Flags for the {@code pastSigningCertificates} collection, which indicate the capabilities
|
||||
* the including APK wishes to grant to its past signing certificates.
|
||||
*/
|
||||
@Nullable
|
||||
public final int[] pastSigningCertificatesFlags;
|
||||
|
||||
/** A representation of unknown signing details. Use instead of null. */
|
||||
public static final SigningDetails UNKNOWN =
|
||||
new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null);
|
||||
new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null, null);
|
||||
|
||||
@VisibleForTesting
|
||||
public SigningDetails(Signature[] signatures,
|
||||
@SignatureSchemeVersion int signatureSchemeVersion,
|
||||
ArraySet<PublicKey> keys) {
|
||||
ArraySet<PublicKey> keys, Signature[] pastSigningCertificates,
|
||||
int[] pastSigningCertificatesFlags) {
|
||||
this.signatures = signatures;
|
||||
this.signatureSchemeVersion = signatureSchemeVersion;
|
||||
this.publicKeys = keys;
|
||||
this.pastSigningCertificates = pastSigningCertificates;
|
||||
this.pastSigningCertificatesFlags = pastSigningCertificatesFlags;
|
||||
}
|
||||
|
||||
public SigningDetails(Signature[] signatures,
|
||||
@SignatureSchemeVersion int signatureSchemeVersion,
|
||||
Signature[] pastSigningCertificates, int[] pastSigningCertificatesFlags)
|
||||
throws CertificateException {
|
||||
this(signatures, signatureSchemeVersion, toSigningKeys(signatures),
|
||||
pastSigningCertificates, pastSigningCertificatesFlags);
|
||||
}
|
||||
|
||||
public SigningDetails(Signature[] signatures,
|
||||
@SignatureSchemeVersion int signatureSchemeVersion)
|
||||
throws CertificateException {
|
||||
this(signatures, signatureSchemeVersion, toSigningKeys(signatures));
|
||||
this(signatures, signatureSchemeVersion,
|
||||
null, null);
|
||||
}
|
||||
|
||||
public SigningDetails(SigningDetails orig) {
|
||||
if (orig != null) {
|
||||
if (orig.signatures != null) {
|
||||
this.signatures = orig.signatures.clone();
|
||||
} else {
|
||||
this.signatures = null;
|
||||
}
|
||||
this.signatureSchemeVersion = orig.signatureSchemeVersion;
|
||||
this.publicKeys = new ArraySet<>(orig.publicKeys);
|
||||
if (orig.pastSigningCertificates != null) {
|
||||
this.pastSigningCertificates = orig.pastSigningCertificates.clone();
|
||||
this.pastSigningCertificatesFlags = orig.pastSigningCertificatesFlags.clone();
|
||||
} else {
|
||||
this.pastSigningCertificates = null;
|
||||
this.pastSigningCertificatesFlags = null;
|
||||
}
|
||||
} else {
|
||||
this.signatures = null;
|
||||
this.signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
|
||||
this.publicKeys = null;
|
||||
this.pastSigningCertificates = null;
|
||||
this.pastSigningCertificatesFlags = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the signing details have one or more signatures. */
|
||||
@@ -5728,6 +5779,8 @@ public class PackageParser {
|
||||
dest.writeTypedArray(this.signatures, flags);
|
||||
dest.writeInt(this.signatureSchemeVersion);
|
||||
dest.writeArraySet(this.publicKeys);
|
||||
dest.writeTypedArray(this.pastSigningCertificates, flags);
|
||||
dest.writeIntArray(this.pastSigningCertificatesFlags);
|
||||
}
|
||||
|
||||
protected SigningDetails(Parcel in) {
|
||||
@@ -5735,6 +5788,8 @@ public class PackageParser {
|
||||
this.signatures = in.createTypedArray(Signature.CREATOR);
|
||||
this.signatureSchemeVersion = in.readInt();
|
||||
this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
|
||||
this.pastSigningCertificates = in.createTypedArray(Signature.CREATOR);
|
||||
this.pastSigningCertificatesFlags = in.createIntArray();
|
||||
}
|
||||
|
||||
public static final Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() {
|
||||
@@ -5761,8 +5816,23 @@ public class PackageParser {
|
||||
|
||||
if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
|
||||
if (!Signature.areExactMatch(signatures, that.signatures)) return false;
|
||||
return publicKeys != null ? publicKeys.equals(that.publicKeys)
|
||||
: that.publicKeys == null;
|
||||
if (publicKeys != null) {
|
||||
if (!publicKeys.equals((that.publicKeys))) {
|
||||
return false;
|
||||
}
|
||||
} else if (that.publicKeys != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// can't use Signature.areExactMatch() because order matters with the past signing certs
|
||||
if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(pastSigningCertificatesFlags, that.pastSigningCertificatesFlags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -5770,8 +5840,77 @@ public class PackageParser {
|
||||
int result = +Arrays.hashCode(signatures);
|
||||
result = 31 * result + signatureSchemeVersion;
|
||||
result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0);
|
||||
result = 31 * result + Arrays.hashCode(pastSigningCertificates);
|
||||
result = 31 * result + Arrays.hashCode(pastSigningCertificatesFlags);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of {@code SigningDetails} instances.
|
||||
*/
|
||||
public static class Builder {
|
||||
private Signature[] mSignatures;
|
||||
private int mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
|
||||
private Signature[] mPastSigningCertificates;
|
||||
private int[] mPastSigningCertificatesFlags;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/** get signing certificates used to sign the current APK */
|
||||
public Builder setSignatures(Signature[] signatures) {
|
||||
mSignatures = signatures;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** set the signature scheme version used to sign the APK */
|
||||
public Builder setSignatureSchemeVersion(int signatureSchemeVersion) {
|
||||
mSignatureSchemeVersion = signatureSchemeVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** set the signing certificates by which the APK proved it can be authenticated */
|
||||
public Builder setPastSigningCertificates(Signature[] pastSigningCertificates) {
|
||||
mPastSigningCertificates = pastSigningCertificates;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** set the flags for the {@code pastSigningCertificates} */
|
||||
public Builder setPastSigningCertificatesFlags(int[] pastSigningCertificatesFlags) {
|
||||
mPastSigningCertificatesFlags = pastSigningCertificatesFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void checkInvariants() {
|
||||
// must have signatures and scheme version set
|
||||
if (mSignatures == null) {
|
||||
throw new IllegalStateException("SigningDetails requires the current signing"
|
||||
+ " certificates.");
|
||||
}
|
||||
|
||||
// pastSigningCerts and flags must match up
|
||||
boolean pastMismatch = false;
|
||||
if (mPastSigningCertificates != null && mPastSigningCertificatesFlags != null) {
|
||||
if (mPastSigningCertificates.length != mPastSigningCertificatesFlags.length) {
|
||||
pastMismatch = true;
|
||||
}
|
||||
} else if (!(mPastSigningCertificates == null
|
||||
&& mPastSigningCertificatesFlags == null)) {
|
||||
pastMismatch = true;
|
||||
}
|
||||
if (pastMismatch) {
|
||||
throw new IllegalStateException("SigningDetails must have a one to one mapping "
|
||||
+ "between pastSigningCertificates and pastSigningCertificatesFlags");
|
||||
}
|
||||
}
|
||||
/** build a {@code SigningDetails} object */
|
||||
public SigningDetails build()
|
||||
throws CertificateException {
|
||||
checkInvariants();
|
||||
return new SigningDetails(mSignatures, mSignatureSchemeVersion,
|
||||
mPastSigningCertificates, mPastSigningCertificatesFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,10 +80,22 @@ public class ApkSignatureVerifier {
|
||||
ApkSignatureSchemeV3Verifier.verify(apkPath);
|
||||
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
|
||||
Signature[] signerSigs = convertToSignatures(signerCerts);
|
||||
return new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V3);
|
||||
Signature[] pastSignerSigs = null;
|
||||
int[] pastSignerSigsFlags = null;
|
||||
if (vSigner.por != null) {
|
||||
// populate proof-of-rotation information
|
||||
pastSignerSigs = new Signature[vSigner.por.certs.size()];
|
||||
pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
|
||||
for (int i = 0; i < pastSignerSigs.length; i++) {
|
||||
pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
|
||||
pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
|
||||
}
|
||||
}
|
||||
return new PackageParser.SigningDetails(
|
||||
signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
|
||||
pastSignerSigs, pastSignerSigsFlags);
|
||||
} catch (SignatureNotFoundException e) {
|
||||
// not signed with v2, try older if allowed
|
||||
// not signed with v3, try older if allowed
|
||||
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
|
||||
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
|
||||
"No APK Signature Scheme v3 signature in package " + apkPath, e);
|
||||
@@ -92,7 +104,7 @@ public class ApkSignatureVerifier {
|
||||
// APK Signature Scheme v2 signature found but did not verify
|
||||
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
|
||||
"Failed to collect certificates from " + apkPath
|
||||
+ " using APK Signature Scheme v2", e);
|
||||
+ " using APK Signature Scheme v3", e);
|
||||
} finally {
|
||||
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
||||
}
|
||||
@@ -304,25 +316,37 @@ public class ApkSignatureVerifier {
|
||||
}
|
||||
|
||||
// first try v3
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV3");
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "certsOnlyV3");
|
||||
try {
|
||||
ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
|
||||
ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
|
||||
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
|
||||
Signature[] signerSigs = convertToSignatures(signerCerts);
|
||||
return new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V3);
|
||||
Signature[] pastSignerSigs = null;
|
||||
int[] pastSignerSigsFlags = null;
|
||||
if (vSigner.por != null) {
|
||||
// populate proof-of-rotation information
|
||||
pastSignerSigs = new Signature[vSigner.por.certs.size()];
|
||||
pastSignerSigsFlags = new int[vSigner.por.flagsList.size()];
|
||||
for (int i = 0; i < pastSignerSigs.length; i++) {
|
||||
pastSignerSigs[i] = new Signature(vSigner.por.certs.get(i).getEncoded());
|
||||
pastSignerSigsFlags[i] = vSigner.por.flagsList.get(i);
|
||||
}
|
||||
}
|
||||
return new PackageParser.SigningDetails(
|
||||
signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V3,
|
||||
pastSignerSigs, pastSignerSigsFlags);
|
||||
} catch (SignatureNotFoundException e) {
|
||||
// not signed with v2, try older if allowed
|
||||
// not signed with v3, try older if allowed
|
||||
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
|
||||
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
|
||||
"No APK Signature Scheme v3 signature in package " + apkPath, e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// APK Signature Scheme v2 signature found but did not verify
|
||||
// APK Signature Scheme v3 signature found but did not verify
|
||||
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
|
||||
"Failed to collect certificates from " + apkPath
|
||||
+ " using APK Signature Scheme v2", e);
|
||||
+ " using APK Signature Scheme v3", e);
|
||||
} finally {
|
||||
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user