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 a APK Signature Scheme v2 signature of this APK does not verify. 102 * @throws IOException if an I/O error occurs while reading the APK file. 103 */ verify(String apkFile)104 public static X509Certificate[][] verify(String apkFile) 105 throws SignatureNotFoundException, SecurityException, IOException { 106 VerifiedSigner vSigner = verify(apkFile, true); 107 return vSigner.certs; 108 } 109 110 /** 111 * Returns the certificates associated with each signer for the given APK without verification. 112 * This method is dangerous and should not be used, unless the caller is absolutely certain the 113 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v2 114 * Block while gathering signer information. The APK contents are not verified. 115 * 116 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 117 * @throws IOException if an I/O error occurs while reading the APK file. 118 */ unsafeGetCertsWithoutVerification(String apkFile)119 public static X509Certificate[][] unsafeGetCertsWithoutVerification(String apkFile) 120 throws SignatureNotFoundException, SecurityException, IOException { 121 VerifiedSigner vSigner = verify(apkFile, false); 122 return vSigner.certs; 123 } 124 125 /** 126 * Same as above returns the full signer object, containing additional info e.g. digest. 127 */ verify(String apkFile, boolean verifyIntegrity)128 public static VerifiedSigner verify(String apkFile, boolean verifyIntegrity) 129 throws SignatureNotFoundException, SecurityException, IOException { 130 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { 131 return verify(apk, verifyIntegrity); 132 } 133 } 134 135 /** 136 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates 137 * associated with each signer. 138 * 139 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 140 * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not 141 * verify. 142 * @throws IOException if an I/O error occurs while reading the APK file. 143 */ verify(RandomAccessFile apk, boolean verifyIntegrity)144 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity) 145 throws SignatureNotFoundException, SecurityException, IOException { 146 SignatureInfo signatureInfo = findSignature(apk); 147 return verify(apk, signatureInfo, verifyIntegrity); 148 } 149 150 /** 151 * Returns the APK Signature Scheme v2 block contained in the provided APK file and the 152 * additional information relevant for verifying the block against the file. 153 * 154 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 155 * @throws IOException if an I/O error occurs while reading the APK file. 156 */ findSignature(RandomAccessFile apk)157 public static SignatureInfo findSignature(RandomAccessFile apk) 158 throws IOException, SignatureNotFoundException { 159 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID); 160 } 161 162 /** 163 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2 164 * Block. 165 * 166 * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it 167 * against the APK file. 168 */ verify( RandomAccessFile apk, SignatureInfo signatureInfo, boolean doVerifyIntegrity)169 private static VerifiedSigner verify( 170 RandomAccessFile apk, 171 SignatureInfo signatureInfo, 172 boolean doVerifyIntegrity) throws SecurityException, IOException { 173 int signerCount = 0; 174 Map<Integer, byte[]> contentDigests = new ArrayMap<>(); 175 List<X509Certificate[]> signerCerts = new ArrayList<>(); 176 CertificateFactory certFactory; 177 try { 178 certFactory = CertificateFactory.getInstance("X.509"); 179 } catch (CertificateException e) { 180 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 181 } 182 ByteBuffer signers; 183 try { 184 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); 185 } catch (IOException e) { 186 throw new SecurityException("Failed to read list of signers", e); 187 } 188 while (signers.hasRemaining()) { 189 signerCount++; 190 if (signerCount > MAX_V2_SIGNERS) { 191 throw new SecurityException( 192 "APK Signature Scheme v2 only supports a maximum of " + MAX_V2_SIGNERS 193 + " signers"); 194 } 195 try { 196 ByteBuffer signer = getLengthPrefixedSlice(signers); 197 X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory); 198 signerCerts.add(certs); 199 } catch (IOException | BufferUnderflowException | SecurityException e) { 200 throw new SecurityException( 201 "Failed to parse/verify signer #" + signerCount + " block", 202 e); 203 } 204 } 205 206 if (signerCount < 1) { 207 throw new SecurityException("No signers found"); 208 } 209 210 if (contentDigests.isEmpty()) { 211 throw new SecurityException("No content digests found"); 212 } 213 214 if (doVerifyIntegrity) { 215 ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo); 216 } 217 218 byte[] verityRootHash = null; 219 if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { 220 byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256); 221 verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength( 222 verityDigest, apk.getChannel().size(), signatureInfo); 223 } 224 225 return new VerifiedSigner( 226 signerCerts.toArray(new X509Certificate[signerCerts.size()][]), 227 verityRootHash, contentDigests); 228 } 229 verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)230 private static X509Certificate[] verifySigner( 231 ByteBuffer signerBlock, 232 Map<Integer, byte[]> contentDigests, 233 CertificateFactory certFactory) throws SecurityException, IOException { 234 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); 235 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); 236 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); 237 238 int signatureCount = 0; 239 int bestSigAlgorithm = -1; 240 byte[] bestSigAlgorithmSignatureBytes = null; 241 List<Integer> signaturesSigAlgorithms = new ArrayList<>(); 242 while (signatures.hasRemaining()) { 243 signatureCount++; 244 try { 245 ByteBuffer signature = getLengthPrefixedSlice(signatures); 246 if (signature.remaining() < 8) { 247 throw new SecurityException("Signature record too short"); 248 } 249 int sigAlgorithm = signature.getInt(); 250 signaturesSigAlgorithms.add(sigAlgorithm); 251 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) { 252 continue; 253 } 254 if ((bestSigAlgorithm == -1) 255 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { 256 bestSigAlgorithm = sigAlgorithm; 257 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature); 258 } 259 } catch (IOException | BufferUnderflowException e) { 260 throw new SecurityException( 261 "Failed to parse signature record #" + signatureCount, 262 e); 263 } 264 } 265 if (bestSigAlgorithm == -1) { 266 if (signatureCount == 0) { 267 throw new SecurityException("No signatures found"); 268 } else { 269 throw new SecurityException("No supported signatures found"); 270 } 271 } 272 273 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm); 274 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams = 275 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm); 276 String jcaSignatureAlgorithm = signatureAlgorithmParams.first; 277 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second; 278 boolean sigVerified; 279 try { 280 PublicKey publicKey = 281 KeyFactory.getInstance(keyAlgorithm) 282 .generatePublic(new X509EncodedKeySpec(publicKeyBytes)); 283 Signature sig = Signature.getInstance(jcaSignatureAlgorithm); 284 sig.initVerify(publicKey); 285 if (jcaSignatureAlgorithmParams != null) { 286 sig.setParameter(jcaSignatureAlgorithmParams); 287 } 288 sig.update(signedData); 289 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); 290 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException 291 | InvalidAlgorithmParameterException | SignatureException e) { 292 throw new SecurityException( 293 "Failed to verify " + jcaSignatureAlgorithm + " signature", e); 294 } 295 if (!sigVerified) { 296 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); 297 } 298 299 // Signature over signedData has verified. 300 301 byte[] contentDigest = null; 302 signedData.clear(); 303 ByteBuffer digests = getLengthPrefixedSlice(signedData); 304 List<Integer> digestsSigAlgorithms = new ArrayList<>(); 305 int digestCount = 0; 306 while (digests.hasRemaining()) { 307 digestCount++; 308 try { 309 ByteBuffer digest = getLengthPrefixedSlice(digests); 310 if (digest.remaining() < 8) { 311 throw new IOException("Record too short"); 312 } 313 int sigAlgorithm = digest.getInt(); 314 digestsSigAlgorithms.add(sigAlgorithm); 315 if (sigAlgorithm == bestSigAlgorithm) { 316 contentDigest = readLengthPrefixedByteArray(digest); 317 } 318 } catch (IOException | BufferUnderflowException e) { 319 throw new IOException("Failed to parse digest record #" + digestCount, e); 320 } 321 } 322 323 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) { 324 throw new SecurityException( 325 "Signature algorithms don't match between digests and signatures records"); 326 } 327 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm); 328 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest); 329 if ((previousSignerDigest != null) 330 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) { 331 throw new SecurityException( 332 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) 333 + " contents digest does not match the digest specified by a preceding signer"); 334 } 335 336 ByteBuffer certificates = getLengthPrefixedSlice(signedData); 337 List<X509Certificate> certs = new ArrayList<>(); 338 int certificateCount = 0; 339 while (certificates.hasRemaining()) { 340 certificateCount++; 341 byte[] encodedCert = readLengthPrefixedByteArray(certificates); 342 X509Certificate certificate; 343 try { 344 certificate = (X509Certificate) 345 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); 346 } catch (CertificateException e) { 347 throw new SecurityException("Failed to decode certificate #" + certificateCount, e); 348 } 349 certificate = new VerbatimX509Certificate(certificate, encodedCert); 350 certs.add(certificate); 351 } 352 353 if (certs.isEmpty()) { 354 throw new SecurityException("No certificates listed"); 355 } 356 X509Certificate mainCertificate = certs.get(0); 357 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); 358 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { 359 throw new SecurityException( 360 "Public key mismatch between certificate and signature record"); 361 } 362 363 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData); 364 verifyAdditionalAttributes(additionalAttrs); 365 366 return certs.toArray(new X509Certificate[certs.size()]); 367 } 368 369 // Attribute to check whether a newer APK Signature Scheme signature was stripped 370 private static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d; 371 verifyAdditionalAttributes(ByteBuffer attrs)372 private static void verifyAdditionalAttributes(ByteBuffer attrs) 373 throws SecurityException, IOException { 374 while (attrs.hasRemaining()) { 375 ByteBuffer attr = getLengthPrefixedSlice(attrs); 376 if (attr.remaining() < 4) { 377 throw new IOException("Remaining buffer too short to contain additional attribute " 378 + "ID. Remaining: " + attr.remaining()); 379 } 380 int id = attr.getInt(); 381 switch (id) { 382 case STRIPPING_PROTECTION_ATTR_ID: 383 if (attr.remaining() < 4) { 384 throw new IOException("V2 Signature Scheme Stripping Protection Attribute " 385 + " value too small. Expected 4 bytes, but found " 386 + attr.remaining()); 387 } 388 int vers = attr.getInt(); 389 if (vers == ApkSignatureSchemeV3Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) { 390 throw new SecurityException("V2 signature indicates APK is signed using APK" 391 + " Signature Scheme v3, but none was found. Signature stripped?"); 392 } 393 break; 394 default: 395 // not the droid we're looking for, move along, move along. 396 break; 397 } 398 } 399 return; 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