1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.signapk;
18 
19 import java.nio.BufferUnderflowException;
20 import java.nio.ByteBuffer;
21 import java.nio.ByteOrder;
22 import java.security.DigestException;
23 import java.security.InvalidAlgorithmParameterException;
24 import java.security.InvalidKeyException;
25 import java.security.KeyFactory;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.PrivateKey;
29 import java.security.PublicKey;
30 import java.security.Signature;
31 import java.security.SignatureException;
32 import java.security.cert.CertificateEncodingException;
33 import java.security.cert.X509Certificate;
34 import java.security.spec.AlgorithmParameterSpec;
35 import java.security.spec.InvalidKeySpecException;
36 import java.security.spec.MGF1ParameterSpec;
37 import java.security.spec.PSSParameterSpec;
38 import java.security.spec.X509EncodedKeySpec;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import 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  */
53 public 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 
ApkSignerV2()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      */
sign( ByteBuffer inputApk, List<SignerConfig> signerConfigs)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 
computeContentDigests( Set<Integer> digestAlgorithms, ByteBuffer[] contents)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 
getChunkCount(int inputSize, int chunkSize)337     private static final int getChunkCount(int inputSize, int chunkSize) {
338         return (inputSize + chunkSize - 1) / chunkSize;
339     }
340 
setUnsignedInt32LittleEngian(int value, byte[] result, int offset)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 
generateApkSigningBlock( List<SignerConfig> signerConfigs, Map<Integer, byte[]> contentDigests)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 
generateApkSigningBlock(byte[] apkSignatureSchemeV2Block)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 
generateApkSignatureSchemeV2Block( List<SignerConfig> signerConfigs, Map<Integer, byte[]> contentDigests)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 
generateSignerBlock( SignerConfig signerConfig, Map<Integer, byte[]> contentDigests)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 
encodePublicKey(PublicKey publicKey)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 
encodeCertificates(List<X509Certificate> certificates)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 
encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence)573     private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
574         return encodeAsSequenceOfLengthPrefixedElements(
575                 sequence.toArray(new byte[sequence.size()][]));
576     }
577 
encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence)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 
encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( List<Pair<Integer, byte[]>> sequence)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      */
getByteBuffer(ByteBuffer source, int size)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>
getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm)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 
getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm)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 
getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm)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 
getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm)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 
ApkParseException(String message)721         public ApkParseException(String message) {
722             super(message);
723         }
724 
ApkParseException(String message, Throwable cause)725         public ApkParseException(String message, Throwable cause) {
726             super(message, cause);
727         }
728     }
729 }
730