Loading core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +114 −14 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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. Loading Loading @@ -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); } } /** Loading @@ -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; } /** Loading @@ -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<Integer, byte[]> contentDigests = new ArrayMap<>(); Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> result = null; Loading Loading @@ -191,8 +231,14 @@ public class ApkSignatureSchemeV3Verifier { } if (signerCount < 1 || result == null) { // 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) { throw new SecurityException("APK Signature Scheme V3 only supports one signer: " Loading @@ -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<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, Loading @@ -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 " Loading Loading @@ -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) { Loading @@ -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<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs, CertificateFactory certFactory) throws IOException { CertificateFactory certFactory) throws IOException, PlatformNotSupportedException { X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); ApkSigningBlockUtils.VerifiedProofOfRotation por = null; Loading Loading @@ -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. Loading Loading
core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +114 −14 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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. Loading Loading @@ -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); } } /** Loading @@ -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; } /** Loading @@ -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<Integer, byte[]> contentDigests = new ArrayMap<>(); Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> result = null; Loading Loading @@ -191,8 +231,14 @@ public class ApkSignatureSchemeV3Verifier { } if (signerCount < 1 || result == null) { // 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) { throw new SecurityException("APK Signature Scheme V3 only supports one signer: " Loading @@ -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<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, Loading @@ -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 " Loading Loading @@ -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) { Loading @@ -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<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs, CertificateFactory certFactory) throws IOException { CertificateFactory certFactory) throws IOException, PlatformNotSupportedException { X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); ApkSigningBlockUtils.VerifiedProofOfRotation por = null; Loading Loading @@ -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. Loading