1 /* 2 * Copyright (C) 2016 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.SIGNATURE_DSA_WITH_SHA256; 21 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256; 22 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512; 23 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256; 24 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512; 25 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256; 26 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512; 27 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_DSA_WITH_SHA256; 28 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_ECDSA_WITH_SHA256; 29 import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256; 30 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; 31 import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm; 32 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; 33 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; 34 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm; 35 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm; 36 import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; 37 38 import android.util.ArrayMap; 39 import android.util.Pair; 40 41 import java.io.ByteArrayInputStream; 42 import java.io.IOException; 43 import java.io.RandomAccessFile; 44 import java.nio.BufferUnderflowException; 45 import java.nio.ByteBuffer; 46 import java.security.DigestException; 47 import java.security.InvalidAlgorithmParameterException; 48 import java.security.InvalidKeyException; 49 import java.security.KeyFactory; 50 import java.security.MessageDigest; 51 import java.security.NoSuchAlgorithmException; 52 import java.security.PublicKey; 53 import java.security.Signature; 54 import java.security.SignatureException; 55 import java.security.cert.CertificateException; 56 import java.security.cert.CertificateFactory; 57 import java.security.cert.X509Certificate; 58 import java.security.spec.AlgorithmParameterSpec; 59 import java.security.spec.InvalidKeySpecException; 60 import java.security.spec.X509EncodedKeySpec; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.List; 64 import java.util.Map; 65 66 /** 67 * APK Signature Scheme v2 verifier. 68 * 69 * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single 70 * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and 71 * uncompressed contents of ZIP entries. 72 * 73 * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a> 74 * 75 * @hide for internal use only. 76 */ 77 public class ApkSignatureSchemeV2Verifier { 78 79 /** 80 * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing. 81 */ 82 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2; 83 84 private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; 85 86 /** 87 * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature. 88 * 89 * <p><b>NOTE: This method does not verify the signature.</b> 90 */ hasSignature(String apkFile)91 public static boolean hasSignature(String apkFile) throws IOException { 92 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { 93 findSignature(apk); 94 return true; 95 } catch (SignatureNotFoundException e) { 96 return false; 97 } 98 } 99 100 /** 101 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates 102 * associated with each signer. 103 * 104 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 105 * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify. 106 * @throws IOException if an I/O error occurs while reading the APK file. 107 */ verify(String apkFile)108 public static X509Certificate[][] verify(String apkFile) 109 throws SignatureNotFoundException, SecurityException, IOException { 110 VerifiedSigner vSigner = verify(apkFile, true); 111 return vSigner.certs; 112 } 113 114 /** 115 * Returns the certificates associated with each signer for the given APK without verification. 116 * This method is dangerous and should not be used, unless the caller is absolutely certain the 117 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v2 118 * Block while gathering signer information. The APK contents are not verified. 119 * 120 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 121 * @throws IOException if an I/O error occurs while reading the APK file. 122 */ unsafeGetCertsWithoutVerification(String apkFile)123 public static X509Certificate[][] unsafeGetCertsWithoutVerification(String apkFile) 124 throws SignatureNotFoundException, SecurityException, IOException { 125 VerifiedSigner vSigner = verify(apkFile, false); 126 return vSigner.certs; 127 } 128 verify(String apkFile, boolean verifyIntegrity)129 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) 130 throws SignatureNotFoundException, SecurityException, IOException { 131 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { 132 return verify(apk, verifyIntegrity); 133 } 134 } 135 136 /** 137 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates 138 * associated with each signer. 139 * 140 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 141 * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not 142 * verify. 143 * @throws IOException if an I/O error occurs while reading the APK file. 144 */ verify(RandomAccessFile apk, boolean verifyIntegrity)145 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) 146 throws SignatureNotFoundException, SecurityException, IOException { 147 SignatureInfo signatureInfo = findSignature(apk); 148 return verify(apk, signatureInfo, verifyIntegrity); 149 } 150 151 /** 152 * Returns the APK Signature Scheme v2 block contained in the provided APK file and the 153 * additional information relevant for verifying the block against the file. 154 * 155 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 156 * @throws IOException if an I/O error occurs while reading the APK file. 157 */ findSignature(RandomAccessFile apk)158 private static SignatureInfo findSignature(RandomAccessFile apk) 159 throws IOException, SignatureNotFoundException { 160 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID); 161 } 162 163 /** 164 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2 165 * Block. 166 * 167 * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it 168 * against the APK file. 169 */ verify( RandomAccessFile apk, SignatureInfo signatureInfo, boolean doVerifyIntegrity)170 private static VerifiedSigner verify( 171 RandomAccessFile apk, 172 SignatureInfo signatureInfo, 173 boolean doVerifyIntegrity) throws SecurityException, IOException { 174 int signerCount = 0; 175 Map<Integer, byte[]> contentDigests = new ArrayMap<>(); 176 List<X509Certificate[]> signerCerts = new ArrayList<>(); 177 CertificateFactory certFactory; 178 try { 179 certFactory = CertificateFactory.getInstance("X.509"); 180 } catch (CertificateException e) { 181 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 182 } 183 ByteBuffer signers; 184 try { 185 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); 186 } catch (IOException e) { 187 throw new SecurityException("Failed to read list of signers", e); 188 } 189 while (signers.hasRemaining()) { 190 signerCount++; 191 try { 192 ByteBuffer signer = getLengthPrefixedSlice(signers); 193 X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); 194 signerCerts.add(certs); 195 } catch (IOException | BufferUnderflowException | SecurityException e) { 196 throw new SecurityException( 197 "Failed to parse/verify signer #" + signerCount + " block", 198 e); 199 } 200 } 201 202 if (signerCount < 1) { 203 throw new SecurityException("No signers found"); 204 } 205 206 if (contentDigests.isEmpty()) { 207 throw new SecurityException("No content digests found"); 208 } 209 210 if (doVerifyIntegrity) { 211 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); 212 } 213 214 byte[] verityRootHash = null; 215 if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { 216 byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); 217 verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength( 218 verityDigest, apk.length(), signatureInfo); 219 } 220 221 return new VerifiedSigner( 222 signerCerts.toArray(new X509Certificate[signerCerts.size()][]), 223 verityRootHash); 224 } 225 verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)226 private static X509Certificate[] verifySigner( 227 ByteBuffer signerBlock, 228 Map<Integer, byte[]> contentDigests, 229 CertificateFactory certFactory) throws SecurityException, IOException { 230 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); 231 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); 232 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); 233 234 int signatureCount = 0; 235 int bestSigAlgorithm = -1; 236 byte[] bestSigAlgorithmSignatureBytes = null; 237 List<Integer> signaturesSigAlgorithms = new ArrayList<>(); 238 while (signatures.hasRemaining()) { 239 signatureCount++; 240 try { 241 ByteBuffer signature = getLengthPrefixedSlice(signatures); 242 if (signature.remaining() < 8) { 243 throw new SecurityException("Signature record too short"); 244 } 245 int sigAlgorithm = signature.getInt(); 246 signaturesSigAlgorithms.add(sigAlgorithm); 247 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) { 248 continue; 249 } 250 if ((bestSigAlgorithm == -1) 251 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { 252 bestSigAlgorithm = sigAlgorithm; 253 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature); 254 } 255 } catch (IOException | BufferUnderflowException e) { 256 throw new SecurityException( 257 "Failed to parse signature record #" + signatureCount, 258 e); 259 } 260 } 261 if (bestSigAlgorithm == -1) { 262 if (signatureCount == 0) { 263 throw new SecurityException("No signatures found"); 264 } else { 265 throw new SecurityException("No supported signatures found"); 266 } 267 } 268 269 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm); 270 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = 271 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm); 272 String jcaSignatureAlgorithm = signatureAlgorithmParams.first; 273 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; 274 boolean sigVerified; 275 try { 276 PublicKey publicKey = 277 KeyFactory.getInstance(keyAlgorithm) 278 .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); 279 Signature sig = Signature.getInstance(jcaSignatureAlgorithm); 280 sig.initVerify(publicKey); 281 if (jcaSignatureAlgorithmParams != null) { 282 sig.setParameter(jcaSignatureAlgorithmParams); 283 } 284 sig.update(signedData); 285 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); 286 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException 287 | InvalidAlgorithmParameterException | SignatureException e) { 288 throw new SecurityException( 289 "Failed to verify " + jcaSignatureAlgorithm + " signature", e); 290 } 291 if (!sigVerified) { 292 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); 293 } 294 295 // Signature over signedData has verified. 296 297 byte[] contentDigest = null; 298 signedData.clear(); 299 ByteBuffer digests = getLengthPrefixedSlice(signedData); 300 List<Integer> digestsSigAlgorithms = new ArrayList<>(); 301 int digestCount = 0; 302 while (digests.hasRemaining()) { 303 digestCount++; 304 try { 305 ByteBuffer digest = getLengthPrefixedSlice(digests); 306 if (digest.remaining() < 8) { 307 throw new IOException("Record too short"); 308 } 309 int sigAlgorithm = digest.getInt(); 310 digestsSigAlgorithms.add(sigAlgorithm); 311 if (sigAlgorithm == bestSigAlgorithm) { 312 contentDigest = readLengthPrefixedByteArray(digest); 313 } 314 } catch (IOException | BufferUnderflowException e) { 315 throw new IOException("Failed to parse digest record #" + digestCount, e); 316 } 317 } 318 319 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) { 320 throw new SecurityException( 321 "Signature algorithms don't match between digests and signatures records"); 322 } 323 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm); 324 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest); 325 if ((previousSignerDigest != null) 326 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { 327 throw new SecurityException( 328 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) 329 + " contents digest does not match the digest specified by a preceding signer"); 330 } 331 332 ByteBuffer certificates = getLengthPrefixedSlice(signedData); 333 List<X509Certificate> certs = new ArrayList<>(); 334 int certificateCount = 0; 335 while (certificates.hasRemaining()) { 336 certificateCount++; 337 byte[] encodedCert = readLengthPrefixedByteArray(certificates); 338 X509Certificate certificate; 339 try { 340 certificate = (X509Certificate) 341 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); 342 } catch (CertificateException e) { 343 throw new SecurityException("Failed to decode certificate #" + certificateCount, e); 344 } 345 certificate = new VerbatimX509Certificate( 346 certificate, encodedCert); 347 certs.add(certificate); 348 } 349 350 if (certs.isEmpty()) { 351 throw new SecurityException("No certificates listed"); 352 } 353 X509Certificate mainCertificate = certs.get(0); 354 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); 355 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { 356 throw new SecurityException( 357 "Public key mismatch between certificate and signature record"); 358 } 359 360 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); 361 verifyAdditionalAttributes(additionalAttrs); 362 363 return certs.toArray(new X509Certificate[certs.size()]); 364 } 365 366 // Attribute to check whether a newer APK Signature Scheme signature was stripped 367 private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d; 368 verifyAdditionalAttributes(ByteBuffer attrs)369 private static void verifyAdditionalAttributes(ByteBuffer attrs) 370 throws SecurityException, IOException { 371 while (attrs.hasRemaining()) { 372 ByteBuffer attr = getLengthPrefixedSlice(attrs); 373 if (attr.remaining() < 4) { 374 throw new IOException("Remaining buffer too short to contain additional attribute " 375 + "ID. Remaining: " + attr.remaining()); 376 } 377 int id = attr.getInt(); 378 switch (id) { 379 case STRIPPING_PROTECTION_ATTR_ID: 380 if (attr.remaining() < 4) { 381 throw new IOException("V2 Signature Scheme Stripping Protection Attribute " 382 + " value too small. Expected 4 bytes, but found " 383 + attr.remaining()); 384 } 385 int vers = attr.getInt(); 386 if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { 387 throw new SecurityException("V2 signature indicates APK is signed using APK" 388 + " Signature Scheme v3, but none was found. Signature stripped?"); 389 } 390 break; 391 default: 392 // not the droid we're looking for, move along, move along. 393 break; 394 } 395 } 396 return; 397 } 398 getVerityRootHash(String apkPath)399 static byte[] getVerityRootHash(String apkPath) 400 throws IOException, SignatureNotFoundException, SecurityException { 401 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 402 SignatureInfo signatureInfo = findSignature(apk); 403 VerifiedSigner vSigner = verify(apk, false); 404 return vSigner.verityRootHash; 405 } 406 } 407 generateApkVerity(String apkPath, ByteBufferFactory bufferFactory)408 static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory) 409 throws IOException, SignatureNotFoundException, SecurityException, DigestException, 410 NoSuchAlgorithmException { 411 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 412 SignatureInfo signatureInfo = findSignature(apk); 413 return VerityBuilder.generateApkVerity(apkPath, bufferFactory, signatureInfo); 414 } 415 } 416 generateApkVerityRootHash(String apkPath)417 static byte[] generateApkVerityRootHash(String apkPath) 418 throws IOException, SignatureNotFoundException, DigestException, 419 NoSuchAlgorithmException { 420 try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) { 421 SignatureInfo signatureInfo = findSignature(apk); 422 VerifiedSigner vSigner = verify(apk, false); 423 if (vSigner.verityRootHash == null) { 424 return null; 425 } 426 return VerityBuilder.generateApkVerityRootHash( 427 apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo); 428 } 429 } 430 isSupportedSignatureAlgorithm(int sigAlgorithm)431 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { 432 switch (sigAlgorithm) { 433 case SIGNATURE_RSA_PSS_WITH_SHA256: 434 case SIGNATURE_RSA_PSS_WITH_SHA512: 435 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 436 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 437 case SIGNATURE_ECDSA_WITH_SHA256: 438 case SIGNATURE_ECDSA_WITH_SHA512: 439 case SIGNATURE_DSA_WITH_SHA256: 440 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 441 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 442 case SIGNATURE_VERITY_DSA_WITH_SHA256: 443 return true; 444 default: 445 return false; 446 } 447 } 448 449 /** 450 * Verified APK Signature Scheme v2 signer. 451 * 452 * @hide for internal use only. 453 */ 454 public static class VerifiedSigner { 455 public final X509Certificate[][] certs; 456 public final byte[] verityRootHash; 457 VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash)458 public VerifiedSigner(X509Certificate[][] certs, byte[] verityRootHash) { 459 this.certs = certs; 460 this.verityRootHash = verityRootHash; 461 } 462 463 } 464 } 465