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 android.system.ErrnoException;
20 import android.system.OsConstants;
21 import android.util.ArrayMap;
22 import android.util.Pair;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.FileDescriptor;
26 import java.io.IOException;
27 import java.io.RandomAccessFile;
28 import java.math.BigInteger;
29 import java.nio.BufferUnderflowException;
30 import java.nio.ByteBuffer;
31 import java.nio.ByteOrder;
32 import java.nio.DirectByteBuffer;
33 import java.security.DigestException;
34 import java.security.InvalidAlgorithmParameterException;
35 import java.security.InvalidKeyException;
36 import java.security.KeyFactory;
37 import java.security.MessageDigest;
38 import java.security.NoSuchAlgorithmException;
39 import java.security.NoSuchProviderException;
40 import java.security.Principal;
41 import java.security.PublicKey;
42 import java.security.Signature;
43 import java.security.SignatureException;
44 import java.security.cert.CertificateEncodingException;
45 import java.security.cert.CertificateException;
46 import java.security.cert.CertificateExpiredException;
47 import java.security.cert.CertificateFactory;
48 import java.security.cert.CertificateNotYetValidException;
49 import java.security.cert.X509Certificate;
50 import java.security.spec.AlgorithmParameterSpec;
51 import java.security.spec.InvalidKeySpecException;
52 import java.security.spec.MGF1ParameterSpec;
53 import java.security.spec.PSSParameterSpec;
54 import java.security.spec.X509EncodedKeySpec;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Date;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Set;
61 
62 import libcore.io.Libcore;
63 import libcore.io.Os;
64 
65 /**
66  * APK Signature Scheme v2 verifier.
67  *
68  * @hide for internal use only.
69  */
70 public class ApkSignatureSchemeV2Verifier {
71 
72     /**
73      * {@code .SF} file header section attribute indicating that the APK is signed not just with
74      * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
75      * facilitates v2 signature stripping detection.
76      *
77      * <p>The attribute contains a comma-separated set of signature scheme IDs.
78      */
79     public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
80     public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
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         try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
107             return verify(apk);
108         }
109     }
110 
111     /**
112      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
113      * associated with each signer.
114      *
115      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
116      * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not
117      *         verify.
118      * @throws IOException if an I/O error occurs while reading the APK file.
119      */
verify(RandomAccessFile apk)120     private static X509Certificate[][] verify(RandomAccessFile apk)
121             throws SignatureNotFoundException, SecurityException, IOException {
122         SignatureInfo signatureInfo = findSignature(apk);
123         return verify(apk.getFD(), signatureInfo);
124     }
125 
126     /**
127      * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
128      * contained in the block against the file.
129      */
130     private static class SignatureInfo {
131         /** Contents of APK Signature Scheme v2 block. */
132         private final ByteBuffer signatureBlock;
133 
134         /** Position of the APK Signing Block in the file. */
135         private final long apkSigningBlockOffset;
136 
137         /** Position of the ZIP Central Directory in the file. */
138         private final long centralDirOffset;
139 
140         /** Position of the ZIP End of Central Directory (EoCD) in the file. */
141         private final long eocdOffset;
142 
143         /** Contents of ZIP End of Central Directory (EoCD) of the file. */
144         private final ByteBuffer eocd;
145 
SignatureInfo( ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset, long eocdOffset, ByteBuffer eocd)146         private SignatureInfo(
147                 ByteBuffer signatureBlock,
148                 long apkSigningBlockOffset,
149                 long centralDirOffset,
150                 long eocdOffset,
151                 ByteBuffer eocd) {
152             this.signatureBlock = signatureBlock;
153             this.apkSigningBlockOffset = apkSigningBlockOffset;
154             this.centralDirOffset = centralDirOffset;
155             this.eocdOffset = eocdOffset;
156             this.eocd = eocd;
157         }
158     }
159 
160     /**
161      * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
162      * additional information relevant for verifying the block against the file.
163      *
164      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
165      * @throws IOException if an I/O error occurs while reading the APK file.
166      */
findSignature(RandomAccessFile apk)167     private static SignatureInfo findSignature(RandomAccessFile apk)
168             throws IOException, SignatureNotFoundException {
169         // Find the ZIP End of Central Directory (EoCD) record.
170         Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
171         ByteBuffer eocd = eocdAndOffsetInFile.first;
172         long eocdOffset = eocdAndOffsetInFile.second;
173         if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
174             throw new SignatureNotFoundException("ZIP64 APK not supported");
175         }
176 
177         // Find the APK Signing Block. The block immediately precedes the Central Directory.
178         long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
179         Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
180                 findApkSigningBlock(apk, centralDirOffset);
181         ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
182         long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
183 
184         // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
185         ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
186 
187         return new SignatureInfo(
188                 apkSignatureSchemeV2Block,
189                 apkSigningBlockOffset,
190                 centralDirOffset,
191                 eocdOffset,
192                 eocd);
193     }
194 
195     /**
196      * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2
197      * Block.
198      *
199      * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it
200      *        against the APK file.
201      */
verify( FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo)202     private static X509Certificate[][] verify(
203             FileDescriptor apkFileDescriptor,
204             SignatureInfo signatureInfo) throws SecurityException {
205         int signerCount = 0;
206         Map<Integer, byte[]> contentDigests = new ArrayMap<>();
207         List<X509Certificate[]> signerCerts = new ArrayList<>();
208         CertificateFactory certFactory;
209         try {
210             certFactory = CertificateFactory.getInstance("X.509");
211         } catch (CertificateException e) {
212             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
213         }
214         ByteBuffer signers;
215         try {
216             signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
217         } catch (IOException e) {
218             throw new SecurityException("Failed to read list of signers", e);
219         }
220         while (signers.hasRemaining()) {
221             signerCount++;
222             try {
223                 ByteBuffer signer = getLengthPrefixedSlice(signers);
224                 X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
225                 signerCerts.add(certs);
226             } catch (IOException | BufferUnderflowException | SecurityException e) {
227                 throw new SecurityException(
228                         "Failed to parse/verify signer #" + signerCount + " block",
229                         e);
230             }
231         }
232 
233         if (signerCount < 1) {
234             throw new SecurityException("No signers found");
235         }
236 
237         if (contentDigests.isEmpty()) {
238             throw new SecurityException("No content digests found");
239         }
240 
241         verifyIntegrity(
242                 contentDigests,
243                 apkFileDescriptor,
244                 signatureInfo.apkSigningBlockOffset,
245                 signatureInfo.centralDirOffset,
246                 signatureInfo.eocdOffset,
247                 signatureInfo.eocd);
248 
249         return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
250     }
251 
verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)252     private static X509Certificate[] verifySigner(
253             ByteBuffer signerBlock,
254             Map<Integer, byte[]> contentDigests,
255             CertificateFactory certFactory) throws SecurityException, IOException {
256         ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
257         ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
258         byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
259 
260         int signatureCount = 0;
261         int bestSigAlgorithm = -1;
262         byte[] bestSigAlgorithmSignatureBytes = null;
263         List<Integer> signaturesSigAlgorithms = new ArrayList<>();
264         while (signatures.hasRemaining()) {
265             signatureCount++;
266             try {
267                 ByteBuffer signature = getLengthPrefixedSlice(signatures);
268                 if (signature.remaining() < 8) {
269                     throw new SecurityException("Signature record too short");
270                 }
271                 int sigAlgorithm = signature.getInt();
272                 signaturesSigAlgorithms.add(sigAlgorithm);
273                 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
274                     continue;
275                 }
276                 if ((bestSigAlgorithm == -1)
277                         || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
278                     bestSigAlgorithm = sigAlgorithm;
279                     bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
280                 }
281             } catch (IOException | BufferUnderflowException e) {
282                 throw new SecurityException(
283                         "Failed to parse signature record #" + signatureCount,
284                         e);
285             }
286         }
287         if (bestSigAlgorithm == -1) {
288             if (signatureCount == 0) {
289                 throw new SecurityException("No signatures found");
290             } else {
291                 throw new SecurityException("No supported signatures found");
292             }
293         }
294 
295         String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
296         Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
297                 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
298         String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
299         AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
300         boolean sigVerified;
301         try {
302             PublicKey publicKey =
303                     KeyFactory.getInstance(keyAlgorithm)
304                             .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
305             Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
306             sig.initVerify(publicKey);
307             if (jcaSignatureAlgorithmParams != null) {
308                 sig.setParameter(jcaSignatureAlgorithmParams);
309             }
310             sig.update(signedData);
311             sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
312         } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
313                 | InvalidAlgorithmParameterException | SignatureException e) {
314             throw new SecurityException(
315                     "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
316         }
317         if (!sigVerified) {
318             throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
319         }
320 
321         // Signature over signedData has verified.
322 
323         byte[] contentDigest = null;
324         signedData.clear();
325         ByteBuffer digests = getLengthPrefixedSlice(signedData);
326         List<Integer> digestsSigAlgorithms = new ArrayList<>();
327         int digestCount = 0;
328         while (digests.hasRemaining()) {
329             digestCount++;
330             try {
331                 ByteBuffer digest = getLengthPrefixedSlice(digests);
332                 if (digest.remaining() < 8) {
333                     throw new IOException("Record too short");
334                 }
335                 int sigAlgorithm = digest.getInt();
336                 digestsSigAlgorithms.add(sigAlgorithm);
337                 if (sigAlgorithm == bestSigAlgorithm) {
338                     contentDigest = readLengthPrefixedByteArray(digest);
339                 }
340             } catch (IOException | BufferUnderflowException e) {
341                 throw new IOException("Failed to parse digest record #" + digestCount, e);
342             }
343         }
344 
345         if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
346             throw new SecurityException(
347                     "Signature algorithms don't match between digests and signatures records");
348         }
349         int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
350         byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
351         if ((previousSignerDigest != null)
352                 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
353             throw new SecurityException(
354                     getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
355                     + " contents digest does not match the digest specified by a preceding signer");
356         }
357 
358         ByteBuffer certificates = getLengthPrefixedSlice(signedData);
359         List<X509Certificate> certs = new ArrayList<>();
360         int certificateCount = 0;
361         while (certificates.hasRemaining()) {
362             certificateCount++;
363             byte[] encodedCert = readLengthPrefixedByteArray(certificates);
364             X509Certificate certificate;
365             try {
366                 certificate = (X509Certificate)
367                         certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
368             } catch (CertificateException e) {
369                 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
370             }
371             certificate = new VerbatimX509Certificate(certificate, encodedCert);
372             certs.add(certificate);
373         }
374 
375         if (certs.isEmpty()) {
376             throw new SecurityException("No certificates listed");
377         }
378         X509Certificate mainCertificate = certs.get(0);
379         byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
380         if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
381             throw new SecurityException(
382                     "Public key mismatch between certificate and signature record");
383         }
384 
385         return certs.toArray(new X509Certificate[certs.size()]);
386     }
387 
verifyIntegrity( Map<Integer, byte[]> expectedDigests, FileDescriptor apkFileDescriptor, long apkSigningBlockOffset, long centralDirOffset, long eocdOffset, ByteBuffer eocdBuf)388     private static void verifyIntegrity(
389             Map<Integer, byte[]> expectedDigests,
390             FileDescriptor apkFileDescriptor,
391             long apkSigningBlockOffset,
392             long centralDirOffset,
393             long eocdOffset,
394             ByteBuffer eocdBuf) throws SecurityException {
395 
396         if (expectedDigests.isEmpty()) {
397             throw new SecurityException("No digests provided");
398         }
399 
400         // We need to verify the integrity of the following three sections of the file:
401         // 1. Everything up to the start of the APK Signing Block.
402         // 2. ZIP Central Directory.
403         // 3. ZIP End of Central Directory (EoCD).
404         // Each of these sections is represented as a separate DataSource instance below.
405 
406         // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
407         // avoid wasting physical memory. In most APK verification scenarios, the contents of the
408         // APK are already there in the OS's page cache and thus mmap does not use additional
409         // physical memory.
410         DataSource beforeApkSigningBlock =
411                 new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
412         DataSource centralDir =
413                 new MemoryMappedFileDataSource(
414                         apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
415 
416         // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
417         // Central Directory must be considered to point to the offset of the APK Signing Block.
418         eocdBuf = eocdBuf.duplicate();
419         eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
420         ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
421         DataSource eocd = new ByteBufferDataSource(eocdBuf);
422 
423         int[] digestAlgorithms = new int[expectedDigests.size()];
424         int digestAlgorithmCount = 0;
425         for (int digestAlgorithm : expectedDigests.keySet()) {
426             digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
427             digestAlgorithmCount++;
428         }
429         byte[][] actualDigests;
430         try {
431             actualDigests =
432                     computeContentDigests(
433                             digestAlgorithms,
434                             new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
435         } catch (DigestException e) {
436             throw new SecurityException("Failed to compute digest(s) of contents", e);
437         }
438         for (int i = 0; i < digestAlgorithms.length; i++) {
439             int digestAlgorithm = digestAlgorithms[i];
440             byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
441             byte[] actualDigest = actualDigests[i];
442             if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
443                 throw new SecurityException(
444                         getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
445                                 + " digest of contents did not verify");
446             }
447         }
448     }
449 
computeContentDigests( int[] digestAlgorithms, DataSource[] contents)450     private static byte[][] computeContentDigests(
451             int[] digestAlgorithms,
452             DataSource[] contents) throws DigestException {
453         // For each digest algorithm the result is computed as follows:
454         // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
455         //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
456         //    No chunks are produced for empty (zero length) segments.
457         // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
458         //    length in bytes (uint32 little-endian) and the chunk's contents.
459         // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
460         //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
461         //    segments in-order.
462 
463         long totalChunkCountLong = 0;
464         for (DataSource input : contents) {
465             totalChunkCountLong += getChunkCount(input.size());
466         }
467         if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
468             throw new DigestException("Too many chunks: " + totalChunkCountLong);
469         }
470         int totalChunkCount = (int) totalChunkCountLong;
471 
472         byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
473         for (int i = 0; i < digestAlgorithms.length; i++) {
474             int digestAlgorithm = digestAlgorithms[i];
475             int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
476             byte[] concatenationOfChunkCountAndChunkDigests =
477                     new byte[5 + totalChunkCount * digestOutputSizeBytes];
478             concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
479             setUnsignedInt32LittleEndian(
480                     totalChunkCount,
481                     concatenationOfChunkCountAndChunkDigests,
482                     1);
483             digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
484         }
485 
486         byte[] chunkContentPrefix = new byte[5];
487         chunkContentPrefix[0] = (byte) 0xa5;
488         int chunkIndex = 0;
489         MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
490         for (int i = 0; i < digestAlgorithms.length; i++) {
491             String jcaAlgorithmName =
492                     getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
493             try {
494                 mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
495             } catch (NoSuchAlgorithmException e) {
496                 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
497             }
498         }
499         // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
500         // into how to parallelize (if at all) based on the capabilities of the hardware on which
501         // this code is running and based on the size of input.
502         int dataSourceIndex = 0;
503         for (DataSource input : contents) {
504             long inputOffset = 0;
505             long inputRemaining = input.size();
506             while (inputRemaining > 0) {
507                 int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
508                 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
509                 for (int i = 0; i < mds.length; i++) {
510                     mds[i].update(chunkContentPrefix);
511                 }
512                 try {
513                     input.feedIntoMessageDigests(mds, inputOffset, chunkSize);
514                 } catch (IOException e) {
515                     throw new DigestException(
516                             "Failed to digest chunk #" + chunkIndex + " of section #"
517                                     + dataSourceIndex,
518                             e);
519                 }
520                 for (int i = 0; i < digestAlgorithms.length; i++) {
521                     int digestAlgorithm = digestAlgorithms[i];
522                     byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
523                     int expectedDigestSizeBytes =
524                             getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
525                     MessageDigest md = mds[i];
526                     int actualDigestSizeBytes =
527                             md.digest(
528                                     concatenationOfChunkCountAndChunkDigests,
529                                     5 + chunkIndex * expectedDigestSizeBytes,
530                                     expectedDigestSizeBytes);
531                     if (actualDigestSizeBytes != expectedDigestSizeBytes) {
532                         throw new RuntimeException(
533                                 "Unexpected output size of " + md.getAlgorithm() + " digest: "
534                                         + actualDigestSizeBytes);
535                     }
536                 }
537                 inputOffset += chunkSize;
538                 inputRemaining -= chunkSize;
539                 chunkIndex++;
540             }
541             dataSourceIndex++;
542         }
543 
544         byte[][] result = new byte[digestAlgorithms.length][];
545         for (int i = 0; i < digestAlgorithms.length; i++) {
546             int digestAlgorithm = digestAlgorithms[i];
547             byte[] input = digestsOfChunks[i];
548             String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
549             MessageDigest md;
550             try {
551                 md = MessageDigest.getInstance(jcaAlgorithmName);
552             } catch (NoSuchAlgorithmException e) {
553                 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
554             }
555             byte[] output = md.digest(input);
556             result[i] = output;
557         }
558         return result;
559     }
560 
561     /**
562      * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
563      *
564      * @throws IOException if an I/O error occurs while reading the file.
565      * @throws SignatureNotFoundException if the EoCD could not be found.
566      */
getEocd(RandomAccessFile apk)567     private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
568             throws IOException, SignatureNotFoundException {
569         Pair<ByteBuffer, Long> eocdAndOffsetInFile =
570                 ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
571         if (eocdAndOffsetInFile == null) {
572             throw new SignatureNotFoundException(
573                     "Not an APK file: ZIP End of Central Directory record not found");
574         }
575         return eocdAndOffsetInFile;
576     }
577 
getCentralDirOffset(ByteBuffer eocd, long eocdOffset)578     private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
579             throws SignatureNotFoundException {
580         // Look up the offset of ZIP Central Directory.
581         long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
582         if (centralDirOffset >= eocdOffset) {
583             throw new SignatureNotFoundException(
584                     "ZIP Central Directory offset out of range: " + centralDirOffset
585                     + ". ZIP End of Central Directory offset: " + eocdOffset);
586         }
587         long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
588         if (centralDirOffset + centralDirSize != eocdOffset) {
589             throw new SignatureNotFoundException(
590                     "ZIP Central Directory is not immediately followed by End of Central"
591                     + " Directory");
592         }
593         return centralDirOffset;
594     }
595 
getChunkCount(long inputSizeBytes)596     private static final long getChunkCount(long inputSizeBytes) {
597         return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
598     }
599 
600     private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
601 
602     private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
603     private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
604     private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
605     private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
606     private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
607     private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
608     private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
609 
610     private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
611     private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
612 
isSupportedSignatureAlgorithm(int sigAlgorithm)613     private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
614         switch (sigAlgorithm) {
615             case SIGNATURE_RSA_PSS_WITH_SHA256:
616             case SIGNATURE_RSA_PSS_WITH_SHA512:
617             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
618             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
619             case SIGNATURE_ECDSA_WITH_SHA256:
620             case SIGNATURE_ECDSA_WITH_SHA512:
621             case SIGNATURE_DSA_WITH_SHA256:
622                 return true;
623             default:
624                 return false;
625         }
626     }
627 
compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2)628     private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
629         int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
630         int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
631         return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
632     }
633 
compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2)634     private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
635         switch (digestAlgorithm1) {
636             case CONTENT_DIGEST_CHUNKED_SHA256:
637                 switch (digestAlgorithm2) {
638                     case CONTENT_DIGEST_CHUNKED_SHA256:
639                         return 0;
640                     case CONTENT_DIGEST_CHUNKED_SHA512:
641                         return -1;
642                     default:
643                         throw new IllegalArgumentException(
644                                 "Unknown digestAlgorithm2: " + digestAlgorithm2);
645                 }
646             case CONTENT_DIGEST_CHUNKED_SHA512:
647                 switch (digestAlgorithm2) {
648                     case CONTENT_DIGEST_CHUNKED_SHA256:
649                         return 1;
650                     case CONTENT_DIGEST_CHUNKED_SHA512:
651                         return 0;
652                     default:
653                         throw new IllegalArgumentException(
654                                 "Unknown digestAlgorithm2: " + digestAlgorithm2);
655                 }
656             default:
657                 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
658         }
659     }
660 
getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm)661     private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
662         switch (sigAlgorithm) {
663             case SIGNATURE_RSA_PSS_WITH_SHA256:
664             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
665             case SIGNATURE_ECDSA_WITH_SHA256:
666             case SIGNATURE_DSA_WITH_SHA256:
667                 return CONTENT_DIGEST_CHUNKED_SHA256;
668             case SIGNATURE_RSA_PSS_WITH_SHA512:
669             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
670             case SIGNATURE_ECDSA_WITH_SHA512:
671                 return CONTENT_DIGEST_CHUNKED_SHA512;
672             default:
673                 throw new IllegalArgumentException(
674                         "Unknown signature algorithm: 0x"
675                                 + Long.toHexString(sigAlgorithm & 0xffffffff));
676         }
677     }
678 
getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm)679     private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
680         switch (digestAlgorithm) {
681             case CONTENT_DIGEST_CHUNKED_SHA256:
682                 return "SHA-256";
683             case CONTENT_DIGEST_CHUNKED_SHA512:
684                 return "SHA-512";
685             default:
686                 throw new IllegalArgumentException(
687                         "Unknown content digest algorthm: " + digestAlgorithm);
688         }
689     }
690 
getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm)691     private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
692         switch (digestAlgorithm) {
693             case CONTENT_DIGEST_CHUNKED_SHA256:
694                 return 256 / 8;
695             case CONTENT_DIGEST_CHUNKED_SHA512:
696                 return 512 / 8;
697             default:
698                 throw new IllegalArgumentException(
699                         "Unknown content digest algorthm: " + digestAlgorithm);
700         }
701     }
702 
getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm)703     private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
704         switch (sigAlgorithm) {
705             case SIGNATURE_RSA_PSS_WITH_SHA256:
706             case SIGNATURE_RSA_PSS_WITH_SHA512:
707             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
708             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
709                 return "RSA";
710             case SIGNATURE_ECDSA_WITH_SHA256:
711             case SIGNATURE_ECDSA_WITH_SHA512:
712                 return "EC";
713             case SIGNATURE_DSA_WITH_SHA256:
714                 return "DSA";
715             default:
716                 throw new IllegalArgumentException(
717                         "Unknown signature algorithm: 0x"
718                                 + Long.toHexString(sigAlgorithm & 0xffffffff));
719         }
720     }
721 
722     private static Pair<String, ? extends AlgorithmParameterSpec>
getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm)723             getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
724         switch (sigAlgorithm) {
725             case SIGNATURE_RSA_PSS_WITH_SHA256:
726                 return Pair.create(
727                         "SHA256withRSA/PSS",
728                         new PSSParameterSpec(
729                                 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
730             case SIGNATURE_RSA_PSS_WITH_SHA512:
731                 return Pair.create(
732                         "SHA512withRSA/PSS",
733                         new PSSParameterSpec(
734                                 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
735             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
736                 return Pair.create("SHA256withRSA", null);
737             case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
738                 return Pair.create("SHA512withRSA", null);
739             case SIGNATURE_ECDSA_WITH_SHA256:
740                 return Pair.create("SHA256withECDSA", null);
741             case SIGNATURE_ECDSA_WITH_SHA512:
742                 return Pair.create("SHA512withECDSA", null);
743             case SIGNATURE_DSA_WITH_SHA256:
744                 return Pair.create("SHA256withDSA", null);
745             default:
746                 throw new IllegalArgumentException(
747                         "Unknown signature algorithm: 0x"
748                                 + Long.toHexString(sigAlgorithm & 0xffffffff));
749         }
750     }
751 
752     /**
753      * Returns new byte buffer whose content is a shared subsequence of this buffer's content
754      * between the specified start (inclusive) and end (exclusive) positions. As opposed to
755      * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
756      * buffer's byte order.
757      */
sliceFromTo(ByteBuffer source, int start, int end)758     private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
759         if (start < 0) {
760             throw new IllegalArgumentException("start: " + start);
761         }
762         if (end < start) {
763             throw new IllegalArgumentException("end < start: " + end + " < " + start);
764         }
765         int capacity = source.capacity();
766         if (end > source.capacity()) {
767             throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
768         }
769         int originalLimit = source.limit();
770         int originalPosition = source.position();
771         try {
772             source.position(0);
773             source.limit(end);
774             source.position(start);
775             ByteBuffer result = source.slice();
776             result.order(source.order());
777             return result;
778         } finally {
779             source.position(0);
780             source.limit(originalLimit);
781             source.position(originalPosition);
782         }
783     }
784 
785     /**
786      * Relative <em>get</em> method for reading {@code size} number of bytes from the current
787      * position of this buffer.
788      *
789      * <p>This method reads the next {@code size} bytes at this buffer's current position,
790      * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
791      * {@code size}, byte order set to this buffer's byte order; and then increments the position by
792      * {@code size}.
793      */
getByteBuffer(ByteBuffer source, int size)794     private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
795             throws BufferUnderflowException {
796         if (size < 0) {
797             throw new IllegalArgumentException("size: " + size);
798         }
799         int originalLimit = source.limit();
800         int position = source.position();
801         int limit = position + size;
802         if ((limit < position) || (limit > originalLimit)) {
803             throw new BufferUnderflowException();
804         }
805         source.limit(limit);
806         try {
807             ByteBuffer result = source.slice();
808             result.order(source.order());
809             source.position(limit);
810             return result;
811         } finally {
812             source.limit(originalLimit);
813         }
814     }
815 
getLengthPrefixedSlice(ByteBuffer source)816     private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
817         if (source.remaining() < 4) {
818             throw new IOException(
819                     "Remaining buffer too short to contain length of length-prefixed field."
820                             + " Remaining: " + source.remaining());
821         }
822         int len = source.getInt();
823         if (len < 0) {
824             throw new IllegalArgumentException("Negative length");
825         } else if (len > source.remaining()) {
826             throw new IOException("Length-prefixed field longer than remaining buffer."
827                     + " Field length: " + len + ", remaining: " + source.remaining());
828         }
829         return getByteBuffer(source, len);
830     }
831 
readLengthPrefixedByteArray(ByteBuffer buf)832     private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
833         int len = buf.getInt();
834         if (len < 0) {
835             throw new IOException("Negative length");
836         } else if (len > buf.remaining()) {
837             throw new IOException("Underflow while reading length-prefixed value. Length: " + len
838                     + ", available: " + buf.remaining());
839         }
840         byte[] result = new byte[len];
841         buf.get(result);
842         return result;
843     }
844 
setUnsignedInt32LittleEndian(int value, byte[] result, int offset)845     private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
846         result[offset] = (byte) (value & 0xff);
847         result[offset + 1] = (byte) ((value >>> 8) & 0xff);
848         result[offset + 2] = (byte) ((value >>> 16) & 0xff);
849         result[offset + 3] = (byte) ((value >>> 24) & 0xff);
850     }
851 
852     private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
853     private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
854     private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
855 
856     private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
857 
findApkSigningBlock( RandomAccessFile apk, long centralDirOffset)858     private static Pair<ByteBuffer, Long> findApkSigningBlock(
859             RandomAccessFile apk, long centralDirOffset)
860                     throws IOException, SignatureNotFoundException {
861         // FORMAT:
862         // OFFSET       DATA TYPE  DESCRIPTION
863         // * @+0  bytes uint64:    size in bytes (excluding this field)
864         // * @+8  bytes payload
865         // * @-24 bytes uint64:    size in bytes (same as the one above)
866         // * @-16 bytes uint128:   magic
867 
868         if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
869             throw new SignatureNotFoundException(
870                     "APK too small for APK Signing Block. ZIP Central Directory offset: "
871                             + centralDirOffset);
872         }
873         // Read the magic and offset in file from the footer section of the block:
874         // * uint64:   size of block
875         // * 16 bytes: magic
876         ByteBuffer footer = ByteBuffer.allocate(24);
877         footer.order(ByteOrder.LITTLE_ENDIAN);
878         apk.seek(centralDirOffset - footer.capacity());
879         apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
880         if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
881                 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
882             throw new SignatureNotFoundException(
883                     "No APK Signing Block before ZIP Central Directory");
884         }
885         // Read and compare size fields
886         long apkSigBlockSizeInFooter = footer.getLong(0);
887         if ((apkSigBlockSizeInFooter < footer.capacity())
888                 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
889             throw new SignatureNotFoundException(
890                     "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
891         }
892         int totalSize = (int) (apkSigBlockSizeInFooter + 8);
893         long apkSigBlockOffset = centralDirOffset - totalSize;
894         if (apkSigBlockOffset < 0) {
895             throw new SignatureNotFoundException(
896                     "APK Signing Block offset out of range: " + apkSigBlockOffset);
897         }
898         ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
899         apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
900         apk.seek(apkSigBlockOffset);
901         apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
902         long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
903         if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
904             throw new SignatureNotFoundException(
905                     "APK Signing Block sizes in header and footer do not match: "
906                             + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
907         }
908         return Pair.create(apkSigBlock, apkSigBlockOffset);
909     }
910 
findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)911     private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
912             throws SignatureNotFoundException {
913         checkByteOrderLittleEndian(apkSigningBlock);
914         // FORMAT:
915         // OFFSET       DATA TYPE  DESCRIPTION
916         // * @+0  bytes uint64:    size in bytes (excluding this field)
917         // * @+8  bytes pairs
918         // * @-24 bytes uint64:    size in bytes (same as the one above)
919         // * @-16 bytes uint128:   magic
920         ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
921 
922         int entryCount = 0;
923         while (pairs.hasRemaining()) {
924             entryCount++;
925             if (pairs.remaining() < 8) {
926                 throw new SignatureNotFoundException(
927                         "Insufficient data to read size of APK Signing Block entry #" + entryCount);
928             }
929             long lenLong = pairs.getLong();
930             if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
931                 throw new SignatureNotFoundException(
932                         "APK Signing Block entry #" + entryCount
933                                 + " size out of range: " + lenLong);
934             }
935             int len = (int) lenLong;
936             int nextEntryPos = pairs.position() + len;
937             if (len > pairs.remaining()) {
938                 throw new SignatureNotFoundException(
939                         "APK Signing Block entry #" + entryCount + " size out of range: " + len
940                                 + ", available: " + pairs.remaining());
941             }
942             int id = pairs.getInt();
943             if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
944                 return getByteBuffer(pairs, len - 4);
945             }
946             pairs.position(nextEntryPos);
947         }
948 
949         throw new SignatureNotFoundException(
950                 "No APK Signature Scheme v2 block in APK Signing Block");
951     }
952 
checkByteOrderLittleEndian(ByteBuffer buffer)953     private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
954         if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
955             throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
956         }
957     }
958 
959     public static class SignatureNotFoundException extends Exception {
960         private static final long serialVersionUID = 1L;
961 
SignatureNotFoundException(String message)962         public SignatureNotFoundException(String message) {
963             super(message);
964         }
965 
SignatureNotFoundException(String message, Throwable cause)966         public SignatureNotFoundException(String message, Throwable cause) {
967             super(message, cause);
968         }
969     }
970 
971     /**
972      * Source of data to be digested.
973      */
974     private static interface DataSource {
975 
976         /**
977          * Returns the size (in bytes) of the data offered by this source.
978          */
size()979         long size();
980 
981         /**
982          * Feeds the specified region of this source's data into the provided digests. Each digest
983          * instance gets the same data.
984          *
985          * @param offset offset of the region inside this data source.
986          * @param size size (in bytes) of the region.
987          */
feedIntoMessageDigests(MessageDigest[] mds, long offset, int size)988         void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException;
989     }
990 
991     /**
992      * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
993      * of the file requested by
994      * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
995      */
996     private static final class MemoryMappedFileDataSource implements DataSource {
997         private static final Os OS = Libcore.os;
998         private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE);
999 
1000         private final FileDescriptor mFd;
1001         private final long mFilePosition;
1002         private final long mSize;
1003 
1004         /**
1005          * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
1006          *
1007          * @param position start position of the region in the file.
1008          * @param size size (in bytes) of the region.
1009          */
MemoryMappedFileDataSource(FileDescriptor fd, long position, long size)1010         public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
1011             mFd = fd;
1012             mFilePosition = position;
1013             mSize = size;
1014         }
1015 
1016         @Override
size()1017         public long size() {
1018             return mSize;
1019         }
1020 
1021         @Override
feedIntoMessageDigests( MessageDigest[] mds, long offset, int size)1022         public void feedIntoMessageDigests(
1023                 MessageDigest[] mds, long offset, int size) throws IOException {
1024             // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
1025             // method was settled on a straightforward mmap with prefaulting.
1026             //
1027             // This method is not using FileChannel.map API because that API does not offset a way
1028             // to "prefault" the resulting memory pages. Without prefaulting, performance is about
1029             // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
1030             // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
1031             // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
1032             // time, which is not compensated for by faster reads.
1033 
1034             // We mmap the smallest region of the file containing the requested data. mmap requires
1035             // that the start offset in the file must be a multiple of memory page size. We thus may
1036             // need to mmap from an offset less than the requested offset.
1037             long filePosition = mFilePosition + offset;
1038             long mmapFilePosition =
1039                     (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
1040             int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
1041             long mmapRegionSize = size + dataStartOffsetInMmapRegion;
1042             long mmapPtr = 0;
1043             try {
1044                 mmapPtr = OS.mmap(
1045                         0, // let the OS choose the start address of the region in memory
1046                         mmapRegionSize,
1047                         OsConstants.PROT_READ,
1048                         OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
1049                         mFd,
1050                         mmapFilePosition);
1051                 // Feeding a memory region into MessageDigest requires the region to be represented
1052                 // as a direct ByteBuffer.
1053                 ByteBuffer buf = new DirectByteBuffer(
1054                         size,
1055                         mmapPtr + dataStartOffsetInMmapRegion,
1056                         mFd,  // not really needed, but just in case
1057                         null, // no need to clean up -- it's taken care of by the finally block
1058                         true  // read only buffer
1059                         );
1060                 for (MessageDigest md : mds) {
1061                     buf.position(0);
1062                     md.update(buf);
1063                 }
1064             } catch (ErrnoException e) {
1065                 throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
1066             } finally {
1067                 if (mmapPtr != 0) {
1068                     try {
1069                         OS.munmap(mmapPtr, mmapRegionSize);
1070                     } catch (ErrnoException ignored) {}
1071                 }
1072             }
1073         }
1074     }
1075 
1076     /**
1077      * {@link DataSource} which provides data from a {@link ByteBuffer}.
1078      */
1079     private static final class ByteBufferDataSource implements DataSource {
1080         /**
1081          * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
1082          * The buffer's position is 0 and limit is equal to capacity.
1083          */
1084         private final ByteBuffer mBuf;
1085 
ByteBufferDataSource(ByteBuffer buf)1086         public ByteBufferDataSource(ByteBuffer buf) {
1087             // Defensive copy, to avoid changes to mBuf being visible in buf.
1088             mBuf = buf.slice();
1089         }
1090 
1091         @Override
size()1092         public long size() {
1093             return mBuf.capacity();
1094         }
1095 
1096         @Override
feedIntoMessageDigests( MessageDigest[] mds, long offset, int size)1097         public void feedIntoMessageDigests(
1098                 MessageDigest[] mds, long offset, int size) throws IOException {
1099             // There's no way to tell MessageDigest to read data from ByteBuffer from a position
1100             // other than the buffer's current position. We thus need to change the buffer's
1101             // position to match the requested offset.
1102             //
1103             // In the future, it may be necessary to compute digests of multiple regions in
1104             // parallel. Given that digest computation is a slow operation, we enable multiple
1105             // such requests to be fulfilled by this instance. This is achieved by serially
1106             // creating a new ByteBuffer corresponding to the requested data range and then,
1107             // potentially concurrently, feeding these buffers into MessageDigest instances.
1108             ByteBuffer region;
1109             synchronized (mBuf) {
1110                 mBuf.position((int) offset);
1111                 mBuf.limit((int) offset + size);
1112                 region = mBuf.slice();
1113             }
1114 
1115             for (MessageDigest md : mds) {
1116                 // Need to reset position to 0 at the start of each iteration because
1117                 // MessageDigest.update below sets it to the buffer's limit.
1118                 region.position(0);
1119                 md.update(region);
1120             }
1121         }
1122     }
1123 
1124     /**
1125      * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
1126      * of letting the underlying implementation have a shot at re-encoding the data.
1127      */
1128     private static class VerbatimX509Certificate extends WrappedX509Certificate {
1129         private byte[] encodedVerbatim;
1130 
VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim)1131         public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
1132             super(wrapped);
1133             this.encodedVerbatim = encodedVerbatim;
1134         }
1135 
1136         @Override
getEncoded()1137         public byte[] getEncoded() throws CertificateEncodingException {
1138             return encodedVerbatim;
1139         }
1140     }
1141 
1142     private static class WrappedX509Certificate extends X509Certificate {
1143         private final X509Certificate wrapped;
1144 
WrappedX509Certificate(X509Certificate wrapped)1145         public WrappedX509Certificate(X509Certificate wrapped) {
1146             this.wrapped = wrapped;
1147         }
1148 
1149         @Override
getCriticalExtensionOIDs()1150         public Set<String> getCriticalExtensionOIDs() {
1151             return wrapped.getCriticalExtensionOIDs();
1152         }
1153 
1154         @Override
getExtensionValue(String oid)1155         public byte[] getExtensionValue(String oid) {
1156             return wrapped.getExtensionValue(oid);
1157         }
1158 
1159         @Override
getNonCriticalExtensionOIDs()1160         public Set<String> getNonCriticalExtensionOIDs() {
1161             return wrapped.getNonCriticalExtensionOIDs();
1162         }
1163 
1164         @Override
hasUnsupportedCriticalExtension()1165         public boolean hasUnsupportedCriticalExtension() {
1166             return wrapped.hasUnsupportedCriticalExtension();
1167         }
1168 
1169         @Override
checkValidity()1170         public void checkValidity()
1171                 throws CertificateExpiredException, CertificateNotYetValidException {
1172             wrapped.checkValidity();
1173         }
1174 
1175         @Override
checkValidity(Date date)1176         public void checkValidity(Date date)
1177                 throws CertificateExpiredException, CertificateNotYetValidException {
1178             wrapped.checkValidity(date);
1179         }
1180 
1181         @Override
getVersion()1182         public int getVersion() {
1183             return wrapped.getVersion();
1184         }
1185 
1186         @Override
getSerialNumber()1187         public BigInteger getSerialNumber() {
1188             return wrapped.getSerialNumber();
1189         }
1190 
1191         @Override
getIssuerDN()1192         public Principal getIssuerDN() {
1193             return wrapped.getIssuerDN();
1194         }
1195 
1196         @Override
getSubjectDN()1197         public Principal getSubjectDN() {
1198             return wrapped.getSubjectDN();
1199         }
1200 
1201         @Override
getNotBefore()1202         public Date getNotBefore() {
1203             return wrapped.getNotBefore();
1204         }
1205 
1206         @Override
getNotAfter()1207         public Date getNotAfter() {
1208             return wrapped.getNotAfter();
1209         }
1210 
1211         @Override
getTBSCertificate()1212         public byte[] getTBSCertificate() throws CertificateEncodingException {
1213             return wrapped.getTBSCertificate();
1214         }
1215 
1216         @Override
getSignature()1217         public byte[] getSignature() {
1218             return wrapped.getSignature();
1219         }
1220 
1221         @Override
getSigAlgName()1222         public String getSigAlgName() {
1223             return wrapped.getSigAlgName();
1224         }
1225 
1226         @Override
getSigAlgOID()1227         public String getSigAlgOID() {
1228             return wrapped.getSigAlgOID();
1229         }
1230 
1231         @Override
getSigAlgParams()1232         public byte[] getSigAlgParams() {
1233             return wrapped.getSigAlgParams();
1234         }
1235 
1236         @Override
getIssuerUniqueID()1237         public boolean[] getIssuerUniqueID() {
1238             return wrapped.getIssuerUniqueID();
1239         }
1240 
1241         @Override
getSubjectUniqueID()1242         public boolean[] getSubjectUniqueID() {
1243             return wrapped.getSubjectUniqueID();
1244         }
1245 
1246         @Override
getKeyUsage()1247         public boolean[] getKeyUsage() {
1248             return wrapped.getKeyUsage();
1249         }
1250 
1251         @Override
getBasicConstraints()1252         public int getBasicConstraints() {
1253             return wrapped.getBasicConstraints();
1254         }
1255 
1256         @Override
getEncoded()1257         public byte[] getEncoded() throws CertificateEncodingException {
1258             return wrapped.getEncoded();
1259         }
1260 
1261         @Override
verify(PublicKey key)1262         public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
1263                 InvalidKeyException, NoSuchProviderException, SignatureException {
1264             wrapped.verify(key);
1265         }
1266 
1267         @Override
verify(PublicKey key, String sigProvider)1268         public void verify(PublicKey key, String sigProvider)
1269                 throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
1270                 NoSuchProviderException, SignatureException {
1271             wrapped.verify(key, sigProvider);
1272         }
1273 
1274         @Override
toString()1275         public String toString() {
1276             return wrapped.toString();
1277         }
1278 
1279         @Override
getPublicKey()1280         public PublicKey getPublicKey() {
1281             return wrapped.getPublicKey();
1282         }
1283     }
1284 }
1285