diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index b07b5223d2961..8e4e99ec28fea 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -56,6 +56,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.OptionalInt; /** * APK Signature Scheme v3 verifier. @@ -70,6 +71,7 @@ public class ApkSignatureSchemeV3Verifier { public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3; private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; + private static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61; /** * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature. @@ -133,8 +135,23 @@ public class ApkSignatureSchemeV3Verifier { */ private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) throws SignatureNotFoundException, SecurityException, IOException { - SignatureInfo signatureInfo = findSignature(apk); - return verify(apk, signatureInfo, verifyIntegrity); + ApkSignatureSchemeV3Verifier verifier = new ApkSignatureSchemeV3Verifier(apk, + verifyIntegrity); + try { + SignatureInfo signatureInfo = findSignature(apk, APK_SIGNATURE_SCHEME_V31_BLOCK_ID); + return verifier.verify(signatureInfo, APK_SIGNATURE_SCHEME_V31_BLOCK_ID); + } catch (SignatureNotFoundException ignored) { + // This is expected if the APK is not using v3.1 to target rotation. + } catch (PlatformNotSupportedException ignored) { + // This is expected if the APK is targeting a platform version later than that of the + // device for rotation. + } + try { + SignatureInfo signatureInfo = findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); + return verifier.verify(signatureInfo, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); + } catch (PlatformNotSupportedException e) { + throw new SecurityException(e); + } } /** @@ -146,7 +163,31 @@ public class ApkSignatureSchemeV3Verifier { */ public static SignatureInfo findSignature(RandomAccessFile apk) throws IOException, SignatureNotFoundException { - return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); + return findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); + } + + /* + * Returns the APK Signature Scheme v3 block in the provided {@code apk} file with the specified + * {@code blockId} and the additional information relevant for verifying the block against the + * file. + * + * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3 + * @throws IOException if an I/O error occurs while reading the APK file + **/ + private static SignatureInfo findSignature(RandomAccessFile apk, int blockId) + throws IOException, SignatureNotFoundException { + return ApkSigningBlockUtils.findSignature(apk, blockId); + } + + private final RandomAccessFile mApk; + private final boolean mVerifyIntegrity; + private OptionalInt mOptionalRotationMinSdkVersion = OptionalInt.empty(); + private int mSignerMinSdkVersion; + private int mBlockId; + + private ApkSignatureSchemeV3Verifier(RandomAccessFile apk, boolean verifyIntegrity) { + mApk = apk; + mVerifyIntegrity = verifyIntegrity; } /** @@ -156,10 +197,9 @@ public class ApkSignatureSchemeV3Verifier { * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it * against the APK file. */ - private static VerifiedSigner verify( - RandomAccessFile apk, - SignatureInfo signatureInfo, - boolean doVerifyIntegrity) throws SecurityException, IOException { + private VerifiedSigner verify(SignatureInfo signatureInfo, int blockId) + throws SecurityException, IOException, PlatformNotSupportedException { + mBlockId = blockId; int signerCount = 0; Map contentDigests = new ArrayMap<>(); Pair result = null; @@ -191,7 +231,13 @@ public class ApkSignatureSchemeV3Verifier { } if (signerCount < 1 || result == null) { - throw new SecurityException("No signers found"); + // There must always be a valid signer targeting the device SDK version for a v3.0 + // signature. + if (blockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) { + throw new SecurityException("No signers found"); + } + throw new PlatformNotSupportedException( + "None of the signers support the current platform version"); } if (signerCount != 1) { @@ -203,21 +249,21 @@ public class ApkSignatureSchemeV3Verifier { throw new SecurityException("No content digests found"); } - if (doVerifyIntegrity) { - ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); + if (mVerifyIntegrity) { + ApkSigningBlockUtils.verifyIntegrity(contentDigests, mApk, signatureInfo); } byte[] verityRootHash = null; if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength( - verityDigest, apk.getChannel().size(), signatureInfo); + verityDigest, mApk.getChannel().size(), signatureInfo); } return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests); } - private static Pair + private Pair verifySigner( ByteBuffer signerBlock, Map contentDigests, @@ -228,6 +274,14 @@ public class ApkSignatureSchemeV3Verifier { int maxSdkVersion = signerBlock.getInt(); if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) { + // if this is a v3.1 block then save the minimum SDK version for rotation for comparison + // against the v3.0 additional attribute. + if (mBlockId == APK_SIGNATURE_SCHEME_V31_BLOCK_ID) { + if (!mOptionalRotationMinSdkVersion.isPresent() + || mOptionalRotationMinSdkVersion.getAsInt() > minSdkVersion) { + mOptionalRotationMinSdkVersion = OptionalInt.of(minSdkVersion); + } + } // this signature isn't meant to be used with this platform, skip it. throw new PlatformNotSupportedException( "Signer not supported by this platform " @@ -370,6 +424,7 @@ public class ApkSignatureSchemeV3Verifier { throw new SecurityException( "minSdkVersion mismatch between signed and unsigned in v3 signer block."); } + mSignerMinSdkVersion = signedMinSDK; int signedMaxSDK = signedData.getInt(); if (signedMaxSDK != maxSdkVersion) { @@ -382,10 +437,12 @@ public class ApkSignatureSchemeV3Verifier { } private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; + private static final int ROTATION_MIN_SDK_VERSION_ATTR_ID = 0x559f8b02; + private static final int ROTATION_ON_DEV_RELEASE_ATTR_ID = 0xc2a6b3ba; - private static Pair + private Pair verifyAdditionalAttributes(ByteBuffer attrs, List certs, - CertificateFactory certFactory) throws IOException { + CertificateFactory certFactory) throws IOException, PlatformNotSupportedException { X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); ApkSigningBlockUtils.VerifiedProofOfRotation por = null; @@ -417,6 +474,49 @@ public class ApkSignatureSchemeV3Verifier { + " Proof-of-rotation record and signing certificate", e); } + break; + case ROTATION_MIN_SDK_VERSION_ATTR_ID: + if (attr.remaining() < 4) { + throw new IOException( + "Remaining buffer too short to contain rotation minSdkVersion " + + "value. Remaining: " + + attr.remaining()); + } + int attrRotationMinSdkVersion = attr.getInt(); + if (!mOptionalRotationMinSdkVersion.isPresent()) { + throw new SecurityException( + "Expected a v3.1 signing block targeting SDK version " + + attrRotationMinSdkVersion + + ", but a v3.1 block was not found"); + } + int rotationMinSdkVersion = mOptionalRotationMinSdkVersion.getAsInt(); + if (rotationMinSdkVersion != attrRotationMinSdkVersion) { + throw new SecurityException( + "Expected a v3.1 signing block targeting SDK version " + + attrRotationMinSdkVersion + + ", but the v3.1 block was targeting " + + rotationMinSdkVersion); + } + break; + case ROTATION_ON_DEV_RELEASE_ATTR_ID: + // This attribute should only be used in a v3.1 signer as it allows the v3.1 + // signature scheme to target a platform under development. + if (mBlockId == APK_SIGNATURE_SCHEME_V31_BLOCK_ID) { + // A platform under development uses the same SDK version as the most + // recently released platform; if this platform's SDK version is the same as + // the minimum of the signer, then only allow this signer to be used if this + // is not a "REL" platform. + if (Build.VERSION.SDK_INT == mSignerMinSdkVersion + && "REL".equals(Build.VERSION.CODENAME)) { + // Set the rotation-min-sdk-version to be verified against the stripping + // attribute in the v3.0 block. + mOptionalRotationMinSdkVersion = OptionalInt.of(mSignerMinSdkVersion); + throw new PlatformNotSupportedException( + "The device is running a release version of " + + mSignerMinSdkVersion + + ", but the signer is targeting a dev release"); + } + } break; default: // not the droid we're looking for, move along, move along.