blob: e43dee35606468f77dac8584597876c533fc1355 [file] [log] [blame]
Daniel Cashman67096e02017-12-28 12:46:33 -08001/*
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
17package android.util.apk;
18
19import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_DSA_WITH_SHA256;
20import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA256;
21import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_ECDSA_WITH_SHA512;
22import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
23import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
24import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA256;
25import static android.util.apk.ApkSigningBlockUtils.SIGNATURE_RSA_PSS_WITH_SHA512;
26import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
27import static android.util.apk.ApkSigningBlockUtils.getContentDigestAlgorithmJcaDigestAlgorithm;
28import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
29import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
30import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
31import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
32import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
33
34import android.os.Build;
35import android.util.ArrayMap;
36import android.util.Pair;
37
38import java.io.ByteArrayInputStream;
39import java.io.FileDescriptor;
40import java.io.IOException;
41import java.io.RandomAccessFile;
42import java.nio.BufferUnderflowException;
43import java.nio.ByteBuffer;
44import java.security.InvalidAlgorithmParameterException;
45import java.security.InvalidKeyException;
46import java.security.KeyFactory;
47import java.security.MessageDigest;
48import java.security.NoSuchAlgorithmException;
49import java.security.PublicKey;
50import java.security.Signature;
51import java.security.SignatureException;
Dan Cashmancd4cb812018-01-02 14:55:58 -080052import java.security.cert.CertificateEncodingException;
Daniel Cashman67096e02017-12-28 12:46:33 -080053import java.security.cert.CertificateException;
54import java.security.cert.CertificateFactory;
55import java.security.cert.X509Certificate;
56import java.security.spec.AlgorithmParameterSpec;
57import java.security.spec.InvalidKeySpecException;
58import java.security.spec.X509EncodedKeySpec;
59import java.util.ArrayList;
60import java.util.Arrays;
61import java.util.List;
62import java.util.Map;
63
64/**
65 * APK Signature Scheme v3 verifier.
66 *
67 * @hide for internal use only.
68 */
69public class ApkSignatureSchemeV3Verifier {
70
71 /**
72 * ID of this signature scheme as used in X-Android-APK-Signed header used in JAR signing.
73 */
74 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
75
76 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
77
78 /**
79 * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
80 *
81 * <p><b>NOTE: This method does not verify the signature.</b>
82 */
83 public static boolean hasSignature(String apkFile) throws IOException {
84 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
85 findSignature(apk);
86 return true;
87 } catch (SignatureNotFoundException e) {
88 return false;
89 }
90 }
91
92 /**
93 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
94 * associated with each signer.
95 *
96 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
97 * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
98 * verify.
99 * @throws IOException if an I/O error occurs while reading the APK file.
100 */
101 public static VerifiedSigner verify(String apkFile)
102 throws SignatureNotFoundException, SecurityException, IOException {
103 return verify(apkFile, true);
104 }
105
106 /**
107 * Returns the certificates associated with each signer for the given APK without verification.
108 * This method is dangerous and should not be used, unless the caller is absolutely certain the
109 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme v3
110 * Block while gathering signer information. The APK contents are not verified.
111 *
112 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
113 * @throws IOException if an I/O error occurs while reading the APK file.
114 */
115 public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
116 throws SignatureNotFoundException, SecurityException, IOException {
117 return verify(apkFile, false);
118 }
119
120 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
121 throws SignatureNotFoundException, SecurityException, IOException {
122 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
123 return verify(apk, verifyIntegrity);
124 }
125 }
126
127 /**
128 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
129 * associated with each signer.
130 *
131 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
132 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
133 * verify.
134 * @throws IOException if an I/O error occurs while reading the APK file.
135 */
136 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
137 throws SignatureNotFoundException, SecurityException, IOException {
138 SignatureInfo signatureInfo = findSignature(apk);
139 return verify(apk.getFD(), signatureInfo, verifyIntegrity);
140 }
141
142 /**
143 * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
144 * additional information relevant for verifying the block against the file.
145 *
146 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
147 * @throws IOException if an I/O error occurs while reading the APK file.
148 */
149 private static SignatureInfo findSignature(RandomAccessFile apk)
150 throws IOException, SignatureNotFoundException {
151 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
152 }
153
154 /**
155 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
156 * Block.
157 *
158 * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
159 * against the APK file.
160 */
161 private static VerifiedSigner verify(
162 FileDescriptor apkFileDescriptor,
163 SignatureInfo signatureInfo,
164 boolean doVerifyIntegrity) throws SecurityException {
165 int signerCount = 0;
166 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
167 VerifiedSigner result = null;
168 CertificateFactory certFactory;
169 try {
170 certFactory = CertificateFactory.getInstance("X.509");
171 } catch (CertificateException e) {
172 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
173 }
174 ByteBuffer signers;
175 try {
176 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
177 } catch (IOException e) {
178 throw new SecurityException("Failed to read list of signers", e);
179 }
180 while (signers.hasRemaining()) {
181 try {
182 ByteBuffer signer = getLengthPrefixedSlice(signers);
183 result = verifySigner(signer, contentDigests, certFactory);
184 signerCount++;
185 } catch (PlatformNotSupportedException e) {
186 // this signer is for a different platform, ignore it.
187 continue;
188 } catch (IOException | BufferUnderflowException | SecurityException e) {
189 throw new SecurityException(
190 "Failed to parse/verify signer #" + signerCount + " block",
191 e);
192 }
193 }
194
195 if (signerCount < 1 || result == null) {
196 throw new SecurityException("No signers found");
197 }
198
199 if (signerCount != 1) {
200 throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
201 + "multiple signers found.");
202 }
203
204 if (contentDigests.isEmpty()) {
205 throw new SecurityException("No content digests found");
206 }
207
208 if (doVerifyIntegrity) {
209 ApkSigningBlockUtils.verifyIntegrity(
210 contentDigests,
211 apkFileDescriptor,
212 signatureInfo.apkSigningBlockOffset,
213 signatureInfo.centralDirOffset,
214 signatureInfo.eocdOffset,
215 signatureInfo.eocd);
216 }
217
218 return result;
219 }
220
221 private static VerifiedSigner 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 preceding signer");
338 }
339
340 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
341 List<X509Certificate> certs = new ArrayList<>();
342 int certificateCount = 0;
343 while (certificates.hasRemaining()) {
344 certificateCount++;
345 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
346 X509Certificate certificate;
347 try {
348 certificate = (X509Certificate)
349 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
350 } catch (CertificateException e) {
351 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
352 }
353 certificate = new VerbatimX509Certificate(
354 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 VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
387 List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
388 X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
389 VerifiedProofOfRotation por = null;
390
391 while (attrs.hasRemaining()) {
392 ByteBuffer attr = getLengthPrefixedSlice(attrs);
393 if (attr.remaining() < 4) {
394 throw new IOException("Remaining buffer too short to contain additional attribute "
395 + "ID. Remaining: " + attr.remaining());
396 }
397 int id = attr.getInt();
398 switch(id) {
399 case PROOF_OF_ROTATION_ATTR_ID:
400 if (por != null) {
401 throw new SecurityException("Encountered multiple Proof-of-rotation records"
Dan Cashmancd4cb812018-01-02 14:55:58 -0800402 + " when verifying APK Signature Scheme v3 signature");
Daniel Cashman67096e02017-12-28 12:46:33 -0800403 }
404 por = verifyProofOfRotationStruct(attr, certFactory);
Dan Cashmancd4cb812018-01-02 14:55:58 -0800405 // make sure that the last certificate in the Proof-of-rotation record matches
406 // the one used to sign this APK.
407 try {
408 if (por.certs.size() > 0
409 && !Arrays.equals(por.certs.get(por.certs.size() - 1).getEncoded(),
410 certChain[0].getEncoded())) {
411 throw new SecurityException("Terminal certificate in Proof-of-rotation"
412 + " record does not match APK signing certificate");
413 }
414 } catch (CertificateEncodingException e) {
415 throw new SecurityException("Failed to encode certificate when comparing"
416 + " Proof-of-rotation record and signing certificate", e);
417 }
418
Daniel Cashman67096e02017-12-28 12:46:33 -0800419 break;
420 default:
421 // not the droid we're looking for, move along, move along.
422 break;
423 }
424 }
425 return new VerifiedSigner(certChain, por);
426 }
427
428 private static VerifiedProofOfRotation verifyProofOfRotationStruct(
429 ByteBuffer porBuf,
430 CertificateFactory certFactory)
431 throws SecurityException, IOException {
432 int levelCount = 0;
433 int lastSigAlgorithm = -1;
434 X509Certificate lastCert = null;
435 List<X509Certificate> certs = new ArrayList<>();
436 List<Integer> flagsList = new ArrayList<>();
437
438 // Proof-of-rotation struct:
439 // is basically a singly linked list of nodes, called levels here, each of which have the
440 // following structure:
441 // * length-prefix for the entire level
442 // - length-prefixed signed data (if previous level exists)
443 // * length-prefixed X509 Certificate
444 // * uint32 signature algorithm ID describing how this signed data was signed
445 // - uint32 flags describing how to treat the cert contained in this level
446 // - uint32 signature algorithm ID to use to verify the signature of the next level. The
447 // algorithm here must match the one in the signed data section of the next level.
448 // - length-prefixed signature over the signed data in this level. The signature here
449 // is verified using the certificate from the previous level.
450 // The linking is provided by the certificate of each level signing the one of the next.
451 while (porBuf.hasRemaining()) {
452 levelCount++;
453 try {
454 ByteBuffer level = getLengthPrefixedSlice(porBuf);
455 ByteBuffer signedData = getLengthPrefixedSlice(level);
456 int flags = level.getInt();
457 int sigAlgorithm = level.getInt();
458 byte[] signature = readLengthPrefixedByteArray(level);
459
460 if (lastCert != null) {
461 // Use previous level cert to verify current level
462 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
463 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
464 PublicKey publicKey = lastCert.getPublicKey();
465 Signature sig = Signature.getInstance(sigAlgParams.first);
466 sig.initVerify(publicKey);
467 if (sigAlgParams.second != null) {
468 sig.setParameter(sigAlgParams.second);
469 }
470 sig.update(signedData);
471 if (!sig.verify(signature)) {
472 throw new SecurityException("Unable to verify signature of certificate #"
473 + levelCount + " using " + sigAlgParams.first + " when verifying"
474 + " Proof-of-rotation record");
475 }
476 }
477
478 byte[] encodedCert = readLengthPrefixedByteArray(signedData);
479 int signedSigAlgorithm = signedData.getInt();
480 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
481 throw new SecurityException("Signing algorithm ID mismatch for certificate #"
482 + levelCount + " when verifying Proof-of-rotation record");
483 }
484 lastCert = (X509Certificate)
485 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
486 lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
487
488 lastSigAlgorithm = sigAlgorithm;
489 certs.add(lastCert);
490 flagsList.add(flags);
491 } catch (IOException | BufferUnderflowException e) {
492 throw new IOException("Failed to parse Proof-of-rotation record", e);
493 } catch (NoSuchAlgorithmException | InvalidKeyException
494 | InvalidAlgorithmParameterException | SignatureException e) {
495 throw new SecurityException(
496 "Failed to verify signature over signed data for certificate #"
497 + levelCount + " when verifying Proof-of-rotation record", e);
498 } catch (CertificateException e) {
499 throw new SecurityException("Failed to decode certificate #" + levelCount
500 + " when verifying Proof-of-rotation record", e);
501 }
502 }
503 return new VerifiedProofOfRotation(certs, flagsList);
504 }
505
506 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
507 switch (sigAlgorithm) {
508 case SIGNATURE_RSA_PSS_WITH_SHA256:
509 case SIGNATURE_RSA_PSS_WITH_SHA512:
510 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
511 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
512 case SIGNATURE_ECDSA_WITH_SHA256:
513 case SIGNATURE_ECDSA_WITH_SHA512:
514 case SIGNATURE_DSA_WITH_SHA256:
515 return true;
516 default:
517 return false;
518 }
519 }
520
521 /**
522 * Verified processed proof of rotation.
523 *
524 * @hide for internal use only.
525 */
526 public static class VerifiedProofOfRotation {
527 public final List<X509Certificate> certs;
528 public final List<Integer> flagsList;
529
530 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
531 this.certs = certs;
532 this.flagsList = flagsList;
533 }
534 }
535
536 /**
537 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
538 *
539 * @hide for internal use only.
540 */
541 public static class VerifiedSigner {
542 public final X509Certificate[] certs;
543 public final VerifiedProofOfRotation por;
544
545 public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
546 this.certs = certs;
547 this.por = por;
548 }
549
550 }
551
552 private static class PlatformNotSupportedException extends Exception {
553
554 PlatformNotSupportedException(String s) {
555 super(s);
556 }
557 }
558}