diff options
11 files changed, 382 insertions, 152 deletions
diff --git a/Android.bp b/Android.bp index 53417e8e8042..3b62ecc30fc6 100644 --- a/Android.bp +++ b/Android.bp @@ -973,7 +973,6 @@ filegroup { srcs: [ "core/java/android/os/incremental/IIncrementalService.aidl", "core/java/android/os/incremental/IncrementalNewFileParams.aidl", - "core/java/android/os/incremental/IncrementalSignature.aidl", ], path: "core/java", } diff --git a/core/java/android/os/incremental/IncrementalNewFileParams.aidl b/core/java/android/os/incremental/IncrementalNewFileParams.aidl index 182732cebdf1..8faf158b72a0 100644 --- a/core/java/android/os/incremental/IncrementalNewFileParams.aidl +++ b/core/java/android/os/incremental/IncrementalNewFileParams.aidl @@ -16,8 +16,6 @@ package android.os.incremental; -import android.os.incremental.IncrementalSignature; - /** * All the parameters to create a new file on IncFS * FileId is a 16 byte-long identifier. @@ -27,5 +25,5 @@ parcelable IncrementalNewFileParams { long size; byte[] fileId; byte[] metadata; - @nullable IncrementalSignature signature; + @nullable byte[] signature; } diff --git a/core/java/android/os/incremental/IncrementalSignature.aidl b/core/java/android/os/incremental/IncrementalSignature.aidl deleted file mode 100644 index 729e8e5556a8..000000000000 --- a/core/java/android/os/incremental/IncrementalSignature.aidl +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.os.incremental; - -/** {@hide} */ -parcelable IncrementalSignature { - /* - * Stable AIDL doesn't support constants, but here's the possible values - * const int HASH_ALGO_NONE = 0; - * const int HASH_ALGO_SHA256 = 1; - */ - - int hashAlgorithm = 0; - byte[] rootHash; - byte[] additionalData; - byte[] signature; -} diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index bf31bc206278..7092751c0d7e 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -180,11 +178,12 @@ public final class IncrementalStorage { if (id == null && metadata == null) { throw new IOException("File ID and metadata cannot both be null"); } + validateV4Signature(v4signatureBytes); final IncrementalNewFileParams params = new IncrementalNewFileParams(); params.size = size; params.metadata = (metadata == null ? new byte[0] : metadata); params.fileId = idToBytes(id); - params.signature = parseV4Signature(v4signatureBytes); + params.signature = v4signatureBytes; int res = mService.makeFile(mId, path, params); if (res != 0) { throw new IOException("makeFile() failed with errno " + -res); @@ -415,27 +414,23 @@ public final class IncrementalStorage { return new UUID(msb, lsb); } - private static final int INCFS_HASH_SHA256 = 1; private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256 private static final int INCFS_MAX_ADD_DATA_SIZE = 128; /** * Deserialize and validate v4 signature bytes. */ - private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes) + private static void validateV4Signature(@Nullable byte[] v4signatureBytes) throws IOException { if (v4signatureBytes == null || v4signatureBytes.length == 0) { - return null; + return; } final V4Signature signature; - try (DataInputStream input = new DataInputStream( - new ByteArrayInputStream(v4signatureBytes))) { - try { - signature = V4Signature.readFrom(input); - } catch (IOException e) { - throw new IOException("Failed to read v4 signature:", e); - } + try { + signature = V4Signature.readFrom(v4signatureBytes); + } catch (IOException e) { + throw new IOException("Failed to read v4 signature:", e); } if (!signature.isVersionSupported()) { @@ -443,25 +438,27 @@ public final class IncrementalStorage { + " is not supported"); } - final byte[] rootHash = signature.verityRootHash; - final byte[] additionalData = signature.v3Digest; - final byte[] pkcs7Signature = signature.pkcs7SignatureBlock; + final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray( + signature.hashingInfo); + final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray( + signature.signingInfo); - if (rootHash.length != INCFS_MAX_HASH_SIZE) { - throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) { + throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm); + } + if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) { + throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize); } - if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { + if (hashingInfo.salt != null && hashingInfo.salt.length > 0) { + throw new IOException("Unsupported salt: " + hashingInfo.salt); + } + if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) { + throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + } + if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { throw new IOException( "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes"); } - - IncrementalSignature result = new IncrementalSignature(); - result.hashAlgorithm = INCFS_HASH_SHA256; - result.rootHash = rootHash; - result.additionalData = additionalData; - result.signature = pkcs7Signature; - - return result; } /** diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 6d334f539fc9..71f931da1a92 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -20,9 +20,12 @@ import android.os.ParcelFileDescriptor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * V4 signature fields. @@ -31,30 +34,95 @@ import java.io.IOException; */ public class V4Signature { public static final String EXT = ".idsig"; - public static final int SUPPORTED_VERSION = 1; + public static final int SUPPORTED_VERSION = 2; - public final int version; - public final byte[] verityRootHash; - public final byte[] v3Digest; - public final byte[] pkcs7SignatureBlock; + public static final int HASHING_ALGORITHM_SHA256 = 1; + public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12; + + /** + * IncFS hashing data. + */ + public static class HashingInfo { + public final int hashAlgorithm; // only 1 == SHA256 supported + public final byte log2BlockSize; // only 12 (block size 4096) supported now + public final byte[] salt; // used exactly as in fs-verity, 32 bytes max + public final byte[] rawRootHash; // salted digest of the first Merkle tree page + + HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) { + this.hashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + this.salt = salt; + this.rawRootHash = rawRootHash; + } + + /** + * Constructs HashingInfo from byte array. + */ + public static HashingInfo fromByteArray(byte[] bytes) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + final int hashAlgorithm = buffer.getInt(); + final byte log2BlockSize = buffer.get(); + byte[] salt = readBytes(buffer); + byte[] rawRootHash = readBytes(buffer); + return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash); + } + } + + /** + * V4 signature data. + */ + public static class SigningInfo { + public final byte[] v3Digest; // used to match with the corresponding APK + public final byte[] certificate; // ASN.1 DER form + public final byte[] additionalData; // a free-form binary data blob + public final byte[] publicKey; // ASN.1 DER, must match the certificate + public final int signatureAlgorithmId; // see the APK v2 doc for the list + public final byte[] signature; + + SigningInfo(byte[] v3Digest, byte[] certificate, byte[] additionalData, + byte[] publicKey, int signatureAlgorithmId, byte[] signature) { + this.v3Digest = v3Digest; + this.certificate = certificate; + this.additionalData = additionalData; + this.publicKey = publicKey; + this.signatureAlgorithmId = signatureAlgorithmId; + this.signature = signature; + } + + /** + * Constructs SigningInfo from byte array. + */ + public static SigningInfo fromByteArray(byte[] bytes) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + byte[] v3Digest = readBytes(buffer); + byte[] certificate = readBytes(buffer); + byte[] additionalData = readBytes(buffer); + byte[] publicKey = readBytes(buffer); + int signatureAlgorithmId = buffer.getInt(); + byte[] signature = readBytes(buffer); + return new SigningInfo(v3Digest, certificate, additionalData, publicKey, + signatureAlgorithmId, signature); + } + } + + public final int version; // Always 2 for now. + public final byte[] hashingInfo; + public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later. /** * Construct a V4Signature from .idsig file. */ public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException { - final ParcelFileDescriptor dupedFd = pfd.dup(); - final ParcelFileDescriptor.AutoCloseInputStream fdInputStream = - new ParcelFileDescriptor.AutoCloseInputStream(dupedFd); - try (DataInputStream stream = new DataInputStream(fdInputStream)) { + try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) { return readFrom(stream); } } /** - * Construct a V4Signature from .idsig file. + * Construct a V4Signature from a byte array. */ public static V4Signature readFrom(byte[] bytes) throws IOException { - try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) { + try (InputStream stream = new ByteArrayInputStream(bytes)) { return readFrom(stream); } } @@ -63,51 +131,131 @@ public class V4Signature { * Store the V4Signature to a byte-array. */ public byte[] toByteArray() { - try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { - try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) { - this.writeTo(steam); - steam.flush(); - } - return byteArrayOutputStream.toByteArray(); + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + this.writeTo(stream); + return stream.toByteArray(); } catch (IOException e) { return null; } } - boolean isVersionSupported() { - return this.version == SUPPORTED_VERSION; + /** + * Combines necessary data to a signed data blob. + * The blob can be validated against signingInfo.signature. + * + * @param fileSize - size of the signed file (APK) + */ + public static byte[] getSigningData(long fileSize, HashingInfo hashingInfo, + SigningInfo signingInfo) { + final int size = + 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( + hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( + signingInfo.v3Digest) + bytesSize(signingInfo.certificate) + bytesSize( + signingInfo.additionalData); + ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(size); + buffer.putLong(fileSize); + buffer.putInt(hashingInfo.hashAlgorithm); + buffer.put(hashingInfo.log2BlockSize); + writeBytes(buffer, hashingInfo.salt); + writeBytes(buffer, hashingInfo.rawRootHash); + writeBytes(buffer, signingInfo.v3Digest); + writeBytes(buffer, signingInfo.certificate); + writeBytes(buffer, signingInfo.additionalData); + return buffer.array(); } - static V4Signature readFrom(DataInputStream stream) throws IOException { - final int version = stream.readInt(); - byte[] verityRootHash = readBytes(stream); - byte[] v3Digest = readBytes(stream); - byte[] pkcs7SignatureBlock = readBytes(stream); - return new V4Signature(version, verityRootHash, v3Digest, pkcs7SignatureBlock); + public boolean isVersionSupported() { + return this.version == SUPPORTED_VERSION; } - V4Signature(int version, byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { + private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) { this.version = version; - this.verityRootHash = verityRootHash; - this.v3Digest = v3Digest; - this.pkcs7SignatureBlock = pkcs7SignatureBlock; + this.hashingInfo = hashingInfo; + this.signingInfo = signingInfo; } - void writeTo(DataOutputStream stream) throws IOException { - stream.writeInt(this.version); - writeBytes(stream, this.verityRootHash); - writeBytes(stream, this.v3Digest); - writeBytes(stream, this.pkcs7SignatureBlock); + private static V4Signature readFrom(InputStream stream) throws IOException { + final int version = readIntLE(stream); + final byte[] hashingInfo = readBytes(stream); + final byte[] signingInfo = readBytes(stream); + return new V4Signature(version, hashingInfo, signingInfo); } - private static byte[] readBytes(DataInputStream stream) throws IOException { - byte[] result = new byte[stream.readInt()]; - stream.read(result); - return result; + private void writeTo(OutputStream stream) throws IOException { + writeIntLE(stream, this.version); + writeBytes(stream, this.hashingInfo); + writeBytes(stream, this.signingInfo); } - private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { - stream.writeInt(bytes.length); + // Utility methods. + private static int bytesSize(byte[] bytes) { + return 4/*length*/ + (bytes == null ? 0 : bytes.length); + } + + private static void readFully(InputStream stream, byte[] buffer) throws IOException { + int len = buffer.length; + int n = 0; + while (n < len) { + int count = stream.read(buffer, n, len - n); + if (count < 0) { + throw new EOFException(); + } + n += count; + } + } + + private static int readIntLE(InputStream stream) throws IOException { + final byte[] buffer = new byte[4]; + readFully(stream, buffer); + return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } + + private static void writeIntLE(OutputStream stream, int v) throws IOException { + final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt( + v).array(); + stream.write(buffer); + } + + private static byte[] readBytes(InputStream stream) throws IOException { + try { + final int size = readIntLE(stream); + final byte[] bytes = new byte[size]; + readFully(stream, bytes); + return bytes; + } catch (EOFException ignored) { + return null; + } + } + + private static byte[] readBytes(ByteBuffer buffer) throws IOException { + if (buffer.remaining() < 4) { + throw new EOFException(); + } + final int size = buffer.getInt(); + if (buffer.remaining() < size) { + throw new EOFException(); + } + final byte[] bytes = new byte[size]; + buffer.get(bytes); + return bytes; + } + + private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException { + if (bytes == null) { + writeIntLE(stream, 0); + return; + } + writeIntLE(stream, bytes.length); stream.write(bytes); } + + private static void writeBytes(ByteBuffer buffer, byte[] bytes) { + if (bytes == null) { + buffer.putInt(0); + return; + } + buffer.putInt(bytes.length); + buffer.put(bytes); + } } diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index abd04cc2b0e7..79eb9f6e749c 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -16,6 +16,8 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; @@ -211,6 +213,12 @@ public class ApkSignatureSchemeV3Verifier { verityDigest, apk.length(), signatureInfo); } + if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) { + result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA512); + } else if (contentDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) { + result.digest = contentDigests.get(CONTENT_DIGEST_CHUNKED_SHA256); + } + return result; } @@ -568,6 +576,7 @@ public class ApkSignatureSchemeV3Verifier { public final VerifiedProofOfRotation por; public byte[] verityRootHash; + public byte[] digest; public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) { this.certs = certs; diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java index b6b8089b1743..8c240d99f590 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java @@ -16,13 +16,32 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; +import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; + import android.os.incremental.IncrementalManager; +import android.os.incremental.V4Signature; +import android.util.Pair; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; import java.security.cert.Certificate; - -import sun.security.pkcs.PKCS7; -import sun.security.pkcs.ParsingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; /** * APK Signature Scheme v4 verifier. @@ -30,24 +49,118 @@ import sun.security.pkcs.ParsingException; * @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. + * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the + * certificates associated with each signer. */ - public static Certificate[] extractCertificates(String apkFile) + public static VerifiedSigner 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."); + final File apk = new File(apkFile); + final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature( + apk.getAbsolutePath()); + if (signatureBytes == null || signatureBytes.length == 0) { + throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS."); + } + + final V4Signature signature; + final V4Signature.HashingInfo hashingInfo; + final V4Signature.SigningInfo signingInfo; + try { + signature = V4Signature.readFrom(signatureBytes); + + if (!signature.isVersionSupported()) { + throw new SecurityException( + "v4 signature version " + signature.version + " is not supported"); + } + + hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo); + signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo); + } catch (IOException e) { + throw new SignatureNotFoundException("Failed to read V4 signature.", e); } + final byte[] signedData = V4Signature.getSigningData(apk.length(), hashingInfo, + signingInfo); + + return verifySigner(signingInfo, signedData); + } + + private static VerifiedSigner verifySigner(V4Signature.SigningInfo signingInfo, + final byte[] signedData) throws SecurityException { + if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) { + throw new SecurityException("No supported signatures found"); + } + + final int signatureAlgorithmId = signingInfo.signatureAlgorithmId; + final byte[] signatureBytes = signingInfo.signature; + final byte[] publicKeyBytes = signingInfo.publicKey; + final byte[] encodedCert = signingInfo.certificate; + + String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(signatureAlgorithmId); + Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithmId); + String jcaSignatureAlgorithm = signatureAlgorithmParams.first; + AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; + boolean sigVerified; try { - PKCS7 pkcs7 = new PKCS7(rawSignature); - return pkcs7.getCertificates(); - } catch (ParsingException e) { - throw new SecurityException("Failed to parse signature and extract certificates", e); + PublicKey publicKey = + KeyFactory.getInstance(keyAlgorithm) + .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + Signature sig = Signature.getInstance(jcaSignatureAlgorithm); + sig.initVerify(publicKey); + if (jcaSignatureAlgorithmParams != null) { + sig.setParameter(jcaSignatureAlgorithmParams); + } + sig.update(signedData); + sigVerified = sig.verify(signatureBytes); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify " + jcaSignatureAlgorithm + " signature", e); } + if (!sigVerified) { + throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); + } + + // Signature over signedData has verified. + CertificateFactory certFactory; + try { + certFactory = CertificateFactory.getInstance("X.509"); + } catch (CertificateException e) { + throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); + } + + X509Certificate certificate; + try { + certificate = (X509Certificate) + certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate", e); + } + certificate = new VerbatimX509Certificate(certificate, encodedCert); + + byte[] certificatePublicKeyBytes = certificate.getPublicKey().getEncoded(); + if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { + throw new SecurityException( + "Public key mismatch between certificate and signature record"); + } + + return new VerifiedSigner(new Certificate[]{certificate}, signingInfo.v3Digest); + } + + /** + * Verified APK Signature Scheme v4 signer, including V3 digest. + * + * @hide for internal use only. + */ + public static class VerifiedSigner { + public final Certificate[] certs; + public byte[] v3Digest; + + public VerifiedSigner(Certificate[] certs, byte[] v3Digest) { + this.certs = certs; + this.v3Digest = v3Digest; + } + } } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index f325c2171c23..c1cee48cc663 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -168,7 +168,7 @@ public class ApkSignatureVerifier { /** * Verifies the provided APK using V4 schema. * - * @param verifyFull whether to verify all contents of this APK or just collect certificates. + * @param verifyFull whether to verify (V4 vs V3) 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 @@ -178,30 +178,34 @@ public class ApkSignatureVerifier { throws SignatureNotFoundException, PackageParserException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4"); try { - Certificate[] certs = ApkSignatureSchemeV4Verifier.extractCertificates(apkPath); - Certificate[][] signerCerts = new Certificate[][]{certs}; + ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner = + ApkSignatureSchemeV4Verifier.extractCertificates(apkPath); + Certificate[][] signerCerts = new Certificate[][]{vSigner.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) { + // v4 is an add-on and requires v3 signature to validate against its certificates + ApkSignatureSchemeV3Verifier.VerifiedSigner nonstreaming = + ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath); + Certificate[][] nonstreamingCerts = new Certificate[][]{nonstreaming.certs}; + Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts); + + if (nonstreamingSigs.length != signerSigs.length) { 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); + "Invalid number of certificates: " + nonstreaming.certs.length); } 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); + if (!nonstreamingSigs[i].equals(signerSigs[i])) { + throw new SecurityException("V4 signature certificate does not match V3"); } } + + // TODO(b/151240006): add support for v2 digest and make it mandatory. + if (!ArrayUtils.isEmpty(vSigner.v3Digest) && !ArrayUtils.equals(vSigner.v3Digest, + nonstreaming.digest, vSigner.v3Digest.length)) { + throw new SecurityException("V3 digest in V4 signature does not match V3"); + } } return new PackageParser.SigningDetails(signerSigs, diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index 6f9d012d3145..b96fbf5f5359 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -172,26 +172,25 @@ static bool readChunk(int fd, std::vector<uint8_t>& data) { BlockHeader readHeader(std::span<uint8_t>& data); -static inline int32_t readBEInt32(borrowed_fd fd) { +static inline int32_t readLEInt32(borrowed_fd fd) { int32_t result; ReadFully(fd, &result, sizeof(result)); - result = int32_t(be32toh(result)); + result = int32_t(le32toh(result)); return result; } static inline std::vector<char> readBytes(borrowed_fd fd) { - int32_t size = readBEInt32(fd); + int32_t size = readLEInt32(fd); std::vector<char> result(size); ReadFully(fd, result.data(), size); return result; } static inline int32_t skipIdSigHeaders(borrowed_fd fd) { - readBEInt32(fd); // version - readBytes(fd); // verityRootHash - readBytes(fd); // v3Digest - readBytes(fd); // pkcs7SignatureBlock - return readBEInt32(fd); // size of the verity tree + readLEInt32(fd); // version + readBytes(fd); // hashingInfo + readBytes(fd); // signingInfo + return readLEInt32(fd); // size of the verity tree } static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) { diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 3fcb57a83cf5..2dbbc5ac6806 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -178,15 +178,9 @@ static std::tuple<int, incfs::FileId, incfs::NewFileParams> toMakeFileParams( nfp.size = params.size; nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()}; if (!params.signature) { - nfp.verification = {}; + nfp.signature = {}; } else { - nfp.verification.hashAlgorithm = IncFsHashAlgortithm(params.signature->hashAlgorithm); - nfp.verification.rootHash = {(const char*)params.signature->rootHash.data(), - (IncFsSize)params.signature->rootHash.size()}; - nfp.verification.additionalData = {(const char*)params.signature->additionalData.data(), - (IncFsSize)params.signature->additionalData.size()}; - nfp.verification.signature = {(const char*)params.signature->signature.data(), - (IncFsSize)params.signature->signature.size()}; + nfp.signature = {(const char*)params.signature->data(), (IncFsSize)params.signature->size()}; } return {0, id, nfp}; } diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index cccd01339177..727593664895 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1155,7 +1155,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ // Create new lib file without signature info incfs::NewFileParams libFileParams{}; libFileParams.size = uncompressedLen; - libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE; + libFileParams.signature = {}; // Metadata of the new lib file is its relative path IncFsSpan libFileMetadata; libFileMetadata.data = targetLibPath.c_str(); |