Merge "v4 signing schema parsing and verification."

This commit is contained in:
Alex Buynytskyy
2020-02-12 19:19:06 +00:00
committed by Android (Google) Code Review
3 changed files with 134 additions and 3 deletions

View File

@@ -61,7 +61,6 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper;
import android.content.pm.parsing.AndroidPackage;
import android.content.pm.parsing.ApkParseUtils;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.PackageInfoUtils;
import android.content.pm.parsing.ParsedPackage;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
@@ -5967,12 +5966,14 @@ public class PackageParser {
@IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN,
SigningDetails.SignatureSchemeVersion.JAR,
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3})
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4})
public @interface SignatureSchemeVersion {
int UNKNOWN = 0;
int JAR = 1;
int SIGNING_BLOCK_V2 = 2;
int SIGNING_BLOCK_V3 = 3;
int SIGNING_BLOCK_V4 = 4;
}
@Nullable

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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 android.util.apk;
import android.os.incremental.IncrementalManager;
import java.io.File;
import java.security.cert.Certificate;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.ParsingException;
/**
* APK Signature Scheme v4 verifier.
*
* @hide for internal use only.
*/
public class ApkSignatureSchemeV4Verifier {
/**
* Extracts APK Signature Scheme v4 signatures of the provided APK and returns the certificates
* associated with each signer.
*/
public static Certificate[] extractCertificates(String apkFile)
throws SignatureNotFoundException, SecurityException {
final byte[] rawSignature = IncrementalManager.unsafeGetFileSignature(
new File(apkFile).getAbsolutePath());
if (rawSignature == null || rawSignature.length == 0) {
throw new SignatureNotFoundException("Failed to obtain raw signature from IncFS.");
}
try {
PKCS7 pkcs7 = new PKCS7(rawSignature);
return pkcs7.getCertificates();
} catch (ParsingException e) {
throw new SecurityException("Failed to parse signature and extract certificates", e);
}
}
}

View File

@@ -92,6 +92,24 @@ public class ApkSignatureVerifier {
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
throws PackageParserException {
if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
// V3 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
+ " or newer for package " + apkPath);
}
// first try v4
try {
return verifyV4Signature(apkPath, minSignatureSchemeVersion, verifyFull);
} catch (SignatureNotFoundException e) {
// not signed with v4, try older if allowed
if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v4 signature in package " + apkPath, e);
}
}
if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
// V3 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
@@ -99,7 +117,13 @@ public class ApkSignatureVerifier {
+ " or newer for package " + apkPath);
}
// first try v3
return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
}
private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
throws PackageParserException {
// try v3
try {
return verifyV3Signature(apkPath, verifyFull);
} catch (SignatureNotFoundException e) {
@@ -141,6 +165,59 @@ public class ApkSignatureVerifier {
return verifyV1Signature(apkPath, verifyFull);
}
/**
* Verifies the provided APK using V4 schema.
*
* @param verifyFull whether to verify all contents of this APK or just collect certificates.
* @return the certificates associated with each signer.
* @throws SignatureNotFoundException if there are no V4 signatures in the APK
* @throws PackageParserException if there was a problem collecting certificates
*/
private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
throws SignatureNotFoundException, PackageParserException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
try {
Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
Certificate[][] signerCerts = new Certificate[][]{certs};
Signature[] signerSigs = convertToSignatures(signerCerts);
if (verifyFull) {
// v4 is an add-on and requires v2/v3 signature to validate against its certificates
final PackageParser.SigningDetails nonstreaming = verifyV3AndBelowSignatures(
apkPath, minSignatureSchemeVersion, false);
if (nonstreaming.signatureSchemeVersion <= SignatureSchemeVersion.JAR) {
throw new SecurityException(
"V4 signing block can only be verified along with V2 and above.");
}
if (nonstreaming.signatures.length == 0
|| nonstreaming.signatures.length != signerSigs.length) {
throw new SecurityException("Invalid number of signatures in "
+ nonstreaming.signatureSchemeVersion);
}
for (int i = 0, size = signerSigs.length; i < size; ++i) {
if (!nonstreaming.signatures[i].equals(signerSigs[i])) {
throw new SecurityException("V4 signature certificate does not match "
+ nonstreaming.signatureSchemeVersion);
}
}
}
return new PackageParser.SigningDetails(signerSigs,
SignatureSchemeVersion.SIGNING_BLOCK_V4);
} catch (SignatureNotFoundException e) {
throw e;
} catch (Exception e) {
// APK Signature Scheme v4 signature found but did not verify
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Failed to collect certificates from " + apkPath
+ " using APK Signature Scheme v4", e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
/**
* Verifies the provided APK using V3 schema.
*