blob: 0a54f3a59a7dc9da4ab7881da0bf19c6ae49b86e [file] [log] [blame]
Alex Klyubine4157182016-01-05 13:27:05 -08001/*
2 * Copyright (C) 2016 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
17package android.util.apk;
18
Alex Klyubin0722ffc2016-03-18 16:09:06 -070019import android.util.ArrayMap;
Alex Klyubine4157182016-01-05 13:27:05 -080020import android.util.Pair;
21
22import java.io.ByteArrayInputStream;
Alex Klyubin0722ffc2016-03-18 16:09:06 -070023import java.io.FileDescriptor;
Alex Klyubine4157182016-01-05 13:27:05 -080024import java.io.IOException;
25import java.io.RandomAccessFile;
26import java.math.BigInteger;
27import java.nio.BufferUnderflowException;
28import java.nio.ByteBuffer;
29import java.nio.ByteOrder;
Alex Klyubine4157182016-01-05 13:27:05 -080030import java.security.DigestException;
31import java.security.InvalidAlgorithmParameterException;
32import java.security.InvalidKeyException;
33import java.security.KeyFactory;
34import java.security.MessageDigest;
35import java.security.NoSuchAlgorithmException;
36import java.security.NoSuchProviderException;
37import java.security.Principal;
38import java.security.PublicKey;
39import java.security.Signature;
40import java.security.SignatureException;
41import java.security.cert.CertificateEncodingException;
42import java.security.cert.CertificateException;
43import java.security.cert.CertificateExpiredException;
44import java.security.cert.CertificateFactory;
45import java.security.cert.CertificateNotYetValidException;
46import java.security.cert.X509Certificate;
47import java.security.spec.AlgorithmParameterSpec;
48import java.security.spec.InvalidKeySpecException;
49import java.security.spec.MGF1ParameterSpec;
50import java.security.spec.PSSParameterSpec;
51import java.security.spec.X509EncodedKeySpec;
52import java.util.ArrayList;
53import java.util.Arrays;
54import java.util.Date;
Alex Klyubine4157182016-01-05 13:27:05 -080055import java.util.List;
56import java.util.Map;
57import java.util.Set;
58
59/**
60 * APK Signature Scheme v2 verifier.
61 *
62 * @hide for internal use only.
63 */
64public class ApkSignatureSchemeV2Verifier {
65
66 /**
67 * {@code .SF} file header section attribute indicating that the APK is signed not just with
68 * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
69 * facilitates v2 signature stripping detection.
70 *
71 * <p>The attribute contains a comma-separated set of signature scheme IDs.
72 */
73 public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
Alex Klyubin3a0095f2016-02-16 12:37:17 -080074 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
Alex Klyubine4157182016-01-05 13:27:05 -080075
76 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -070077 * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
78 *
79 * <p><b>NOTE: This method does not verify the signature.</b>
Todd Kennedy66c55532016-02-26 16:22:11 -080080 */
81 public static boolean hasSignature(String apkFile) throws IOException {
82 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
Alex Klyubin0722ffc2016-03-18 16:09:06 -070083 findSignature(apk);
Todd Kennedy66c55532016-02-26 16:22:11 -080084 return true;
85 } catch (SignatureNotFoundException e) {
Alex Klyubin0722ffc2016-03-18 16:09:06 -070086 return false;
Todd Kennedy66c55532016-02-26 16:22:11 -080087 }
Todd Kennedy66c55532016-02-26 16:22:11 -080088 }
89
90 /**
Alex Klyubine4157182016-01-05 13:27:05 -080091 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
92 * associated with each signer.
93 *
94 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
95 * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
96 * @throws IOException if an I/O error occurs while reading the APK file.
97 */
98 public static X509Certificate[][] verify(String apkFile)
99 throws SignatureNotFoundException, SecurityException, IOException {
Dan Cashman636ea5e2017-12-18 10:38:20 -0800100 return verify(apkFile, true);
Alex Klyubine4157182016-01-05 13:27:05 -0800101 }
102
103 /**
Dan Cashman636ea5e2017-12-18 10:38:20 -0800104 * Returns the certificates associated with each signer for the given APK without verification.
105 * This method is dangerous and should not be used, unless the caller is absolutely certain the
106 * APK is trusted. Specifically, verification is only done for the APK Signature Scheme V2
107 * Block while gathering signer information. The APK contents are not verified.
108 *
109 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
110 * @throws IOException if an I/O error occurs while reading the APK file.
111 */
112 public static X509Certificate[][] plsCertsNoVerifyOnlyCerts(String apkFile)
113 throws SignatureNotFoundException, SecurityException, IOException {
114 return verify(apkFile, false);
115 }
116
117 private static X509Certificate[][] verify(String apkFile, boolean verifyIntegrity)
118 throws SignatureNotFoundException, SecurityException, IOException {
119 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
120 return verify(apk, verifyIntegrity);
121 }
122 }
123 /**
Alex Klyubine4157182016-01-05 13:27:05 -0800124 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
125 * associated with each signer.
126 *
127 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700128 * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not
129 * verify.
Alex Klyubine4157182016-01-05 13:27:05 -0800130 * @throws IOException if an I/O error occurs while reading the APK file.
131 */
Dan Cashman636ea5e2017-12-18 10:38:20 -0800132 private static X509Certificate[][] verify(RandomAccessFile apk, boolean verifyIntegrity)
Alex Klyubine4157182016-01-05 13:27:05 -0800133 throws SignatureNotFoundException, SecurityException, IOException {
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700134 SignatureInfo signatureInfo = findSignature(apk);
Dan Cashman636ea5e2017-12-18 10:38:20 -0800135 return verify(apk.getFD(), signatureInfo, verifyIntegrity);
Alex Klyubine4157182016-01-05 13:27:05 -0800136 }
137
138 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700139 * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
140 * additional information relevant for verifying the block against the file.
Alex Klyubine4157182016-01-05 13:27:05 -0800141 *
142 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700143 * @throws IOException if an I/O error occurs while reading the APK file.
Alex Klyubine4157182016-01-05 13:27:05 -0800144 */
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700145 private static SignatureInfo findSignature(RandomAccessFile apk)
146 throws IOException, SignatureNotFoundException {
147 // Find the ZIP End of Central Directory (EoCD) record.
148 Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
149 ByteBuffer eocd = eocdAndOffsetInFile.first;
150 long eocdOffset = eocdAndOffsetInFile.second;
151 if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
152 throw new SignatureNotFoundException("ZIP64 APK not supported");
153 }
Alex Klyubine4157182016-01-05 13:27:05 -0800154
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700155 // Find the APK Signing Block. The block immediately precedes the Central Directory.
156 long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
157 Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
158 findApkSigningBlock(apk, centralDirOffset);
159 ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
160 long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
Alex Klyubine4157182016-01-05 13:27:05 -0800161
162 // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
163 ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
164
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700165 return new SignatureInfo(
Alex Klyubine4157182016-01-05 13:27:05 -0800166 apkSignatureSchemeV2Block,
167 apkSigningBlockOffset,
168 centralDirOffset,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700169 eocdOffset,
170 eocd);
Alex Klyubine4157182016-01-05 13:27:05 -0800171 }
172
173 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700174 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2
175 * Block.
176 *
177 * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it
178 * against the APK file.
Alex Klyubine4157182016-01-05 13:27:05 -0800179 */
180 private static X509Certificate[][] verify(
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700181 FileDescriptor apkFileDescriptor,
Dan Cashman636ea5e2017-12-18 10:38:20 -0800182 SignatureInfo signatureInfo,
183 boolean doVerifyIntegrity) throws SecurityException {
Alex Klyubine4157182016-01-05 13:27:05 -0800184 int signerCount = 0;
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700185 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
Alex Klyubine4157182016-01-05 13:27:05 -0800186 List<X509Certificate[]> signerCerts = new ArrayList<>();
187 CertificateFactory certFactory;
188 try {
189 certFactory = CertificateFactory.getInstance("X.509");
190 } catch (CertificateException e) {
191 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
192 }
193 ByteBuffer signers;
194 try {
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700195 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
Alex Klyubine4157182016-01-05 13:27:05 -0800196 } catch (IOException e) {
197 throw new SecurityException("Failed to read list of signers", e);
198 }
199 while (signers.hasRemaining()) {
200 signerCount++;
201 try {
202 ByteBuffer signer = getLengthPrefixedSlice(signers);
203 X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
204 signerCerts.add(certs);
205 } catch (IOException | BufferUnderflowException | SecurityException e) {
206 throw new SecurityException(
207 "Failed to parse/verify signer #" + signerCount + " block",
208 e);
209 }
210 }
211
212 if (signerCount < 1) {
213 throw new SecurityException("No signers found");
214 }
215
216 if (contentDigests.isEmpty()) {
217 throw new SecurityException("No content digests found");
218 }
219
Dan Cashman636ea5e2017-12-18 10:38:20 -0800220 if (doVerifyIntegrity) {
221 verifyIntegrity(
222 contentDigests,
223 apkFileDescriptor,
224 signatureInfo.apkSigningBlockOffset,
225 signatureInfo.centralDirOffset,
226 signatureInfo.eocdOffset,
227 signatureInfo.eocd);
228 }
Alex Klyubine4157182016-01-05 13:27:05 -0800229
230 return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
231 }
232
233 private static X509Certificate[] verifySigner(
234 ByteBuffer signerBlock,
235 Map<Integer, byte[]> contentDigests,
236 CertificateFactory certFactory) throws SecurityException, IOException {
237 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
238 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
239 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
240
241 int signatureCount = 0;
242 int bestSigAlgorithm = -1;
243 byte[] bestSigAlgorithmSignatureBytes = null;
244 List<Integer> signaturesSigAlgorithms = new ArrayList<>();
245 while (signatures.hasRemaining()) {
246 signatureCount++;
247 try {
248 ByteBuffer signature = getLengthPrefixedSlice(signatures);
249 if (signature.remaining() < 8) {
250 throw new SecurityException("Signature record too short");
251 }
252 int sigAlgorithm = signature.getInt();
253 signaturesSigAlgorithms.add(sigAlgorithm);
254 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
255 continue;
256 }
257 if ((bestSigAlgorithm == -1)
258 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
259 bestSigAlgorithm = sigAlgorithm;
260 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
261 }
262 } catch (IOException | BufferUnderflowException e) {
263 throw new SecurityException(
264 "Failed to parse signature record #" + signatureCount,
265 e);
266 }
267 }
268 if (bestSigAlgorithm == -1) {
269 if (signatureCount == 0) {
270 throw new SecurityException("No signatures found");
271 } else {
272 throw new SecurityException("No supported signatures found");
273 }
274 }
275
276 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
277 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
278 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
279 String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
280 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
281 boolean sigVerified;
282 try {
283 PublicKey publicKey =
284 KeyFactory.getInstance(keyAlgorithm)
285 .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
286 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
287 sig.initVerify(publicKey);
288 if (jcaSignatureAlgorithmParams != null) {
289 sig.setParameter(jcaSignatureAlgorithmParams);
290 }
291 sig.update(signedData);
292 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
293 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
294 | InvalidAlgorithmParameterException | SignatureException e) {
295 throw new SecurityException(
296 "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
297 }
298 if (!sigVerified) {
299 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
300 }
301
302 // Signature over signedData has verified.
303
304 byte[] contentDigest = null;
305 signedData.clear();
306 ByteBuffer digests = getLengthPrefixedSlice(signedData);
307 List<Integer> digestsSigAlgorithms = new ArrayList<>();
308 int digestCount = 0;
309 while (digests.hasRemaining()) {
310 digestCount++;
311 try {
312 ByteBuffer digest = getLengthPrefixedSlice(digests);
313 if (digest.remaining() < 8) {
314 throw new IOException("Record too short");
315 }
316 int sigAlgorithm = digest.getInt();
317 digestsSigAlgorithms.add(sigAlgorithm);
318 if (sigAlgorithm == bestSigAlgorithm) {
319 contentDigest = readLengthPrefixedByteArray(digest);
320 }
321 } catch (IOException | BufferUnderflowException e) {
322 throw new IOException("Failed to parse digest record #" + digestCount, e);
323 }
324 }
325
326 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
327 throw new SecurityException(
328 "Signature algorithms don't match between digests and signatures records");
329 }
330 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
331 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
332 if ((previousSignerDigest != null)
333 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
334 throw new SecurityException(
335 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
336 + " contents digest does not match the digest specified by a preceding signer");
337 }
338
339 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
340 List<X509Certificate> certs = new ArrayList<>();
341 int certificateCount = 0;
342 while (certificates.hasRemaining()) {
343 certificateCount++;
344 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
345 X509Certificate certificate;
346 try {
347 certificate = (X509Certificate)
348 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
349 } catch (CertificateException e) {
350 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
351 }
352 certificate = new VerbatimX509Certificate(certificate, encodedCert);
353 certs.add(certificate);
354 }
355
356 if (certs.isEmpty()) {
357 throw new SecurityException("No certificates listed");
358 }
359 X509Certificate mainCertificate = certs.get(0);
360 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
361 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
362 throw new SecurityException(
363 "Public key mismatch between certificate and signature record");
364 }
365
366 return certs.toArray(new X509Certificate[certs.size()]);
367 }
368
369 private static void verifyIntegrity(
370 Map<Integer, byte[]> expectedDigests,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700371 FileDescriptor apkFileDescriptor,
372 long apkSigningBlockOffset,
373 long centralDirOffset,
374 long eocdOffset,
375 ByteBuffer eocdBuf) throws SecurityException {
Alex Klyubine4157182016-01-05 13:27:05 -0800376
377 if (expectedDigests.isEmpty()) {
378 throw new SecurityException("No digests provided");
379 }
380
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700381 // We need to verify the integrity of the following three sections of the file:
382 // 1. Everything up to the start of the APK Signing Block.
383 // 2. ZIP Central Directory.
384 // 3. ZIP End of Central Directory (EoCD).
385 // Each of these sections is represented as a separate DataSource instance below.
386
387 // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
388 // avoid wasting physical memory. In most APK verification scenarios, the contents of the
389 // APK are already there in the OS's page cache and thus mmap does not use additional
390 // physical memory.
391 DataSource beforeApkSigningBlock =
392 new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
393 DataSource centralDir =
394 new MemoryMappedFileDataSource(
395 apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
396
Alex Klyubine4157182016-01-05 13:27:05 -0800397 // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
398 // Central Directory must be considered to point to the offset of the APK Signing Block.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700399 eocdBuf = eocdBuf.duplicate();
400 eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
401 ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
402 DataSource eocd = new ByteBufferDataSource(eocdBuf);
Alex Klyubine4157182016-01-05 13:27:05 -0800403
404 int[] digestAlgorithms = new int[expectedDigests.size()];
405 int digestAlgorithmCount = 0;
406 for (int digestAlgorithm : expectedDigests.keySet()) {
407 digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
408 digestAlgorithmCount++;
409 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700410 byte[][] actualDigests;
Alex Klyubine4157182016-01-05 13:27:05 -0800411 try {
412 actualDigests =
413 computeContentDigests(
414 digestAlgorithms,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700415 new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
Alex Klyubine4157182016-01-05 13:27:05 -0800416 } catch (DigestException e) {
417 throw new SecurityException("Failed to compute digest(s) of contents", e);
418 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700419 for (int i = 0; i < digestAlgorithms.length; i++) {
420 int digestAlgorithm = digestAlgorithms[i];
421 byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
422 byte[] actualDigest = actualDigests[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800423 if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
424 throw new SecurityException(
425 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700426 + " digest of contents did not verify");
Alex Klyubine4157182016-01-05 13:27:05 -0800427 }
428 }
429 }
430
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700431 private static byte[][] computeContentDigests(
Alex Klyubine4157182016-01-05 13:27:05 -0800432 int[] digestAlgorithms,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700433 DataSource[] contents) throws DigestException {
Alex Klyubine4157182016-01-05 13:27:05 -0800434 // For each digest algorithm the result is computed as follows:
435 // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
436 // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
437 // No chunks are produced for empty (zero length) segments.
438 // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
439 // length in bytes (uint32 little-endian) and the chunk's contents.
440 // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
441 // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
442 // segments in-order.
443
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700444 long totalChunkCountLong = 0;
445 for (DataSource input : contents) {
446 totalChunkCountLong += getChunkCount(input.size());
Alex Klyubine4157182016-01-05 13:27:05 -0800447 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700448 if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
449 throw new DigestException("Too many chunks: " + totalChunkCountLong);
450 }
451 int totalChunkCount = (int) totalChunkCountLong;
Alex Klyubine4157182016-01-05 13:27:05 -0800452
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700453 byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
454 for (int i = 0; i < digestAlgorithms.length; i++) {
455 int digestAlgorithm = digestAlgorithms[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800456 int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
457 byte[] concatenationOfChunkCountAndChunkDigests =
458 new byte[5 + totalChunkCount * digestOutputSizeBytes];
459 concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
460 setUnsignedInt32LittleEndian(
461 totalChunkCount,
462 concatenationOfChunkCountAndChunkDigests,
463 1);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700464 digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
Alex Klyubine4157182016-01-05 13:27:05 -0800465 }
466
467 byte[] chunkContentPrefix = new byte[5];
468 chunkContentPrefix[0] = (byte) 0xa5;
469 int chunkIndex = 0;
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700470 MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
471 for (int i = 0; i < digestAlgorithms.length; i++) {
472 String jcaAlgorithmName =
473 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
474 try {
475 mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
476 } catch (NoSuchAlgorithmException e) {
477 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
478 }
479 }
480 // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
481 // into how to parallelize (if at all) based on the capabilities of the hardware on which
482 // this code is running and based on the size of input.
Victor Hsieh3271d042017-10-24 15:46:32 -0700483 DataDigester digester = new MultipleDigestDataDigester(mds);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700484 int dataSourceIndex = 0;
485 for (DataSource input : contents) {
486 long inputOffset = 0;
487 long inputRemaining = input.size();
488 while (inputRemaining > 0) {
489 int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
490 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
491 for (int i = 0; i < mds.length; i++) {
492 mds[i].update(chunkContentPrefix);
493 }
494 try {
Victor Hsieh3271d042017-10-24 15:46:32 -0700495 input.feedIntoDataDigester(digester, inputOffset, chunkSize);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700496 } catch (IOException e) {
497 throw new DigestException(
498 "Failed to digest chunk #" + chunkIndex + " of section #"
499 + dataSourceIndex,
500 e);
501 }
502 for (int i = 0; i < digestAlgorithms.length; i++) {
503 int digestAlgorithm = digestAlgorithms[i];
504 byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800505 int expectedDigestSizeBytes =
506 getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700507 MessageDigest md = mds[i];
508 int actualDigestSizeBytes =
509 md.digest(
510 concatenationOfChunkCountAndChunkDigests,
511 5 + chunkIndex * expectedDigestSizeBytes,
512 expectedDigestSizeBytes);
Alex Klyubine4157182016-01-05 13:27:05 -0800513 if (actualDigestSizeBytes != expectedDigestSizeBytes) {
514 throw new RuntimeException(
515 "Unexpected output size of " + md.getAlgorithm() + " digest: "
516 + actualDigestSizeBytes);
517 }
518 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700519 inputOffset += chunkSize;
520 inputRemaining -= chunkSize;
Alex Klyubine4157182016-01-05 13:27:05 -0800521 chunkIndex++;
522 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700523 dataSourceIndex++;
Alex Klyubine4157182016-01-05 13:27:05 -0800524 }
525
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700526 byte[][] result = new byte[digestAlgorithms.length][];
527 for (int i = 0; i < digestAlgorithms.length; i++) {
528 int digestAlgorithm = digestAlgorithms[i];
529 byte[] input = digestsOfChunks[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800530 String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
531 MessageDigest md;
532 try {
533 md = MessageDigest.getInstance(jcaAlgorithmName);
534 } catch (NoSuchAlgorithmException e) {
535 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
536 }
537 byte[] output = md.digest(input);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700538 result[i] = output;
Alex Klyubine4157182016-01-05 13:27:05 -0800539 }
540 return result;
541 }
542
Todd Kennedy66c55532016-02-26 16:22:11 -0800543 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700544 * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
Todd Kennedy66c55532016-02-26 16:22:11 -0800545 *
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700546 * @throws IOException if an I/O error occurs while reading the file.
547 * @throws SignatureNotFoundException if the EoCD could not be found.
Todd Kennedy66c55532016-02-26 16:22:11 -0800548 */
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700549 private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
550 throws IOException, SignatureNotFoundException {
551 Pair<ByteBuffer, Long> eocdAndOffsetInFile =
552 ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
553 if (eocdAndOffsetInFile == null) {
Todd Kennedy66c55532016-02-26 16:22:11 -0800554 throw new SignatureNotFoundException(
555 "Not an APK file: ZIP End of Central Directory record not found");
556 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700557 return eocdAndOffsetInFile;
Todd Kennedy66c55532016-02-26 16:22:11 -0800558 }
559
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700560 private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
Todd Kennedy66c55532016-02-26 16:22:11 -0800561 throws SignatureNotFoundException {
Todd Kennedy66c55532016-02-26 16:22:11 -0800562 // Look up the offset of ZIP Central Directory.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700563 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
Alex Klyubin96946572016-12-19 10:52:52 -0800564 if (centralDirOffset > eocdOffset) {
Todd Kennedy66c55532016-02-26 16:22:11 -0800565 throw new SignatureNotFoundException(
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700566 "ZIP Central Directory offset out of range: " + centralDirOffset
Todd Kennedy66c55532016-02-26 16:22:11 -0800567 + ". ZIP End of Central Directory offset: " + eocdOffset);
568 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700569 long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
570 if (centralDirOffset + centralDirSize != eocdOffset) {
Todd Kennedy66c55532016-02-26 16:22:11 -0800571 throw new SignatureNotFoundException(
572 "ZIP Central Directory is not immediately followed by End of Central"
573 + " Directory");
574 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700575 return centralDirOffset;
Todd Kennedy66c55532016-02-26 16:22:11 -0800576 }
577
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700578 private static final long getChunkCount(long inputSizeBytes) {
Alex Klyubine4157182016-01-05 13:27:05 -0800579 return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
580 }
581
582 private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
583
584 private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
585 private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
586 private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
587 private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
588 private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
589 private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
590 private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
Alex Klyubine4157182016-01-05 13:27:05 -0800591
592 private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
593 private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
594
595 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
596 switch (sigAlgorithm) {
597 case SIGNATURE_RSA_PSS_WITH_SHA256:
598 case SIGNATURE_RSA_PSS_WITH_SHA512:
599 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
600 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
601 case SIGNATURE_ECDSA_WITH_SHA256:
602 case SIGNATURE_ECDSA_WITH_SHA512:
603 case SIGNATURE_DSA_WITH_SHA256:
Alex Klyubine4157182016-01-05 13:27:05 -0800604 return true;
605 default:
606 return false;
607 }
608 }
609
610 private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
611 int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
612 int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
613 return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
614 }
615
616 private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
617 switch (digestAlgorithm1) {
618 case CONTENT_DIGEST_CHUNKED_SHA256:
619 switch (digestAlgorithm2) {
620 case CONTENT_DIGEST_CHUNKED_SHA256:
621 return 0;
622 case CONTENT_DIGEST_CHUNKED_SHA512:
623 return -1;
624 default:
625 throw new IllegalArgumentException(
626 "Unknown digestAlgorithm2: " + digestAlgorithm2);
627 }
628 case CONTENT_DIGEST_CHUNKED_SHA512:
629 switch (digestAlgorithm2) {
630 case CONTENT_DIGEST_CHUNKED_SHA256:
631 return 1;
632 case CONTENT_DIGEST_CHUNKED_SHA512:
633 return 0;
634 default:
635 throw new IllegalArgumentException(
636 "Unknown digestAlgorithm2: " + digestAlgorithm2);
637 }
638 default:
639 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
640 }
641 }
642
643 private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
644 switch (sigAlgorithm) {
645 case SIGNATURE_RSA_PSS_WITH_SHA256:
646 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
647 case SIGNATURE_ECDSA_WITH_SHA256:
648 case SIGNATURE_DSA_WITH_SHA256:
649 return CONTENT_DIGEST_CHUNKED_SHA256;
650 case SIGNATURE_RSA_PSS_WITH_SHA512:
651 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
652 case SIGNATURE_ECDSA_WITH_SHA512:
Alex Klyubine4157182016-01-05 13:27:05 -0800653 return CONTENT_DIGEST_CHUNKED_SHA512;
654 default:
655 throw new IllegalArgumentException(
656 "Unknown signature algorithm: 0x"
657 + Long.toHexString(sigAlgorithm & 0xffffffff));
658 }
659 }
660
661 private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
662 switch (digestAlgorithm) {
663 case CONTENT_DIGEST_CHUNKED_SHA256:
664 return "SHA-256";
665 case CONTENT_DIGEST_CHUNKED_SHA512:
666 return "SHA-512";
667 default:
668 throw new IllegalArgumentException(
669 "Unknown content digest algorthm: " + digestAlgorithm);
670 }
671 }
672
673 private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
674 switch (digestAlgorithm) {
675 case CONTENT_DIGEST_CHUNKED_SHA256:
676 return 256 / 8;
677 case CONTENT_DIGEST_CHUNKED_SHA512:
678 return 512 / 8;
679 default:
680 throw new IllegalArgumentException(
681 "Unknown content digest algorthm: " + digestAlgorithm);
682 }
683 }
684
685 private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
686 switch (sigAlgorithm) {
687 case SIGNATURE_RSA_PSS_WITH_SHA256:
688 case SIGNATURE_RSA_PSS_WITH_SHA512:
689 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
690 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
691 return "RSA";
692 case SIGNATURE_ECDSA_WITH_SHA256:
693 case SIGNATURE_ECDSA_WITH_SHA512:
694 return "EC";
695 case SIGNATURE_DSA_WITH_SHA256:
Alex Klyubine4157182016-01-05 13:27:05 -0800696 return "DSA";
697 default:
698 throw new IllegalArgumentException(
699 "Unknown signature algorithm: 0x"
700 + Long.toHexString(sigAlgorithm & 0xffffffff));
701 }
702 }
703
704 private static Pair<String, ? extends AlgorithmParameterSpec>
705 getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
706 switch (sigAlgorithm) {
707 case SIGNATURE_RSA_PSS_WITH_SHA256:
708 return Pair.create(
709 "SHA256withRSA/PSS",
710 new PSSParameterSpec(
711 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
712 case SIGNATURE_RSA_PSS_WITH_SHA512:
713 return Pair.create(
714 "SHA512withRSA/PSS",
715 new PSSParameterSpec(
716 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
717 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
718 return Pair.create("SHA256withRSA", null);
719 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
720 return Pair.create("SHA512withRSA", null);
721 case SIGNATURE_ECDSA_WITH_SHA256:
722 return Pair.create("SHA256withECDSA", null);
723 case SIGNATURE_ECDSA_WITH_SHA512:
724 return Pair.create("SHA512withECDSA", null);
725 case SIGNATURE_DSA_WITH_SHA256:
726 return Pair.create("SHA256withDSA", null);
Alex Klyubine4157182016-01-05 13:27:05 -0800727 default:
728 throw new IllegalArgumentException(
729 "Unknown signature algorithm: 0x"
730 + Long.toHexString(sigAlgorithm & 0xffffffff));
731 }
732 }
733
734 /**
735 * Returns new byte buffer whose content is a shared subsequence of this buffer's content
736 * between the specified start (inclusive) and end (exclusive) positions. As opposed to
737 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
738 * buffer's byte order.
739 */
740 private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
741 if (start < 0) {
742 throw new IllegalArgumentException("start: " + start);
743 }
744 if (end < start) {
745 throw new IllegalArgumentException("end < start: " + end + " < " + start);
746 }
747 int capacity = source.capacity();
748 if (end > source.capacity()) {
749 throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
750 }
751 int originalLimit = source.limit();
752 int originalPosition = source.position();
753 try {
754 source.position(0);
755 source.limit(end);
756 source.position(start);
757 ByteBuffer result = source.slice();
758 result.order(source.order());
759 return result;
760 } finally {
761 source.position(0);
762 source.limit(originalLimit);
763 source.position(originalPosition);
764 }
765 }
766
767 /**
768 * Relative <em>get</em> method for reading {@code size} number of bytes from the current
769 * position of this buffer.
770 *
771 * <p>This method reads the next {@code size} bytes at this buffer's current position,
772 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
773 * {@code size}, byte order set to this buffer's byte order; and then increments the position by
774 * {@code size}.
775 */
776 private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
777 throws BufferUnderflowException {
778 if (size < 0) {
779 throw new IllegalArgumentException("size: " + size);
780 }
781 int originalLimit = source.limit();
782 int position = source.position();
783 int limit = position + size;
784 if ((limit < position) || (limit > originalLimit)) {
785 throw new BufferUnderflowException();
786 }
787 source.limit(limit);
788 try {
789 ByteBuffer result = source.slice();
790 result.order(source.order());
791 source.position(limit);
792 return result;
793 } finally {
794 source.limit(originalLimit);
795 }
796 }
797
798 private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
799 if (source.remaining() < 4) {
800 throw new IOException(
801 "Remaining buffer too short to contain length of length-prefixed field."
802 + " Remaining: " + source.remaining());
803 }
804 int len = source.getInt();
805 if (len < 0) {
806 throw new IllegalArgumentException("Negative length");
807 } else if (len > source.remaining()) {
808 throw new IOException("Length-prefixed field longer than remaining buffer."
809 + " Field length: " + len + ", remaining: " + source.remaining());
810 }
811 return getByteBuffer(source, len);
812 }
813
814 private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
815 int len = buf.getInt();
816 if (len < 0) {
817 throw new IOException("Negative length");
818 } else if (len > buf.remaining()) {
819 throw new IOException("Underflow while reading length-prefixed value. Length: " + len
820 + ", available: " + buf.remaining());
821 }
822 byte[] result = new byte[len];
823 buf.get(result);
824 return result;
825 }
826
827 private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
828 result[offset] = (byte) (value & 0xff);
829 result[offset + 1] = (byte) ((value >>> 8) & 0xff);
830 result[offset + 2] = (byte) ((value >>> 16) & 0xff);
831 result[offset + 3] = (byte) ((value >>> 24) & 0xff);
832 }
833
834 private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
835 private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
836 private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
837
838 private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
839
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700840 private static Pair<ByteBuffer, Long> findApkSigningBlock(
841 RandomAccessFile apk, long centralDirOffset)
842 throws IOException, SignatureNotFoundException {
Alex Klyubine4157182016-01-05 13:27:05 -0800843 // FORMAT:
844 // OFFSET DATA TYPE DESCRIPTION
845 // * @+0 bytes uint64: size in bytes (excluding this field)
846 // * @+8 bytes payload
847 // * @-24 bytes uint64: size in bytes (same as the one above)
848 // * @-16 bytes uint128: magic
849
850 if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
851 throw new SignatureNotFoundException(
852 "APK too small for APK Signing Block. ZIP Central Directory offset: "
853 + centralDirOffset);
854 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700855 // Read the magic and offset in file from the footer section of the block:
856 // * uint64: size of block
857 // * 16 bytes: magic
858 ByteBuffer footer = ByteBuffer.allocate(24);
859 footer.order(ByteOrder.LITTLE_ENDIAN);
860 apk.seek(centralDirOffset - footer.capacity());
861 apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
862 if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
863 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
Alex Klyubine4157182016-01-05 13:27:05 -0800864 throw new SignatureNotFoundException(
865 "No APK Signing Block before ZIP Central Directory");
866 }
867 // Read and compare size fields
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700868 long apkSigBlockSizeInFooter = footer.getLong(0);
869 if ((apkSigBlockSizeInFooter < footer.capacity())
870 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
Alex Klyubine4157182016-01-05 13:27:05 -0800871 throw new SignatureNotFoundException(
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700872 "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
Alex Klyubine4157182016-01-05 13:27:05 -0800873 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700874 int totalSize = (int) (apkSigBlockSizeInFooter + 8);
875 long apkSigBlockOffset = centralDirOffset - totalSize;
Alex Klyubine4157182016-01-05 13:27:05 -0800876 if (apkSigBlockOffset < 0) {
877 throw new SignatureNotFoundException(
878 "APK Signing Block offset out of range: " + apkSigBlockOffset);
879 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700880 ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
881 apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
882 apk.seek(apkSigBlockOffset);
883 apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
884 long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
885 if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
Alex Klyubine4157182016-01-05 13:27:05 -0800886 throw new SignatureNotFoundException(
887 "APK Signing Block sizes in header and footer do not match: "
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700888 + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
Alex Klyubine4157182016-01-05 13:27:05 -0800889 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700890 return Pair.create(apkSigBlock, apkSigBlockOffset);
Alex Klyubine4157182016-01-05 13:27:05 -0800891 }
892
893 private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
894 throws SignatureNotFoundException {
895 checkByteOrderLittleEndian(apkSigningBlock);
896 // FORMAT:
897 // OFFSET DATA TYPE DESCRIPTION
898 // * @+0 bytes uint64: size in bytes (excluding this field)
899 // * @+8 bytes pairs
900 // * @-24 bytes uint64: size in bytes (same as the one above)
901 // * @-16 bytes uint128: magic
902 ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
903
904 int entryCount = 0;
905 while (pairs.hasRemaining()) {
906 entryCount++;
907 if (pairs.remaining() < 8) {
908 throw new SignatureNotFoundException(
909 "Insufficient data to read size of APK Signing Block entry #" + entryCount);
910 }
911 long lenLong = pairs.getLong();
912 if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
913 throw new SignatureNotFoundException(
914 "APK Signing Block entry #" + entryCount
915 + " size out of range: " + lenLong);
916 }
917 int len = (int) lenLong;
918 int nextEntryPos = pairs.position() + len;
919 if (len > pairs.remaining()) {
920 throw new SignatureNotFoundException(
921 "APK Signing Block entry #" + entryCount + " size out of range: " + len
922 + ", available: " + pairs.remaining());
923 }
924 int id = pairs.getInt();
925 if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
926 return getByteBuffer(pairs, len - 4);
927 }
928 pairs.position(nextEntryPos);
929 }
930
931 throw new SignatureNotFoundException(
932 "No APK Signature Scheme v2 block in APK Signing Block");
933 }
934
935 private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
936 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
937 throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
938 }
939 }
940
Alex Klyubine4157182016-01-05 13:27:05 -0800941 /**
Victor Hsieh3271d042017-10-24 15:46:32 -0700942 * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700943 */
Victor Hsieh3271d042017-10-24 15:46:32 -0700944 private static class MultipleDigestDataDigester implements DataDigester {
945 private final MessageDigest[] mMds;
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700946
Victor Hsieh3271d042017-10-24 15:46:32 -0700947 MultipleDigestDataDigester(MessageDigest[] mds) {
948 mMds = mds;
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700949 }
950
951 @Override
Victor Hsieh3271d042017-10-24 15:46:32 -0700952 public void consume(ByteBuffer buffer) {
953 buffer = buffer.slice();
954 for (MessageDigest md : mMds) {
955 buffer.position(0);
956 md.update(buffer);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700957 }
958 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700959
960 @Override
Victor Hsieh3271d042017-10-24 15:46:32 -0700961 public void finish() {}
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700962 }
963
964 /**
Alex Klyubine4157182016-01-05 13:27:05 -0800965 * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
966 * of letting the underlying implementation have a shot at re-encoding the data.
967 */
968 private static class VerbatimX509Certificate extends WrappedX509Certificate {
969 private byte[] encodedVerbatim;
970
971 public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
972 super(wrapped);
973 this.encodedVerbatim = encodedVerbatim;
974 }
975
976 @Override
977 public byte[] getEncoded() throws CertificateEncodingException {
978 return encodedVerbatim;
979 }
980 }
981
982 private static class WrappedX509Certificate extends X509Certificate {
983 private final X509Certificate wrapped;
984
985 public WrappedX509Certificate(X509Certificate wrapped) {
986 this.wrapped = wrapped;
987 }
988
989 @Override
990 public Set<String> getCriticalExtensionOIDs() {
991 return wrapped.getCriticalExtensionOIDs();
992 }
993
994 @Override
995 public byte[] getExtensionValue(String oid) {
996 return wrapped.getExtensionValue(oid);
997 }
998
999 @Override
1000 public Set<String> getNonCriticalExtensionOIDs() {
1001 return wrapped.getNonCriticalExtensionOIDs();
1002 }
1003
1004 @Override
1005 public boolean hasUnsupportedCriticalExtension() {
1006 return wrapped.hasUnsupportedCriticalExtension();
1007 }
1008
1009 @Override
1010 public void checkValidity()
1011 throws CertificateExpiredException, CertificateNotYetValidException {
1012 wrapped.checkValidity();
1013 }
1014
1015 @Override
1016 public void checkValidity(Date date)
1017 throws CertificateExpiredException, CertificateNotYetValidException {
1018 wrapped.checkValidity(date);
1019 }
1020
1021 @Override
1022 public int getVersion() {
1023 return wrapped.getVersion();
1024 }
1025
1026 @Override
1027 public BigInteger getSerialNumber() {
1028 return wrapped.getSerialNumber();
1029 }
1030
1031 @Override
1032 public Principal getIssuerDN() {
1033 return wrapped.getIssuerDN();
1034 }
1035
1036 @Override
1037 public Principal getSubjectDN() {
1038 return wrapped.getSubjectDN();
1039 }
1040
1041 @Override
1042 public Date getNotBefore() {
1043 return wrapped.getNotBefore();
1044 }
1045
1046 @Override
1047 public Date getNotAfter() {
1048 return wrapped.getNotAfter();
1049 }
1050
1051 @Override
1052 public byte[] getTBSCertificate() throws CertificateEncodingException {
1053 return wrapped.getTBSCertificate();
1054 }
1055
1056 @Override
1057 public byte[] getSignature() {
1058 return wrapped.getSignature();
1059 }
1060
1061 @Override
1062 public String getSigAlgName() {
1063 return wrapped.getSigAlgName();
1064 }
1065
1066 @Override
1067 public String getSigAlgOID() {
1068 return wrapped.getSigAlgOID();
1069 }
1070
1071 @Override
1072 public byte[] getSigAlgParams() {
1073 return wrapped.getSigAlgParams();
1074 }
1075
1076 @Override
1077 public boolean[] getIssuerUniqueID() {
1078 return wrapped.getIssuerUniqueID();
1079 }
1080
1081 @Override
1082 public boolean[] getSubjectUniqueID() {
1083 return wrapped.getSubjectUniqueID();
1084 }
1085
1086 @Override
1087 public boolean[] getKeyUsage() {
1088 return wrapped.getKeyUsage();
1089 }
1090
1091 @Override
1092 public int getBasicConstraints() {
1093 return wrapped.getBasicConstraints();
1094 }
1095
1096 @Override
1097 public byte[] getEncoded() throws CertificateEncodingException {
1098 return wrapped.getEncoded();
1099 }
1100
1101 @Override
1102 public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
1103 InvalidKeyException, NoSuchProviderException, SignatureException {
1104 wrapped.verify(key);
1105 }
1106
1107 @Override
1108 public void verify(PublicKey key, String sigProvider)
1109 throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
1110 NoSuchProviderException, SignatureException {
1111 wrapped.verify(key, sigProvider);
1112 }
1113
1114 @Override
1115 public String toString() {
1116 return wrapped.toString();
1117 }
1118
1119 @Override
1120 public PublicKey getPublicKey() {
1121 return wrapped.getPublicKey();
1122 }
1123 }
1124}