Home | History | Annotate | Download | only in apk
      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      */
     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      */
    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      */
    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 
    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      */
    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      */
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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>
    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      */
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    962         public SignatureNotFoundException(String message) {
    963             super(message);
    964         }
    965 
    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          */
    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          */
    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          */
   1010         public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
   1011             mFd = fd;
   1012             mFilePosition = position;
   1013             mSize = size;
   1014         }
   1015 
   1016         @Override
   1017         public long size() {
   1018             return mSize;
   1019         }
   1020 
   1021         @Override
   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 
   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
   1092         public long size() {
   1093             return mBuf.capacity();
   1094         }
   1095 
   1096         @Override
   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 
   1131         public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
   1132             super(wrapped);
   1133             this.encodedVerbatim = encodedVerbatim;
   1134         }
   1135 
   1136         @Override
   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 
   1145         public WrappedX509Certificate(X509Certificate wrapped) {
   1146             this.wrapped = wrapped;
   1147         }
   1148 
   1149         @Override
   1150         public Set<String> getCriticalExtensionOIDs() {
   1151             return wrapped.getCriticalExtensionOIDs();
   1152         }
   1153 
   1154         @Override
   1155         public byte[] getExtensionValue(String oid) {
   1156             return wrapped.getExtensionValue(oid);
   1157         }
   1158 
   1159         @Override
   1160         public Set<String> getNonCriticalExtensionOIDs() {
   1161             return wrapped.getNonCriticalExtensionOIDs();
   1162         }
   1163 
   1164         @Override
   1165         public boolean hasUnsupportedCriticalExtension() {
   1166             return wrapped.hasUnsupportedCriticalExtension();
   1167         }
   1168 
   1169         @Override
   1170         public void checkValidity()
   1171                 throws CertificateExpiredException, CertificateNotYetValidException {
   1172             wrapped.checkValidity();
   1173         }
   1174 
   1175         @Override
   1176         public void checkValidity(Date date)
   1177                 throws CertificateExpiredException, CertificateNotYetValidException {
   1178             wrapped.checkValidity(date);
   1179         }
   1180 
   1181         @Override
   1182         public int getVersion() {
   1183             return wrapped.getVersion();
   1184         }
   1185 
   1186         @Override
   1187         public BigInteger getSerialNumber() {
   1188             return wrapped.getSerialNumber();
   1189         }
   1190 
   1191         @Override
   1192         public Principal getIssuerDN() {
   1193             return wrapped.getIssuerDN();
   1194         }
   1195 
   1196         @Override
   1197         public Principal getSubjectDN() {
   1198             return wrapped.getSubjectDN();
   1199         }
   1200 
   1201         @Override
   1202         public Date getNotBefore() {
   1203             return wrapped.getNotBefore();
   1204         }
   1205 
   1206         @Override
   1207         public Date getNotAfter() {
   1208             return wrapped.getNotAfter();
   1209         }
   1210 
   1211         @Override
   1212         public byte[] getTBSCertificate() throws CertificateEncodingException {
   1213             return wrapped.getTBSCertificate();
   1214         }
   1215 
   1216         @Override
   1217         public byte[] getSignature() {
   1218             return wrapped.getSignature();
   1219         }
   1220 
   1221         @Override
   1222         public String getSigAlgName() {
   1223             return wrapped.getSigAlgName();
   1224         }
   1225 
   1226         @Override
   1227         public String getSigAlgOID() {
   1228             return wrapped.getSigAlgOID();
   1229         }
   1230 
   1231         @Override
   1232         public byte[] getSigAlgParams() {
   1233             return wrapped.getSigAlgParams();
   1234         }
   1235 
   1236         @Override
   1237         public boolean[] getIssuerUniqueID() {
   1238             return wrapped.getIssuerUniqueID();
   1239         }
   1240 
   1241         @Override
   1242         public boolean[] getSubjectUniqueID() {
   1243             return wrapped.getSubjectUniqueID();
   1244         }
   1245 
   1246         @Override
   1247         public boolean[] getKeyUsage() {
   1248             return wrapped.getKeyUsage();
   1249         }
   1250 
   1251         @Override
   1252         public int getBasicConstraints() {
   1253             return wrapped.getBasicConstraints();
   1254         }
   1255 
   1256         @Override
   1257         public byte[] getEncoded() throws CertificateEncodingException {
   1258             return wrapped.getEncoded();
   1259         }
   1260 
   1261         @Override
   1262         public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
   1263                 InvalidKeyException, NoSuchProviderException, SignatureException {
   1264             wrapped.verify(key);
   1265         }
   1266 
   1267         @Override
   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
   1275         public String toString() {
   1276             return wrapped.toString();
   1277         }
   1278 
   1279         @Override
   1280         public PublicKey getPublicKey() {
   1281             return wrapped.getPublicKey();
   1282         }
   1283     }
   1284 }
   1285