ApkSigningBlockUtils.java revision 8d1553b9b1be06100ce4f4cc4c8c5088b48995a2
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.util.apk;
18
19import android.util.ArrayMap;
20import android.util.Pair;
21
22import java.io.FileDescriptor;
23import java.io.IOException;
24import java.io.RandomAccessFile;
25import java.nio.BufferUnderflowException;
26import java.nio.ByteBuffer;
27import java.nio.ByteOrder;
28import java.security.DigestException;
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.security.spec.AlgorithmParameterSpec;
32import java.security.spec.MGF1ParameterSpec;
33import java.security.spec.PSSParameterSpec;
34import java.util.Arrays;
35import java.util.Map;
36
37/**
38 * Utility class for an APK Signature Scheme using the APK Signing Block.
39 *
40 * @hide for internal use only.
41 */
42final class ApkSigningBlockUtils {
43
44    private ApkSigningBlockUtils() {
45    }
46
47    /**
48     * Returns the APK Signature Scheme block contained in the provided APK file and the
49     * additional information relevant for verifying the block against the file.
50     *
51     * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
52     *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
53     *                block ID.
54     *
55     * @throws SignatureNotFoundException if the APK is not signed using this scheme.
56     * @throws IOException if an I/O error occurs while reading the APK file.
57     */
58    static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
59            throws IOException, SignatureNotFoundException {
60        // Find the ZIP End of Central Directory (EoCD) record.
61        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
62        ByteBuffer eocd = eocdAndOffsetInFile.first;
63        long eocdOffset = eocdAndOffsetInFile.second;
64        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
65            throw new SignatureNotFoundException("ZIP64 APK not supported");
66        }
67
68        // Find the APK Signing Block. The block immediately precedes the Central Directory.
69        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
70        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
71                findApkSigningBlock(apk, centralDirOffset);
72        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
73        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
74
75        // Find the APK Signature Scheme Block inside the APK Signing Block.
76        ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
77                blockId);
78
79        return new SignatureInfo(
80                apkSignatureSchemeBlock,
81                apkSigningBlockOffset,
82                centralDirOffset,
83                eocdOffset,
84                eocd);
85    }
86
87    static void verifyIntegrity(
88            Map<Integer, byte[]> expectedDigests,
89            RandomAccessFile apk,
90            SignatureInfo signatureInfo) throws SecurityException {
91        if (expectedDigests.isEmpty()) {
92            throw new SecurityException("No digests provided");
93        }
94
95        boolean neverVerified = true;
96
97        Map<Integer, byte[]> expected1MbChunkDigests = new ArrayMap<>();
98        if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) {
99            expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA256,
100                    expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA256));
101        }
102        if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) {
103            expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA512,
104                    expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA512));
105        }
106        if (!expected1MbChunkDigests.isEmpty()) {
107            try {
108                verifyIntegrityFor1MbChunkBasedAlgorithm(expected1MbChunkDigests, apk.getFD(),
109                        signatureInfo);
110                neverVerified = false;
111            } catch (IOException e) {
112                throw new SecurityException("Cannot get FD", e);
113            }
114        }
115
116        if (expectedDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
117            verifyIntegrityForVerityBasedAlgorithm(
118                    expectedDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256), apk, signatureInfo);
119            neverVerified = false;
120        }
121
122        if (neverVerified) {
123            throw new SecurityException("No known digest exists for integrity check");
124        }
125    }
126
127    private static void verifyIntegrityFor1MbChunkBasedAlgorithm(
128            Map<Integer, byte[]> expectedDigests,
129            FileDescriptor apkFileDescriptor,
130            SignatureInfo signatureInfo) throws SecurityException {
131        // We need to verify the integrity of the following three sections of the file:
132        // 1. Everything up to the start of the APK Signing Block.
133        // 2. ZIP Central Directory.
134        // 3. ZIP End of Central Directory (EoCD).
135        // Each of these sections is represented as a separate DataSource instance below.
136
137        // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
138        // avoid wasting physical memory. In most APK verification scenarios, the contents of the
139        // APK are already there in the OS's page cache and thus mmap does not use additional
140        // physical memory.
141        DataSource beforeApkSigningBlock =
142                new MemoryMappedFileDataSource(apkFileDescriptor, 0,
143                        signatureInfo.apkSigningBlockOffset);
144        DataSource centralDir =
145                new MemoryMappedFileDataSource(
146                        apkFileDescriptor, signatureInfo.centralDirOffset,
147                        signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
148
149        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
150        // Central Directory must be considered to point to the offset of the APK Signing Block.
151        ByteBuffer eocdBuf = signatureInfo.eocd.duplicate();
152        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
153        ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
154        DataSource eocd = new ByteBufferDataSource(eocdBuf);
155
156        int[] digestAlgorithms = new int[expectedDigests.size()];
157        int digestAlgorithmCount = 0;
158        for (int digestAlgorithm : expectedDigests.keySet()) {
159            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
160            digestAlgorithmCount++;
161        }
162        byte[][] actualDigests;
163        try {
164            actualDigests =
165                    computeContentDigestsPer1MbChunk(
166                            digestAlgorithms,
167                            new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
168        } catch (DigestException e) {
169            throw new SecurityException("Failed to compute digest(s) of contents", e);
170        }
171        for (int i = 0; i < digestAlgorithms.length; i++) {
172            int digestAlgorithm = digestAlgorithms[i];
173            byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
174            byte[] actualDigest = actualDigests[i];
175            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
176                throw new SecurityException(
177                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
178                                + " digest of contents did not verify");
179            }
180        }
181    }
182
183    private static byte[][] computeContentDigestsPer1MbChunk(
184            int[] digestAlgorithms,
185            DataSource[] contents) throws DigestException {
186        // For each digest algorithm the result is computed as follows:
187        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
188        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
189        //    No chunks are produced for empty (zero length) segments.
190        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
191        //    length in bytes (uint32 little-endian) and the chunk's contents.
192        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
193        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
194        //    segments in-order.
195
196        long totalChunkCountLong = 0;
197        for (DataSource input : contents) {
198            totalChunkCountLong += getChunkCount(input.size());
199        }
200        if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
201            throw new DigestException("Too many chunks: " + totalChunkCountLong);
202        }
203        int totalChunkCount = (int) totalChunkCountLong;
204
205        byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
206        for (int i = 0; i < digestAlgorithms.length; i++) {
207            int digestAlgorithm = digestAlgorithms[i];
208            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
209            byte[] concatenationOfChunkCountAndChunkDigests =
210                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
211            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
212            setUnsignedInt32LittleEndian(
213                    totalChunkCount,
214                    concatenationOfChunkCountAndChunkDigests,
215                    1);
216            digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
217        }
218
219        byte[] chunkContentPrefix = new byte[5];
220        chunkContentPrefix[0] = (byte) 0xa5;
221        int chunkIndex = 0;
222        MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
223        for (int i = 0; i < digestAlgorithms.length; i++) {
224            String jcaAlgorithmName =
225                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
226            try {
227                mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
228            } catch (NoSuchAlgorithmException e) {
229                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
230            }
231        }
232        // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
233        // into how to parallelize (if at all) based on the capabilities of the hardware on which
234        // this code is running and based on the size of input.
235        DataDigester digester = new MultipleDigestDataDigester(mds);
236        int dataSourceIndex = 0;
237        for (DataSource input : contents) {
238            long inputOffset = 0;
239            long inputRemaining = input.size();
240            while (inputRemaining > 0) {
241                int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
242                setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
243                for (int i = 0; i < mds.length; i++) {
244                    mds[i].update(chunkContentPrefix);
245                }
246                try {
247                    input.feedIntoDataDigester(digester, inputOffset, chunkSize);
248                } catch (IOException e) {
249                    throw new DigestException(
250                            "Failed to digest chunk #" + chunkIndex + " of section #"
251                                    + dataSourceIndex,
252                            e);
253                }
254                for (int i = 0; i < digestAlgorithms.length; i++) {
255                    int digestAlgorithm = digestAlgorithms[i];
256                    byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
257                    int expectedDigestSizeBytes =
258                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
259                    MessageDigest md = mds[i];
260                    int actualDigestSizeBytes =
261                            md.digest(
262                                    concatenationOfChunkCountAndChunkDigests,
263                                    5 + chunkIndex * expectedDigestSizeBytes,
264                                    expectedDigestSizeBytes);
265                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
266                        throw new RuntimeException(
267                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
268                                        + actualDigestSizeBytes);
269                    }
270                }
271                inputOffset += chunkSize;
272                inputRemaining -= chunkSize;
273                chunkIndex++;
274            }
275            dataSourceIndex++;
276        }
277
278        byte[][] result = new byte[digestAlgorithms.length][];
279        for (int i = 0; i < digestAlgorithms.length; i++) {
280            int digestAlgorithm = digestAlgorithms[i];
281            byte[] input = digestsOfChunks[i];
282            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
283            MessageDigest md;
284            try {
285                md = MessageDigest.getInstance(jcaAlgorithmName);
286            } catch (NoSuchAlgorithmException e) {
287                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
288            }
289            byte[] output = md.digest(input);
290            result[i] = output;
291        }
292        return result;
293    }
294
295    /**
296     * Return the verity digest only if the length of digest content looks correct.
297     * When verity digest is generated, the last incomplete 4k chunk is padded with 0s before
298     * hashing. This means two almost identical APKs with different number of 0 at the end will have
299     * the same verity digest. To avoid this problem, the length of the source content (excluding
300     * Signing Block) is appended to the verity digest, and the digest is returned only if the
301     * length is consistent to the current APK.
302     */
303    static byte[] parseVerityDigestAndVerifySourceLength(
304            byte[] data, long fileSize, SignatureInfo signatureInfo) throws SecurityException {
305        // FORMAT:
306        // OFFSET       DATA TYPE  DESCRIPTION
307        // * @+0  bytes uint8[32]  Merkle tree root hash of SHA-256
308        // * @+32 bytes int64      Length of source data
309        int kRootHashSize = 32;
310        int kSourceLengthSize = 8;
311
312        if (data.length != kRootHashSize + kSourceLengthSize) {
313            throw new SecurityException("Verity digest size is wrong: " + data.length);
314        }
315        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
316        buffer.position(kRootHashSize);
317        long expectedSourceLength = buffer.getLong();
318
319        long signingBlockSize = signatureInfo.centralDirOffset
320                - signatureInfo.apkSigningBlockOffset;
321        if (expectedSourceLength != fileSize - signingBlockSize) {
322            throw new SecurityException("APK content size did not verify");
323        }
324
325        return Arrays.copyOfRange(data, 0, kRootHashSize);
326    }
327
328    private static void verifyIntegrityForVerityBasedAlgorithm(
329            byte[] expectedDigest,
330            RandomAccessFile apk,
331            SignatureInfo signatureInfo) throws SecurityException {
332        try {
333            byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest,
334                    apk.length(), signatureInfo);
335            ApkVerityBuilder.ApkVerityResult verity = ApkVerityBuilder.generateApkVerity(apk,
336                    signatureInfo, new ByteBufferFactory() {
337                        @Override
338                        public ByteBuffer create(int capacity) {
339                            return ByteBuffer.allocate(capacity);
340                        }
341                    });
342            if (!Arrays.equals(expectedRootHash, verity.rootHash)) {
343                throw new SecurityException("APK verity digest of contents did not verify");
344            }
345        } catch (DigestException | IOException | NoSuchAlgorithmException e) {
346            throw new SecurityException("Error during verification", e);
347        }
348    }
349
350    /**
351     * Generates the fsverity header and hash tree to be used by kernel for the given apk. This
352     * method does not check whether the root hash exists in the Signing Block or not.
353     *
354     * <p>The output is stored in the {@link ByteBuffer} created by the given {@link
355     * ByteBufferFactory}.
356     *
357     * @return the root hash of the generated hash tree.
358     */
359    public static byte[] generateApkVerity(String apkPath, ByteBufferFactory bufferFactory,
360            SignatureInfo signatureInfo)
361            throws IOException, SignatureNotFoundException, SecurityException, DigestException,
362                   NoSuchAlgorithmException {
363        try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
364            ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateApkVerity(apk,
365                    signatureInfo, bufferFactory);
366            return result.rootHash;
367        }
368    }
369
370    /**
371     * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
372     *
373     * @throws IOException if an I/O error occurs while reading the file.
374     * @throws SignatureNotFoundException if the EoCD could not be found.
375     */
376    static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
377            throws IOException, SignatureNotFoundException {
378        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
379                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
380        if (eocdAndOffsetInFile == null) {
381            throw new SignatureNotFoundException(
382                    "Not an APK file: ZIP End of Central Directory record not found");
383        }
384        return eocdAndOffsetInFile;
385    }
386
387    static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
388            throws SignatureNotFoundException {
389        // Look up the offset of ZIP Central Directory.
390        long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
391        if (centralDirOffset > eocdOffset) {
392            throw new SignatureNotFoundException(
393                    "ZIP Central Directory offset out of range: " + centralDirOffset
394                    + ". ZIP End of Central Directory offset: " + eocdOffset);
395        }
396        long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
397        if (centralDirOffset + centralDirSize != eocdOffset) {
398            throw new SignatureNotFoundException(
399                    "ZIP Central Directory is not immediately followed by End of Central"
400                    + " Directory");
401        }
402        return centralDirOffset;
403    }
404
405    private static long getChunkCount(long inputSizeBytes) {
406        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
407    }
408
409    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
410
411    static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
412    static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
413    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
414    static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
415    static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
416    static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
417    static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
418    static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0421;
419    static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423;
420    static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425;
421
422    static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
423    static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
424    static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
425
426    static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
427        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
428        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
429        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
430    }
431
432    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
433        switch (digestAlgorithm1) {
434            case CONTENT_DIGEST_CHUNKED_SHA256:
435                switch (digestAlgorithm2) {
436                    case CONTENT_DIGEST_CHUNKED_SHA256:
437                        return 0;
438                    case CONTENT_DIGEST_CHUNKED_SHA512:
439                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
440                        return -1;
441                    default:
442                        throw new IllegalArgumentException(
443                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
444                }
445            case CONTENT_DIGEST_CHUNKED_SHA512:
446                switch (digestAlgorithm2) {
447                    case CONTENT_DIGEST_CHUNKED_SHA256:
448                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
449                        return 1;
450                    case CONTENT_DIGEST_CHUNKED_SHA512:
451                        return 0;
452                    default:
453                        throw new IllegalArgumentException(
454                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
455                }
456            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
457                switch (digestAlgorithm2) {
458                    case CONTENT_DIGEST_CHUNKED_SHA512:
459                        return -1;
460                    case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
461                        return 0;
462                    case CONTENT_DIGEST_CHUNKED_SHA256:
463                        return 1;
464                    default:
465                        throw new IllegalArgumentException(
466                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
467                }
468            default:
469                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
470        }
471    }
472
473    static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
474        switch (sigAlgorithm) {
475            case SIGNATURE_RSA_PSS_WITH_SHA256:
476            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
477            case SIGNATURE_ECDSA_WITH_SHA256:
478            case SIGNATURE_DSA_WITH_SHA256:
479                return CONTENT_DIGEST_CHUNKED_SHA256;
480            case SIGNATURE_RSA_PSS_WITH_SHA512:
481            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
482            case SIGNATURE_ECDSA_WITH_SHA512:
483                return CONTENT_DIGEST_CHUNKED_SHA512;
484            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
485            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
486            case SIGNATURE_VERITY_DSA_WITH_SHA256:
487                return CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
488            default:
489                throw new IllegalArgumentException(
490                        "Unknown signature algorithm: 0x"
491                                + Long.toHexString(sigAlgorithm & 0xffffffff));
492        }
493    }
494
495    static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
496        switch (digestAlgorithm) {
497            case CONTENT_DIGEST_CHUNKED_SHA256:
498            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
499                return "SHA-256";
500            case CONTENT_DIGEST_CHUNKED_SHA512:
501                return "SHA-512";
502            default:
503                throw new IllegalArgumentException(
504                        "Unknown content digest algorthm: " + digestAlgorithm);
505        }
506    }
507
508    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
509        switch (digestAlgorithm) {
510            case CONTENT_DIGEST_CHUNKED_SHA256:
511            case CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
512                return 256 / 8;
513            case CONTENT_DIGEST_CHUNKED_SHA512:
514                return 512 / 8;
515            default:
516                throw new IllegalArgumentException(
517                        "Unknown content digest algorthm: " + digestAlgorithm);
518        }
519    }
520
521    static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
522        switch (sigAlgorithm) {
523            case SIGNATURE_RSA_PSS_WITH_SHA256:
524            case SIGNATURE_RSA_PSS_WITH_SHA512:
525            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
526            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
527            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
528                return "RSA";
529            case SIGNATURE_ECDSA_WITH_SHA256:
530            case SIGNATURE_ECDSA_WITH_SHA512:
531            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
532                return "EC";
533            case SIGNATURE_DSA_WITH_SHA256:
534            case SIGNATURE_VERITY_DSA_WITH_SHA256:
535                return "DSA";
536            default:
537                throw new IllegalArgumentException(
538                        "Unknown signature algorithm: 0x"
539                                + Long.toHexString(sigAlgorithm & 0xffffffff));
540        }
541    }
542
543    static Pair<String, ? extends AlgorithmParameterSpec>
544            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
545        switch (sigAlgorithm) {
546            case SIGNATURE_RSA_PSS_WITH_SHA256:
547                return Pair.create(
548                        "SHA256withRSA/PSS",
549                        new PSSParameterSpec(
550                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
551            case SIGNATURE_RSA_PSS_WITH_SHA512:
552                return Pair.create(
553                        "SHA512withRSA/PSS",
554                        new PSSParameterSpec(
555                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
556            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
557            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
558                return Pair.create("SHA256withRSA", null);
559            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
560                return Pair.create("SHA512withRSA", null);
561            case SIGNATURE_ECDSA_WITH_SHA256:
562            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
563                return Pair.create("SHA256withECDSA", null);
564            case SIGNATURE_ECDSA_WITH_SHA512:
565                return Pair.create("SHA512withECDSA", null);
566            case SIGNATURE_DSA_WITH_SHA256:
567            case SIGNATURE_VERITY_DSA_WITH_SHA256:
568                return Pair.create("SHA256withDSA", null);
569            default:
570                throw new IllegalArgumentException(
571                        "Unknown signature algorithm: 0x"
572                                + Long.toHexString(sigAlgorithm & 0xffffffff));
573        }
574    }
575
576    /**
577     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
578     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
579     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
580     * buffer's byte order.
581     */
582    static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
583        if (start < 0) {
584            throw new IllegalArgumentException("start: " + start);
585        }
586        if (end < start) {
587            throw new IllegalArgumentException("end < start: " + end + " < " + start);
588        }
589        int capacity = source.capacity();
590        if (end > source.capacity()) {
591            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
592        }
593        int originalLimit = source.limit();
594        int originalPosition = source.position();
595        try {
596            source.position(0);
597            source.limit(end);
598            source.position(start);
599            ByteBuffer result = source.slice();
600            result.order(source.order());
601            return result;
602        } finally {
603            source.position(0);
604            source.limit(originalLimit);
605            source.position(originalPosition);
606        }
607    }
608
609    /**
610     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
611     * position of this buffer.
612     *
613     * <p>This method reads the next {@code size} bytes at this buffer's current position,
614     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
615     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
616     * {@code size}.
617     */
618    static ByteBuffer getByteBuffer(ByteBuffer source, int size)
619            throws BufferUnderflowException {
620        if (size < 0) {
621            throw new IllegalArgumentException("size: " + size);
622        }
623        int originalLimit = source.limit();
624        int position = source.position();
625        int limit = position + size;
626        if ((limit < position) || (limit > originalLimit)) {
627            throw new BufferUnderflowException();
628        }
629        source.limit(limit);
630        try {
631            ByteBuffer result = source.slice();
632            result.order(source.order());
633            source.position(limit);
634            return result;
635        } finally {
636            source.limit(originalLimit);
637        }
638    }
639
640    static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
641        if (source.remaining() < 4) {
642            throw new IOException(
643                    "Remaining buffer too short to contain length of length-prefixed field."
644                            + " Remaining: " + source.remaining());
645        }
646        int len = source.getInt();
647        if (len < 0) {
648            throw new IllegalArgumentException("Negative length");
649        } else if (len > source.remaining()) {
650            throw new IOException("Length-prefixed field longer than remaining buffer."
651                    + " Field length: " + len + ", remaining: " + source.remaining());
652        }
653        return getByteBuffer(source, len);
654    }
655
656    static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
657        int len = buf.getInt();
658        if (len < 0) {
659            throw new IOException("Negative length");
660        } else if (len > buf.remaining()) {
661            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
662                    + ", available: " + buf.remaining());
663        }
664        byte[] result = new byte[len];
665        buf.get(result);
666        return result;
667    }
668
669    static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
670        result[offset] = (byte) (value & 0xff);
671        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
672        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
673        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
674    }
675
676    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
677    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
678    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
679
680    static Pair<ByteBuffer, Long> findApkSigningBlock(
681            RandomAccessFile apk, long centralDirOffset)
682                    throws IOException, SignatureNotFoundException {
683        // FORMAT:
684        // OFFSET       DATA TYPE  DESCRIPTION
685        // * @+0  bytes uint64:    size in bytes (excluding this field)
686        // * @+8  bytes payload
687        // * @-24 bytes uint64:    size in bytes (same as the one above)
688        // * @-16 bytes uint128:   magic
689
690        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
691            throw new SignatureNotFoundException(
692                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
693                            + centralDirOffset);
694        }
695        // Read the magic and offset in file from the footer section of the block:
696        // * uint64:   size of block
697        // * 16 bytes: magic
698        ByteBuffer footer = ByteBuffer.allocate(24);
699        footer.order(ByteOrder.LITTLE_ENDIAN);
700        apk.seek(centralDirOffset - footer.capacity());
701        apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
702        if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
703                || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
704            throw new SignatureNotFoundException(
705                    "No APK Signing Block before ZIP Central Directory");
706        }
707        // Read and compare size fields
708        long apkSigBlockSizeInFooter = footer.getLong(0);
709        if ((apkSigBlockSizeInFooter < footer.capacity())
710                || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
711            throw new SignatureNotFoundException(
712                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
713        }
714        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
715        long apkSigBlockOffset = centralDirOffset - totalSize;
716        if (apkSigBlockOffset < 0) {
717            throw new SignatureNotFoundException(
718                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
719        }
720        ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
721        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
722        apk.seek(apkSigBlockOffset);
723        apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
724        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
725        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
726            throw new SignatureNotFoundException(
727                    "APK Signing Block sizes in header and footer do not match: "
728                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
729        }
730        return Pair.create(apkSigBlock, apkSigBlockOffset);
731    }
732
733    static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
734            throws SignatureNotFoundException {
735        checkByteOrderLittleEndian(apkSigningBlock);
736        // FORMAT:
737        // OFFSET       DATA TYPE  DESCRIPTION
738        // * @+0  bytes uint64:    size in bytes (excluding this field)
739        // * @+8  bytes pairs
740        // * @-24 bytes uint64:    size in bytes (same as the one above)
741        // * @-16 bytes uint128:   magic
742        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
743
744        int entryCount = 0;
745        while (pairs.hasRemaining()) {
746            entryCount++;
747            if (pairs.remaining() < 8) {
748                throw new SignatureNotFoundException(
749                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
750            }
751            long lenLong = pairs.getLong();
752            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
753                throw new SignatureNotFoundException(
754                        "APK Signing Block entry #" + entryCount
755                                + " size out of range: " + lenLong);
756            }
757            int len = (int) lenLong;
758            int nextEntryPos = pairs.position() + len;
759            if (len > pairs.remaining()) {
760                throw new SignatureNotFoundException(
761                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
762                                + ", available: " + pairs.remaining());
763            }
764            int id = pairs.getInt();
765            if (id == blockId) {
766                return getByteBuffer(pairs, len - 4);
767            }
768            pairs.position(nextEntryPos);
769        }
770
771        throw new SignatureNotFoundException(
772                "No block with ID " + blockId + " in APK Signing Block.");
773    }
774
775    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
776        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
777            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
778        }
779    }
780
781    /**
782     * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed.
783     */
784    private static class MultipleDigestDataDigester implements DataDigester {
785        private final MessageDigest[] mMds;
786
787        MultipleDigestDataDigester(MessageDigest[] mds) {
788            mMds = mds;
789        }
790
791        @Override
792        public void consume(ByteBuffer buffer) {
793            buffer = buffer.slice();
794            for (MessageDigest md : mMds) {
795                buffer.position(0);
796                md.update(buffer);
797            }
798        }
799    }
800
801}
802