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