1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.util.apk; 18 19 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; 20 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; 21 import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; 22 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; 23 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; 24 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; 25 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; 26 import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm; 27 import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; 28 import static android.util.apk.ApkSigningBlockUtils.verifyProofOfRotationStruct; 29 30 import android.os.Build; 31 import android.util.ArrayMap; 32 import android.util.Pair; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.IOException; 36 import java.io.RandomAccessFile; 37 import java.nio.BufferUnderflowException; 38 import java.nio.ByteBuffer; 39 import java.security.DigestException; 40 import java.security.InvalidAlgorithmParameterException; 41 import java.security.InvalidKeyException; 42 import java.security.KeyFactory; 43 import java.security.MessageDigest; 44 import java.security.NoSuchAlgorithmException; 45 import java.security.PublicKey; 46 import java.security.Signature; 47 import java.security.SignatureException; 48 import java.security.cert.CertificateEncodingException; 49 import java.security.cert.CertificateException; 50 import java.security.cert.CertificateFactory; 51 import java.security.cert.X509Certificate; 52 import java.security.spec.AlgorithmParameterSpec; 53 import java.security.spec.InvalidKeySpecException; 54 import java.security.spec.X509EncodedKeySpec; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.List; 58 import java.util.Map; 59 60 /** 61 * APK Signature Scheme v3 verifier. 62 * 63 * @hide for internal use only. 64 */ 65 public class ApkSignatureSchemeV3Verifier { 66 67 /** 68 * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing. 69 */ 70 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3; 71 72 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; 73 74 /** 75 * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature. 76 * 77 * <p><b>NOTE: This method does not verify the signature.</b> 78 */ hasSignature(String apkFile)79 public static boolean hasSignature(String apkFile) throws IOException { 80 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { 81 findSignature(apk); 82 return true; 83 } catch (SignatureNotFoundException e) { 84 return false; 85 } 86 } 87 88 /** 89 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates 90 * associated with each signer. 91 * 92 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. 93 * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does 94 * not 95 * verify. 96 * @throws IOException if an I/O error occurs while reading the APK file. 97 */ verify(String apkFile)98 public static VerifiedSigner verify(String apkFile) 99 throws SignatureNotFoundException, SecurityException, IOException { 100 return verify(apkFile, true); 101 } 102 103 /** 104 * Returns the certificates associated with each signer for the given APK without verification. 105 * This method is dangerous and should not be used, unless the caller is absolutely certain the 106 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3 107 * Block while gathering signer information. The APK contents are not verified. 108 * 109 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. 110 * @throws IOException if an I/O error occurs while reading the APK file. 111 */ unsafeGetCertsWithoutVerification(String apkFile)112 public static VerifiedSigner unsafeGetCertsWithoutVerification(String apkFile) 113 throws SignatureNotFoundException, SecurityException, IOException { 114 return verify(apkFile, false); 115 } 116 verify(String apkFile, boolean verifyIntegrity)117 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) 118 throws SignatureNotFoundException, SecurityException, IOException { 119 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { 120 return verify(apk, verifyIntegrity); 121 } 122 } 123 124 /** 125 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates 126 * associated with each signer. 127 * 128 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. 129 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does 130 * not 131 * verify. 132 * @throws IOException if an I/O error occurs while reading the APK file. 133 */ verify(RandomAccessFile apk, boolean verifyIntegrity)134 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) 135 throws SignatureNotFoundException, SecurityException, IOException { 136 SignatureInfo signatureInfo = findSignature(apk); 137 return verify(apk, signatureInfo, verifyIntegrity); 138 } 139 140 /** 141 * Returns the APK Signature Scheme v3 block contained in the provided APK file and the 142 * additional information relevant for verifying the block against the file. 143 * 144 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3. 145 * @throws IOException if an I/O error occurs while reading the APK file. 146 */ findSignature(RandomAccessFile apk)147 public static SignatureInfo findSignature(RandomAccessFile apk) 148 throws IOException, SignatureNotFoundException { 149 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); 150 } 151 152 /** 153 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3 154 * Block. 155 * 156 * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it 157 * against the APK file. 158 */ verify( RandomAccessFile apk, SignatureInfo signatureInfo, boolean doVerifyIntegrity)159 private static VerifiedSigner verify( 160 RandomAccessFile apk, 161 SignatureInfo signatureInfo, 162 boolean doVerifyIntegrity) throws SecurityException, IOException { 163 int signerCount = 0; 164 Map<Integer, byte[]> contentDigests = new ArrayMap<>(); 165 Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> result = null; 166 CertificateFactory certFactory; 167 try { 168 certFactory = CertificateFactory.getInstance("X.509"); 169 } catch (CertificateException e) { 170 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 171 } 172 ByteBuffer signers; 173 try { 174 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); 175 } catch (IOException e) { 176 throw new SecurityException("Failed to read list of signers", e); 177 } 178 while (signers.hasRemaining()) { 179 try { 180 ByteBuffer signer = getLengthPrefixedSlice(signers); 181 result = verifySigner(signer, contentDigests, certFactory); 182 signerCount++; 183 } catch (PlatformNotSupportedException e) { 184 // this signer is for a different platform, ignore it. 185 continue; 186 } catch (IOException | BufferUnderflowException | SecurityException e) { 187 throw new SecurityException( 188 "Failed to parse/verify signer #" + signerCount + " block", 189 e); 190 } 191 } 192 193 if (signerCount < 1 || result == null) { 194 throw new SecurityException("No signers found"); 195 } 196 197 if (signerCount != 1) { 198 throw new SecurityException("APK Signature Scheme V3 only supports one signer: " 199 + "multiple signers found."); 200 } 201 202 if (contentDigests.isEmpty()) { 203 throw new SecurityException("No content digests found"); 204 } 205 206 if (doVerifyIntegrity) { 207 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); 208 } 209 210 byte[] verityRootHash = null; 211 if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { 212 byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); 213 verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength( 214 verityDigest, apk.length(), signatureInfo); 215 } 216 217 return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests); 218 } 219 220 private static Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)221 verifySigner( 222 ByteBuffer signerBlock, 223 Map<Integer, byte[]> contentDigests, 224 CertificateFactory certFactory) 225 throws SecurityException, IOException, PlatformNotSupportedException { 226 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); 227 int minSdkVersion = signerBlock.getInt(); 228 int maxSdkVersion = signerBlock.getInt(); 229 230 if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) { 231 // this signature isn't meant to be used with this platform, skip it. 232 throw new PlatformNotSupportedException( 233 "Signer not supported by this platform " 234 + "version. This platform: " + Build.VERSION.SDK_INT 235 + ", signer minSdkVersion: " + minSdkVersion 236 + ", maxSdkVersion: " + maxSdkVersion); 237 } 238 239 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); 240 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); 241 242 int signatureCount = 0; 243 int bestSigAlgorithm = -1; 244 byte[] bestSigAlgorithmSignatureBytes = null; 245 List<Integer> signaturesSigAlgorithms = new ArrayList<>(); 246 while (signatures.hasRemaining()) { 247 signatureCount++; 248 try { 249 ByteBuffer signature = getLengthPrefixedSlice(signatures); 250 if (signature.remaining() < 8) { 251 throw new SecurityException("Signature record too short"); 252 } 253 int sigAlgorithm = signature.getInt(); 254 signaturesSigAlgorithms.add(sigAlgorithm); 255 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) { 256 continue; 257 } 258 if ((bestSigAlgorithm == -1) 259 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { 260 bestSigAlgorithm = sigAlgorithm; 261 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature); 262 } 263 } catch (IOException | BufferUnderflowException e) { 264 throw new SecurityException( 265 "Failed to parse signature record #" + signatureCount, 266 e); 267 } 268 } 269 if (bestSigAlgorithm == -1) { 270 if (signatureCount == 0) { 271 throw new SecurityException("No signatures found"); 272 } else { 273 throw new SecurityException("No supported signatures found"); 274 } 275 } 276 277 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm); 278 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = 279 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm); 280 String jcaSignatureAlgorithm = signatureAlgorithmParams.first; 281 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; 282 boolean sigVerified; 283 try { 284 PublicKey publicKey = 285 KeyFactory.getInstance(keyAlgorithm) 286 .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); 287 Signature sig = Signature.getInstance(jcaSignatureAlgorithm); 288 sig.initVerify(publicKey); 289 if (jcaSignatureAlgorithmParams != null) { 290 sig.setParameter(jcaSignatureAlgorithmParams); 291 } 292 sig.update(signedData); 293 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); 294 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException 295 | InvalidAlgorithmParameterException | SignatureException e) { 296 throw new SecurityException( 297 "Failed to verify " + jcaSignatureAlgorithm + " signature", e); 298 } 299 if (!sigVerified) { 300 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); 301 } 302 303 // Signature over signedData has verified. 304 305 byte[] contentDigest = null; 306 signedData.clear(); 307 ByteBuffer digests = getLengthPrefixedSlice(signedData); 308 List<Integer> digestsSigAlgorithms = new ArrayList<>(); 309 int digestCount = 0; 310 while (digests.hasRemaining()) { 311 digestCount++; 312 try { 313 ByteBuffer digest = getLengthPrefixedSlice(digests); 314 if (digest.remaining() < 8) { 315 throw new IOException("Record too short"); 316 } 317 int sigAlgorithm = digest.getInt(); 318 digestsSigAlgorithms.add(sigAlgorithm); 319 if (sigAlgorithm == bestSigAlgorithm) { 320 contentDigest = readLengthPrefixedByteArray(digest); 321 } 322 } catch (IOException | BufferUnderflowException e) { 323 throw new IOException("Failed to parse digest record #" + digestCount, e); 324 } 325 } 326 327 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) { 328 throw new SecurityException( 329 "Signature algorithms don't match between digests and signatures records"); 330 } 331 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm); 332 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest); 333 if ((previousSignerDigest != null) 334 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { 335 throw new SecurityException( 336 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) 337 + " contents digest does not match the digest specified by a " 338 + "preceding signer"); 339 } 340 341 ByteBuffer certificates = getLengthPrefixedSlice(signedData); 342 List<X509Certificate> certs = new ArrayList<>(); 343 int certificateCount = 0; 344 while (certificates.hasRemaining()) { 345 certificateCount++; 346 byte[] encodedCert = readLengthPrefixedByteArray(certificates); 347 X509Certificate certificate; 348 try { 349 certificate = (X509Certificate) 350 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); 351 } catch (CertificateException e) { 352 throw new SecurityException("Failed to decode certificate #" + certificateCount, e); 353 } 354 certificate = new VerbatimX509Certificate(certificate, encodedCert); 355 certs.add(certificate); 356 } 357 358 if (certs.isEmpty()) { 359 throw new SecurityException("No certificates listed"); 360 } 361 X509Certificate mainCertificate = certs.get(0); 362 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); 363 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { 364 throw new SecurityException( 365 "Public key mismatch between certificate and signature record"); 366 } 367 368 int signedMinSDK = signedData.getInt(); 369 if (signedMinSDK != minSdkVersion) { 370 throw new SecurityException( 371 "minSdkVersion mismatch between signed and unsigned in v3 signer block."); 372 } 373 374 int signedMaxSDK = signedData.getInt(); 375 if (signedMaxSDK != maxSdkVersion) { 376 throw new SecurityException( 377 "maxSdkVersion mismatch between signed and unsigned in v3 signer block."); 378 } 379 380 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); 381 return verifyAdditionalAttributes(additionalAttrs, certs, certFactory); 382 } 383 384 private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; 385 386 private static Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation> verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs, CertificateFactory certFactory)387 verifyAdditionalAttributes(ByteBuffer attrs, List<X509Certificate> certs, 388 CertificateFactory certFactory) throws IOException { 389 X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]); 390 ApkSigningBlockUtils.VerifiedProofOfRotation por = null; 391 392 while (attrs.hasRemaining()) { 393 ByteBuffer attr = getLengthPrefixedSlice(attrs); 394 if (attr.remaining() < 4) { 395 throw new IOException("Remaining buffer too short to contain additional attribute " 396 + "ID. Remaining: " + attr.remaining()); 397 } 398 int id = attr.getInt(); 399 switch (id) { 400 case PROOF_OF_ROTATION_ATTR_ID: 401 if (por != null) { 402 throw new SecurityException("Encountered multiple Proof-of-rotation records" 403 + " when verifying APK Signature Scheme v3 signature"); 404 } 405 por = verifyProofOfRotationStruct(attr, certFactory); 406 // make sure that the last certificate in the Proof-of-rotation record matches 407 // the one used to sign this APK. 408 try { 409 if (por.certs.size() > 0 410 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(), 411 certChain[0].getEncoded())) { 412 throw new SecurityException("Terminal certificate in Proof-of-rotation" 413 + " record does not match APK signing certificate"); 414 } 415 } catch (CertificateEncodingException e) { 416 throw new SecurityException("Failed to encode certificate when comparing" 417 + " Proof-of-rotation record and signing certificate", e); 418 } 419 420 break; 421 default: 422 // not the droid we're looking for, move along, move along. 423 break; 424 } 425 } 426 return Pair.create(certChain, por); 427 } 428 getVerityRootHash(String apkPath)429 static byte[] getVerityRootHash(String apkPath) 430 throws IOException, SignatureNotFoundException, SecurityException { 431 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 432 SignatureInfo signatureInfo = findSignature(apk); 433 VerifiedSigner vSigner = verify(apk, false); 434 return vSigner.verityRootHash; 435 } 436 } 437 generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)438 static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) 439 throws IOException, SignatureNotFoundException, SecurityException, DigestException, 440 NoSuchAlgorithmException { 441 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 442 SignatureInfo signatureInfo = findSignature(apk); 443 return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); 444 } 445 } 446 generateApkVerityRootHash(String apkPath)447 static byte[] generateApkVerityRootHash(String apkPath) 448 throws NoSuchAlgorithmException, DigestException, IOException, 449 SignatureNotFoundException { 450 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 451 SignatureInfo signatureInfo = findSignature(apk); 452 VerifiedSigner vSigner = verify(apk, false); 453 if (vSigner.verityRootHash == null) { 454 return null; 455 } 456 return VerityBuilder.generateApkVerityRootHash( 457 apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); 458 } 459 } 460 461 /** 462 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure. 463 * 464 * @hide for internal use only. 465 */ 466 public static class VerifiedSigner { 467 public final X509Certificate[] certs; 468 public final ApkSigningBlockUtils.VerifiedProofOfRotation por; 469 470 public final byte[] verityRootHash; 471 // Algorithm -> digest map of signed digests in the signature. 472 // All these are verified if requested. 473 public final Map<Integer, byte[]> contentDigests; 474 VerifiedSigner(X509Certificate[] certs, ApkSigningBlockUtils.VerifiedProofOfRotation por, byte[] verityRootHash, Map<Integer, byte[]> contentDigests)475 public VerifiedSigner(X509Certificate[] certs, 476 ApkSigningBlockUtils.VerifiedProofOfRotation por, 477 byte[] verityRootHash, Map<Integer, byte[]> contentDigests) { 478 this.certs = certs; 479 this.por = por; 480 this.verityRootHash = verityRootHash; 481 this.contentDigests = contentDigests; 482 } 483 484 } 485 486 private static class PlatformNotSupportedException extends Exception { 487 PlatformNotSupportedException(String s)488 PlatformNotSupportedException(String s) { 489 super(s); 490 } 491 } 492 } 493