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