• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.apksig.internal.apk;
18 
19 import static com.android.apksig.Constants.OID_RSA_ENCRYPTION;
20 import static com.android.apksig.internal.apk.ContentDigestAlgorithm.CHUNKED_SHA256;
21 import static com.android.apksig.internal.apk.ContentDigestAlgorithm.CHUNKED_SHA512;
22 import static com.android.apksig.internal.apk.ContentDigestAlgorithm.VERITY_CHUNKED_SHA256;
23 
24 import com.android.apksig.ApkVerifier;
25 import com.android.apksig.SigningCertificateLineage;
26 import com.android.apksig.apk.ApkFormatException;
27 import com.android.apksig.apk.ApkUtils;
28 import com.android.apksig.internal.asn1.Asn1BerParser;
29 import com.android.apksig.internal.asn1.Asn1DecodingException;
30 import com.android.apksig.internal.asn1.Asn1DerEncoder;
31 import com.android.apksig.internal.asn1.Asn1EncodingException;
32 import com.android.apksig.internal.asn1.Asn1OpaqueObject;
33 import com.android.apksig.internal.pkcs7.AlgorithmIdentifier;
34 import com.android.apksig.internal.pkcs7.ContentInfo;
35 import com.android.apksig.internal.pkcs7.EncapsulatedContentInfo;
36 import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber;
37 import com.android.apksig.internal.pkcs7.Pkcs7Constants;
38 import com.android.apksig.internal.pkcs7.SignedData;
39 import com.android.apksig.internal.pkcs7.SignerIdentifier;
40 import com.android.apksig.internal.pkcs7.SignerInfo;
41 import com.android.apksig.internal.util.ByteBufferDataSource;
42 import com.android.apksig.internal.util.ChainedDataSource;
43 import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate;
44 import com.android.apksig.internal.util.Pair;
45 import com.android.apksig.internal.util.VerityTreeBuilder;
46 import com.android.apksig.internal.util.X509CertificateUtils;
47 import com.android.apksig.internal.x509.RSAPublicKey;
48 import com.android.apksig.internal.x509.SubjectPublicKeyInfo;
49 import com.android.apksig.internal.zip.ZipUtils;
50 import com.android.apksig.util.DataSink;
51 import com.android.apksig.util.DataSinks;
52 import com.android.apksig.util.DataSource;
53 import com.android.apksig.util.DataSources;
54 import com.android.apksig.util.RunnablesExecutor;
55 
56 import java.io.IOException;
57 import java.math.BigInteger;
58 import java.nio.ByteBuffer;
59 import java.nio.ByteOrder;
60 import java.security.DigestException;
61 import java.security.InvalidAlgorithmParameterException;
62 import java.security.InvalidKeyException;
63 import java.security.KeyFactory;
64 import java.security.MessageDigest;
65 import java.security.NoSuchAlgorithmException;
66 import java.security.PrivateKey;
67 import java.security.PublicKey;
68 import java.security.Signature;
69 import java.security.SignatureException;
70 import java.security.cert.CertificateEncodingException;
71 import java.security.cert.CertificateException;
72 import java.security.cert.X509Certificate;
73 import java.security.spec.AlgorithmParameterSpec;
74 import java.security.spec.InvalidKeySpecException;
75 import java.security.spec.X509EncodedKeySpec;
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.Collections;
79 import java.util.HashMap;
80 import java.util.HashSet;
81 import java.util.List;
82 import java.util.Map;
83 import java.util.Set;
84 import java.util.concurrent.atomic.AtomicInteger;
85 import java.util.function.Supplier;
86 
87 import javax.security.auth.x500.X500Principal;
88 
89 public class ApkSigningBlockUtils {
90 
91     private static final long CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
92     public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
93     private static final byte[] APK_SIGNING_BLOCK_MAGIC =
94           new byte[] {
95               0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
96               0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
97           };
98     public static final int VERITY_PADDING_BLOCK_ID = 0x42726577;
99 
100     private static final ContentDigestAlgorithm[] V4_CONTENT_DIGEST_ALGORITHMS =
101             {CHUNKED_SHA512, VERITY_CHUNKED_SHA256, CHUNKED_SHA256};
102 
103     public static final int VERSION_SOURCE_STAMP = 0;
104     public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
105     public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
106     public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
107     public static final int VERSION_APK_SIGNATURE_SCHEME_V31 = 31;
108     public static final int VERSION_APK_SIGNATURE_SCHEME_V4 = 4;
109 
110     /**
111      * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
112      * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
113      */
compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2)114     public static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) {
115         return ApkSigningBlockUtilsLite.compareSignatureAlgorithm(alg1, alg2);
116     }
117 
118     /**
119      * Verifies integrity of the APK outside of the APK Signing Block by computing digests of the
120      * APK and comparing them against the digests listed in APK Signing Block. The expected digests
121      * are taken from {@code SignerInfos} of the provided {@code result}.
122      *
123      * <p>This method adds one or more errors to the {@code result} if a verification error is
124      * expected to be encountered on Android. No errors are added to the {@code result} if the APK's
125      * integrity is expected to verify on Android for each algorithm in
126      * {@code contentDigestAlgorithms}.
127      *
128      * <p>The reason this method is currently not parameterized by a
129      * {@code [minSdkVersion, maxSdkVersion]} range is that up until now content digest algorithms
130      * exhibit the same behavior on all Android platform versions.
131      */
verifyIntegrity( RunnablesExecutor executor, DataSource beforeApkSigningBlock, DataSource centralDir, ByteBuffer eocd, Set<ContentDigestAlgorithm> contentDigestAlgorithms, Result result)132     public static void verifyIntegrity(
133             RunnablesExecutor executor,
134             DataSource beforeApkSigningBlock,
135             DataSource centralDir,
136             ByteBuffer eocd,
137             Set<ContentDigestAlgorithm> contentDigestAlgorithms,
138             Result result) throws IOException, NoSuchAlgorithmException {
139         if (contentDigestAlgorithms.isEmpty()) {
140             // This should never occur because this method is invoked once at least one signature
141             // is verified, meaning at least one content digest is known.
142             throw new RuntimeException("No content digests found");
143         }
144 
145         // For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be
146         // treated as though its Central Directory offset points to the start of APK Signing Block.
147         // We thus modify the EoCD accordingly.
148         ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining());
149         int eocdSavedPos = eocd.position();
150         modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
151         modifiedEocd.put(eocd);
152         modifiedEocd.flip();
153 
154         // restore eocd to position prior to modification in case it is to be used elsewhere
155         eocd.position(eocdSavedPos);
156         ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size());
157         Map<ContentDigestAlgorithm, byte[]> actualContentDigests;
158         try {
159             actualContentDigests =
160                     computeContentDigests(
161                             executor,
162                             contentDigestAlgorithms,
163                             beforeApkSigningBlock,
164                             centralDir,
165                             new ByteBufferDataSource(modifiedEocd));
166             // Special checks for the verity algorithm requirements.
167             if (actualContentDigests.containsKey(VERITY_CHUNKED_SHA256)) {
168                 if ((beforeApkSigningBlock.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) {
169                     throw new RuntimeException(
170                             "APK Signing Block is not aligned on 4k boundary: " +
171                             beforeApkSigningBlock.size());
172                 }
173 
174                 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
175                 long signingBlockSize = centralDirOffset - beforeApkSigningBlock.size();
176                 if (signingBlockSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) {
177                     throw new RuntimeException(
178                             "APK Signing Block size is not multiple of page size: " +
179                             signingBlockSize);
180                 }
181             }
182         } catch (DigestException e) {
183             throw new RuntimeException("Failed to compute content digests", e);
184         }
185         if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) {
186             throw new RuntimeException(
187                     "Mismatch between sets of requested and computed content digests"
188                             + " . Requested: " + contentDigestAlgorithms
189                             + ", computed: " + actualContentDigests.keySet());
190         }
191 
192         // Compare digests computed over the rest of APK against the corresponding expected digests
193         // in signer blocks.
194         for (Result.SignerInfo signerInfo : result.signers) {
195             for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) {
196                 SignatureAlgorithm signatureAlgorithm =
197                         SignatureAlgorithm.findById(expected.getSignatureAlgorithmId());
198                 if (signatureAlgorithm == null) {
199                     continue;
200                 }
201                 ContentDigestAlgorithm contentDigestAlgorithm =
202                         signatureAlgorithm.getContentDigestAlgorithm();
203                 // if the current digest algorithm is not in the list provided by the caller then
204                 // ignore it; the signer may contain digests not recognized by the specified SDK
205                 // range.
206                 if (!contentDigestAlgorithms.contains(contentDigestAlgorithm)) {
207                     continue;
208                 }
209                 byte[] expectedDigest = expected.getValue();
210                 byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm);
211                 if (!Arrays.equals(expectedDigest, actualDigest)) {
212                     if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2) {
213                         signerInfo.addError(
214                                 ApkVerifier.Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY,
215                                 contentDigestAlgorithm,
216                                 toHex(expectedDigest),
217                                 toHex(actualDigest));
218                     } else if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3) {
219                         signerInfo.addError(
220                                 ApkVerifier.Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY,
221                                 contentDigestAlgorithm,
222                                 toHex(expectedDigest),
223                                 toHex(actualDigest));
224                     }
225                     continue;
226                 }
227                 signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest);
228             }
229         }
230     }
231 
findApkSignatureSchemeBlock( ByteBuffer apkSigningBlock, int blockId, Result result)232     public static ByteBuffer findApkSignatureSchemeBlock(
233             ByteBuffer apkSigningBlock,
234             int blockId,
235             Result result) throws SignatureNotFoundException {
236         try {
237             return ApkSigningBlockUtilsLite.findApkSignatureSchemeBlock(apkSigningBlock, blockId);
238         } catch (com.android.apksig.internal.apk.SignatureNotFoundException e) {
239             throw new SignatureNotFoundException(e.getMessage());
240         }
241     }
242 
checkByteOrderLittleEndian(ByteBuffer buffer)243     public static void checkByteOrderLittleEndian(ByteBuffer buffer) {
244         ApkSigningBlockUtilsLite.checkByteOrderLittleEndian(buffer);
245     }
246 
getLengthPrefixedSlice(ByteBuffer source)247     public static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException {
248         return ApkSigningBlockUtilsLite.getLengthPrefixedSlice(source);
249     }
250 
readLengthPrefixedByteArray(ByteBuffer buf)251     public static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException {
252         return ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(buf);
253     }
254 
toHex(byte[] value)255     public static String toHex(byte[] value) {
256         return ApkSigningBlockUtilsLite.toHex(value);
257     }
258 
computeContentDigests( RunnablesExecutor executor, Set<ContentDigestAlgorithm> digestAlgorithms, DataSource beforeCentralDir, DataSource centralDir, DataSource eocd)259     public static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
260             RunnablesExecutor executor,
261             Set<ContentDigestAlgorithm> digestAlgorithms,
262             DataSource beforeCentralDir,
263             DataSource centralDir,
264             DataSource eocd) throws IOException, NoSuchAlgorithmException, DigestException {
265         Map<ContentDigestAlgorithm, byte[]> contentDigests = new HashMap<>();
266         Set<ContentDigestAlgorithm> oneMbChunkBasedAlgorithm = new HashSet<>();
267         for (ContentDigestAlgorithm digestAlgorithm : digestAlgorithms) {
268             if (digestAlgorithm == ContentDigestAlgorithm.CHUNKED_SHA256
269                     || digestAlgorithm == ContentDigestAlgorithm.CHUNKED_SHA512) {
270                 oneMbChunkBasedAlgorithm.add(digestAlgorithm);
271             }
272         }
273         computeOneMbChunkContentDigests(
274                 executor,
275                 oneMbChunkBasedAlgorithm,
276                 new DataSource[] { beforeCentralDir, centralDir, eocd },
277                 contentDigests);
278 
279         if (digestAlgorithms.contains(VERITY_CHUNKED_SHA256)) {
280             computeApkVerityDigest(beforeCentralDir, centralDir, eocd, contentDigests);
281         }
282         return contentDigests;
283     }
284 
computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)285     static void computeOneMbChunkContentDigests(
286             Set<ContentDigestAlgorithm> digestAlgorithms,
287             DataSource[] contents,
288             Map<ContentDigestAlgorithm, byte[]> outputContentDigests)
289             throws IOException, NoSuchAlgorithmException, DigestException {
290         // For each digest algorithm the result is computed as follows:
291         // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
292         //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
293         //    No chunks are produced for empty (zero length) segments.
294         // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
295         //    length in bytes (uint32 little-endian) and the chunk's contents.
296         // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
297         //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
298         //    segments in-order.
299 
300         long chunkCountLong = 0;
301         for (DataSource input : contents) {
302             chunkCountLong +=
303                     getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
304         }
305         if (chunkCountLong > Integer.MAX_VALUE) {
306             throw new DigestException("Input too long: " + chunkCountLong + " chunks");
307         }
308         int chunkCount = (int) chunkCountLong;
309 
310         ContentDigestAlgorithm[] digestAlgorithmsArray =
311                 digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]);
312         MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length];
313         byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][];
314         int[] digestOutputSizes = new int[digestAlgorithmsArray.length];
315         for (int i = 0; i < digestAlgorithmsArray.length; i++) {
316             ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
317             int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes();
318             digestOutputSizes[i] = digestOutputSizeBytes;
319             byte[] concatenationOfChunkCountAndChunkDigests =
320                     new byte[5 + chunkCount * digestOutputSizeBytes];
321             concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
322             setUnsignedInt32LittleEndian(
323                     chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
324             digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
325             String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
326             mds[i] = MessageDigest.getInstance(jcaAlgorithm);
327         }
328 
329         DataSink mdSink = DataSinks.asDataSink(mds);
330         byte[] chunkContentPrefix = new byte[5];
331         chunkContentPrefix[0] = (byte) 0xa5;
332         int chunkIndex = 0;
333         // Optimization opportunity: digests of chunks can be computed in parallel. However,
334         // determining the number of computations to be performed in parallel is non-trivial. This
335         // depends on a wide range of factors, such as data source type (e.g., in-memory or fetched
336         // from file), CPU/memory/disk cache bandwidth and latency, interconnect architecture of CPU
337         // cores, load on the system from other threads of execution and other processes, size of
338         // input.
339         // For now, we compute these digests sequentially and thus have the luxury of improving
340         // performance by writing the digest of each chunk into a pre-allocated buffer at exactly
341         // the right position. This avoids unnecessary allocations, copying, and enables the final
342         // digest to be more efficient because it's presented with all of its input in one go.
343         for (DataSource input : contents) {
344             long inputOffset = 0;
345             long inputRemaining = input.size();
346             while (inputRemaining > 0) {
347                 int chunkSize =
348                         (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
349                 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
350                 for (int i = 0; i < mds.length; i++) {
351                     mds[i].update(chunkContentPrefix);
352                 }
353                 try {
354                     input.feed(inputOffset, chunkSize, mdSink);
355                 } catch (IOException e) {
356                     throw new IOException("Failed to read chunk #" + chunkIndex, e);
357                 }
358                 for (int i = 0; i < digestAlgorithmsArray.length; i++) {
359                     MessageDigest md = mds[i];
360                     byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
361                     int expectedDigestSizeBytes = digestOutputSizes[i];
362                     int actualDigestSizeBytes =
363                             md.digest(
364                                     concatenationOfChunkCountAndChunkDigests,
365                                     5 + chunkIndex * expectedDigestSizeBytes,
366                                     expectedDigestSizeBytes);
367                     if (actualDigestSizeBytes != expectedDigestSizeBytes) {
368                         throw new RuntimeException(
369                                 "Unexpected output size of " + md.getAlgorithm()
370                                         + " digest: " + actualDigestSizeBytes);
371                     }
372                 }
373                 inputOffset += chunkSize;
374                 inputRemaining -= chunkSize;
375                 chunkIndex++;
376             }
377         }
378 
379         for (int i = 0; i < digestAlgorithmsArray.length; i++) {
380             ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
381             byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
382             MessageDigest md = mds[i];
383             byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests);
384             outputContentDigests.put(digestAlgorithm, digest);
385         }
386     }
387 
computeOneMbChunkContentDigests( RunnablesExecutor executor, Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)388     static void computeOneMbChunkContentDigests(
389             RunnablesExecutor executor,
390             Set<ContentDigestAlgorithm> digestAlgorithms,
391             DataSource[] contents,
392             Map<ContentDigestAlgorithm, byte[]> outputContentDigests)
393             throws NoSuchAlgorithmException, DigestException {
394         long chunkCountLong = 0;
395         for (DataSource input : contents) {
396             chunkCountLong +=
397                     getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
398         }
399         if (chunkCountLong > Integer.MAX_VALUE) {
400             throw new DigestException("Input too long: " + chunkCountLong + " chunks");
401         }
402         int chunkCount = (int) chunkCountLong;
403 
404         List<ChunkDigests> chunkDigestsList = new ArrayList<>(digestAlgorithms.size());
405         for (ContentDigestAlgorithm algorithms : digestAlgorithms) {
406             chunkDigestsList.add(new ChunkDigests(algorithms, chunkCount));
407         }
408 
409         ChunkSupplier chunkSupplier = new ChunkSupplier(contents);
410         executor.execute(() -> new ChunkDigester(chunkSupplier, chunkDigestsList));
411 
412         // Compute and write out final digest for each algorithm.
413         for (ChunkDigests chunkDigests : chunkDigestsList) {
414             MessageDigest messageDigest = chunkDigests.createMessageDigest();
415             outputContentDigests.put(
416                     chunkDigests.algorithm,
417                     messageDigest.digest(chunkDigests.concatOfDigestsOfChunks));
418         }
419     }
420 
421     private static class ChunkDigests {
422         private final ContentDigestAlgorithm algorithm;
423         private final int digestOutputSize;
424         private final byte[] concatOfDigestsOfChunks;
425 
ChunkDigests(ContentDigestAlgorithm algorithm, int chunkCount)426         private ChunkDigests(ContentDigestAlgorithm algorithm, int chunkCount) {
427             this.algorithm = algorithm;
428             digestOutputSize = this.algorithm.getChunkDigestOutputSizeBytes();
429             concatOfDigestsOfChunks = new byte[1 + 4 + chunkCount * digestOutputSize];
430 
431             // Fill the initial values of the concatenated digests of chunks, which is
432             // {0x5a, 4-bytes-of-little-endian-chunk-count, digests*...}.
433             concatOfDigestsOfChunks[0] = 0x5a;
434             setUnsignedInt32LittleEndian(chunkCount, concatOfDigestsOfChunks, 1);
435         }
436 
createMessageDigest()437         private MessageDigest createMessageDigest() throws NoSuchAlgorithmException {
438             return MessageDigest.getInstance(algorithm.getJcaMessageDigestAlgorithm());
439         }
440 
getOffset(int chunkIndex)441         private int getOffset(int chunkIndex) {
442             return 1 + 4 + chunkIndex * digestOutputSize;
443         }
444     }
445 
446     /**
447      * A per-thread digest worker.
448      */
449     private static class ChunkDigester implements Runnable {
450         private final ChunkSupplier dataSupplier;
451         private final List<ChunkDigests> chunkDigests;
452         private final List<MessageDigest> messageDigests;
453         private final DataSink mdSink;
454 
ChunkDigester(ChunkSupplier dataSupplier, List<ChunkDigests> chunkDigests)455         private ChunkDigester(ChunkSupplier dataSupplier, List<ChunkDigests> chunkDigests) {
456             this.dataSupplier = dataSupplier;
457             this.chunkDigests = chunkDigests;
458             messageDigests = new ArrayList<>(chunkDigests.size());
459             for (ChunkDigests chunkDigest : chunkDigests) {
460                 try {
461                     messageDigests.add(chunkDigest.createMessageDigest());
462                 } catch (NoSuchAlgorithmException ex) {
463                     throw new RuntimeException(ex);
464                 }
465             }
466             mdSink = DataSinks.asDataSink(messageDigests.toArray(new MessageDigest[0]));
467         }
468 
469         @Override
run()470         public void run() {
471             byte[] chunkContentPrefix = new byte[5];
472             chunkContentPrefix[0] = (byte) 0xa5;
473 
474             try {
475                 for (ChunkSupplier.Chunk chunk = dataSupplier.get();
476                      chunk != null;
477                      chunk = dataSupplier.get()) {
478                     int size = chunk.size;
479                     if (size > CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES) {
480                         throw new RuntimeException("Chunk size greater than expected: " + size);
481                     }
482 
483                     // First update with the chunk prefix.
484                     setUnsignedInt32LittleEndian(size, chunkContentPrefix, 1);
485                     mdSink.consume(chunkContentPrefix, 0, chunkContentPrefix.length);
486 
487                     // Then update with the chunk data.
488                     mdSink.consume(chunk.data);
489 
490                     // Now finalize chunk for all algorithms.
491                     for (int i = 0; i < chunkDigests.size(); i++) {
492                         ChunkDigests chunkDigest = chunkDigests.get(i);
493                         int actualDigestSize = messageDigests.get(i).digest(
494                                 chunkDigest.concatOfDigestsOfChunks,
495                                 chunkDigest.getOffset(chunk.chunkIndex),
496                                 chunkDigest.digestOutputSize);
497                         if (actualDigestSize != chunkDigest.digestOutputSize) {
498                             throw new RuntimeException(
499                                     "Unexpected output size of " + chunkDigest.algorithm
500                                             + " digest: " + actualDigestSize);
501                         }
502                     }
503                 }
504             } catch (IOException | DigestException e) {
505                 throw new RuntimeException(e);
506             }
507         }
508     }
509 
510     /**
511      * Thread-safe 1MB DataSource chunk supplier. When bounds are met in a
512      * supplied {@link DataSource}, the data from the next {@link DataSource}
513      * are NOT concatenated. Only the next call to get() will fetch from the
514      * next {@link DataSource} in the input {@link DataSource} array.
515      */
516     private static class ChunkSupplier implements Supplier<ChunkSupplier.Chunk> {
517         private final DataSource[] dataSources;
518         private final int[] chunkCounts;
519         private final int totalChunkCount;
520         private final AtomicInteger nextIndex;
521 
ChunkSupplier(DataSource[] dataSources)522         private ChunkSupplier(DataSource[] dataSources) {
523             this.dataSources = dataSources;
524             chunkCounts = new int[dataSources.length];
525             int totalChunkCount = 0;
526             for (int i = 0; i < dataSources.length; i++) {
527                 long chunkCount = getChunkCount(dataSources[i].size(),
528                         CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
529                 if (chunkCount > Integer.MAX_VALUE) {
530                     throw new RuntimeException(
531                             String.format(
532                                     "Number of chunks in dataSource[%d] is greater than max int.",
533                                     i));
534                 }
535                 chunkCounts[i] = (int)chunkCount;
536                 totalChunkCount = (int) (totalChunkCount + chunkCount);
537             }
538             this.totalChunkCount = totalChunkCount;
539             nextIndex = new AtomicInteger(0);
540         }
541 
542         /**
543          * We map an integer index to the termination-adjusted dataSources 1MB chunks.
544          * Note that {@link Chunk}s could be less than 1MB, namely the last 1MB-aligned
545          * blocks in each input {@link DataSource} (unless the DataSource itself is
546          * 1MB-aligned).
547          */
548         @Override
get()549         public ChunkSupplier.Chunk get() {
550             int index = nextIndex.getAndIncrement();
551             if (index < 0 || index >= totalChunkCount) {
552                 return null;
553             }
554 
555             int dataSourceIndex = 0;
556             long dataSourceChunkOffset = index;
557             for (; dataSourceIndex < dataSources.length; dataSourceIndex++) {
558                 if (dataSourceChunkOffset < chunkCounts[dataSourceIndex]) {
559                     break;
560                 }
561                 dataSourceChunkOffset -= chunkCounts[dataSourceIndex];
562             }
563 
564             long remainingSize = Math.min(
565                     dataSources[dataSourceIndex].size() -
566                             dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES,
567                     CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
568 
569             final int size = (int)remainingSize;
570             final ByteBuffer buffer = ByteBuffer.allocate(size);
571             try {
572                 dataSources[dataSourceIndex].copyTo(
573                         dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES, size,
574                         buffer);
575             } catch (IOException e) {
576                 throw new IllegalStateException("Failed to read chunk", e);
577             }
578             buffer.rewind();
579 
580             return new Chunk(index, buffer, size);
581         }
582 
583         static class Chunk {
584             private final int chunkIndex;
585             private final ByteBuffer data;
586             private final int size;
587 
Chunk(int chunkIndex, ByteBuffer data, int size)588             private Chunk(int chunkIndex, ByteBuffer data, int size) {
589                 this.chunkIndex = chunkIndex;
590                 this.data = data;
591                 this.size = size;
592             }
593         }
594     }
595 
596     @SuppressWarnings("ByteBufferBackingArray")
computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)597     private static void computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir,
598             DataSource eocd, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)
599             throws IOException, NoSuchAlgorithmException {
600         ByteBuffer encoded = createVerityDigestBuffer(true);
601         // Use 0s as salt for now.  This also needs to be consistent in the fsverify header for
602         // kernel to use.
603         try (VerityTreeBuilder builder = new VerityTreeBuilder(new byte[8])) {
604             byte[] rootHash = builder.generateVerityTreeRootHash(beforeCentralDir, centralDir,
605                     eocd);
606             encoded.put(rootHash);
607             encoded.putLong(beforeCentralDir.size() + centralDir.size() + eocd.size());
608             outputContentDigests.put(VERITY_CHUNKED_SHA256, encoded.array());
609         }
610     }
611 
createVerityDigestBuffer(boolean includeSourceDataSize)612     private static ByteBuffer createVerityDigestBuffer(boolean includeSourceDataSize) {
613         // FORMAT:
614         // OFFSET       DATA TYPE  DESCRIPTION
615         // * @+0  bytes uint8[32]  Merkle tree root hash of SHA-256
616         // * @+32 bytes int64      (optional) Length of source data
617         int backBufferSize =
618                 VERITY_CHUNKED_SHA256.getChunkDigestOutputSizeBytes();
619         if (includeSourceDataSize) {
620             backBufferSize += Long.SIZE / Byte.SIZE;
621         }
622         ByteBuffer encoded = ByteBuffer.allocate(backBufferSize);
623         encoded.order(ByteOrder.LITTLE_ENDIAN);
624         return encoded;
625     }
626 
627     public static class VerityTreeAndDigest {
628         public final ContentDigestAlgorithm contentDigestAlgorithm;
629         public final byte[] rootHash;
630         public final byte[] tree;
631 
VerityTreeAndDigest(ContentDigestAlgorithm contentDigestAlgorithm, byte[] rootHash, byte[] tree)632         VerityTreeAndDigest(ContentDigestAlgorithm contentDigestAlgorithm, byte[] rootHash,
633                 byte[] tree) {
634             this.contentDigestAlgorithm = contentDigestAlgorithm;
635             this.rootHash = rootHash;
636             this.tree = tree;
637         }
638     }
639 
640     @SuppressWarnings("ByteBufferBackingArray")
computeChunkVerityTreeAndDigest(DataSource dataSource)641     public static VerityTreeAndDigest computeChunkVerityTreeAndDigest(DataSource dataSource)
642             throws IOException, NoSuchAlgorithmException {
643         ByteBuffer encoded = createVerityDigestBuffer(false);
644         // Use 0s as salt for now.  This also needs to be consistent in the fsverify header for
645         // kernel to use.
646         try (VerityTreeBuilder builder = new VerityTreeBuilder(null)) {
647             ByteBuffer tree = builder.generateVerityTree(dataSource);
648             byte[] rootHash = builder.getRootHashFromTree(tree);
649             encoded.put(rootHash);
650             return new VerityTreeAndDigest(VERITY_CHUNKED_SHA256, encoded.array(), tree.array());
651         }
652     }
653 
getChunkCount(long inputSize, long chunkSize)654     private static long getChunkCount(long inputSize, long chunkSize) {
655         return (inputSize + chunkSize - 1) / chunkSize;
656     }
657 
setUnsignedInt32LittleEndian(int value, byte[] result, int offset)658     private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
659         result[offset] = (byte) (value & 0xff);
660         result[offset + 1] = (byte) ((value >> 8) & 0xff);
661         result[offset + 2] = (byte) ((value >> 16) & 0xff);
662         result[offset + 3] = (byte) ((value >> 24) & 0xff);
663     }
664 
encodePublicKey(PublicKey publicKey)665     public static byte[] encodePublicKey(PublicKey publicKey)
666             throws InvalidKeyException, NoSuchAlgorithmException {
667         byte[] encodedPublicKey = null;
668         if ("X.509".equals(publicKey.getFormat())) {
669             encodedPublicKey = publicKey.getEncoded();
670             // if the key is an RSA key check for a negative modulus
671             String keyAlgorithm = publicKey.getAlgorithm();
672             if ("RSA".equals(keyAlgorithm) || OID_RSA_ENCRYPTION.equals(keyAlgorithm)) {
673                 try {
674                     // Parse the encoded public key into the separate elements of the
675                     // SubjectPublicKeyInfo to obtain the SubjectPublicKey.
676                     ByteBuffer encodedPublicKeyBuffer = ByteBuffer.wrap(encodedPublicKey);
677                     SubjectPublicKeyInfo subjectPublicKeyInfo = Asn1BerParser.parse(
678                             encodedPublicKeyBuffer, SubjectPublicKeyInfo.class);
679                     // The SubjectPublicKey is encoded as a bit string within the
680                     // SubjectPublicKeyInfo. The first byte of the encoding is the number of padding
681                     // bits; store this and decode the rest of the bit string into the RSA modulus
682                     // and exponent.
683                     ByteBuffer subjectPublicKeyBuffer = subjectPublicKeyInfo.subjectPublicKey;
684                     byte padding = subjectPublicKeyBuffer.get();
685                     RSAPublicKey rsaPublicKey = Asn1BerParser.parse(subjectPublicKeyBuffer,
686                             RSAPublicKey.class);
687                     // if the modulus is negative then attempt to reencode it with a leading 0 sign
688                     // byte.
689                     if (rsaPublicKey.modulus.compareTo(BigInteger.ZERO) < 0) {
690                         // A negative modulus indicates the leading bit in the integer is 1. Per
691                         // ASN.1 encoding rules to encode a positive integer with the leading bit
692                         // set to 1 a byte containing all zeros should precede the integer encoding.
693                         byte[] encodedModulus = rsaPublicKey.modulus.toByteArray();
694                         byte[] reencodedModulus = new byte[encodedModulus.length + 1];
695                         reencodedModulus[0] = 0;
696                         System.arraycopy(encodedModulus, 0, reencodedModulus, 1,
697                                 encodedModulus.length);
698                         rsaPublicKey.modulus = new BigInteger(reencodedModulus);
699                         // Once the modulus has been corrected reencode the RSAPublicKey, then
700                         // restore the padding value in the bit string and reencode the entire
701                         // SubjectPublicKeyInfo to be returned to the caller.
702                         byte[] reencodedRSAPublicKey = Asn1DerEncoder.encode(rsaPublicKey);
703                         byte[] reencodedSubjectPublicKey =
704                                 new byte[reencodedRSAPublicKey.length + 1];
705                         reencodedSubjectPublicKey[0] = padding;
706                         System.arraycopy(reencodedRSAPublicKey, 0, reencodedSubjectPublicKey, 1,
707                                 reencodedRSAPublicKey.length);
708                         subjectPublicKeyInfo.subjectPublicKey = ByteBuffer.wrap(
709                                 reencodedSubjectPublicKey);
710                         encodedPublicKey = Asn1DerEncoder.encode(subjectPublicKeyInfo);
711                     }
712                 } catch (Asn1DecodingException | Asn1EncodingException e) {
713                     System.out.println("Caught a exception encoding the public key: " + e);
714                     e.printStackTrace();
715                     encodedPublicKey = null;
716                 }
717             }
718         }
719         if (encodedPublicKey == null) {
720             try {
721                 encodedPublicKey =
722                         KeyFactory.getInstance(publicKey.getAlgorithm())
723                                 .getKeySpec(publicKey, X509EncodedKeySpec.class)
724                                 .getEncoded();
725             } catch (InvalidKeySpecException e) {
726                 throw new InvalidKeyException(
727                         "Failed to obtain X.509 encoded form of public key " + publicKey
728                                 + " of class " + publicKey.getClass().getName(),
729                         e);
730             }
731         }
732         if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
733             throw new InvalidKeyException(
734                     "Failed to obtain X.509 encoded form of public key " + publicKey
735                             + " of class " + publicKey.getClass().getName());
736         }
737         return encodedPublicKey;
738     }
739 
encodeCertificates(List<X509Certificate> certificates)740     public static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
741             throws CertificateEncodingException {
742         List<byte[]> result = new ArrayList<>(certificates.size());
743         for (X509Certificate certificate : certificates) {
744             result.add(certificate.getEncoded());
745         }
746         return result;
747     }
748 
encodeAsLengthPrefixedElement(byte[] bytes)749     public static byte[] encodeAsLengthPrefixedElement(byte[] bytes) {
750         byte[][] adapterBytes = new byte[1][];
751         adapterBytes[0] = bytes;
752         return encodeAsSequenceOfLengthPrefixedElements(adapterBytes);
753     }
754 
encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence)755     public static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
756         return encodeAsSequenceOfLengthPrefixedElements(
757                 sequence.toArray(new byte[sequence.size()][]));
758     }
759 
encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence)760     public static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
761         int payloadSize = 0;
762         for (byte[] element : sequence) {
763             payloadSize += 4 + element.length;
764         }
765         ByteBuffer result = ByteBuffer.allocate(payloadSize);
766         result.order(ByteOrder.LITTLE_ENDIAN);
767         for (byte[] element : sequence) {
768             result.putInt(element.length);
769             result.put(element);
770         }
771         return result.array();
772       }
773 
encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( List<Pair<Integer, byte[]>> sequence)774     public static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
775             List<Pair<Integer, byte[]>> sequence) {
776         return ApkSigningBlockUtilsLite
777                 .encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(sequence);
778       }
779 
780     /**
781      * Returns the APK Signature Scheme block contained in the provided APK file for the given ID
782      * and the additional information relevant for verifying the block against the file.
783      *
784      * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs
785      *                identifying the appropriate block to find, e.g. the APK Signature Scheme v2
786      *                block ID.
787      *
788      * @throws SignatureNotFoundException if the APK is not signed using given APK Signature Scheme
789      * @throws IOException if an I/O error occurs while reading the APK
790      */
findSignature( DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result)791     public static SignatureInfo findSignature(
792             DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result)
793                     throws IOException, SignatureNotFoundException {
794         try {
795             return ApkSigningBlockUtilsLite.findSignature(apk, zipSections, blockId);
796         } catch (com.android.apksig.internal.apk.SignatureNotFoundException e) {
797             throw new SignatureNotFoundException(e.getMessage());
798         }
799     }
800 
801     /**
802      * Generates a new DataSource representing the APK contents before the Central Directory with
803      * padding, if padding is requested.  If the existing data entries before the Central Directory
804      * are already aligned, or no padding is requested, the original DataSource is used.  This
805      * padding is used to allow for verity-based APK verification.
806      *
807      * @return {@code Pair} containing the potentially new {@code DataSource} and the amount of
808      *         padding used.
809      */
generateApkSigningBlockPadding( DataSource beforeCentralDir, boolean apkSigningBlockPaddingSupported)810     public static Pair<DataSource, Integer> generateApkSigningBlockPadding(
811             DataSource beforeCentralDir,
812             boolean apkSigningBlockPaddingSupported) {
813 
814         // Ensure APK Signing Block starts from page boundary.
815         int padSizeBeforeSigningBlock = 0;
816         if (apkSigningBlockPaddingSupported &&
817                 (beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) {
818             padSizeBeforeSigningBlock = (int) (
819                     ANDROID_COMMON_PAGE_ALIGNMENT_BYTES -
820                             beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES);
821             beforeCentralDir = new ChainedDataSource(
822                     beforeCentralDir,
823                     DataSources.asDataSource(
824                             ByteBuffer.allocate(padSizeBeforeSigningBlock)));
825         }
826         return Pair.of(beforeCentralDir, padSizeBeforeSigningBlock);
827     }
828 
copyWithModifiedCDOffset( DataSource beforeCentralDir, DataSource eocd)829     public static DataSource copyWithModifiedCDOffset(
830             DataSource beforeCentralDir, DataSource eocd) throws IOException {
831 
832         // Ensure that, when digesting, ZIP End of Central Directory record's Central Directory
833         // offset field is treated as pointing to the offset at which the APK Signing Block will
834         // start.
835         long centralDirOffsetForDigesting = beforeCentralDir.size();
836         ByteBuffer eocdBuf = ByteBuffer.allocate((int) eocd.size());
837         eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
838         eocd.copyTo(0, (int) eocd.size(), eocdBuf);
839         eocdBuf.flip();
840         ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting);
841         return DataSources.asDataSource(eocdBuf);
842     }
843 
generateApkSigningBlock( List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs)844     public static byte[] generateApkSigningBlock(
845             List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs) {
846         // FORMAT:
847         // uint64:  size (excluding this field)
848         // repeated ID-value pairs:
849         //     uint64:           size (excluding this field)
850         //     uint32:           ID
851         //     (size - 4) bytes: value
852         // (extra verity ID-value for padding to make block size a multiple of 4096 bytes)
853         // uint64:  size (same as the one above)
854         // uint128: magic
855 
856         int blocksSize = 0;
857         for (Pair<byte[], Integer> schemeBlockPair : apkSignatureSchemeBlockPairs) {
858             blocksSize += 8 + 4 + schemeBlockPair.getFirst().length; // size + id + value
859         }
860 
861         int resultSize =
862                 8 // size
863                 + blocksSize
864                 + 8 // size
865                 + 16 // magic
866                 ;
867         ByteBuffer paddingPair = null;
868         if (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) {
869             int padding = ANDROID_COMMON_PAGE_ALIGNMENT_BYTES -
870                     (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES);
871             if (padding < 12) {  // minimum size of an ID-value pair
872                 padding += ANDROID_COMMON_PAGE_ALIGNMENT_BYTES;
873             }
874             paddingPair = ByteBuffer.allocate(padding).order(ByteOrder.LITTLE_ENDIAN);
875             paddingPair.putLong(padding - 8);
876             paddingPair.putInt(VERITY_PADDING_BLOCK_ID);
877             paddingPair.rewind();
878             resultSize += padding;
879         }
880 
881         ByteBuffer result = ByteBuffer.allocate(resultSize);
882         result.order(ByteOrder.LITTLE_ENDIAN);
883         long blockSizeFieldValue = resultSize - 8L;
884         result.putLong(blockSizeFieldValue);
885 
886         for (Pair<byte[], Integer> schemeBlockPair : apkSignatureSchemeBlockPairs) {
887             byte[] apkSignatureSchemeBlock = schemeBlockPair.getFirst();
888             int apkSignatureSchemeId = schemeBlockPair.getSecond();
889             long pairSizeFieldValue = 4L + apkSignatureSchemeBlock.length;
890             result.putLong(pairSizeFieldValue);
891             result.putInt(apkSignatureSchemeId);
892             result.put(apkSignatureSchemeBlock);
893         }
894 
895         if (paddingPair != null) {
896             result.put(paddingPair);
897         }
898 
899         result.putLong(blockSizeFieldValue);
900         result.put(APK_SIGNING_BLOCK_MAGIC);
901 
902         return result.array();
903     }
904 
905     /**
906      * Returns the individual APK signature blocks within the provided {@code apkSigningBlock} in a
907      * {@code List} of {@code Pair} instances where the first element in the {@code Pair} is the
908      * contents / value of the signature block and the second element is the ID of the block.
909      *
910      * @throws IOException if an error is encountered reading the provided {@code apkSigningBlock}
911      */
getApkSignatureBlocks( DataSource apkSigningBlock)912     public static List<Pair<byte[], Integer>> getApkSignatureBlocks(
913             DataSource apkSigningBlock) throws IOException {
914         // FORMAT:
915         // uint64:  size (excluding this field)
916         // repeated ID-value pairs:
917         //     uint64:           size (excluding this field)
918         //     uint32:           ID
919         //     (size - 4) bytes: value
920         // (extra verity ID-value for padding to make block size a multiple of 4096 bytes)
921         // uint64:  size (same as the one above)
922         // uint128: magic
923         long apkSigningBlockSize = apkSigningBlock.size();
924         if (apkSigningBlock.size() > Integer.MAX_VALUE || apkSigningBlockSize < 32) {
925             throw new IllegalArgumentException(
926                     "APK signing block size out of range: " + apkSigningBlockSize);
927         }
928         // Remove the header and footer from the signing block to iterate over only the repeated
929         // ID-value pairs.
930         ByteBuffer apkSigningBlockBuffer = apkSigningBlock.getByteBuffer(8,
931                 (int) apkSigningBlock.size() - 32);
932         apkSigningBlockBuffer.order(ByteOrder.LITTLE_ENDIAN);
933         List<Pair<byte[], Integer>> signatureBlocks = new ArrayList<>();
934         while (apkSigningBlockBuffer.hasRemaining()) {
935             long blockLength = apkSigningBlockBuffer.getLong();
936             if (blockLength > Integer.MAX_VALUE || blockLength < 4) {
937                 throw new IllegalArgumentException(
938                         "Block index " + (signatureBlocks.size() + 1) + " size out of range: "
939                                 + blockLength);
940             }
941             int blockId = apkSigningBlockBuffer.getInt();
942             // Since the block ID has already been read from the signature block read the next
943             // blockLength - 4 bytes as the value.
944             byte[] blockValue = new byte[(int) blockLength - 4];
945             apkSigningBlockBuffer.get(blockValue);
946             signatureBlocks.add(Pair.of(blockValue, blockId));
947         }
948         return signatureBlocks;
949     }
950 
951     /**
952      * Returns the individual APK signers within the provided {@code signatureBlock} in a {@code
953      * List} of {@code Pair} instances where the first element is a {@code List} of {@link
954      * X509Certificate}s and the second element is a byte array of the individual signer's block.
955      *
956      * <p>This method supports any signature block that adheres to the following format up to the
957      * signing certificate(s):
958      * <pre>
959      * * length-prefixed sequence of length-prefixed signers
960      *   * length-prefixed signed data
961      *     * length-prefixed sequence of length-prefixed digests:
962      *       * uint32: signature algorithm ID
963      *       * length-prefixed bytes: digest of contents
964      *     * length-prefixed sequence of certificates:
965      *       * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
966      * </pre>
967      *
968      * <p>Note, this is a convenience method to obtain any signers from an existing signature block;
969      * the signature of each signer will not be verified.
970      *
971      * @throws ApkFormatException if an error is encountered while parsing the provided {@code
972      * signatureBlock}
973      * @throws CertificateException if the signing certificate(s) within an individual signer block
974      * cannot be parsed
975      */
getApkSignatureBlockSigners( byte[] signatureBlock)976     public static List<Pair<List<X509Certificate>, byte[]>> getApkSignatureBlockSigners(
977             byte[] signatureBlock) throws ApkFormatException, CertificateException {
978         ByteBuffer signatureBlockBuffer = ByteBuffer.wrap(signatureBlock);
979         signatureBlockBuffer.order(ByteOrder.LITTLE_ENDIAN);
980         ByteBuffer signersBuffer = getLengthPrefixedSlice(signatureBlockBuffer);
981         List<Pair<List<X509Certificate>, byte[]>> signers = new ArrayList<>();
982         while (signersBuffer.hasRemaining()) {
983             // Parse the next signer block, save all of its bytes for the resulting List, and
984             // rewind the buffer to allow the signing certificate(s) to be parsed.
985             ByteBuffer signer = getLengthPrefixedSlice(signersBuffer);
986             byte[] signerBytes = new byte[signer.remaining()];
987             signer.get(signerBytes);
988             signer.rewind();
989 
990             ByteBuffer signedData = getLengthPrefixedSlice(signer);
991             // The first length prefixed slice is the sequence of digests which are not required
992             // when obtaining the signing certificate(s).
993             getLengthPrefixedSlice(signedData);
994             ByteBuffer certificatesBuffer = getLengthPrefixedSlice(signedData);
995             List<X509Certificate> certificates = new ArrayList<>();
996             while (certificatesBuffer.hasRemaining()) {
997                 int certLength = certificatesBuffer.getInt();
998                 byte[] certBytes = new byte[certLength];
999                 if (certLength > certificatesBuffer.remaining()) {
1000                     throw new IllegalArgumentException(
1001                             "Cert index " + (certificates.size() + 1) + " under signer index "
1002                                     + (signers.size() + 1) + " size out of range: " + certLength);
1003                 }
1004                 certificatesBuffer.get(certBytes);
1005                 GuaranteedEncodedFormX509Certificate signerCert =
1006                         new GuaranteedEncodedFormX509Certificate(
1007                                 X509CertificateUtils.generateCertificate(certBytes), certBytes);
1008                 certificates.add(signerCert);
1009             }
1010             signers.add(Pair.of(certificates, signerBytes));
1011         }
1012         return signers;
1013     }
1014 
1015     /**
1016      * Computes the digests of the given APK components according to the algorithms specified in the
1017      * given SignerConfigs.
1018      *
1019      * @param signerConfigs signer configurations, one for each signer At least one signer config
1020      *        must be provided.
1021      *
1022      * @throws IOException if an I/O error occurs
1023      * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
1024      *         missing
1025      * @throws SignatureException if an error occurs when computing digests of generating
1026      *         signatures
1027      */
1028     public static Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>>
computeContentDigests( RunnablesExecutor executor, DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List<SignerConfig> signerConfigs)1029             computeContentDigests(
1030                     RunnablesExecutor executor,
1031                     DataSource beforeCentralDir,
1032                     DataSource centralDir,
1033                     DataSource eocd,
1034                     List<SignerConfig> signerConfigs)
1035                             throws IOException, NoSuchAlgorithmException, SignatureException {
1036         if (signerConfigs.isEmpty()) {
1037             throw new IllegalArgumentException(
1038                     "No signer configs provided. At least one is required");
1039         }
1040 
1041         // Figure out which digest(s) to use for APK contents.
1042         Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<>(1);
1043         for (SignerConfig signerConfig : signerConfigs) {
1044             for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
1045                 contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm());
1046             }
1047         }
1048 
1049         // Compute digests of APK contents.
1050         Map<ContentDigestAlgorithm, byte[]> contentDigests; // digest algorithm ID -> digest
1051         try {
1052             contentDigests =
1053                     computeContentDigests(
1054                             executor,
1055                             contentDigestAlgorithms,
1056                             beforeCentralDir,
1057                             centralDir,
1058                             eocd);
1059         } catch (IOException e) {
1060             throw new IOException("Failed to read APK being signed", e);
1061         } catch (DigestException e) {
1062             throw new SignatureException("Failed to compute digests of APK", e);
1063         }
1064 
1065         // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
1066         return Pair.of(signerConfigs, contentDigests);
1067     }
1068 
1069     /**
1070      * Returns the subset of signatures which are expected to be verified by at least one Android
1071      * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is
1072      * guaranteed to contain at least one signature.
1073      *
1074      * <p>Each Android platform version typically verifies exactly one signature from the provided
1075      * {@code signatures} set. This method returns the set of these signatures collected over all
1076      * requested platform versions. As a result, the result may contain more than one signature.
1077      *
1078      * @throws NoSupportedSignaturesException if no supported signatures were
1079      *         found for an Android platform version in the range.
1080      */
getSignaturesToVerify( List<T> signatures, int minSdkVersion, int maxSdkVersion)1081     public static <T extends ApkSupportedSignature> List<T> getSignaturesToVerify(
1082             List<T> signatures, int minSdkVersion, int maxSdkVersion)
1083             throws NoSupportedSignaturesException {
1084         return getSignaturesToVerify(signatures, minSdkVersion, maxSdkVersion, false);
1085     }
1086 
1087     /**
1088      * Returns the subset of signatures which are expected to be verified by at least one Android
1089      * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is
1090      * guaranteed to contain at least one signature.
1091      *
1092      * <p>{@code onlyRequireJcaSupport} can be set to true for cases that only require verifying a
1093      * signature within the signing block using the standard JCA.
1094      *
1095      * <p>Each Android platform version typically verifies exactly one signature from the provided
1096      * {@code signatures} set. This method returns the set of these signatures collected over all
1097      * requested platform versions. As a result, the result may contain more than one signature.
1098      *
1099      * @throws NoSupportedSignaturesException if no supported signatures were
1100      *         found for an Android platform version in the range.
1101      */
getSignaturesToVerify( List<T> signatures, int minSdkVersion, int maxSdkVersion, boolean onlyRequireJcaSupport)1102     public static <T extends ApkSupportedSignature> List<T> getSignaturesToVerify(
1103             List<T> signatures, int minSdkVersion, int maxSdkVersion,
1104             boolean onlyRequireJcaSupport) throws NoSupportedSignaturesException {
1105         try {
1106             return ApkSigningBlockUtilsLite.getSignaturesToVerify(signatures, minSdkVersion,
1107                     maxSdkVersion, onlyRequireJcaSupport);
1108         } catch (NoApkSupportedSignaturesException e) {
1109             throw new NoSupportedSignaturesException(e.getMessage());
1110         }
1111     }
1112 
1113     public static class NoSupportedSignaturesException extends NoApkSupportedSignaturesException {
NoSupportedSignaturesException(String message)1114         public NoSupportedSignaturesException(String message) {
1115             super(message);
1116         }
1117     }
1118 
1119     public static class SignatureNotFoundException extends Exception {
1120         private static final long serialVersionUID = 1L;
1121 
SignatureNotFoundException(String message)1122         public SignatureNotFoundException(String message) {
1123             super(message);
1124         }
1125 
SignatureNotFoundException(String message, Throwable cause)1126         public SignatureNotFoundException(String message, Throwable cause) {
1127             super(message, cause);
1128         }
1129     }
1130 
1131     /**
1132      * uses the SignatureAlgorithms in the provided signerConfig to sign the provided data
1133      *
1134      * @return list of signature algorithm IDs and their corresponding signatures over the data.
1135      */
generateSignaturesOverData( SignerConfig signerConfig, byte[] data)1136     public static List<Pair<Integer, byte[]>> generateSignaturesOverData(
1137             SignerConfig signerConfig, byte[] data)
1138                     throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
1139         List<Pair<Integer, byte[]>> signatures =
1140                 new ArrayList<>(signerConfig.signatureAlgorithms.size());
1141         PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
1142         for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
1143             Pair<String, ? extends AlgorithmParameterSpec> sigAlgAndParams =
1144                     signatureAlgorithm.getJcaSignatureAlgorithmAndParams();
1145             String jcaSignatureAlgorithm = sigAlgAndParams.getFirst();
1146             AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond();
1147             byte[] signatureBytes;
1148             try {
1149                 Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
1150                 signature.initSign(signerConfig.privateKey);
1151                 if (jcaSignatureAlgorithmParams != null) {
1152                     signature.setParameter(jcaSignatureAlgorithmParams);
1153                 }
1154                 signature.update(data);
1155                 signatureBytes = signature.sign();
1156             } catch (InvalidKeyException e) {
1157                 throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e);
1158             } catch (InvalidAlgorithmParameterException | SignatureException e) {
1159                 throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e);
1160             }
1161 
1162             try {
1163                 Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
1164                 signature.initVerify(publicKey);
1165                 if (jcaSignatureAlgorithmParams != null) {
1166                     signature.setParameter(jcaSignatureAlgorithmParams);
1167                 }
1168                 signature.update(data);
1169                 if (!signature.verify(signatureBytes)) {
1170                     throw new SignatureException("Failed to verify generated "
1171                             + jcaSignatureAlgorithm
1172                             + " signature using public key from certificate");
1173                 }
1174             } catch (InvalidKeyException e) {
1175                 throw new InvalidKeyException(
1176                         "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
1177                                 + " public key from certificate", e);
1178             } catch (InvalidAlgorithmParameterException | SignatureException e) {
1179                 throw new SignatureException(
1180                         "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
1181                                 + " public key from certificate", e);
1182             }
1183 
1184             signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes));
1185         }
1186         return signatures;
1187     }
1188 
1189     /**
1190      * Wrap the signature according to CMS PKCS #7 RFC 5652.
1191      * The high-level simplified structure is as follows:
1192      * // ContentInfo
1193      *     //   digestAlgorithm
1194      *     //   SignedData
1195      *     //     bag of certificates
1196      *     //     SignerInfo
1197      *     //       signing cert issuer and serial number (for locating the cert in the above bag)
1198      *     //       digestAlgorithm
1199      *     //       signatureAlgorithm
1200      *     //       signature
1201      *
1202      * @throws Asn1EncodingException if the ASN.1 structure could not be encoded
1203      */
generatePkcs7DerEncodedMessage( byte[] signatureBytes, ByteBuffer data, List<X509Certificate> signerCerts, AlgorithmIdentifier digestAlgorithmId, AlgorithmIdentifier signatureAlgorithmId)1204     public static byte[] generatePkcs7DerEncodedMessage(
1205             byte[] signatureBytes, ByteBuffer data, List<X509Certificate> signerCerts,
1206             AlgorithmIdentifier digestAlgorithmId, AlgorithmIdentifier signatureAlgorithmId)
1207             throws Asn1EncodingException, CertificateEncodingException {
1208         SignerInfo signerInfo = new SignerInfo();
1209         signerInfo.version = 1;
1210         X509Certificate signingCert = signerCerts.get(0);
1211         X500Principal signerCertIssuer = signingCert.getIssuerX500Principal();
1212         signerInfo.sid =
1213                 new SignerIdentifier(
1214                         new IssuerAndSerialNumber(
1215                                 new Asn1OpaqueObject(signerCertIssuer.getEncoded()),
1216                                 signingCert.getSerialNumber()));
1217 
1218         signerInfo.digestAlgorithm = digestAlgorithmId;
1219         signerInfo.signatureAlgorithm = signatureAlgorithmId;
1220         signerInfo.signature = ByteBuffer.wrap(signatureBytes);
1221 
1222         SignedData signedData = new SignedData();
1223         signedData.certificates = new ArrayList<>(signerCerts.size());
1224         for (X509Certificate cert : signerCerts) {
1225             signedData.certificates.add(new Asn1OpaqueObject(cert.getEncoded()));
1226         }
1227         signedData.version = 1;
1228         signedData.digestAlgorithms = Collections.singletonList(digestAlgorithmId);
1229         signedData.encapContentInfo = new EncapsulatedContentInfo(Pkcs7Constants.OID_DATA);
1230         // If data is not null, data will be embedded as is in the result -- an attached pcsk7
1231         signedData.encapContentInfo.content = data;
1232         signedData.signerInfos = Collections.singletonList(signerInfo);
1233         ContentInfo contentInfo = new ContentInfo();
1234         contentInfo.contentType = Pkcs7Constants.OID_SIGNED_DATA;
1235         contentInfo.content = new Asn1OpaqueObject(Asn1DerEncoder.encode(signedData));
1236         return Asn1DerEncoder.encode(contentInfo);
1237     }
1238 
1239     /**
1240      * Picks the correct v2/v3 digest for v4 signature verification.
1241      *
1242      * Keep in sync with pickBestDigestForV4 in framework's ApkSigningBlockUtils.
1243      */
pickBestDigestForV4(Map<ContentDigestAlgorithm, byte[]> contentDigests)1244     public static byte[] pickBestDigestForV4(Map<ContentDigestAlgorithm, byte[]> contentDigests) {
1245         for (ContentDigestAlgorithm algo : V4_CONTENT_DIGEST_ALGORITHMS) {
1246             if (contentDigests.containsKey(algo)) {
1247                 return contentDigests.get(algo);
1248             }
1249         }
1250         return null;
1251     }
1252 
1253     /**
1254      * Signer configuration.
1255      */
1256     public static class SignerConfig {
1257         /** Private key. */
1258         public PrivateKey privateKey;
1259 
1260         /**
1261          * Certificates, with the first certificate containing the public key corresponding to
1262          * {@link #privateKey}.
1263          */
1264         public List<X509Certificate> certificates;
1265 
1266         /**
1267          * List of signature algorithms with which to sign.
1268          */
1269         public List<SignatureAlgorithm> signatureAlgorithms;
1270 
1271         public int minSdkVersion;
1272         public int maxSdkVersion;
1273         public SigningCertificateLineage mSigningCertificateLineage;
1274     }
1275 
1276     public static class Result extends ApkSigResult {
1277         public SigningCertificateLineage signingCertificateLineage = null;
1278         public final List<Result.SignerInfo> signers = new ArrayList<>();
1279         private final List<ApkVerifier.IssueWithParams> mWarnings = new ArrayList<>();
1280         private final List<ApkVerifier.IssueWithParams> mErrors = new ArrayList<>();
1281 
Result(int signatureSchemeVersion)1282         public Result(int signatureSchemeVersion) {
1283             super(signatureSchemeVersion);
1284         }
1285 
containsErrors()1286         public boolean containsErrors() {
1287             if (!mErrors.isEmpty()) {
1288                 return true;
1289             }
1290             if (!signers.isEmpty()) {
1291                 for (Result.SignerInfo signer : signers) {
1292                     if (signer.containsErrors()) {
1293                         return true;
1294                     }
1295                 }
1296             }
1297             return false;
1298         }
1299 
containsWarnings()1300         public boolean containsWarnings() {
1301             if (!mWarnings.isEmpty()) {
1302                 return true;
1303             }
1304             if (!signers.isEmpty()) {
1305                 for (Result.SignerInfo signer : signers) {
1306                     if (signer.containsWarnings()) {
1307                         return true;
1308                     }
1309                 }
1310             }
1311             return false;
1312         }
1313 
addError(ApkVerifier.Issue msg, Object... parameters)1314         public void addError(ApkVerifier.Issue msg, Object... parameters) {
1315             mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters));
1316         }
1317 
addWarning(ApkVerifier.Issue msg, Object... parameters)1318         public void addWarning(ApkVerifier.Issue msg, Object... parameters) {
1319             mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters));
1320         }
1321 
1322         @Override
getErrors()1323         public List<ApkVerifier.IssueWithParams> getErrors() {
1324             return mErrors;
1325         }
1326 
1327         @Override
getWarnings()1328         public List<ApkVerifier.IssueWithParams> getWarnings() {
1329             return mWarnings;
1330         }
1331 
1332         public static class SignerInfo extends ApkSignerInfo {
1333             public List<ContentDigest> contentDigests = new ArrayList<>();
1334             public Map<ContentDigestAlgorithm, byte[]> verifiedContentDigests = new HashMap<>();
1335             public List<Signature> signatures = new ArrayList<>();
1336             public Map<SignatureAlgorithm, byte[]> verifiedSignatures = new HashMap<>();
1337             public List<AdditionalAttribute> additionalAttributes = new ArrayList<>();
1338             public byte[] signedData;
1339             public int minSdkVersion;
1340             public int maxSdkVersion;
1341             public SigningCertificateLineage signingCertificateLineage;
1342 
1343             private final List<ApkVerifier.IssueWithParams> mWarnings = new ArrayList<>();
1344             private final List<ApkVerifier.IssueWithParams> mErrors = new ArrayList<>();
1345 
addError(ApkVerifier.Issue msg, Object... parameters)1346             public void addError(ApkVerifier.Issue msg, Object... parameters) {
1347                 mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters));
1348             }
1349 
addWarning(ApkVerifier.Issue msg, Object... parameters)1350             public void addWarning(ApkVerifier.Issue msg, Object... parameters) {
1351                 mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters));
1352             }
1353 
containsErrors()1354             public boolean containsErrors() {
1355                 return !mErrors.isEmpty();
1356             }
1357 
containsWarnings()1358             public boolean containsWarnings() {
1359                 return !mWarnings.isEmpty();
1360             }
1361 
getErrors()1362             public List<ApkVerifier.IssueWithParams> getErrors() {
1363                 return mErrors;
1364             }
1365 
getWarnings()1366             public List<ApkVerifier.IssueWithParams> getWarnings() {
1367                 return mWarnings;
1368             }
1369 
1370             public static class ContentDigest {
1371                 private final int mSignatureAlgorithmId;
1372                 private final byte[] mValue;
1373 
ContentDigest(int signatureAlgorithmId, byte[] value)1374                 public ContentDigest(int signatureAlgorithmId, byte[] value) {
1375                     mSignatureAlgorithmId  = signatureAlgorithmId;
1376                     mValue = value;
1377                 }
1378 
getSignatureAlgorithmId()1379                 public int getSignatureAlgorithmId() {
1380                     return mSignatureAlgorithmId;
1381                 }
1382 
getValue()1383                 public byte[] getValue() {
1384                     return mValue;
1385                 }
1386             }
1387 
1388             public static class Signature {
1389                 private final int mAlgorithmId;
1390                 private final byte[] mValue;
1391 
Signature(int algorithmId, byte[] value)1392                 public Signature(int algorithmId, byte[] value) {
1393                     mAlgorithmId  = algorithmId;
1394                     mValue = value;
1395                 }
1396 
getAlgorithmId()1397                 public int getAlgorithmId() {
1398                     return mAlgorithmId;
1399                 }
1400 
getValue()1401                 public byte[] getValue() {
1402                     return mValue;
1403                 }
1404             }
1405 
1406             public static class AdditionalAttribute {
1407                 private final int mId;
1408                 private final byte[] mValue;
1409 
AdditionalAttribute(int id, byte[] value)1410                 public AdditionalAttribute(int id, byte[] value) {
1411                     mId  = id;
1412                     mValue = value.clone();
1413                 }
1414 
getId()1415                 public int getId() {
1416                     return mId;
1417                 }
1418 
getValue()1419                 public byte[] getValue() {
1420                     return mValue.clone();
1421                 }
1422             }
1423         }
1424     }
1425 
1426     public static class SupportedSignature extends ApkSupportedSignature {
SupportedSignature(SignatureAlgorithm algorithm, byte[] signature)1427         public SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) {
1428             super(algorithm, signature);
1429         }
1430     }
1431 
1432     public static class SigningSchemeBlockAndDigests {
1433         public final Pair<byte[], Integer> signingSchemeBlock;
1434         public final Map<ContentDigestAlgorithm, byte[]> digestInfo;
1435 
SigningSchemeBlockAndDigests( Pair<byte[], Integer> signingSchemeBlock, Map<ContentDigestAlgorithm, byte[]> digestInfo)1436         public SigningSchemeBlockAndDigests(
1437                 Pair<byte[], Integer> signingSchemeBlock,
1438                 Map<ContentDigestAlgorithm, byte[]> digestInfo) {
1439             this.signingSchemeBlock = signingSchemeBlock;
1440             this.digestInfo = digestInfo;
1441         }
1442     }
1443 }
1444