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