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