1 /* 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 17 package android.util.apk; 18 19 import android.system.ErrnoException; 20 import android.system.OsConstants; 21 import android.util.ArrayMap; 22 import android.util.Pair; 23 24 import java.io.ByteArrayInputStream; 25 import java.io.FileDescriptor; 26 import java.io.IOException; 27 import java.io.RandomAccessFile; 28 import java.math.BigInteger; 29 import java.nio.BufferUnderflowException; 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 import java.nio.DirectByteBuffer; 33 import java.security.DigestException; 34 import java.security.InvalidAlgorithmParameterException; 35 import java.security.InvalidKeyException; 36 import java.security.KeyFactory; 37 import java.security.MessageDigest; 38 import java.security.NoSuchAlgorithmException; 39 import java.security.NoSuchProviderException; 40 import java.security.Principal; 41 import java.security.PublicKey; 42 import java.security.Signature; 43 import java.security.SignatureException; 44 import java.security.cert.CertificateEncodingException; 45 import java.security.cert.CertificateException; 46 import java.security.cert.CertificateExpiredException; 47 import java.security.cert.CertificateFactory; 48 import java.security.cert.CertificateNotYetValidException; 49 import java.security.cert.X509Certificate; 50 import java.security.spec.AlgorithmParameterSpec; 51 import java.security.spec.InvalidKeySpecException; 52 import java.security.spec.MGF1ParameterSpec; 53 import java.security.spec.PSSParameterSpec; 54 import java.security.spec.X509EncodedKeySpec; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Date; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 62 import libcore.io.Libcore; 63 import libcore.io.Os; 64 65 /** 66 * APK Signature Scheme v2 verifier. 67 * 68 * @hide for internal use only. 69 */ 70 public 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"; 80 public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2; 81 82 /** 83 * 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> 86 */ hasSignature(String apkFile)87 public static boolean hasSignature(String apkFile) throws IOException { 88 try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { 89 findSignature(apk); 90 return true; 91 } catch (SignatureNotFoundException e) { 92 return false; 93 } 94 } 95 96 /** 97 * 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 */ verify(String apkFile)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. 116 * @throws SecurityException if an APK Signature Scheme v2 signature of this APK does not 117 * verify. 118 * @throws IOException if an I/O error occurs while reading the APK file. 119 */ verify(RandomAccessFile apk)120 private static X509Certificate[][] verify(RandomAccessFile apk) 121 throws SignatureNotFoundException, SecurityException, IOException { 122 SignatureInfo signatureInfo = findSignature(apk); 123 return verify(apk.getFD(), signatureInfo); 124 } 125 126 /** 127 * 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 SignatureInfo( ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset, long eocdOffset, ByteBuffer eocd)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. 163 * 164 * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2. 165 * @throws IOException if an I/O error occurs while reading the APK file. 166 */ findSignature(RandomAccessFile apk)167 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 } 176 177 // 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; 183 184 // Find the APK Signature Scheme v2 Block inside the APK Signing Block. 185 ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock); 186 187 return new SignatureInfo( 188 apkSignatureSchemeV2Block, 189 apkSigningBlockOffset, 190 centralDirOffset, 191 eocdOffset, 192 eocd); 193 } 194 195 /** 196 * 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. 201 */ verify( FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo)202 private static X509Certificate[][] verify( 203 FileDescriptor apkFileDescriptor, 204 SignatureInfo signatureInfo) throws SecurityException { 205 int signerCount = 0; 206 Map<Integer, byte[]> contentDigests = new ArrayMap<>(); 207 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 { 216 signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); 217 } 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, 243 apkFileDescriptor, 244 signatureInfo.apkSigningBlockOffset, 245 signatureInfo.centralDirOffset, 246 signatureInfo.eocdOffset, 247 signatureInfo.eocd); 248 249 return signerCerts.toArray(new X509Certificate[signerCerts.size()][]); 250 } 251 verifySigner( ByteBuffer signerBlock, Map<Integer, byte[]> contentDigests, CertificateFactory certFactory)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 verifyIntegrity( Map<Integer, byte[]> expectedDigests, FileDescriptor apkFileDescriptor, long apkSigningBlockOffset, long centralDirOffset, long eocdOffset, ByteBuffer eocdBuf)388 private static void verifyIntegrity( 389 Map<Integer, byte[]> expectedDigests, 390 FileDescriptor apkFileDescriptor, 391 long apkSigningBlockOffset, 392 long centralDirOffset, 393 long eocdOffset, 394 ByteBuffer eocdBuf) throws SecurityException { 395 396 if (expectedDigests.isEmpty()) { 397 throw new SecurityException("No digests provided"); 398 } 399 400 // 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 416 // 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. 418 eocdBuf = eocdBuf.duplicate(); 419 eocdBuf.order(ByteOrder.LITTLE_ENDIAN); 420 ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, apkSigningBlockOffset); 421 DataSource eocd = new ByteBufferDataSource(eocdBuf); 422 423 int[] digestAlgorithms = new int[expectedDigests.size()]; 424 int digestAlgorithmCount = 0; 425 for (int digestAlgorithm : expectedDigests.keySet()) { 426 digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; 427 digestAlgorithmCount++; 428 } 429 byte[][] actualDigests; 430 try { 431 actualDigests = 432 computeContentDigests( 433 digestAlgorithms, 434 new DataSource[] {beforeApkSigningBlock, centralDir, eocd}); 435 } catch (DigestException e) { 436 throw new SecurityException("Failed to compute digest(s) of contents", e); 437 } 438 for (int i = 0; i < digestAlgorithms.length; i++) { 439 int digestAlgorithm = digestAlgorithms[i]; 440 byte[] expectedDigest = expectedDigests.get(digestAlgorithm); 441 byte[] actualDigest = actualDigests[i]; 442 if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { 443 throw new SecurityException( 444 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) 445 + " digest of contents did not verify"); 446 } 447 } 448 } 449 computeContentDigests( int[] digestAlgorithms, DataSource[] contents)450 private static byte[][] computeContentDigests( 451 int[] digestAlgorithms, 452 DataSource[] contents) throws DigestException { 453 // 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 463 long totalChunkCountLong = 0; 464 for (DataSource input : contents) { 465 totalChunkCountLong += getChunkCount(input.size()); 466 } 467 if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { 468 throw new DigestException("Too many chunks: " + totalChunkCountLong); 469 } 470 int totalChunkCount = (int) totalChunkCountLong; 471 472 byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; 473 for (int i = 0; i < digestAlgorithms.length; i++) { 474 int digestAlgorithm = digestAlgorithms[i]; 475 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); 483 digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; 484 } 485 486 byte[] chunkContentPrefix = new byte[5]; 487 chunkContentPrefix[0] = (byte) 0xa5; 488 int chunkIndex = 0; 489 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]; 523 int expectedDigestSizeBytes = 524 getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); 525 MessageDigest md = mds[i]; 526 int actualDigestSizeBytes = 527 md.digest( 528 concatenationOfChunkCountAndChunkDigests, 529 5 + chunkIndex * expectedDigestSizeBytes, 530 expectedDigestSizeBytes); 531 if (actualDigestSizeBytes != expectedDigestSizeBytes) { 532 throw new RuntimeException( 533 "Unexpected output size of " + md.getAlgorithm() + " digest: " 534 + actualDigestSizeBytes); 535 } 536 } 537 inputOffset += chunkSize; 538 inputRemaining -= chunkSize; 539 chunkIndex++; 540 } 541 dataSourceIndex++; 542 } 543 544 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]; 548 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); 556 result[i] = output; 557 } 558 return result; 559 } 560 561 /** 562 * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. 563 * 564 * @throws IOException if an I/O error occurs while reading the file. 565 * @throws SignatureNotFoundException if the EoCD could not be found. 566 */ getEocd(RandomAccessFile apk)567 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) { 572 throw new SignatureNotFoundException( 573 "Not an APK file: ZIP End of Central Directory record not found"); 574 } 575 return eocdAndOffsetInFile; 576 } 577 getCentralDirOffset(ByteBuffer eocd, long eocdOffset)578 private static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) 579 throws SignatureNotFoundException { 580 // Look up the offset of ZIP Central Directory. 581 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); 582 if (centralDirOffset > eocdOffset) { 583 throw new SignatureNotFoundException( 584 "ZIP Central Directory offset out of range: " + centralDirOffset 585 + ". ZIP End of Central Directory offset: " + eocdOffset); 586 } 587 long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); 588 if (centralDirOffset + centralDirSize != eocdOffset) { 589 throw new SignatureNotFoundException( 590 "ZIP Central Directory is not immediately followed by End of Central" 591 + " Directory"); 592 } 593 return centralDirOffset; 594 } 595 getChunkCount(long inputSizeBytes)596 private static final long getChunkCount(long inputSizeBytes) { 597 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; 609 610 private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; 611 private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; 612 isSupportedSignatureAlgorithm(int sigAlgorithm)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: 622 return true; 623 default: 624 return false; 625 } 626 } 627 compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2)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 compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2)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 getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm)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: 671 return CONTENT_DIGEST_CHUNKED_SHA512; 672 default: 673 throw new IllegalArgumentException( 674 "Unknown signature algorithm: 0x" 675 + Long.toHexString(sigAlgorithm & 0xffffffff)); 676 } 677 } 678 getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm)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 getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm)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 getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm)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: 714 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> getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm)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); 745 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 */ sliceFromTo(ByteBuffer source, int start, int end)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 */ getByteBuffer(ByteBuffer source, int size)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 getLengthPrefixedSlice(ByteBuffer source)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 readLengthPrefixedByteArray(ByteBuffer buf)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 setUnsignedInt32LittleEndian(int value, byte[] result, int offset)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 findApkSigningBlock( RandomAccessFile apk, long centralDirOffset)858 private static Pair<ByteBuffer, Long> findApkSigningBlock( 859 RandomAccessFile apk, long centralDirOffset) 860 throws IOException, SignatureNotFoundException { 861 // 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 } 873 // 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)) { 882 throw new SignatureNotFoundException( 883 "No APK Signing Block before ZIP Central Directory"); 884 } 885 // Read and compare size fields 886 long apkSigBlockSizeInFooter = footer.getLong(0); 887 if ((apkSigBlockSizeInFooter < footer.capacity()) 888 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { 889 throw new SignatureNotFoundException( 890 "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); 891 } 892 int totalSize = (int) (apkSigBlockSizeInFooter + 8); 893 long apkSigBlockOffset = centralDirOffset - totalSize; 894 if (apkSigBlockOffset < 0) { 895 throw new SignatureNotFoundException( 896 "APK Signing Block offset out of range: " + apkSigBlockOffset); 897 } 898 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) { 904 throw new SignatureNotFoundException( 905 "APK Signing Block sizes in header and footer do not match: " 906 + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); 907 } 908 return Pair.create(apkSigBlock, apkSigBlockOffset); 909 } 910 findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)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 checkByteOrderLittleEndian(ByteBuffer buffer)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 { 960 private static final long serialVersionUID = 1L; 961 SignatureNotFoundException(String message)962 public SignatureNotFoundException(String message) { 963 super(message); 964 } 965 SignatureNotFoundException(String message, Throwable cause)966 public SignatureNotFoundException(String message, Throwable cause) { 967 super(message, cause); 968 } 969 } 970 971 /** 972 * 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 */ size()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 */ feedIntoMessageDigests(MessageDigest[] mds, long offset, int size)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 */ MemoryMappedFileDataSource(FileDescriptor fd, long position, long size)1010 public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) { 1011 mFd = fd; 1012 mFilePosition = position; 1013 mSize = size; 1014 } 1015 1016 @Override size()1017 public long size() { 1018 return mSize; 1019 } 1020 1021 @Override feedIntoMessageDigests( MessageDigest[] mds, long offset, int size)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 ByteBufferDataSource(ByteBuffer buf)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 size()1092 public long size() { 1093 return mBuf.capacity(); 1094 } 1095 1096 @Override feedIntoMessageDigests( MessageDigest[] mds, long offset, int size)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 /** 1125 * 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 VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim)1131 public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { 1132 super(wrapped); 1133 this.encodedVerbatim = encodedVerbatim; 1134 } 1135 1136 @Override getEncoded()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 WrappedX509Certificate(X509Certificate wrapped)1145 public WrappedX509Certificate(X509Certificate wrapped) { 1146 this.wrapped = wrapped; 1147 } 1148 1149 @Override getCriticalExtensionOIDs()1150 public Set<String> getCriticalExtensionOIDs() { 1151 return wrapped.getCriticalExtensionOIDs(); 1152 } 1153 1154 @Override getExtensionValue(String oid)1155 public byte[] getExtensionValue(String oid) { 1156 return wrapped.getExtensionValue(oid); 1157 } 1158 1159 @Override getNonCriticalExtensionOIDs()1160 public Set<String> getNonCriticalExtensionOIDs() { 1161 return wrapped.getNonCriticalExtensionOIDs(); 1162 } 1163 1164 @Override hasUnsupportedCriticalExtension()1165 public boolean hasUnsupportedCriticalExtension() { 1166 return wrapped.hasUnsupportedCriticalExtension(); 1167 } 1168 1169 @Override checkValidity()1170 public void checkValidity() 1171 throws CertificateExpiredException, CertificateNotYetValidException { 1172 wrapped.checkValidity(); 1173 } 1174 1175 @Override checkValidity(Date date)1176 public void checkValidity(Date date) 1177 throws CertificateExpiredException, CertificateNotYetValidException { 1178 wrapped.checkValidity(date); 1179 } 1180 1181 @Override getVersion()1182 public int getVersion() { 1183 return wrapped.getVersion(); 1184 } 1185 1186 @Override getSerialNumber()1187 public BigInteger getSerialNumber() { 1188 return wrapped.getSerialNumber(); 1189 } 1190 1191 @Override getIssuerDN()1192 public Principal getIssuerDN() { 1193 return wrapped.getIssuerDN(); 1194 } 1195 1196 @Override getSubjectDN()1197 public Principal getSubjectDN() { 1198 return wrapped.getSubjectDN(); 1199 } 1200 1201 @Override getNotBefore()1202 public Date getNotBefore() { 1203 return wrapped.getNotBefore(); 1204 } 1205 1206 @Override getNotAfter()1207 public Date getNotAfter() { 1208 return wrapped.getNotAfter(); 1209 } 1210 1211 @Override getTBSCertificate()1212 public byte[] getTBSCertificate() throws CertificateEncodingException { 1213 return wrapped.getTBSCertificate(); 1214 } 1215 1216 @Override getSignature()1217 public byte[] getSignature() { 1218 return wrapped.getSignature(); 1219 } 1220 1221 @Override getSigAlgName()1222 public String getSigAlgName() { 1223 return wrapped.getSigAlgName(); 1224 } 1225 1226 @Override getSigAlgOID()1227 public String getSigAlgOID() { 1228 return wrapped.getSigAlgOID(); 1229 } 1230 1231 @Override getSigAlgParams()1232 public byte[] getSigAlgParams() { 1233 return wrapped.getSigAlgParams(); 1234 } 1235 1236 @Override getIssuerUniqueID()1237 public boolean[] getIssuerUniqueID() { 1238 return wrapped.getIssuerUniqueID(); 1239 } 1240 1241 @Override getSubjectUniqueID()1242 public boolean[] getSubjectUniqueID() { 1243 return wrapped.getSubjectUniqueID(); 1244 } 1245 1246 @Override getKeyUsage()1247 public boolean[] getKeyUsage() { 1248 return wrapped.getKeyUsage(); 1249 } 1250 1251 @Override getBasicConstraints()1252 public int getBasicConstraints() { 1253 return wrapped.getBasicConstraints(); 1254 } 1255 1256 @Override getEncoded()1257 public byte[] getEncoded() throws CertificateEncodingException { 1258 return wrapped.getEncoded(); 1259 } 1260 1261 @Override verify(PublicKey key)1262 public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, 1263 InvalidKeyException, NoSuchProviderException, SignatureException { 1264 wrapped.verify(key); 1265 } 1266 1267 @Override verify(PublicKey key, String sigProvider)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 toString()1275 public String toString() { 1276 return wrapped.toString(); 1277 } 1278 1279 @Override getPublicKey()1280 public PublicKey getPublicKey() { 1281 return wrapped.getPublicKey(); 1282 } 1283 } 1284 } 1285