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