blob: 3271526df78a5cc73c1b562e93be3b8b7d1f4ed6 [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;
52import java.security.cert.CertificateException;
53import java.security.cert.CertificateFactory;
54import java.security.cert.X509Certificate;
55import java.security.spec.AlgorithmParameterSpec;
56import java.security.spec.InvalidKeySpecException;
57import java.security.spec.X509EncodedKeySpec;
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.util.List;
61import java.util.Map;
62
63/**
64 * APK Signature Scheme v3 verifier.
65 *
66 * @hide for internal use only.
67 */
68public class ApkSignatureSchemeV3Verifier {
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 = 3;
74
75 private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
76
77 /**
78 * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
79 *
80 * <p><b>NOTE: This method does not verify the signature.</b>
81 */
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 v3 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 v3.
96 * @throws SecurityException if the APK Signature Scheme v3 signature of this APK does not
97 * verify.
98 * @throws IOException if an I/O error occurs while reading the APK file.
99 */
100 public static VerifiedSigner verify(String apkFile)
101 throws SignatureNotFoundException, SecurityException, IOException {
102 return verify(apkFile, true);
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 v3
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 v3.
112 * @throws IOException if an I/O error occurs while reading the APK file.
113 */
114 public static VerifiedSigner plsCertsNoVerifyOnlyCerts(String apkFile)
115 throws SignatureNotFoundException, SecurityException, IOException {
116 return verify(apkFile, false);
117 }
118
119 private static VerifiedSigner verify(String apkFile, boolean verifyIntegrity)
120 throws SignatureNotFoundException, SecurityException, IOException {
121 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
122 return verify(apk, verifyIntegrity);
123 }
124 }
125
126 /**
127 * Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
128 * associated with each signer.
129 *
130 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
131 * @throws SecurityException if an APK Signature Scheme v3 signature of this APK does not
132 * verify.
133 * @throws IOException if an I/O error occurs while reading the APK file.
134 */
135 private static VerifiedSigner verify(RandomAccessFile apk, boolean verifyIntegrity)
136 throws SignatureNotFoundException, SecurityException, IOException {
137 SignatureInfo signatureInfo = findSignature(apk);
138 return verify(apk.getFD(), signatureInfo, verifyIntegrity);
139 }
140
141 /**
142 * Returns the APK Signature Scheme v3 block contained in the provided APK file and the
143 * additional information relevant for verifying the block against the file.
144 *
145 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
146 * @throws IOException if an I/O error occurs while reading the APK file.
147 */
148 private static SignatureInfo findSignature(RandomAccessFile apk)
149 throws IOException, SignatureNotFoundException {
150 return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
151 }
152
153 /**
154 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v3
155 * Block.
156 *
157 * @param signatureInfo APK Signature Scheme v3 Block and information relevant for verifying it
158 * against the APK file.
159 */
160 private static VerifiedSigner verify(
161 FileDescriptor apkFileDescriptor,
162 SignatureInfo signatureInfo,
163 boolean doVerifyIntegrity) throws SecurityException {
164 int signerCount = 0;
165 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
166 VerifiedSigner result = null;
167 CertificateFactory certFactory;
168 try {
169 certFactory = CertificateFactory.getInstance("X.509");
170 } catch (CertificateException e) {
171 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
172 }
173 ByteBuffer signers;
174 try {
175 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
176 } catch (IOException e) {
177 throw new SecurityException("Failed to read list of signers", e);
178 }
179 while (signers.hasRemaining()) {
180 try {
181 ByteBuffer signer = getLengthPrefixedSlice(signers);
182 result = verifySigner(signer, contentDigests, certFactory);
183 signerCount++;
184 } catch (PlatformNotSupportedException e) {
185 // this signer is for a different platform, ignore it.
186 continue;
187 } catch (IOException | BufferUnderflowException | SecurityException e) {
188 throw new SecurityException(
189 "Failed to parse/verify signer #" + signerCount + " block",
190 e);
191 }
192 }
193
194 if (signerCount < 1 || result == null) {
195 throw new SecurityException("No signers found");
196 }
197
198 if (signerCount != 1) {
199 throw new SecurityException("APK Signature Scheme V3 only supports one signer: "
200 + "multiple signers found.");
201 }
202
203 if (contentDigests.isEmpty()) {
204 throw new SecurityException("No content digests found");
205 }
206
207 if (doVerifyIntegrity) {
208 ApkSigningBlockUtils.verifyIntegrity(
209 contentDigests,
210 apkFileDescriptor,
211 signatureInfo.apkSigningBlockOffset,
212 signatureInfo.centralDirOffset,
213 signatureInfo.eocdOffset,
214 signatureInfo.eocd);
215 }
216
217 return result;
218 }
219
220 private static VerifiedSigner verifySigner(
221 ByteBuffer signerBlock,
222 Map<Integer, byte[]> contentDigests,
223 CertificateFactory certFactory)
224 throws SecurityException, IOException, PlatformNotSupportedException {
225 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
226 int minSdkVersion = signerBlock.getInt();
227 int maxSdkVersion = signerBlock.getInt();
228
229 if (Build.VERSION.SDK_INT < minSdkVersion || Build.VERSION.SDK_INT > maxSdkVersion) {
230 // this signature isn't meant to be used with this platform, skip it.
231 throw new PlatformNotSupportedException(
232 "Signer not supported by this platform "
233 + "version. This platform: " + Build.VERSION.SDK_INT
234 + ", signer minSdkVersion: " + minSdkVersion
235 + ", maxSdkVersion: " + maxSdkVersion);
236 }
237
238 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
239 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
240
241 int signatureCount = 0;
242 int bestSigAlgorithm = -1;
243 byte[] bestSigAlgorithmSignatureBytes = null;
244 List<Integer> signaturesSigAlgorithms = new ArrayList<>();
245 while (signatures.hasRemaining()) {
246 signatureCount++;
247 try {
248 ByteBuffer signature = getLengthPrefixedSlice(signatures);
249 if (signature.remaining() < 8) {
250 throw new SecurityException("Signature record too short");
251 }
252 int sigAlgorithm = signature.getInt();
253 signaturesSigAlgorithms.add(sigAlgorithm);
254 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
255 continue;
256 }
257 if ((bestSigAlgorithm == -1)
258 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
259 bestSigAlgorithm = sigAlgorithm;
260 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
261 }
262 } catch (IOException | BufferUnderflowException e) {
263 throw new SecurityException(
264 "Failed to parse signature record #" + signatureCount,
265 e);
266 }
267 }
268 if (bestSigAlgorithm == -1) {
269 if (signatureCount == 0) {
270 throw new SecurityException("No signatures found");
271 } else {
272 throw new SecurityException("No supported signatures found");
273 }
274 }
275
276 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
277 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
278 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
279 String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
280 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
281 boolean sigVerified;
282 try {
283 PublicKey publicKey =
284 KeyFactory.getInstance(keyAlgorithm)
285 .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
286 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
287 sig.initVerify(publicKey);
288 if (jcaSignatureAlgorithmParams != null) {
289 sig.setParameter(jcaSignatureAlgorithmParams);
290 }
291 sig.update(signedData);
292 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
293 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
294 | InvalidAlgorithmParameterException | SignatureException e) {
295 throw new SecurityException(
296 "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
297 }
298 if (!sigVerified) {
299 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
300 }
301
302 // Signature over signedData has verified.
303
304 byte[] contentDigest = null;
305 signedData.clear();
306 ByteBuffer digests = getLengthPrefixedSlice(signedData);
307 List<Integer> digestsSigAlgorithms = new ArrayList<>();
308 int digestCount = 0;
309 while (digests.hasRemaining()) {
310 digestCount++;
311 try {
312 ByteBuffer digest = getLengthPrefixedSlice(digests);
313 if (digest.remaining() < 8) {
314 throw new IOException("Record too short");
315 }
316 int sigAlgorithm = digest.getInt();
317 digestsSigAlgorithms.add(sigAlgorithm);
318 if (sigAlgorithm == bestSigAlgorithm) {
319 contentDigest = readLengthPrefixedByteArray(digest);
320 }
321 } catch (IOException | BufferUnderflowException e) {
322 throw new IOException("Failed to parse digest record #" + digestCount, e);
323 }
324 }
325
326 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
327 throw new SecurityException(
328 "Signature algorithms don't match between digests and signatures records");
329 }
330 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
331 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
332 if ((previousSignerDigest != null)
333 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
334 throw new SecurityException(
335 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
336 + " contents digest does not match the digest specified by a preceding signer");
337 }
338
339 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
340 List<X509Certificate> certs = new ArrayList<>();
341 int certificateCount = 0;
342 while (certificates.hasRemaining()) {
343 certificateCount++;
344 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
345 X509Certificate certificate;
346 try {
347 certificate = (X509Certificate)
348 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
349 } catch (CertificateException e) {
350 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
351 }
352 certificate = new VerbatimX509Certificate(
353 certificate, encodedCert);
354 certs.add(certificate);
355 }
356
357 if (certs.isEmpty()) {
358 throw new SecurityException("No certificates listed");
359 }
360 X509Certificate mainCertificate = certs.get(0);
361 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
362 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
363 throw new SecurityException(
364 "Public key mismatch between certificate and signature record");
365 }
366
367 int signedMinSDK = signedData.getInt();
368 if (signedMinSDK != minSdkVersion) {
369 throw new SecurityException(
370 "minSdkVersion mismatch between signed and unsigned in v3 signer block.");
371 }
372
373 int signedMaxSDK = signedData.getInt();
374 if (signedMaxSDK != maxSdkVersion) {
375 throw new SecurityException(
376 "maxSdkVersion mismatch between signed and unsigned in v3 signer block.");
377 }
378
379 ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
380 return verifyAdditionalAttributes(additionalAttrs, certs, certFactory);
381 }
382
383 private static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c;
384
385 private static VerifiedSigner verifyAdditionalAttributes(ByteBuffer attrs,
386 List<X509Certificate> certs, CertificateFactory certFactory) throws IOException {
387 X509Certificate[] certChain = certs.toArray(new X509Certificate[certs.size()]);
388 VerifiedProofOfRotation por = null;
389
390 while (attrs.hasRemaining()) {
391 ByteBuffer attr = getLengthPrefixedSlice(attrs);
392 if (attr.remaining() < 4) {
393 throw new IOException("Remaining buffer too short to contain additional attribute "
394 + "ID. Remaining: " + attr.remaining());
395 }
396 int id = attr.getInt();
397 switch(id) {
398 case PROOF_OF_ROTATION_ATTR_ID:
399 if (por != null) {
400 throw new SecurityException("Encountered multiple Proof-of-rotation records"
401 + " when verifying APK Signature Scheme v3 signature.");
402 }
403 por = verifyProofOfRotationStruct(attr, certFactory);
404 break;
405 default:
406 // not the droid we're looking for, move along, move along.
407 break;
408 }
409 }
410 return new VerifiedSigner(certChain, por);
411 }
412
413 private static VerifiedProofOfRotation verifyProofOfRotationStruct(
414 ByteBuffer porBuf,
415 CertificateFactory certFactory)
416 throws SecurityException, IOException {
417 int levelCount = 0;
418 int lastSigAlgorithm = -1;
419 X509Certificate lastCert = null;
420 List<X509Certificate> certs = new ArrayList<>();
421 List<Integer> flagsList = new ArrayList<>();
422
423 // Proof-of-rotation struct:
424 // is basically a singly linked list of nodes, called levels here, each of which have the
425 // following structure:
426 // * length-prefix for the entire level
427 // - length-prefixed signed data (if previous level exists)
428 // * length-prefixed X509 Certificate
429 // * uint32 signature algorithm ID describing how this signed data was signed
430 // - uint32 flags describing how to treat the cert contained in this level
431 // - uint32 signature algorithm ID to use to verify the signature of the next level. The
432 // algorithm here must match the one in the signed data section of the next level.
433 // - length-prefixed signature over the signed data in this level. The signature here
434 // is verified using the certificate from the previous level.
435 // The linking is provided by the certificate of each level signing the one of the next.
436 while (porBuf.hasRemaining()) {
437 levelCount++;
438 try {
439 ByteBuffer level = getLengthPrefixedSlice(porBuf);
440 ByteBuffer signedData = getLengthPrefixedSlice(level);
441 int flags = level.getInt();
442 int sigAlgorithm = level.getInt();
443 byte[] signature = readLengthPrefixedByteArray(level);
444
445 if (lastCert != null) {
446 // Use previous level cert to verify current level
447 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams =
448 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm);
449 PublicKey publicKey = lastCert.getPublicKey();
450 Signature sig = Signature.getInstance(sigAlgParams.first);
451 sig.initVerify(publicKey);
452 if (sigAlgParams.second != null) {
453 sig.setParameter(sigAlgParams.second);
454 }
455 sig.update(signedData);
456 if (!sig.verify(signature)) {
457 throw new SecurityException("Unable to verify signature of certificate #"
458 + levelCount + " using " + sigAlgParams.first + " when verifying"
459 + " Proof-of-rotation record");
460 }
461 }
462
463 byte[] encodedCert = readLengthPrefixedByteArray(signedData);
464 int signedSigAlgorithm = signedData.getInt();
465 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) {
466 throw new SecurityException("Signing algorithm ID mismatch for certificate #"
467 + levelCount + " when verifying Proof-of-rotation record");
468 }
469 lastCert = (X509Certificate)
470 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
471 lastCert = new VerbatimX509Certificate(lastCert, encodedCert);
472
473 lastSigAlgorithm = sigAlgorithm;
474 certs.add(lastCert);
475 flagsList.add(flags);
476 } catch (IOException | BufferUnderflowException e) {
477 throw new IOException("Failed to parse Proof-of-rotation record", e);
478 } catch (NoSuchAlgorithmException | InvalidKeyException
479 | InvalidAlgorithmParameterException | SignatureException e) {
480 throw new SecurityException(
481 "Failed to verify signature over signed data for certificate #"
482 + levelCount + " when verifying Proof-of-rotation record", e);
483 } catch (CertificateException e) {
484 throw new SecurityException("Failed to decode certificate #" + levelCount
485 + " when verifying Proof-of-rotation record", e);
486 }
487 }
488 return new VerifiedProofOfRotation(certs, flagsList);
489 }
490
491 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
492 switch (sigAlgorithm) {
493 case SIGNATURE_RSA_PSS_WITH_SHA256:
494 case SIGNATURE_RSA_PSS_WITH_SHA512:
495 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
496 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
497 case SIGNATURE_ECDSA_WITH_SHA256:
498 case SIGNATURE_ECDSA_WITH_SHA512:
499 case SIGNATURE_DSA_WITH_SHA256:
500 return true;
501 default:
502 return false;
503 }
504 }
505
506 /**
507 * Verified processed proof of rotation.
508 *
509 * @hide for internal use only.
510 */
511 public static class VerifiedProofOfRotation {
512 public final List<X509Certificate> certs;
513 public final List<Integer> flagsList;
514
515 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) {
516 this.certs = certs;
517 this.flagsList = flagsList;
518 }
519 }
520
521 /**
522 * Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
523 *
524 * @hide for internal use only.
525 */
526 public static class VerifiedSigner {
527 public final X509Certificate[] certs;
528 public final VerifiedProofOfRotation por;
529
530 public VerifiedSigner(X509Certificate[] certs, VerifiedProofOfRotation por) {
531 this.certs = certs;
532 this.por = por;
533 }
534
535 }
536
537 private static class PlatformNotSupportedException extends Exception {
538
539 PlatformNotSupportedException(String s) {
540 super(s);
541 }
542 }
543}