blob: 78d3b7bf81d28e88832ad008c90f091ed66c6f87 [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.system.ErrnoException;
20import android.system.OsConstants;
21import android.util.ArrayMap;
Alex Klyubine4157182016-01-05 13:27:05 -080022import android.util.Pair;
23
24import java.io.ByteArrayInputStream;
Alex Klyubin0722ffc2016-03-18 16:09:06 -070025import java.io.FileDescriptor;
Alex Klyubine4157182016-01-05 13:27:05 -080026import java.io.IOException;
27import java.io.RandomAccessFile;
28import java.math.BigInteger;
29import java.nio.BufferUnderflowException;
30import java.nio.ByteBuffer;
31import java.nio.ByteOrder;
Alex Klyubin0722ffc2016-03-18 16:09:06 -070032import java.nio.DirectByteBuffer;
Alex Klyubine4157182016-01-05 13:27:05 -080033import java.security.DigestException;
34import java.security.InvalidAlgorithmParameterException;
35import java.security.InvalidKeyException;
36import java.security.KeyFactory;
37import java.security.MessageDigest;
38import java.security.NoSuchAlgorithmException;
39import java.security.NoSuchProviderException;
40import java.security.Principal;
41import java.security.PublicKey;
42import java.security.Signature;
43import java.security.SignatureException;
44import java.security.cert.CertificateEncodingException;
45import java.security.cert.CertificateException;
46import java.security.cert.CertificateExpiredException;
47import java.security.cert.CertificateFactory;
48import java.security.cert.CertificateNotYetValidException;
49import java.security.cert.X509Certificate;
50import java.security.spec.AlgorithmParameterSpec;
51import java.security.spec.InvalidKeySpecException;
52import java.security.spec.MGF1ParameterSpec;
53import java.security.spec.PSSParameterSpec;
54import java.security.spec.X509EncodedKeySpec;
55import java.util.ArrayList;
56import java.util.Arrays;
57import java.util.Date;
Alex Klyubine4157182016-01-05 13:27:05 -080058import java.util.List;
59import java.util.Map;
60import java.util.Set;
61
Alex Klyubin0722ffc2016-03-18 16:09:06 -070062import libcore.io.Libcore;
63import libcore.io.Os;
64
Alex Klyubine4157182016-01-05 13:27:05 -080065/**
66 * APK Signature Scheme v2 verifier.
67 *
68 * @hide for internal use only.
69 */
70public class ApkSignatureSchemeV2Verifier {
71
72 /**
73 * {@code .SF} file header section attribute indicating that the APK is signed not just with
74 * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
75 * facilitates v2 signature stripping detection.
76 *
77 * <p>The attribute contains a comma-separated set of signature scheme IDs.
78 */
79 public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
Alex Klyubin3a0095f2016-02-16 12:37:17 -080080 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
Alex Klyubine4157182016-01-05 13:27:05 -080081
82 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -070083 * Returns {@code true} if the provided APK contains an APK Signature Scheme V2 signature.
84 *
85 * <p><b>NOTE: This method does not verify the signature.</b>
Todd Kennedy66c55532016-02-26 16:22:11 -080086 */
87 public static boolean hasSignature(String apkFile) throws IOException {
88 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
Alex Klyubin0722ffc2016-03-18 16:09:06 -070089 findSignature(apk);
Todd Kennedy66c55532016-02-26 16:22:11 -080090 return true;
91 } catch (SignatureNotFoundException e) {
Alex Klyubin0722ffc2016-03-18 16:09:06 -070092 return false;
Todd Kennedy66c55532016-02-26 16:22:11 -080093 }
Todd Kennedy66c55532016-02-26 16:22:11 -080094 }
95
96 /**
Alex Klyubine4157182016-01-05 13:27:05 -080097 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
98 * associated with each signer.
99 *
100 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
101 * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
102 * @throws IOException if an I/O error occurs while reading the APK file.
103 */
104 public static X509Certificate[][] verify(String apkFile)
105 throws SignatureNotFoundException, SecurityException, IOException {
106 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
107 return verify(apk);
108 }
109 }
110
111 /**
112 * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
113 * associated with each signer.
114 *
115 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700116 * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not
117 * verify.
Alex Klyubine4157182016-01-05 13:27:05 -0800118 * @throws IOException if an I/O error occurs while reading the APK file.
119 */
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700120 private static X509Certificate[][] verify(RandomAccessFile apk)
Alex Klyubine4157182016-01-05 13:27:05 -0800121 throws SignatureNotFoundException, SecurityException, IOException {
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700122 SignatureInfo signatureInfo = findSignature(apk);
123 return verify(apk.getFD(), signatureInfo);
Alex Klyubine4157182016-01-05 13:27:05 -0800124 }
125
126 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700127 * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
128 * contained in the block against the file.
129 */
130 private static class SignatureInfo {
131 /** Contents of APK Signature Scheme v2 block. */
132 private final ByteBuffer signatureBlock;
133
134 /** Position of the APK Signing Block in the file. */
135 private final long apkSigningBlockOffset;
136
137 /** Position of the ZIP Central Directory in the file. */
138 private final long centralDirOffset;
139
140 /** Position of the ZIP End of Central Directory (EoCD) in the file. */
141 private final long eocdOffset;
142
143 /** Contents of ZIP End of Central Directory (EoCD) of the file. */
144 private final ByteBuffer eocd;
145
146 private SignatureInfo(
147 ByteBuffer signatureBlock,
148 long apkSigningBlockOffset,
149 long centralDirOffset,
150 long eocdOffset,
151 ByteBuffer eocd) {
152 this.signatureBlock = signatureBlock;
153 this.apkSigningBlockOffset = apkSigningBlockOffset;
154 this.centralDirOffset = centralDirOffset;
155 this.eocdOffset = eocdOffset;
156 this.eocd = eocd;
157 }
158 }
159
160 /**
161 * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
162 * additional information relevant for verifying the block against the file.
Alex Klyubine4157182016-01-05 13:27:05 -0800163 *
164 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700165 * @throws IOException if an I/O error occurs while reading the APK file.
Alex Klyubine4157182016-01-05 13:27:05 -0800166 */
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700167 private static SignatureInfo findSignature(RandomAccessFile apk)
168 throws IOException, SignatureNotFoundException {
169 // Find the ZIP End of Central Directory (EoCD) record.
170 Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
171 ByteBuffer eocd = eocdAndOffsetInFile.first;
172 long eocdOffset = eocdAndOffsetInFile.second;
173 if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
174 throw new SignatureNotFoundException("ZIP64 APK not supported");
175 }
Alex Klyubine4157182016-01-05 13:27:05 -0800176
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700177 // Find the APK Signing Block. The block immediately precedes the Central Directory.
178 long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
179 Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
180 findApkSigningBlock(apk, centralDirOffset);
181 ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
182 long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;
Alex Klyubine4157182016-01-05 13:27:05 -0800183
184 // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
185 ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
186
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700187 return new SignatureInfo(
Alex Klyubine4157182016-01-05 13:27:05 -0800188 apkSignatureSchemeV2Block,
189 apkSigningBlockOffset,
190 centralDirOffset,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700191 eocdOffset,
192 eocd);
Alex Klyubine4157182016-01-05 13:27:05 -0800193 }
194
195 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700196 * Verifies the contents of the provided APK file against the provided APK Signature Scheme v2
197 * Block.
198 *
199 * @param signatureInfo APK Signature Scheme v2 Block and information relevant for verifying it
200 * against the APK file.
Alex Klyubine4157182016-01-05 13:27:05 -0800201 */
202 private static X509Certificate[][] verify(
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700203 FileDescriptor apkFileDescriptor,
204 SignatureInfo signatureInfo) throws SecurityException {
Alex Klyubine4157182016-01-05 13:27:05 -0800205 int signerCount = 0;
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700206 Map<Integer, byte[]> contentDigests = new ArrayMap<>();
Alex Klyubine4157182016-01-05 13:27:05 -0800207 List<X509Certificate[]> signerCerts = new ArrayList<>();
208 CertificateFactory certFactory;
209 try {
210 certFactory = CertificateFactory.getInstance("X.509");
211 } catch (CertificateException e) {
212 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
213 }
214 ByteBuffer signers;
215 try {
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700216 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
Alex Klyubine4157182016-01-05 13:27:05 -0800217 } catch (IOException e) {
218 throw new SecurityException("Failed to read list of signers", e);
219 }
220 while (signers.hasRemaining()) {
221 signerCount++;
222 try {
223 ByteBuffer signer = getLengthPrefixedSlice(signers);
224 X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
225 signerCerts.add(certs);
226 } catch (IOException | BufferUnderflowException | SecurityException e) {
227 throw new SecurityException(
228 "Failed to parse/verify signer #" + signerCount + " block",
229 e);
230 }
231 }
232
233 if (signerCount < 1) {
234 throw new SecurityException("No signers found");
235 }
236
237 if (contentDigests.isEmpty()) {
238 throw new SecurityException("No content digests found");
239 }
240
241 verifyIntegrity(
242 contentDigests,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700243 apkFileDescriptor,
244 signatureInfo.apkSigningBlockOffset,
245 signatureInfo.centralDirOffset,
246 signatureInfo.eocdOffset,
247 signatureInfo.eocd);
Alex Klyubine4157182016-01-05 13:27:05 -0800248
249 return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
250 }
251
252 private static X509Certificate[] verifySigner(
253 ByteBuffer signerBlock,
254 Map<Integer, byte[]> contentDigests,
255 CertificateFactory certFactory) throws SecurityException, IOException {
256 ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
257 ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
258 byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
259
260 int signatureCount = 0;
261 int bestSigAlgorithm = -1;
262 byte[] bestSigAlgorithmSignatureBytes = null;
263 List<Integer> signaturesSigAlgorithms = new ArrayList<>();
264 while (signatures.hasRemaining()) {
265 signatureCount++;
266 try {
267 ByteBuffer signature = getLengthPrefixedSlice(signatures);
268 if (signature.remaining() < 8) {
269 throw new SecurityException("Signature record too short");
270 }
271 int sigAlgorithm = signature.getInt();
272 signaturesSigAlgorithms.add(sigAlgorithm);
273 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
274 continue;
275 }
276 if ((bestSigAlgorithm == -1)
277 || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
278 bestSigAlgorithm = sigAlgorithm;
279 bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
280 }
281 } catch (IOException | BufferUnderflowException e) {
282 throw new SecurityException(
283 "Failed to parse signature record #" + signatureCount,
284 e);
285 }
286 }
287 if (bestSigAlgorithm == -1) {
288 if (signatureCount == 0) {
289 throw new SecurityException("No signatures found");
290 } else {
291 throw new SecurityException("No supported signatures found");
292 }
293 }
294
295 String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
296 Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
297 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
298 String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
299 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
300 boolean sigVerified;
301 try {
302 PublicKey publicKey =
303 KeyFactory.getInstance(keyAlgorithm)
304 .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
305 Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
306 sig.initVerify(publicKey);
307 if (jcaSignatureAlgorithmParams != null) {
308 sig.setParameter(jcaSignatureAlgorithmParams);
309 }
310 sig.update(signedData);
311 sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
312 } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
313 | InvalidAlgorithmParameterException | SignatureException e) {
314 throw new SecurityException(
315 "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
316 }
317 if (!sigVerified) {
318 throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
319 }
320
321 // Signature over signedData has verified.
322
323 byte[] contentDigest = null;
324 signedData.clear();
325 ByteBuffer digests = getLengthPrefixedSlice(signedData);
326 List<Integer> digestsSigAlgorithms = new ArrayList<>();
327 int digestCount = 0;
328 while (digests.hasRemaining()) {
329 digestCount++;
330 try {
331 ByteBuffer digest = getLengthPrefixedSlice(digests);
332 if (digest.remaining() < 8) {
333 throw new IOException("Record too short");
334 }
335 int sigAlgorithm = digest.getInt();
336 digestsSigAlgorithms.add(sigAlgorithm);
337 if (sigAlgorithm == bestSigAlgorithm) {
338 contentDigest = readLengthPrefixedByteArray(digest);
339 }
340 } catch (IOException | BufferUnderflowException e) {
341 throw new IOException("Failed to parse digest record #" + digestCount, e);
342 }
343 }
344
345 if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
346 throw new SecurityException(
347 "Signature algorithms don't match between digests and signatures records");
348 }
349 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
350 byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
351 if ((previousSignerDigest != null)
352 && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
353 throw new SecurityException(
354 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
355 + " contents digest does not match the digest specified by a preceding signer");
356 }
357
358 ByteBuffer certificates = getLengthPrefixedSlice(signedData);
359 List<X509Certificate> certs = new ArrayList<>();
360 int certificateCount = 0;
361 while (certificates.hasRemaining()) {
362 certificateCount++;
363 byte[] encodedCert = readLengthPrefixedByteArray(certificates);
364 X509Certificate certificate;
365 try {
366 certificate = (X509Certificate)
367 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
368 } catch (CertificateException e) {
369 throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
370 }
371 certificate = new VerbatimX509Certificate(certificate, encodedCert);
372 certs.add(certificate);
373 }
374
375 if (certs.isEmpty()) {
376 throw new SecurityException("No certificates listed");
377 }
378 X509Certificate mainCertificate = certs.get(0);
379 byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
380 if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
381 throw new SecurityException(
382 "Public key mismatch between certificate and signature record");
383 }
384
385 return certs.toArray(new X509Certificate[certs.size()]);
386 }
387
388 private static void verifyIntegrity(
389 Map<Integer, byte[]> expectedDigests,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700390 FileDescriptor apkFileDescriptor,
391 long apkSigningBlockOffset,
392 long centralDirOffset,
393 long eocdOffset,
394 ByteBuffer eocdBuf) throws SecurityException {
Alex Klyubine4157182016-01-05 13:27:05 -0800395
396 if (expectedDigests.isEmpty()) {
397 throw new SecurityException("No digests provided");
398 }
399
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700400 // We need to verify the integrity of the following three sections of the file:
401 // 1. Everything up to the start of the APK Signing Block.
402 // 2. ZIP Central Directory.
403 // 3. ZIP End of Central Directory (EoCD).
404 // Each of these sections is represented as a separate DataSource instance below.
405
406 // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to
407 // avoid wasting physical memory. In most APK verification scenarios, the contents of the
408 // APK are already there in the OS's page cache and thus mmap does not use additional
409 // physical memory.
410 DataSource beforeApkSigningBlock =
411 new MemoryMappedFileDataSource(apkFileDescriptor, 0, apkSigningBlockOffset);
412 DataSource centralDir =
413 new MemoryMappedFileDataSource(
414 apkFileDescriptor, centralDirOffset, eocdOffset - centralDirOffset);
415
Alex Klyubine4157182016-01-05 13:27:05 -0800416 // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
417 // Central Directory must be considered to point to the offset of the APK Signing Block.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700418 eocdBuf = eocdBuf.duplicate();
419 eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
420 ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset);
421 DataSource eocd = new ByteBufferDataSource(eocdBuf);
Alex Klyubine4157182016-01-05 13:27:05 -0800422
423 int[] digestAlgorithms = new int[expectedDigests.size()];
424 int digestAlgorithmCount = 0;
425 for (int digestAlgorithm : expectedDigests.keySet()) {
426 digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
427 digestAlgorithmCount++;
428 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700429 byte[][] actualDigests;
Alex Klyubine4157182016-01-05 13:27:05 -0800430 try {
431 actualDigests =
432 computeContentDigests(
433 digestAlgorithms,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700434 new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
Alex Klyubine4157182016-01-05 13:27:05 -0800435 } catch (DigestException e) {
436 throw new SecurityException("Failed to compute digest(s) of contents", e);
437 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700438 for (int i = 0; i < digestAlgorithms.length; i++) {
439 int digestAlgorithm = digestAlgorithms[i];
440 byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
441 byte[] actualDigest = actualDigests[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800442 if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
443 throw new SecurityException(
444 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700445 + " digest of contents did not verify");
Alex Klyubine4157182016-01-05 13:27:05 -0800446 }
447 }
448 }
449
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700450 private static byte[][] computeContentDigests(
Alex Klyubine4157182016-01-05 13:27:05 -0800451 int[] digestAlgorithms,
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700452 DataSource[] contents) throws DigestException {
Alex Klyubine4157182016-01-05 13:27:05 -0800453 // For each digest algorithm the result is computed as follows:
454 // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
455 // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
456 // No chunks are produced for empty (zero length) segments.
457 // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
458 // length in bytes (uint32 little-endian) and the chunk's contents.
459 // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
460 // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
461 // segments in-order.
462
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700463 long totalChunkCountLong = 0;
464 for (DataSource input : contents) {
465 totalChunkCountLong += getChunkCount(input.size());
Alex Klyubine4157182016-01-05 13:27:05 -0800466 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700467 if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) {
468 throw new DigestException("Too many chunks: " + totalChunkCountLong);
469 }
470 int totalChunkCount = (int) totalChunkCountLong;
Alex Klyubine4157182016-01-05 13:27:05 -0800471
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700472 byte[][] digestsOfChunks = new byte[digestAlgorithms.length][];
473 for (int i = 0; i < digestAlgorithms.length; i++) {
474 int digestAlgorithm = digestAlgorithms[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800475 int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
476 byte[] concatenationOfChunkCountAndChunkDigests =
477 new byte[5 + totalChunkCount * digestOutputSizeBytes];
478 concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
479 setUnsignedInt32LittleEndian(
480 totalChunkCount,
481 concatenationOfChunkCountAndChunkDigests,
482 1);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700483 digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
Alex Klyubine4157182016-01-05 13:27:05 -0800484 }
485
486 byte[] chunkContentPrefix = new byte[5];
487 chunkContentPrefix[0] = (byte) 0xa5;
488 int chunkIndex = 0;
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700489 MessageDigest[] mds = new MessageDigest[digestAlgorithms.length];
490 for (int i = 0; i < digestAlgorithms.length; i++) {
491 String jcaAlgorithmName =
492 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]);
493 try {
494 mds[i] = MessageDigest.getInstance(jcaAlgorithmName);
495 } catch (NoSuchAlgorithmException e) {
496 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
497 }
498 }
499 // TODO: Compute digests of chunks in parallel when beneficial. This requires some research
500 // into how to parallelize (if at all) based on the capabilities of the hardware on which
501 // this code is running and based on the size of input.
502 int dataSourceIndex = 0;
503 for (DataSource input : contents) {
504 long inputOffset = 0;
505 long inputRemaining = input.size();
506 while (inputRemaining > 0) {
507 int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES);
508 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
509 for (int i = 0; i < mds.length; i++) {
510 mds[i].update(chunkContentPrefix);
511 }
512 try {
513 input.feedIntoMessageDigests(mds, inputOffset, chunkSize);
514 } catch (IOException e) {
515 throw new DigestException(
516 "Failed to digest chunk #" + chunkIndex + " of section #"
517 + dataSourceIndex,
518 e);
519 }
520 for (int i = 0; i < digestAlgorithms.length; i++) {
521 int digestAlgorithm = digestAlgorithms[i];
522 byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800523 int expectedDigestSizeBytes =
524 getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700525 MessageDigest md = mds[i];
526 int actualDigestSizeBytes =
527 md.digest(
528 concatenationOfChunkCountAndChunkDigests,
529 5 + chunkIndex * expectedDigestSizeBytes,
530 expectedDigestSizeBytes);
Alex Klyubine4157182016-01-05 13:27:05 -0800531 if (actualDigestSizeBytes != expectedDigestSizeBytes) {
532 throw new RuntimeException(
533 "Unexpected output size of " + md.getAlgorithm() + " digest: "
534 + actualDigestSizeBytes);
535 }
536 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700537 inputOffset += chunkSize;
538 inputRemaining -= chunkSize;
Alex Klyubine4157182016-01-05 13:27:05 -0800539 chunkIndex++;
540 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700541 dataSourceIndex++;
Alex Klyubine4157182016-01-05 13:27:05 -0800542 }
543
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700544 byte[][] result = new byte[digestAlgorithms.length][];
545 for (int i = 0; i < digestAlgorithms.length; i++) {
546 int digestAlgorithm = digestAlgorithms[i];
547 byte[] input = digestsOfChunks[i];
Alex Klyubine4157182016-01-05 13:27:05 -0800548 String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
549 MessageDigest md;
550 try {
551 md = MessageDigest.getInstance(jcaAlgorithmName);
552 } catch (NoSuchAlgorithmException e) {
553 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
554 }
555 byte[] output = md.digest(input);
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700556 result[i] = output;
Alex Klyubine4157182016-01-05 13:27:05 -0800557 }
558 return result;
559 }
560
Todd Kennedy66c55532016-02-26 16:22:11 -0800561 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700562 * Returns the ZIP End of Central Directory (EoCD) and its offset in the file.
Todd Kennedy66c55532016-02-26 16:22:11 -0800563 *
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700564 * @throws IOException if an I/O error occurs while reading the file.
565 * @throws SignatureNotFoundException if the EoCD could not be found.
Todd Kennedy66c55532016-02-26 16:22:11 -0800566 */
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700567 private static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk)
568 throws IOException, SignatureNotFoundException {
569 Pair<ByteBuffer, Long> eocdAndOffsetInFile =
570 ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
571 if (eocdAndOffsetInFile == null) {
Todd Kennedy66c55532016-02-26 16:22:11 -0800572 throw new SignatureNotFoundException(
573 "Not an APK file: ZIP End of Central Directory record not found");
574 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700575 return eocdAndOffsetInFile;
Todd Kennedy66c55532016-02-26 16:22:11 -0800576 }
577
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700578 private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset)
Todd Kennedy66c55532016-02-26 16:22:11 -0800579 throws SignatureNotFoundException {
Todd Kennedy66c55532016-02-26 16:22:11 -0800580 // Look up the offset of ZIP Central Directory.
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700581 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
582 if (centralDirOffset >= eocdOffset) {
Todd Kennedy66c55532016-02-26 16:22:11 -0800583 throw new SignatureNotFoundException(
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700584 "ZIP Central Directory offset out of range: " + centralDirOffset
Todd Kennedy66c55532016-02-26 16:22:11 -0800585 + ". ZIP End of Central Directory offset: " + eocdOffset);
586 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700587 long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
588 if (centralDirOffset + centralDirSize != eocdOffset) {
Todd Kennedy66c55532016-02-26 16:22:11 -0800589 throw new SignatureNotFoundException(
590 "ZIP Central Directory is not immediately followed by End of Central"
591 + " Directory");
592 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700593 return centralDirOffset;
Todd Kennedy66c55532016-02-26 16:22:11 -0800594 }
595
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700596 private static final long getChunkCount(long inputSizeBytes) {
Alex Klyubine4157182016-01-05 13:27:05 -0800597 return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
598 }
599
600 private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
601
602 private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
603 private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
604 private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
605 private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
606 private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
607 private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
608 private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
Alex Klyubine4157182016-01-05 13:27:05 -0800609
610 private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
611 private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
612
613 private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
614 switch (sigAlgorithm) {
615 case SIGNATURE_RSA_PSS_WITH_SHA256:
616 case SIGNATURE_RSA_PSS_WITH_SHA512:
617 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
618 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
619 case SIGNATURE_ECDSA_WITH_SHA256:
620 case SIGNATURE_ECDSA_WITH_SHA512:
621 case SIGNATURE_DSA_WITH_SHA256:
Alex Klyubine4157182016-01-05 13:27:05 -0800622 return true;
623 default:
624 return false;
625 }
626 }
627
628 private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
629 int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
630 int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
631 return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
632 }
633
634 private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
635 switch (digestAlgorithm1) {
636 case CONTENT_DIGEST_CHUNKED_SHA256:
637 switch (digestAlgorithm2) {
638 case CONTENT_DIGEST_CHUNKED_SHA256:
639 return 0;
640 case CONTENT_DIGEST_CHUNKED_SHA512:
641 return -1;
642 default:
643 throw new IllegalArgumentException(
644 "Unknown digestAlgorithm2: " + digestAlgorithm2);
645 }
646 case CONTENT_DIGEST_CHUNKED_SHA512:
647 switch (digestAlgorithm2) {
648 case CONTENT_DIGEST_CHUNKED_SHA256:
649 return 1;
650 case CONTENT_DIGEST_CHUNKED_SHA512:
651 return 0;
652 default:
653 throw new IllegalArgumentException(
654 "Unknown digestAlgorithm2: " + digestAlgorithm2);
655 }
656 default:
657 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
658 }
659 }
660
661 private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
662 switch (sigAlgorithm) {
663 case SIGNATURE_RSA_PSS_WITH_SHA256:
664 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
665 case SIGNATURE_ECDSA_WITH_SHA256:
666 case SIGNATURE_DSA_WITH_SHA256:
667 return CONTENT_DIGEST_CHUNKED_SHA256;
668 case SIGNATURE_RSA_PSS_WITH_SHA512:
669 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
670 case SIGNATURE_ECDSA_WITH_SHA512:
Alex Klyubine4157182016-01-05 13:27:05 -0800671 return CONTENT_DIGEST_CHUNKED_SHA512;
672 default:
673 throw new IllegalArgumentException(
674 "Unknown signature algorithm: 0x"
675 + Long.toHexString(sigAlgorithm & 0xffffffff));
676 }
677 }
678
679 private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
680 switch (digestAlgorithm) {
681 case CONTENT_DIGEST_CHUNKED_SHA256:
682 return "SHA-256";
683 case CONTENT_DIGEST_CHUNKED_SHA512:
684 return "SHA-512";
685 default:
686 throw new IllegalArgumentException(
687 "Unknown content digest algorthm: " + digestAlgorithm);
688 }
689 }
690
691 private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
692 switch (digestAlgorithm) {
693 case CONTENT_DIGEST_CHUNKED_SHA256:
694 return 256 / 8;
695 case CONTENT_DIGEST_CHUNKED_SHA512:
696 return 512 / 8;
697 default:
698 throw new IllegalArgumentException(
699 "Unknown content digest algorthm: " + digestAlgorithm);
700 }
701 }
702
703 private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
704 switch (sigAlgorithm) {
705 case SIGNATURE_RSA_PSS_WITH_SHA256:
706 case SIGNATURE_RSA_PSS_WITH_SHA512:
707 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
708 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
709 return "RSA";
710 case SIGNATURE_ECDSA_WITH_SHA256:
711 case SIGNATURE_ECDSA_WITH_SHA512:
712 return "EC";
713 case SIGNATURE_DSA_WITH_SHA256:
Alex Klyubine4157182016-01-05 13:27:05 -0800714 return "DSA";
715 default:
716 throw new IllegalArgumentException(
717 "Unknown signature algorithm: 0x"
718 + Long.toHexString(sigAlgorithm & 0xffffffff));
719 }
720 }
721
722 private static Pair<String, ? extends AlgorithmParameterSpec>
723 getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
724 switch (sigAlgorithm) {
725 case SIGNATURE_RSA_PSS_WITH_SHA256:
726 return Pair.create(
727 "SHA256withRSA/PSS",
728 new PSSParameterSpec(
729 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
730 case SIGNATURE_RSA_PSS_WITH_SHA512:
731 return Pair.create(
732 "SHA512withRSA/PSS",
733 new PSSParameterSpec(
734 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
735 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
736 return Pair.create("SHA256withRSA", null);
737 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
738 return Pair.create("SHA512withRSA", null);
739 case SIGNATURE_ECDSA_WITH_SHA256:
740 return Pair.create("SHA256withECDSA", null);
741 case SIGNATURE_ECDSA_WITH_SHA512:
742 return Pair.create("SHA512withECDSA", null);
743 case SIGNATURE_DSA_WITH_SHA256:
744 return Pair.create("SHA256withDSA", null);
Alex Klyubine4157182016-01-05 13:27:05 -0800745 default:
746 throw new IllegalArgumentException(
747 "Unknown signature algorithm: 0x"
748 + Long.toHexString(sigAlgorithm & 0xffffffff));
749 }
750 }
751
752 /**
753 * Returns new byte buffer whose content is a shared subsequence of this buffer's content
754 * between the specified start (inclusive) and end (exclusive) positions. As opposed to
755 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
756 * buffer's byte order.
757 */
758 private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
759 if (start < 0) {
760 throw new IllegalArgumentException("start: " + start);
761 }
762 if (end < start) {
763 throw new IllegalArgumentException("end < start: " + end + " < " + start);
764 }
765 int capacity = source.capacity();
766 if (end > source.capacity()) {
767 throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
768 }
769 int originalLimit = source.limit();
770 int originalPosition = source.position();
771 try {
772 source.position(0);
773 source.limit(end);
774 source.position(start);
775 ByteBuffer result = source.slice();
776 result.order(source.order());
777 return result;
778 } finally {
779 source.position(0);
780 source.limit(originalLimit);
781 source.position(originalPosition);
782 }
783 }
784
785 /**
786 * Relative <em>get</em> method for reading {@code size} number of bytes from the current
787 * position of this buffer.
788 *
789 * <p>This method reads the next {@code size} bytes at this buffer's current position,
790 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
791 * {@code size}, byte order set to this buffer's byte order; and then increments the position by
792 * {@code size}.
793 */
794 private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
795 throws BufferUnderflowException {
796 if (size < 0) {
797 throw new IllegalArgumentException("size: " + size);
798 }
799 int originalLimit = source.limit();
800 int position = source.position();
801 int limit = position + size;
802 if ((limit < position) || (limit > originalLimit)) {
803 throw new BufferUnderflowException();
804 }
805 source.limit(limit);
806 try {
807 ByteBuffer result = source.slice();
808 result.order(source.order());
809 source.position(limit);
810 return result;
811 } finally {
812 source.limit(originalLimit);
813 }
814 }
815
816 private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
817 if (source.remaining() < 4) {
818 throw new IOException(
819 "Remaining buffer too short to contain length of length-prefixed field."
820 + " Remaining: " + source.remaining());
821 }
822 int len = source.getInt();
823 if (len < 0) {
824 throw new IllegalArgumentException("Negative length");
825 } else if (len > source.remaining()) {
826 throw new IOException("Length-prefixed field longer than remaining buffer."
827 + " Field length: " + len + ", remaining: " + source.remaining());
828 }
829 return getByteBuffer(source, len);
830 }
831
832 private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
833 int len = buf.getInt();
834 if (len < 0) {
835 throw new IOException("Negative length");
836 } else if (len > buf.remaining()) {
837 throw new IOException("Underflow while reading length-prefixed value. Length: " + len
838 + ", available: " + buf.remaining());
839 }
840 byte[] result = new byte[len];
841 buf.get(result);
842 return result;
843 }
844
845 private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
846 result[offset] = (byte) (value & 0xff);
847 result[offset + 1] = (byte) ((value >>> 8) & 0xff);
848 result[offset + 2] = (byte) ((value >>> 16) & 0xff);
849 result[offset + 3] = (byte) ((value >>> 24) & 0xff);
850 }
851
852 private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
853 private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
854 private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
855
856 private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
857
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700858 private static Pair<ByteBuffer, Long> findApkSigningBlock(
859 RandomAccessFile apk, long centralDirOffset)
860 throws IOException, SignatureNotFoundException {
Alex Klyubine4157182016-01-05 13:27:05 -0800861 // FORMAT:
862 // OFFSET DATA TYPE DESCRIPTION
863 // * @+0 bytes uint64: size in bytes (excluding this field)
864 // * @+8 bytes payload
865 // * @-24 bytes uint64: size in bytes (same as the one above)
866 // * @-16 bytes uint128: magic
867
868 if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
869 throw new SignatureNotFoundException(
870 "APK too small for APK Signing Block. ZIP Central Directory offset: "
871 + centralDirOffset);
872 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700873 // Read the magic and offset in file from the footer section of the block:
874 // * uint64: size of block
875 // * 16 bytes: magic
876 ByteBuffer footer = ByteBuffer.allocate(24);
877 footer.order(ByteOrder.LITTLE_ENDIAN);
878 apk.seek(centralDirOffset - footer.capacity());
879 apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity());
880 if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
881 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
Alex Klyubine4157182016-01-05 13:27:05 -0800882 throw new SignatureNotFoundException(
883 "No APK Signing Block before ZIP Central Directory");
884 }
885 // Read and compare size fields
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700886 long apkSigBlockSizeInFooter = footer.getLong(0);
887 if ((apkSigBlockSizeInFooter < footer.capacity())
888 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
Alex Klyubine4157182016-01-05 13:27:05 -0800889 throw new SignatureNotFoundException(
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700890 "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
Alex Klyubine4157182016-01-05 13:27:05 -0800891 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700892 int totalSize = (int) (apkSigBlockSizeInFooter + 8);
893 long apkSigBlockOffset = centralDirOffset - totalSize;
Alex Klyubine4157182016-01-05 13:27:05 -0800894 if (apkSigBlockOffset < 0) {
895 throw new SignatureNotFoundException(
896 "APK Signing Block offset out of range: " + apkSigBlockOffset);
897 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700898 ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize);
899 apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
900 apk.seek(apkSigBlockOffset);
901 apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity());
902 long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
903 if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
Alex Klyubine4157182016-01-05 13:27:05 -0800904 throw new SignatureNotFoundException(
905 "APK Signing Block sizes in header and footer do not match: "
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700906 + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
Alex Klyubine4157182016-01-05 13:27:05 -0800907 }
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700908 return Pair.create(apkSigBlock, apkSigBlockOffset);
Alex Klyubine4157182016-01-05 13:27:05 -0800909 }
910
911 private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
912 throws SignatureNotFoundException {
913 checkByteOrderLittleEndian(apkSigningBlock);
914 // FORMAT:
915 // OFFSET DATA TYPE DESCRIPTION
916 // * @+0 bytes uint64: size in bytes (excluding this field)
917 // * @+8 bytes pairs
918 // * @-24 bytes uint64: size in bytes (same as the one above)
919 // * @-16 bytes uint128: magic
920 ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
921
922 int entryCount = 0;
923 while (pairs.hasRemaining()) {
924 entryCount++;
925 if (pairs.remaining() < 8) {
926 throw new SignatureNotFoundException(
927 "Insufficient data to read size of APK Signing Block entry #" + entryCount);
928 }
929 long lenLong = pairs.getLong();
930 if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
931 throw new SignatureNotFoundException(
932 "APK Signing Block entry #" + entryCount
933 + " size out of range: " + lenLong);
934 }
935 int len = (int) lenLong;
936 int nextEntryPos = pairs.position() + len;
937 if (len > pairs.remaining()) {
938 throw new SignatureNotFoundException(
939 "APK Signing Block entry #" + entryCount + " size out of range: " + len
940 + ", available: " + pairs.remaining());
941 }
942 int id = pairs.getInt();
943 if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
944 return getByteBuffer(pairs, len - 4);
945 }
946 pairs.position(nextEntryPos);
947 }
948
949 throw new SignatureNotFoundException(
950 "No APK Signature Scheme v2 block in APK Signing Block");
951 }
952
953 private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
954 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
955 throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
956 }
957 }
958
959 public static class SignatureNotFoundException extends Exception {
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700960 private static final long serialVersionUID = 1L;
961
Alex Klyubine4157182016-01-05 13:27:05 -0800962 public SignatureNotFoundException(String message) {
963 super(message);
964 }
965
966 public SignatureNotFoundException(String message, Throwable cause) {
967 super(message, cause);
968 }
969 }
970
971 /**
Alex Klyubin0722ffc2016-03-18 16:09:06 -0700972 * Source of data to be digested.
973 */
974 private static interface DataSource {
975
976 /**
977 * Returns the size (in bytes) of the data offered by this source.
978 */
979 long size();
980
981 /**
982 * Feeds the specified region of this source's data into the provided digests. Each digest
983 * instance gets the same data.
984 *
985 * @param offset offset of the region inside this data source.
986 * @param size size (in bytes) of the region.
987 */
988 void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException;
989 }
990
991 /**
992 * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
993 * of the file requested by
994 * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
995 */
996 private static final class MemoryMappedFileDataSource implements DataSource {
997 private static final Os OS = Libcore.os;
998 private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE);
999
1000 private final FileDescriptor mFd;
1001 private final long mFilePosition;
1002 private final long mSize;
1003
1004 /**
1005 * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
1006 *
1007 * @param position start position of the region in the file.
1008 * @param size size (in bytes) of the region.
1009 */
1010 public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
1011 mFd = fd;
1012 mFilePosition = position;
1013 mSize = size;
1014 }
1015
1016 @Override
1017 public long size() {
1018 return mSize;
1019 }
1020
1021 @Override
1022 public void feedIntoMessageDigests(
1023 MessageDigest[] mds, long offset, int size) throws IOException {
1024 // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
1025 // method was settled on a straightforward mmap with prefaulting.
1026 //
1027 // This method is not using FileChannel.map API because that API does not offset a way
1028 // to "prefault" the resulting memory pages. Without prefaulting, performance is about
1029 // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
1030 // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
1031 // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
1032 // time, which is not compensated for by faster reads.
1033
1034 // We mmap the smallest region of the file containing the requested data. mmap requires
1035 // that the start offset in the file must be a multiple of memory page size. We thus may
1036 // need to mmap from an offset less than the requested offset.
1037 long filePosition = mFilePosition + offset;
1038 long mmapFilePosition =
1039 (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
1040 int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
1041 long mmapRegionSize = size + dataStartOffsetInMmapRegion;
1042 long mmapPtr = 0;
1043 try {
1044 mmapPtr = OS.mmap(
1045 0, // let the OS choose the start address of the region in memory
1046 mmapRegionSize,
1047 OsConstants.PROT_READ,
1048 OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
1049 mFd,
1050 mmapFilePosition);
1051 // Feeding a memory region into MessageDigest requires the region to be represented
1052 // as a direct ByteBuffer.
1053 ByteBuffer buf = new DirectByteBuffer(
1054 size,
1055 mmapPtr + dataStartOffsetInMmapRegion,
1056 mFd, // not really needed, but just in case
1057 null, // no need to clean up -- it's taken care of by the finally block
1058 true // read only buffer
1059 );
1060 for (MessageDigest md : mds) {
1061 buf.position(0);
1062 md.update(buf);
1063 }
1064 } catch (ErrnoException e) {
1065 throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
1066 } finally {
1067 if (mmapPtr != 0) {
1068 try {
1069 OS.munmap(mmapPtr, mmapRegionSize);
1070 } catch (ErrnoException ignored) {}
1071 }
1072 }
1073 }
1074 }
1075
1076 /**
1077 * {@link DataSource} which provides data from a {@link ByteBuffer}.
1078 */
1079 private static final class ByteBufferDataSource implements DataSource {
1080 /**
1081 * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
1082 * The buffer's position is 0 and limit is equal to capacity.
1083 */
1084 private final ByteBuffer mBuf;
1085
1086 public ByteBufferDataSource(ByteBuffer buf) {
1087 // Defensive copy, to avoid changes to mBuf being visible in buf.
1088 mBuf = buf.slice();
1089 }
1090
1091 @Override
1092 public long size() {
1093 return mBuf.capacity();
1094 }
1095
1096 @Override
1097 public void feedIntoMessageDigests(
1098 MessageDigest[] mds, long offset, int size) throws IOException {
1099 // There's no way to tell MessageDigest to read data from ByteBuffer from a position
1100 // other than the buffer's current position. We thus need to change the buffer's
1101 // position to match the requested offset.
1102 //
1103 // In the future, it may be necessary to compute digests of multiple regions in
1104 // parallel. Given that digest computation is a slow operation, we enable multiple
1105 // such requests to be fulfilled by this instance. This is achieved by serially
1106 // creating a new ByteBuffer corresponding to the requested data range and then,
1107 // potentially concurrently, feeding these buffers into MessageDigest instances.
1108 ByteBuffer region;
1109 synchronized (mBuf) {
1110 mBuf.position((int) offset);
1111 mBuf.limit((int) offset + size);
1112 region = mBuf.slice();
1113 }
1114
1115 for (MessageDigest md : mds) {
1116 // Need to reset position to 0 at the start of each iteration because
1117 // MessageDigest.update below sets it to the buffer's limit.
1118 region.position(0);
1119 md.update(region);
1120 }
1121 }
1122 }
1123
1124 /**
Alex Klyubine4157182016-01-05 13:27:05 -08001125 * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
1126 * of letting the underlying implementation have a shot at re-encoding the data.
1127 */
1128 private static class VerbatimX509Certificate extends WrappedX509Certificate {
1129 private byte[] encodedVerbatim;
1130
1131 public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
1132 super(wrapped);
1133 this.encodedVerbatim = encodedVerbatim;
1134 }
1135
1136 @Override
1137 public byte[] getEncoded() throws CertificateEncodingException {
1138 return encodedVerbatim;
1139 }
1140 }
1141
1142 private static class WrappedX509Certificate extends X509Certificate {
1143 private final X509Certificate wrapped;
1144
1145 public WrappedX509Certificate(X509Certificate wrapped) {
1146 this.wrapped = wrapped;
1147 }
1148
1149 @Override
1150 public Set<String> getCriticalExtensionOIDs() {
1151 return wrapped.getCriticalExtensionOIDs();
1152 }
1153
1154 @Override
1155 public byte[] getExtensionValue(String oid) {
1156 return wrapped.getExtensionValue(oid);
1157 }
1158
1159 @Override
1160 public Set<String> getNonCriticalExtensionOIDs() {
1161 return wrapped.getNonCriticalExtensionOIDs();
1162 }
1163
1164 @Override
1165 public boolean hasUnsupportedCriticalExtension() {
1166 return wrapped.hasUnsupportedCriticalExtension();
1167 }
1168
1169 @Override
1170 public void checkValidity()
1171 throws CertificateExpiredException, CertificateNotYetValidException {
1172 wrapped.checkValidity();
1173 }
1174
1175 @Override
1176 public void checkValidity(Date date)
1177 throws CertificateExpiredException, CertificateNotYetValidException {
1178 wrapped.checkValidity(date);
1179 }
1180
1181 @Override
1182 public int getVersion() {
1183 return wrapped.getVersion();
1184 }
1185
1186 @Override
1187 public BigInteger getSerialNumber() {
1188 return wrapped.getSerialNumber();
1189 }
1190
1191 @Override
1192 public Principal getIssuerDN() {
1193 return wrapped.getIssuerDN();
1194 }
1195
1196 @Override
1197 public Principal getSubjectDN() {
1198 return wrapped.getSubjectDN();
1199 }
1200
1201 @Override
1202 public Date getNotBefore() {
1203 return wrapped.getNotBefore();
1204 }
1205
1206 @Override
1207 public Date getNotAfter() {
1208 return wrapped.getNotAfter();
1209 }
1210
1211 @Override
1212 public byte[] getTBSCertificate() throws CertificateEncodingException {
1213 return wrapped.getTBSCertificate();
1214 }
1215
1216 @Override
1217 public byte[] getSignature() {
1218 return wrapped.getSignature();
1219 }
1220
1221 @Override
1222 public String getSigAlgName() {
1223 return wrapped.getSigAlgName();
1224 }
1225
1226 @Override
1227 public String getSigAlgOID() {
1228 return wrapped.getSigAlgOID();
1229 }
1230
1231 @Override
1232 public byte[] getSigAlgParams() {
1233 return wrapped.getSigAlgParams();
1234 }
1235
1236 @Override
1237 public boolean[] getIssuerUniqueID() {
1238 return wrapped.getIssuerUniqueID();
1239 }
1240
1241 @Override
1242 public boolean[] getSubjectUniqueID() {
1243 return wrapped.getSubjectUniqueID();
1244 }
1245
1246 @Override
1247 public boolean[] getKeyUsage() {
1248 return wrapped.getKeyUsage();
1249 }
1250
1251 @Override
1252 public int getBasicConstraints() {
1253 return wrapped.getBasicConstraints();
1254 }
1255
1256 @Override
1257 public byte[] getEncoded() throws CertificateEncodingException {
1258 return wrapped.getEncoded();
1259 }
1260
1261 @Override
1262 public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
1263 InvalidKeyException, NoSuchProviderException, SignatureException {
1264 wrapped.verify(key);
1265 }
1266
1267 @Override
1268 public void verify(PublicKey key, String sigProvider)
1269 throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
1270 NoSuchProviderException, SignatureException {
1271 wrapped.verify(key, sigProvider);
1272 }
1273
1274 @Override
1275 public String toString() {
1276 return wrapped.toString();
1277 }
1278
1279 @Override
1280 public PublicKey getPublicKey() {
1281 return wrapped.getPublicKey();
1282 }
1283 }
1284}