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