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