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