ApkSignerV2.java revision dd910c5945272e9820dfd9d7798ba32aa7dfc73f
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
17package com.android.signapk;
18
19import java.nio.BufferUnderflowException;
20import java.nio.ByteBuffer;
21import java.nio.ByteOrder;
22import java.security.DigestException;
23import java.security.InvalidAlgorithmParameterException;
24import java.security.InvalidKeyException;
25import java.security.KeyFactory;
26import java.security.MessageDigest;
27import java.security.NoSuchAlgorithmException;
28import java.security.PrivateKey;
29import java.security.PublicKey;
30import java.security.Signature;
31import java.security.SignatureException;
32import java.security.cert.CertificateEncodingException;
33import java.security.cert.X509Certificate;
34import java.security.spec.AlgorithmParameterSpec;
35import java.security.spec.InvalidKeySpecException;
36import java.security.spec.MGF1ParameterSpec;
37import java.security.spec.PSSParameterSpec;
38import java.security.spec.X509EncodedKeySpec;
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Map;
44import java.util.Set;
45
46/**
47 * APK Signature Scheme v2 signer.
48 *
49 * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
50 * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
51 * uncompressed contents of ZIP entries.
52 */
53public abstract class ApkSignerV2 {
54    /*
55     * The two main goals of APK Signature Scheme v2 are:
56     * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature
57     *    cover every byte of the APK being signed.
58     * 2. Enable much faster signature and integrity verification. This is achieved by requiring
59     *    only a minimal amount of APK parsing before the signature is verified, thus completely
60     *    bypassing ZIP entry decompression and by making integrity verification parallelizable by
61     *    employing a hash tree.
62     *
63     * The generated signature block is wrapped into an APK Signing Block and inserted into the
64     * original APK immediately before the start of ZIP Central Directory. This is to ensure that
65     * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for
66     * extensibility. For example, a future signature scheme could insert its signatures there as
67     * well. The contract of the APK Signing Block is that all contents outside of the block must be
68     * protected by signatures inside the block.
69     */
70
71    public static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
72    public static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
73    public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
74    public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
75    public static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
76    public static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
77    public static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
78    public static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302;
79
80    /**
81     * {@code .SF} file header section attribute indicating that the APK is signed not just with
82     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
83     * facilitates v2 signature stripping detection.
84     *
85     * <p>The attribute contains a comma-separated set of signature scheme IDs.
86     */
87    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
88    // TODO: Adjust the value when signing scheme finalized.
89    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE = "1234567890";
90
91    private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 0;
92    private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 1;
93
94    private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
95
96    private static final byte[] APK_SIGNING_BLOCK_MAGIC =
97          new byte[] {
98              0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
99              0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
100          };
101    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
102
103    private ApkSignerV2() {}
104
105    /**
106     * Signer configuration.
107     */
108    public static final class SignerConfig {
109        /** Private key. */
110        public PrivateKey privateKey;
111
112        /**
113         * Certificates, with the first certificate containing the public key corresponding to
114         * {@link #privateKey}.
115         */
116        public List<X509Certificate> certificates;
117
118        /**
119         * List of signature algorithms with which to sign (see {@code SIGNATURE_...} constants).
120         */
121        public List<Integer> signatureAlgorithms;
122    }
123
124    /**
125     * Signs the provided APK using APK Signature Scheme v2 and returns the signed APK as a list of
126     * consecutive chunks.
127     *
128     * <p>NOTE: To enable APK signature verifier to detect v2 signature stripping, header sections
129     * of META-INF/*.SF files of APK being signed must contain the
130     * {@code X-Android-APK-Signed: true} attribute.
131     *
132     * @param inputApk contents of the APK to be signed. The APK starts at the current position
133     *        of the buffer and ends at the limit of the buffer.
134     * @param signerConfigs signer configurations, one for each signer.
135     *
136     * @throws ApkParseException if the APK cannot be parsed.
137     * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
138     *         cannot be used in general.
139     * @throws SignatureException if an error occurs when computing digests of generating
140     *         signatures.
141     */
142    public static ByteBuffer[] sign(
143            ByteBuffer inputApk,
144            List<SignerConfig> signerConfigs)
145                    throws ApkParseException, InvalidKeyException, SignatureException {
146        // Slice/create a view in the inputApk to make sure that:
147        // 1. inputApk is what's between position and limit of the original inputApk, and
148        // 2. changes to position, limit, and byte order are not reflected in the original.
149        ByteBuffer originalInputApk = inputApk;
150        inputApk = originalInputApk.slice();
151        inputApk.order(ByteOrder.LITTLE_ENDIAN);
152
153        // Locate ZIP End of Central Directory (EoCD), Central Directory, and check that Central
154        // Directory is immediately followed by the ZIP End of Central Directory.
155        int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(inputApk);
156        if (eocdOffset == -1) {
157            throw new ApkParseException("Failed to locate ZIP End of Central Directory");
158        }
159        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(inputApk, eocdOffset)) {
160            throw new ApkParseException("ZIP64 format not supported");
161        }
162        inputApk.position(eocdOffset);
163        long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(inputApk);
164        if (centralDirSizeLong > Integer.MAX_VALUE) {
165            throw new ApkParseException(
166                    "ZIP Central Directory size out of range: " + centralDirSizeLong);
167        }
168        int centralDirSize = (int) centralDirSizeLong;
169        long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(inputApk);
170        if (centralDirOffsetLong > Integer.MAX_VALUE) {
171            throw new ApkParseException(
172                    "ZIP Central Directory offset in file out of range: " + centralDirOffsetLong);
173        }
174        int centralDirOffset = (int) centralDirOffsetLong;
175        int expectedEocdOffset = centralDirOffset + centralDirSize;
176        if (expectedEocdOffset < centralDirOffset) {
177            throw new ApkParseException(
178                    "ZIP Central Directory extent too large. Offset: " + centralDirOffset
179                            + ", size: " + centralDirSize);
180        }
181        if (eocdOffset != expectedEocdOffset) {
182            throw new ApkParseException(
183                    "ZIP Central Directory not immeiately followed by ZIP End of"
184                            + " Central Directory. CD end: " + expectedEocdOffset
185                            + ", EoCD start: " + eocdOffset);
186        }
187
188        // Create ByteBuffers holding the contents of everything before ZIP Central Directory,
189        // ZIP Central Directory, and ZIP End of Central Directory.
190        inputApk.clear();
191        ByteBuffer beforeCentralDir = getByteBuffer(inputApk, centralDirOffset);
192        ByteBuffer centralDir = getByteBuffer(inputApk, eocdOffset - centralDirOffset);
193        // Create a copy of End of Central Directory because we'll need modify its contents later.
194        byte[] eocdBytes = new byte[inputApk.remaining()];
195        inputApk.get(eocdBytes);
196        ByteBuffer eocd = ByteBuffer.wrap(eocdBytes);
197        eocd.order(inputApk.order());
198
199        // Figure which which digests to use for APK contents.
200        Set<Integer> contentDigestAlgorithms = new HashSet<>();
201        for (SignerConfig signerConfig : signerConfigs) {
202            for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
203                contentDigestAlgorithms.add(
204                        getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm));
205            }
206        }
207
208        // Compute digests of APK contents.
209        Map<Integer, byte[]> contentDigests; // digest algorithm ID -> digest
210        try {
211            contentDigests =
212                    computeContentDigests(
213                            contentDigestAlgorithms,
214                            new ByteBuffer[] {beforeCentralDir, centralDir, eocd});
215        } catch (DigestException e) {
216            throw new SignatureException("Failed to compute digests of APK", e);
217        }
218
219        // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
220        ByteBuffer apkSigningBlock =
221                ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));
222
223        // Update Central Directory Offset in End of Central Directory Record. Central Directory
224        // follows the APK Signing Block and thus is shifted by the size of the APK Signing Block.
225        centralDirOffset += apkSigningBlock.remaining();
226        eocd.clear();
227        ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset);
228
229        // Follow the Java NIO pattern for ByteBuffer whose contents have been consumed.
230        originalInputApk.position(originalInputApk.limit());
231
232        // Reset positions (to 0) and limits (to capacity) in the ByteBuffers below to follow the
233        // Java NIO pattern for ByteBuffers which are ready for their contents to be read by caller.
234        // Contrary to the name, this does not clear the contents of these ByteBuffer.
235        beforeCentralDir.clear();
236        centralDir.clear();
237        eocd.clear();
238
239        // Insert APK Signing Block immediately before the ZIP Central Directory.
240        return new ByteBuffer[] {
241            beforeCentralDir,
242            apkSigningBlock,
243            centralDir,
244            eocd,
245        };
246    }
247
248    private static Map<Integer, byte[]> computeContentDigests(
249            Set<Integer> digestAlgorithms,
250            ByteBuffer[] contents) throws DigestException {
251        // For each digest algorithm the result is computed as follows:
252        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
253        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
254        //    No chunks are produced for empty (zero length) segments.
255        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
256        //    length in bytes (uint32 little-endian) and the chunk's contents.
257        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
258        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
259        //    segments in-order.
260
261        int chunkCount = 0;
262        for (ByteBuffer input : contents) {
263            chunkCount += getChunkCount(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
264        }
265
266        final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size());
267        for (int digestAlgorithm : digestAlgorithms) {
268            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
269            byte[] concatenationOfChunkCountAndChunkDigests =
270                    new byte[5 + chunkCount * digestOutputSizeBytes];
271            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
272            setUnsignedInt32LittleEngian(
273                    chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
274            digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
275        }
276
277        int chunkIndex = 0;
278        byte[] chunkContentPrefix = new byte[5];
279        chunkContentPrefix[0] = (byte) 0xa5;
280        // Optimization opportunity: digests of chunks can be computed in parallel.
281        for (ByteBuffer input : contents) {
282            while (input.hasRemaining()) {
283                int chunkSize =
284                        Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
285                final ByteBuffer chunk = getByteBuffer(input, chunkSize);
286                for (int digestAlgorithm : digestAlgorithms) {
287                    String jcaAlgorithmName =
288                            getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
289                    MessageDigest md;
290                    try {
291                        md = MessageDigest.getInstance(jcaAlgorithmName);
292                    } catch (NoSuchAlgorithmException e) {
293                        throw new DigestException(
294                                jcaAlgorithmName + " MessageDigest not supported", e);
295                    }
296                    // Reset position to 0 and limit to capacity. Position would've been modified
297                    // by the preceding iteration of this loop. NOTE: Contrary to the method name,
298                    // this does not modify the contents of the chunk.
299                    chunk.clear();
300                    setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1);
301                    md.update(chunkContentPrefix);
302                    md.update(chunk);
303                    byte[] concatenationOfChunkCountAndChunkDigests =
304                            digestsOfChunks.get(digestAlgorithm);
305                    int expectedDigestSizeBytes =
306                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
307                    int actualDigestSizeBytes =
308                            md.digest(
309                                    concatenationOfChunkCountAndChunkDigests,
310                                    5 + chunkIndex * expectedDigestSizeBytes,
311                                    expectedDigestSizeBytes);
312                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
313                        throw new DigestException(
314                                "Unexpected output size of " + md.getAlgorithm()
315                                        + " digest: " + actualDigestSizeBytes);
316                    }
317                }
318                chunkIndex++;
319            }
320        }
321
322        Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size());
323        for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
324            int digestAlgorithm = entry.getKey();
325            byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue();
326            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
327            MessageDigest md;
328            try {
329                md = MessageDigest.getInstance(jcaAlgorithmName);
330            } catch (NoSuchAlgorithmException e) {
331                throw new DigestException(jcaAlgorithmName + " MessageDigest not supported", e);
332            }
333            result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests));
334        }
335        return result;
336    }
337
338    private static final int getChunkCount(int inputSize, int chunkSize) {
339        return (inputSize + chunkSize - 1) / chunkSize;
340    }
341
342    private static void setUnsignedInt32LittleEngian(int value, byte[] result, int offset) {
343        result[offset] = (byte) (value & 0xff);
344        result[offset + 1] = (byte) ((value >> 8) & 0xff);
345        result[offset + 2] = (byte) ((value >> 16) & 0xff);
346        result[offset + 3] = (byte) ((value >> 24) & 0xff);
347    }
348
349    private static byte[] generateApkSigningBlock(
350            List<SignerConfig> signerConfigs,
351            Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
352        byte[] apkSignatureSchemeV2Block =
353                generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
354        return generateApkSigningBlock(apkSignatureSchemeV2Block);
355    }
356
357    private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {
358        // FORMAT:
359        // uint64:  size (excluding this field)
360        // repeated ID-value pairs:
361        //     uint64:           size (excluding this field)
362        //     uint32:           ID
363        //     (size - 4) bytes: value
364        // uint64:  size (same as the one above)
365        // uint128: magic
366
367        int resultSize =
368                8 // size
369                + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair
370                + 8 // size
371                + 16 // magic
372                ;
373        ByteBuffer result = ByteBuffer.allocate(resultSize);
374        result.order(ByteOrder.LITTLE_ENDIAN);
375        long blockSizeFieldValue = resultSize - 8;
376        result.putLong(blockSizeFieldValue);
377
378        long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;
379        result.putLong(pairSizeFieldValue);
380        result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
381        result.put(apkSignatureSchemeV2Block);
382
383        result.putLong(blockSizeFieldValue);
384        result.put(APK_SIGNING_BLOCK_MAGIC);
385
386        return result.array();
387    }
388
389    private static byte[] generateApkSignatureSchemeV2Block(
390            List<SignerConfig> signerConfigs,
391            Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
392        // FORMAT:
393        // * length-prefixed sequence of length-prefixed signer blocks.
394
395        List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size());
396        int signerNumber = 0;
397        for (SignerConfig signerConfig : signerConfigs) {
398            signerNumber++;
399            byte[] signerBlock;
400            try {
401                signerBlock = generateSignerBlock(signerConfig, contentDigests);
402            } catch (InvalidKeyException e) {
403                throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
404            } catch (SignatureException e) {
405                throw new SignatureException("Signer #" + signerNumber + " failed", e);
406            }
407            signerBlocks.add(signerBlock);
408        }
409
410        return encodeAsSequenceOfLengthPrefixedElements(
411                new byte[][] {
412                    encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
413                });
414    }
415
416    private static byte[] generateSignerBlock(
417            SignerConfig signerConfig,
418            Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
419        if (signerConfig.certificates.isEmpty()) {
420            throw new SignatureException("No certificates configured for signer");
421        }
422        PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
423
424        byte[] encodedPublicKey = encodePublicKey(publicKey);
425
426        V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();
427        try {
428            signedData.certificates = encodeCertificates(signerConfig.certificates);
429        } catch (CertificateEncodingException e) {
430            throw new SignatureException("Failed to encode certificates", e);
431        }
432
433        List<Pair<Integer, byte[]>> digests =
434                new ArrayList<>(signerConfig.signatureAlgorithms.size());
435        for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
436            int contentDigestAlgorithm =
437                    getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm);
438            byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
439            if (contentDigest == null) {
440                throw new RuntimeException(
441                        getContentDigestAlgorithmJcaDigestAlgorithm(contentDigestAlgorithm)
442                        + " content digest for "
443                        + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm)
444                        + " not computed");
445            }
446            digests.add(Pair.create(signatureAlgorithm, contentDigest));
447        }
448        signedData.digests = digests;
449
450        V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();
451        // FORMAT:
452        // * length-prefixed sequence of length-prefixed digests:
453        //   * uint32: signature algorithm ID
454        //   * length-prefixed bytes: digest of contents
455        // * length-prefixed sequence of certificates:
456        //   * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
457        // * length-prefixed sequence of length-prefixed additional attributes:
458        //   * uint32: ID
459        //   * (length - 4) bytes: value
460        signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {
461            encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),
462            encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),
463            // additional attributes
464            new byte[0],
465        });
466        signer.publicKey = encodedPublicKey;
467        signer.signatures = new ArrayList<>();
468        for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
469            Pair<String, ? extends AlgorithmParameterSpec> signatureParams =
470                    getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm);
471            String jcaSignatureAlgorithm = signatureParams.getFirst();
472            AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureParams.getSecond();
473            byte[] signatureBytes;
474            try {
475                Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
476                signature.initSign(signerConfig.privateKey);
477                if (jcaSignatureAlgorithmParams != null) {
478                    signature.setParameter(jcaSignatureAlgorithmParams);
479                }
480                signature.update(signer.signedData);
481                signatureBytes = signature.sign();
482            } catch (InvalidKeyException e) {
483                throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e);
484            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
485                    | SignatureException e) {
486                throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
487            }
488
489            try {
490                Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
491                signature.initVerify(publicKey);
492                if (jcaSignatureAlgorithmParams != null) {
493                    signature.setParameter(jcaSignatureAlgorithmParams);
494                }
495                signature.update(signer.signedData);
496                if (!signature.verify(signatureBytes)) {
497                    throw new SignatureException("Signature did not verify");
498                }
499            } catch (InvalidKeyException e) {
500                throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm
501                        + " signature using public key from certificate", e);
502            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
503                    | SignatureException e) {
504                throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm
505                        + " signature using public key from certificate", e);
506            }
507
508            signer.signatures.add(Pair.create(signatureAlgorithm, signatureBytes));
509        }
510
511        // FORMAT:
512        // * length-prefixed signed data
513        // * length-prefixed sequence of length-prefixed signatures:
514        //   * uint32: signature algorithm ID
515        //   * length-prefixed bytes: signature of signed data
516        // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
517        return encodeAsSequenceOfLengthPrefixedElements(
518                new byte[][] {
519                    signer.signedData,
520                    encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
521                            signer.signatures),
522                    signer.publicKey,
523                });
524    }
525
526    private static final class V2SignatureSchemeBlock {
527        private static final class Signer {
528            public byte[] signedData;
529            public List<Pair<Integer, byte[]>> signatures;
530            public byte[] publicKey;
531        }
532
533        private static final class SignedData {
534            public List<Pair<Integer, byte[]>> digests;
535            public List<byte[]> certificates;
536        }
537    }
538
539    private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException {
540        byte[] encodedPublicKey = null;
541        if ("X.509".equals(publicKey.getFormat())) {
542            encodedPublicKey = publicKey.getEncoded();
543        }
544        if (encodedPublicKey == null) {
545            try {
546                encodedPublicKey =
547                        KeyFactory.getInstance(publicKey.getAlgorithm())
548                                .getKeySpec(publicKey, X509EncodedKeySpec.class)
549                                .getEncoded();
550            } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
551                throw new InvalidKeyException(
552                        "Failed to obtain X.509 encoded form of public key " + publicKey
553                                + " of class " + publicKey.getClass().getName(),
554                        e);
555            }
556        }
557        if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
558            throw new InvalidKeyException(
559                    "Failed to obtain X.509 encoded form of public key " + publicKey
560                            + " of class " + publicKey.getClass().getName());
561        }
562        return encodedPublicKey;
563    }
564
565    public static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
566            throws CertificateEncodingException {
567        List<byte[]> result = new ArrayList<>();
568        for (X509Certificate certificate : certificates) {
569            result.add(certificate.getEncoded());
570        }
571        return result;
572    }
573
574    private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
575        return encodeAsSequenceOfLengthPrefixedElements(
576                sequence.toArray(new byte[sequence.size()][]));
577    }
578
579    private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
580        int payloadSize = 0;
581        for (byte[] element : sequence) {
582            payloadSize += 4 + element.length;
583        }
584        ByteBuffer result = ByteBuffer.allocate(payloadSize);
585        result.order(ByteOrder.LITTLE_ENDIAN);
586        for (byte[] element : sequence) {
587            result.putInt(element.length);
588            result.put(element);
589        }
590        return result.array();
591      }
592
593    private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
594            List<Pair<Integer, byte[]>> sequence) {
595        int resultSize = 0;
596        for (Pair<Integer, byte[]> element : sequence) {
597            resultSize += 12 + element.getSecond().length;
598        }
599        ByteBuffer result = ByteBuffer.allocate(resultSize);
600        result.order(ByteOrder.LITTLE_ENDIAN);
601        for (Pair<Integer, byte[]> element : sequence) {
602            byte[] second = element.getSecond();
603            result.putInt(8 + second.length);
604            result.putInt(element.getFirst());
605            result.putInt(second.length);
606            result.put(second);
607        }
608        return result.array();
609    }
610
611    /**
612     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
613     * position of this buffer.
614     *
615     * <p>This method reads the next {@code size} bytes at this buffer's current position,
616     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
617     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
618     * {@code size}.
619     */
620    private static ByteBuffer getByteBuffer(ByteBuffer source, int size) {
621        if (size < 0) {
622            throw new IllegalArgumentException("size: " + size);
623        }
624        int originalLimit = source.limit();
625        int position = source.position();
626        int limit = position + size;
627        if ((limit < position) || (limit > originalLimit)) {
628            throw new BufferUnderflowException();
629        }
630        source.limit(limit);
631        try {
632            ByteBuffer result = source.slice();
633            result.order(source.order());
634            source.position(limit);
635            return result;
636        } finally {
637            source.limit(originalLimit);
638        }
639    }
640
641    private static Pair<String, ? extends AlgorithmParameterSpec>
642            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
643        switch (sigAlgorithm) {
644            case SIGNATURE_RSA_PSS_WITH_SHA256:
645                return Pair.create(
646                        "SHA256withRSA/PSS",
647                        new PSSParameterSpec(
648                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
649            case SIGNATURE_RSA_PSS_WITH_SHA512:
650                return Pair.create(
651                        "SHA512withRSA/PSS",
652                        new PSSParameterSpec(
653                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
654            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
655                return Pair.create("SHA256withRSA", null);
656            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
657                return Pair.create("SHA512withRSA", null);
658            case SIGNATURE_ECDSA_WITH_SHA256:
659                return Pair.create("SHA256withECDSA", null);
660            case SIGNATURE_ECDSA_WITH_SHA512:
661                return Pair.create("SHA512withECDSA", null);
662            case SIGNATURE_DSA_WITH_SHA256:
663                return Pair.create("SHA256withDSA", null);
664            case SIGNATURE_DSA_WITH_SHA512:
665                return Pair.create("SHA512withDSA", null);
666            default:
667                throw new IllegalArgumentException(
668                        "Unknown signature algorithm: 0x"
669                                + Long.toHexString(sigAlgorithm & 0xffffffff));
670        }
671    }
672
673    private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
674        switch (sigAlgorithm) {
675            case SIGNATURE_RSA_PSS_WITH_SHA256:
676            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
677            case SIGNATURE_ECDSA_WITH_SHA256:
678            case SIGNATURE_DSA_WITH_SHA256:
679                return CONTENT_DIGEST_CHUNKED_SHA256;
680            case SIGNATURE_RSA_PSS_WITH_SHA512:
681            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
682            case SIGNATURE_ECDSA_WITH_SHA512:
683            case SIGNATURE_DSA_WITH_SHA512:
684                return CONTENT_DIGEST_CHUNKED_SHA512;
685            default:
686                throw new IllegalArgumentException(
687                        "Unknown signature algorithm: 0x"
688                                + Long.toHexString(sigAlgorithm & 0xffffffff));
689        }
690    }
691
692    private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
693        switch (digestAlgorithm) {
694            case CONTENT_DIGEST_CHUNKED_SHA256:
695                return "SHA-256";
696            case CONTENT_DIGEST_CHUNKED_SHA512:
697                return "SHA-512";
698            default:
699                throw new IllegalArgumentException(
700                        "Unknown content digest algorthm: " + digestAlgorithm);
701        }
702    }
703
704    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
705        switch (digestAlgorithm) {
706            case CONTENT_DIGEST_CHUNKED_SHA256:
707                return 256 / 8;
708            case CONTENT_DIGEST_CHUNKED_SHA512:
709                return 512 / 8;
710            default:
711                throw new IllegalArgumentException(
712                        "Unknown content digest algorthm: " + digestAlgorithm);
713        }
714    }
715
716    /**
717     * Indicates that APK file could not be parsed.
718     */
719    public static class ApkParseException extends Exception {
720        private static final long serialVersionUID = 1L;
721
722        public ApkParseException(String message) {
723            super(message);
724        }
725
726        public ApkParseException(String message, Throwable cause) {
727            super(message, cause);
728        }
729    }
730}
731