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