1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.util.apk; 18 19 import android.util.ArrayMap; 20 import android.util.Pair; 21 22 import java.io.ByteArrayInputStream; 23 import java.io.FileDescriptor; 24 import java.io.IOException; 25 import java.io.RandomAccessFile; 26 import java.nio.BufferUnderflowException; 27 import java.nio.ByteBuffer; 28 import java.nio.ByteOrder; 29 import java.security.DigestException; 30 import java.security.InvalidAlgorithmParameterException; 31 import java.security.InvalidKeyException; 32 import java.security.MessageDigest; 33 import java.security.NoSuchAlgorithmException; 34 import java.security.PublicKey; 35 import java.security.Signature; 36 import java.security.SignatureException; 37 import java.security.cert.CertificateException; 38 import java.security.cert.CertificateFactory; 39 import java.security.cert.X509Certificate; 40 import java.security.spec.AlgorithmParameterSpec; 41 import java.security.spec.MGF1ParameterSpec; 42 import java.security.spec.PSSParameterSpec; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 49 /** 50 * Utility class for an APK Signature Scheme using the APK Signing Block. 51 * 52 * @hide for internal use only. 53 */ 54 public final class ApkSigningBlockUtils { 55 ApkSigningBlockUtils()56 private ApkSigningBlockUtils() { 57 } 58 59 /** 60 * Returns the APK Signature Scheme block contained in the provided APK file and the 61 * additional information relevant for verifying the block against the file. 62 * 63 * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs 64 * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 65 * block ID. 66 * @throws SignatureNotFoundException if the APK is not signed using this scheme. 67 * @throws IOException if an I/O error occurs while reading the APK file. 68 */ findSignature(RandomAccessFile apk, int blockId)69 static SignatureInfo findSignature(RandomAccessFile apk, int blockId) 70 throws IOException, SignatureNotFoundException { 71 // Find the ZIP End of Central Directory (EoCD) record. 72 Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk); 73 ByteBuffer eocd = eocdAndOffsetInFile.first; 74 long eocdOffset = eocdAndOffsetInFile.second; 75 if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) { 76 throw new SignatureNotFoundException("ZIP64 APK not supported"); 77 } 78 79 // Find the APK Signing Block. The block immediately precedes the Central Directory. 80 long centralDirOffset = getCentralDirOffset(eocd, eocdOffset); 81 Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile = 82 findApkSigningBlock(apk, centralDirOffset); 83 ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first; 84 long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second; 85 86 // Find the APK Signature Scheme Block inside the APK Signing Block. 87 ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock, 88 blockId); 89 90 return new SignatureInfo( 91 apkSignatureSchemeBlock, 92 apkSigningBlockOffset, 93 centralDirOffset, 94 eocdOffset, 95 eocd); 96 } 97 verifyIntegrity( Map<Integer, byte[]> expectedDigests, RandomAccessFile apk, SignatureInfo signatureInfo)98 static void verifyIntegrity( 99 Map<Integer, byte[]> expectedDigests, 100 RandomAccessFile apk, 101 SignatureInfo signatureInfo) throws SecurityException { 102 if (expectedDigests.isEmpty()) { 103 throw new SecurityException("No digests provided"); 104 } 105 106 boolean neverVerified = true; 107 108 Map<Integer, byte[]> expected1MbChunkDigests = new ArrayMap<>(); 109 if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA256)) { 110 expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA256, 111 expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA256)); 112 } 113 if (expectedDigests.containsKey(CONTENT_DIGEST_CHUNKED_SHA512)) { 114 expected1MbChunkDigests.put(CONTENT_DIGEST_CHUNKED_SHA512, 115 expectedDigests.get(CONTENT_DIGEST_CHUNKED_SHA512)); 116 } 117 if (!expected1MbChunkDigests.isEmpty()) { 118 try { 119 verifyIntegrityFor1MbChunkBasedAlgorithm(expected1MbChunkDigests, apk.getFD(), 120 signatureInfo); 121 neverVerified = false; 122 } catch (IOException e) { 123 throw new SecurityException("Cannot get FD", e); 124 } 125 } 126 127 if (expectedDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) { 128 verifyIntegrityForVerityBasedAlgorithm( 129 expectedDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256), apk, signatureInfo); 130 neverVerified = false; 131 } 132 133 if (neverVerified) { 134 throw new SecurityException("No known digest exists for integrity check"); 135 } 136 } 137 isSupportedSignatureAlgorithm(int sigAlgorithm)138 static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) { 139 switch (sigAlgorithm) { 140 case SIGNATURE_RSA_PSS_WITH_SHA256: 141 case SIGNATURE_RSA_PSS_WITH_SHA512: 142 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 143 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 144 case SIGNATURE_ECDSA_WITH_SHA256: 145 case SIGNATURE_ECDSA_WITH_SHA512: 146 case SIGNATURE_DSA_WITH_SHA256: 147 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 148 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 149 case SIGNATURE_VERITY_DSA_WITH_SHA256: 150 return true; 151 default: 152 return false; 153 } 154 } 155 verifyIntegrityFor1MbChunkBasedAlgorithm( Map<Integer, byte[]> expectedDigests, FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo)156 private static void verifyIntegrityFor1MbChunkBasedAlgorithm( 157 Map<Integer, byte[]> expectedDigests, 158 FileDescriptor apkFileDescriptor, 159 SignatureInfo signatureInfo) throws SecurityException { 160 int[] digestAlgorithms = new int[expectedDigests.size()]; 161 int digestAlgorithmCount = 0; 162 for (int digestAlgorithm : expectedDigests.keySet()) { 163 digestAlgorithms[digestAlgorithmCount] = digestAlgorithm; 164 digestAlgorithmCount++; 165 } 166 byte[][] actualDigests; 167 try { 168 actualDigests = computeContentDigestsPer1MbChunk(digestAlgorithms, apkFileDescriptor, 169 signatureInfo); 170 } catch (DigestException e) { 171 throw new SecurityException("Failed to compute digest(s) of contents", e); 172 } 173 for (int i = 0; i < digestAlgorithms.length; i++) { 174 int digestAlgorithm = digestAlgorithms[i]; 175 byte[] expectedDigest = expectedDigests.get(digestAlgorithm); 176 byte[] actualDigest = actualDigests[i]; 177 if (!MessageDigest.isEqual(expectedDigest, actualDigest)) { 178 throw new SecurityException( 179 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm) 180 + " digest of contents did not verify"); 181 } 182 } 183 } 184 185 /** 186 * Calculate digests using digestAlgorithms for apkFileDescriptor. 187 * This will skip signature block described by signatureInfo. 188 */ computeContentDigestsPer1MbChunk(int[] digestAlgorithms, FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo)189 public static byte[][] computeContentDigestsPer1MbChunk(int[] digestAlgorithms, 190 FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo) throws DigestException { 191 // We need to verify the integrity of the following three sections of the file: 192 // 1. Everything up to the start of the APK Signing Block. 193 // 2. ZIP Central Directory. 194 // 3. ZIP End of Central Directory (EoCD). 195 // Each of these sections is represented as a separate DataSource instance below. 196 197 // To handle large APKs, these sections are read in 1 MB chunks using memory-mapped I/O to 198 // avoid wasting physical memory. In most APK verification scenarios, the contents of the 199 // APK are already there in the OS's page cache and thus mmap does not use additional 200 // physical memory. 201 202 DataSource beforeApkSigningBlock = 203 DataSource.create(apkFileDescriptor, 0, signatureInfo.apkSigningBlockOffset); 204 DataSource centralDir = 205 DataSource.create( 206 apkFileDescriptor, signatureInfo.centralDirOffset, 207 signatureInfo.eocdOffset - signatureInfo.centralDirOffset); 208 209 // For the purposes of integrity verification, ZIP End of Central Directory's field Start of 210 // Central Directory must be considered to point to the offset of the APK Signing Block. 211 ByteBuffer eocdBuf = signatureInfo.eocd.duplicate(); 212 eocdBuf.order(ByteOrder.LITTLE_ENDIAN); 213 ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset); 214 DataSource eocd = new ByteBufferDataSource(eocdBuf); 215 216 return computeContentDigestsPer1MbChunk(digestAlgorithms, 217 new DataSource[]{beforeApkSigningBlock, centralDir, eocd}); 218 } 219 computeContentDigestsPer1MbChunk( int[] digestAlgorithms, DataSource[] contents)220 private static byte[][] computeContentDigestsPer1MbChunk( 221 int[] digestAlgorithms, 222 DataSource[] contents) throws DigestException { 223 // For each digest algorithm the result is computed as follows: 224 // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. 225 // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. 226 // No chunks are produced for empty (zero length) segments. 227 // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's 228 // length in bytes (uint32 little-endian) and the chunk's contents. 229 // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of 230 // chunks (uint32 little-endian) and the concatenation of digests of chunks of all 231 // segments in-order. 232 233 long totalChunkCountLong = 0; 234 for (DataSource input : contents) { 235 totalChunkCountLong += getChunkCount(input.size()); 236 } 237 if (totalChunkCountLong >= Integer.MAX_VALUE / 1024) { 238 throw new DigestException("Too many chunks: " + totalChunkCountLong); 239 } 240 int totalChunkCount = (int) totalChunkCountLong; 241 242 byte[][] digestsOfChunks = new byte[digestAlgorithms.length][]; 243 for (int i = 0; i < digestAlgorithms.length; i++) { 244 int digestAlgorithm = digestAlgorithms[i]; 245 int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); 246 byte[] concatenationOfChunkCountAndChunkDigests = 247 new byte[5 + totalChunkCount * digestOutputSizeBytes]; 248 concatenationOfChunkCountAndChunkDigests[0] = 0x5a; 249 setUnsignedInt32LittleEndian( 250 totalChunkCount, 251 concatenationOfChunkCountAndChunkDigests, 252 1); 253 digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; 254 } 255 256 byte[] chunkContentPrefix = new byte[5]; 257 chunkContentPrefix[0] = (byte) 0xa5; 258 int chunkIndex = 0; 259 MessageDigest[] mds = new MessageDigest[digestAlgorithms.length]; 260 for (int i = 0; i < digestAlgorithms.length; i++) { 261 String jcaAlgorithmName = 262 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithms[i]); 263 try { 264 mds[i] = MessageDigest.getInstance(jcaAlgorithmName); 265 } catch (NoSuchAlgorithmException e) { 266 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); 267 } 268 } 269 // TODO: Compute digests of chunks in parallel when beneficial. This requires some research 270 // into how to parallelize (if at all) based on the capabilities of the hardware on which 271 // this code is running and based on the size of input. 272 DataDigester digester = new MultipleDigestDataDigester(mds); 273 int dataSourceIndex = 0; 274 for (DataSource input : contents) { 275 long inputOffset = 0; 276 long inputRemaining = input.size(); 277 while (inputRemaining > 0) { 278 int chunkSize = (int) Math.min(inputRemaining, CHUNK_SIZE_BYTES); 279 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); 280 for (int i = 0; i < mds.length; i++) { 281 mds[i].update(chunkContentPrefix); 282 } 283 try { 284 input.feedIntoDataDigester(digester, inputOffset, chunkSize); 285 } catch (IOException e) { 286 throw new DigestException( 287 "Failed to digest chunk #" + chunkIndex + " of section #" 288 + dataSourceIndex, 289 e); 290 } 291 for (int i = 0; i < digestAlgorithms.length; i++) { 292 int digestAlgorithm = digestAlgorithms[i]; 293 byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; 294 int expectedDigestSizeBytes = 295 getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); 296 MessageDigest md = mds[i]; 297 int actualDigestSizeBytes = 298 md.digest( 299 concatenationOfChunkCountAndChunkDigests, 300 5 + chunkIndex * expectedDigestSizeBytes, 301 expectedDigestSizeBytes); 302 if (actualDigestSizeBytes != expectedDigestSizeBytes) { 303 throw new RuntimeException( 304 "Unexpected output size of " + md.getAlgorithm() + " digest: " 305 + actualDigestSizeBytes); 306 } 307 } 308 inputOffset += chunkSize; 309 inputRemaining -= chunkSize; 310 chunkIndex++; 311 } 312 dataSourceIndex++; 313 } 314 315 byte[][] result = new byte[digestAlgorithms.length][]; 316 for (int i = 0; i < digestAlgorithms.length; i++) { 317 int digestAlgorithm = digestAlgorithms[i]; 318 byte[] input = digestsOfChunks[i]; 319 String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); 320 MessageDigest md; 321 try { 322 md = MessageDigest.getInstance(jcaAlgorithmName); 323 } catch (NoSuchAlgorithmException e) { 324 throw new RuntimeException(jcaAlgorithmName + " digest not supported", e); 325 } 326 byte[] output = md.digest(input); 327 result[i] = output; 328 } 329 return result; 330 } 331 332 /** 333 * Return the verity digest only if the length of digest content looks correct. 334 * When verity digest is generated, the last incomplete 4k chunk is padded with 0s before 335 * hashing. This means two almost identical APKs with different number of 0 at the end will have 336 * the same verity digest. To avoid this problem, the length of the source content (excluding 337 * Signing Block) is appended to the verity digest, and the digest is returned only if the 338 * length is consistent to the current APK. 339 */ parseVerityDigestAndVerifySourceLength( byte[] data, long fileSize, SignatureInfo signatureInfo)340 static byte[] parseVerityDigestAndVerifySourceLength( 341 byte[] data, long fileSize, SignatureInfo signatureInfo) throws SecurityException { 342 // FORMAT: 343 // OFFSET DATA TYPE DESCRIPTION 344 // * @+0 bytes uint8[32] Merkle tree root hash of SHA-256 345 // * @+32 bytes int64 Length of source data 346 int kRootHashSize = 32; 347 int kSourceLengthSize = 8; 348 349 if (data.length != kRootHashSize + kSourceLengthSize) { 350 throw new SecurityException("Verity digest size is wrong: " + data.length); 351 } 352 ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); 353 buffer.position(kRootHashSize); 354 long expectedSourceLength = buffer.getLong(); 355 356 long signingBlockSize = signatureInfo.centralDirOffset 357 - signatureInfo.apkSigningBlockOffset; 358 if (expectedSourceLength != fileSize - signingBlockSize) { 359 throw new SecurityException("APK content size did not verify"); 360 } 361 362 return Arrays.copyOfRange(data, 0, kRootHashSize); 363 } 364 verifyIntegrityForVerityBasedAlgorithm( byte[] expectedDigest, RandomAccessFile apk, SignatureInfo signatureInfo)365 private static void verifyIntegrityForVerityBasedAlgorithm( 366 byte[] expectedDigest, 367 RandomAccessFile apk, 368 SignatureInfo signatureInfo) throws SecurityException { 369 try { 370 byte[] expectedRootHash = parseVerityDigestAndVerifySourceLength(expectedDigest, 371 apk.length(), signatureInfo); 372 VerityBuilder.VerityResult verity = VerityBuilder.generateApkVerityTree(apk, 373 signatureInfo, new ByteBufferFactory() { 374 @Override 375 public ByteBuffer create(int capacity) { 376 return ByteBuffer.allocate(capacity); 377 } 378 }); 379 if (!Arrays.equals(expectedRootHash, verity.rootHash)) { 380 throw new SecurityException("APK verity digest of contents did not verify"); 381 } 382 } catch (DigestException | IOException | NoSuchAlgorithmException e) { 383 throw new SecurityException("Error during verification", e); 384 } 385 } 386 387 /** 388 * Returns the ZIP End of Central Directory (EoCD) and its offset in the file. 389 * 390 * @throws IOException if an I/O error occurs while reading the file. 391 * @throws SignatureNotFoundException if the EoCD could not be found. 392 */ getEocd(RandomAccessFile apk)393 static Pair<ByteBuffer, Long> getEocd(RandomAccessFile apk) 394 throws IOException, SignatureNotFoundException { 395 Pair<ByteBuffer, Long> eocdAndOffsetInFile = 396 ZipUtils.findZipEndOfCentralDirectoryRecord(apk); 397 if (eocdAndOffsetInFile == null) { 398 throw new SignatureNotFoundException( 399 "Not an APK file: ZIP End of Central Directory record not found"); 400 } 401 return eocdAndOffsetInFile; 402 } 403 getCentralDirOffset(ByteBuffer eocd, long eocdOffset)404 static long getCentralDirOffset(ByteBuffer eocd, long eocdOffset) 405 throws SignatureNotFoundException { 406 // Look up the offset of ZIP Central Directory. 407 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); 408 if (centralDirOffset > eocdOffset) { 409 throw new SignatureNotFoundException( 410 "ZIP Central Directory offset out of range: " + centralDirOffset 411 + ". ZIP End of Central Directory offset: " + eocdOffset); 412 } 413 long centralDirSize = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd); 414 if (centralDirOffset + centralDirSize != eocdOffset) { 415 throw new SignatureNotFoundException( 416 "ZIP Central Directory is not immediately followed by End of Central" 417 + " Directory"); 418 } 419 return centralDirOffset; 420 } 421 getChunkCount(long inputSizeBytes)422 private static long getChunkCount(long inputSizeBytes) { 423 return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES; 424 } 425 426 private static final int CHUNK_SIZE_BYTES = 1024 * 1024; 427 428 static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; 429 static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; 430 static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; 431 static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; 432 static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; 433 static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; 434 static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; 435 static final int SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0421; 436 static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423; 437 static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425; 438 439 public static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; 440 public static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; 441 public static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; 442 public static final int CONTENT_DIGEST_SHA256 = 4; 443 compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2)444 static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) { 445 int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1); 446 int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2); 447 return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2); 448 } 449 compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2)450 private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) { 451 switch (digestAlgorithm1) { 452 case CONTENT_DIGEST_CHUNKED_SHA256: 453 switch (digestAlgorithm2) { 454 case CONTENT_DIGEST_CHUNKED_SHA256: 455 return 0; 456 case CONTENT_DIGEST_CHUNKED_SHA512: 457 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 458 return -1; 459 default: 460 throw new IllegalArgumentException( 461 "Unknown digestAlgorithm2: " + digestAlgorithm2); 462 } 463 case CONTENT_DIGEST_CHUNKED_SHA512: 464 switch (digestAlgorithm2) { 465 case CONTENT_DIGEST_CHUNKED_SHA256: 466 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 467 return 1; 468 case CONTENT_DIGEST_CHUNKED_SHA512: 469 return 0; 470 default: 471 throw new IllegalArgumentException( 472 "Unknown digestAlgorithm2: " + digestAlgorithm2); 473 } 474 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 475 switch (digestAlgorithm2) { 476 case CONTENT_DIGEST_CHUNKED_SHA512: 477 return -1; 478 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 479 return 0; 480 case CONTENT_DIGEST_CHUNKED_SHA256: 481 return 1; 482 default: 483 throw new IllegalArgumentException( 484 "Unknown digestAlgorithm2: " + digestAlgorithm2); 485 } 486 default: 487 throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1); 488 } 489 } 490 getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm)491 static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { 492 switch (sigAlgorithm) { 493 case SIGNATURE_RSA_PSS_WITH_SHA256: 494 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 495 case SIGNATURE_ECDSA_WITH_SHA256: 496 case SIGNATURE_DSA_WITH_SHA256: 497 return CONTENT_DIGEST_CHUNKED_SHA256; 498 case SIGNATURE_RSA_PSS_WITH_SHA512: 499 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 500 case SIGNATURE_ECDSA_WITH_SHA512: 501 return CONTENT_DIGEST_CHUNKED_SHA512; 502 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 503 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 504 case SIGNATURE_VERITY_DSA_WITH_SHA256: 505 return CONTENT_DIGEST_VERITY_CHUNKED_SHA256; 506 default: 507 throw new IllegalArgumentException( 508 "Unknown signature algorithm: 0x" 509 + Long.toHexString(sigAlgorithm & 0xffffffff)); 510 } 511 } 512 getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm)513 static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { 514 switch (digestAlgorithm) { 515 case CONTENT_DIGEST_CHUNKED_SHA256: 516 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 517 return "SHA-256"; 518 case CONTENT_DIGEST_CHUNKED_SHA512: 519 return "SHA-512"; 520 default: 521 throw new IllegalArgumentException( 522 "Unknown content digest algorthm: " + digestAlgorithm); 523 } 524 } 525 getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm)526 private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { 527 switch (digestAlgorithm) { 528 case CONTENT_DIGEST_CHUNKED_SHA256: 529 case CONTENT_DIGEST_VERITY_CHUNKED_SHA256: 530 return 256 / 8; 531 case CONTENT_DIGEST_CHUNKED_SHA512: 532 return 512 / 8; 533 default: 534 throw new IllegalArgumentException( 535 "Unknown content digest algorthm: " + digestAlgorithm); 536 } 537 } 538 getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm)539 static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) { 540 switch (sigAlgorithm) { 541 case SIGNATURE_RSA_PSS_WITH_SHA256: 542 case SIGNATURE_RSA_PSS_WITH_SHA512: 543 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 544 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 545 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 546 return "RSA"; 547 case SIGNATURE_ECDSA_WITH_SHA256: 548 case SIGNATURE_ECDSA_WITH_SHA512: 549 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 550 return "EC"; 551 case SIGNATURE_DSA_WITH_SHA256: 552 case SIGNATURE_VERITY_DSA_WITH_SHA256: 553 return "DSA"; 554 default: 555 throw new IllegalArgumentException( 556 "Unknown signature algorithm: 0x" 557 + Long.toHexString(sigAlgorithm & 0xffffffff)); 558 } 559 } 560 561 static Pair<String, ? extends AlgorithmParameterSpec> getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm)562 getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { 563 switch (sigAlgorithm) { 564 case SIGNATURE_RSA_PSS_WITH_SHA256: 565 return Pair.create( 566 "SHA256withRSA/PSS", 567 new PSSParameterSpec( 568 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); 569 case SIGNATURE_RSA_PSS_WITH_SHA512: 570 return Pair.create( 571 "SHA512withRSA/PSS", 572 new PSSParameterSpec( 573 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); 574 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 575 case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256: 576 return Pair.create("SHA256withRSA", null); 577 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 578 return Pair.create("SHA512withRSA", null); 579 case SIGNATURE_ECDSA_WITH_SHA256: 580 case SIGNATURE_VERITY_ECDSA_WITH_SHA256: 581 return Pair.create("SHA256withECDSA", null); 582 case SIGNATURE_ECDSA_WITH_SHA512: 583 return Pair.create("SHA512withECDSA", null); 584 case SIGNATURE_DSA_WITH_SHA256: 585 case SIGNATURE_VERITY_DSA_WITH_SHA256: 586 return Pair.create("SHA256withDSA", null); 587 default: 588 throw new IllegalArgumentException( 589 "Unknown signature algorithm: 0x" 590 + Long.toHexString(sigAlgorithm & 0xffffffff)); 591 } 592 } 593 594 /** 595 * Returns new byte buffer whose content is a shared subsequence of this buffer's content 596 * between the specified start (inclusive) and end (exclusive) positions. As opposed to 597 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source 598 * buffer's byte order. 599 */ sliceFromTo(ByteBuffer source, int start, int end)600 static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { 601 if (start < 0) { 602 throw new IllegalArgumentException("start: " + start); 603 } 604 if (end < start) { 605 throw new IllegalArgumentException("end < start: " + end + " < " + start); 606 } 607 int capacity = source.capacity(); 608 if (end > source.capacity()) { 609 throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); 610 } 611 int originalLimit = source.limit(); 612 int originalPosition = source.position(); 613 try { 614 source.position(0); 615 source.limit(end); 616 source.position(start); 617 ByteBuffer result = source.slice(); 618 result.order(source.order()); 619 return result; 620 } finally { 621 source.position(0); 622 source.limit(originalLimit); 623 source.position(originalPosition); 624 } 625 } 626 627 /** 628 * Relative <em>get</em> method for reading {@code size} number of bytes from the current 629 * position of this buffer. 630 * 631 * <p>This method reads the next {@code size} bytes at this buffer's current position, 632 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to 633 * {@code size}, byte order set to this buffer's byte order; and then increments the position by 634 * {@code size}. 635 */ getByteBuffer(ByteBuffer source, int size)636 static ByteBuffer getByteBuffer(ByteBuffer source, int size) 637 throws BufferUnderflowException { 638 if (size < 0) { 639 throw new IllegalArgumentException("size: " + size); 640 } 641 int originalLimit = source.limit(); 642 int position = source.position(); 643 int limit = position + size; 644 if ((limit < position) || (limit > originalLimit)) { 645 throw new BufferUnderflowException(); 646 } 647 source.limit(limit); 648 try { 649 ByteBuffer result = source.slice(); 650 result.order(source.order()); 651 source.position(limit); 652 return result; 653 } finally { 654 source.limit(originalLimit); 655 } 656 } 657 getLengthPrefixedSlice(ByteBuffer source)658 static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException { 659 if (source.remaining() < 4) { 660 throw new IOException( 661 "Remaining buffer too short to contain length of length-prefixed field." 662 + " Remaining: " + source.remaining()); 663 } 664 int len = source.getInt(); 665 if (len < 0) { 666 throw new IllegalArgumentException("Negative length"); 667 } else if (len > source.remaining()) { 668 throw new IOException("Length-prefixed field longer than remaining buffer." 669 + " Field length: " + len + ", remaining: " + source.remaining()); 670 } 671 return getByteBuffer(source, len); 672 } 673 readLengthPrefixedByteArray(ByteBuffer buf)674 static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException { 675 int len = buf.getInt(); 676 if (len < 0) { 677 throw new IOException("Negative length"); 678 } else if (len > buf.remaining()) { 679 throw new IOException("Underflow while reading length-prefixed value. Length: " + len 680 + ", available: " + buf.remaining()); 681 } 682 byte[] result = new byte[len]; 683 buf.get(result); 684 return result; 685 } 686 setUnsignedInt32LittleEndian(int value, byte[] result, int offset)687 static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { 688 result[offset] = (byte) (value & 0xff); 689 result[offset + 1] = (byte) ((value >>> 8) & 0xff); 690 result[offset + 2] = (byte) ((value >>> 16) & 0xff); 691 result[offset + 3] = (byte) ((value >>> 24) & 0xff); 692 } 693 694 private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; 695 private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; 696 private static final int APK_SIG_BLOCK_MIN_SIZE = 32; 697 findApkSigningBlock( RandomAccessFile apk, long centralDirOffset)698 static Pair<ByteBuffer, Long> findApkSigningBlock( 699 RandomAccessFile apk, long centralDirOffset) 700 throws IOException, SignatureNotFoundException { 701 // FORMAT: 702 // OFFSET DATA TYPE DESCRIPTION 703 // * @+0 bytes uint64: size in bytes (excluding this field) 704 // * @+8 bytes payload 705 // * @-24 bytes uint64: size in bytes (same as the one above) 706 // * @-16 bytes uint128: magic 707 708 if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) { 709 throw new SignatureNotFoundException( 710 "APK too small for APK Signing Block. ZIP Central Directory offset: " 711 + centralDirOffset); 712 } 713 // Read the magic and offset in file from the footer section of the block: 714 // * uint64: size of block 715 // * 16 bytes: magic 716 ByteBuffer footer = ByteBuffer.allocate(24); 717 footer.order(ByteOrder.LITTLE_ENDIAN); 718 apk.seek(centralDirOffset - footer.capacity()); 719 apk.readFully(footer.array(), footer.arrayOffset(), footer.capacity()); 720 if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) 721 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { 722 throw new SignatureNotFoundException( 723 "No APK Signing Block before ZIP Central Directory"); 724 } 725 // Read and compare size fields 726 long apkSigBlockSizeInFooter = footer.getLong(0); 727 if ((apkSigBlockSizeInFooter < footer.capacity()) 728 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { 729 throw new SignatureNotFoundException( 730 "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); 731 } 732 int totalSize = (int) (apkSigBlockSizeInFooter + 8); 733 long apkSigBlockOffset = centralDirOffset - totalSize; 734 if (apkSigBlockOffset < 0) { 735 throw new SignatureNotFoundException( 736 "APK Signing Block offset out of range: " + apkSigBlockOffset); 737 } 738 ByteBuffer apkSigBlock = ByteBuffer.allocate(totalSize); 739 apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); 740 apk.seek(apkSigBlockOffset); 741 apk.readFully(apkSigBlock.array(), apkSigBlock.arrayOffset(), apkSigBlock.capacity()); 742 long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); 743 if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { 744 throw new SignatureNotFoundException( 745 "APK Signing Block sizes in header and footer do not match: " 746 + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); 747 } 748 return Pair.create(apkSigBlock, apkSigBlockOffset); 749 } 750 findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)751 static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId) 752 throws SignatureNotFoundException { 753 checkByteOrderLittleEndian(apkSigningBlock); 754 // FORMAT: 755 // OFFSET DATA TYPE DESCRIPTION 756 // * @+0 bytes uint64: size in bytes (excluding this field) 757 // * @+8 bytes pairs 758 // * @-24 bytes uint64: size in bytes (same as the one above) 759 // * @-16 bytes uint128: magic 760 ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); 761 762 int entryCount = 0; 763 while (pairs.hasRemaining()) { 764 entryCount++; 765 if (pairs.remaining() < 8) { 766 throw new SignatureNotFoundException( 767 "Insufficient data to read size of APK Signing Block entry #" + entryCount); 768 } 769 long lenLong = pairs.getLong(); 770 if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { 771 throw new SignatureNotFoundException( 772 "APK Signing Block entry #" + entryCount 773 + " size out of range: " + lenLong); 774 } 775 int len = (int) lenLong; 776 int nextEntryPos = pairs.position() + len; 777 if (len > pairs.remaining()) { 778 throw new SignatureNotFoundException( 779 "APK Signing Block entry #" + entryCount + " size out of range: " + len 780 + ", available: " + pairs.remaining()); 781 } 782 int id = pairs.getInt(); 783 if (id == blockId) { 784 return getByteBuffer(pairs, len - 4); 785 } 786 pairs.position(nextEntryPos); 787 } 788 789 throw new SignatureNotFoundException( 790 "No block with ID " + blockId + " in APK Signing Block."); 791 } 792 checkByteOrderLittleEndian(ByteBuffer buffer)793 private static void checkByteOrderLittleEndian(ByteBuffer buffer) { 794 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 795 throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 796 } 797 } 798 799 /** 800 * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is fed. 801 */ 802 private static class MultipleDigestDataDigester implements DataDigester { 803 private final MessageDigest[] mMds; 804 MultipleDigestDataDigester(MessageDigest[] mds)805 MultipleDigestDataDigester(MessageDigest[] mds) { 806 mMds = mds; 807 } 808 809 @Override consume(ByteBuffer buffer)810 public void consume(ByteBuffer buffer) { 811 buffer = buffer.slice(); 812 for (MessageDigest md : mMds) { 813 buffer.position(0); 814 md.update(buffer); 815 } 816 } 817 } 818 verifyProofOfRotationStruct( ByteBuffer porBuf, CertificateFactory certFactory)819 static VerifiedProofOfRotation verifyProofOfRotationStruct( 820 ByteBuffer porBuf, 821 CertificateFactory certFactory) 822 throws SecurityException, IOException { 823 int levelCount = 0; 824 int lastSigAlgorithm = -1; 825 X509Certificate lastCert = null; 826 List<X509Certificate> certs = new ArrayList<>(); 827 List<Integer> flagsList = new ArrayList<>(); 828 829 // Proof-of-rotation struct: 830 // A uint32 version code followed by basically a singly linked list of nodes, called levels 831 // here, each of which have the following structure: 832 // * length-prefix for the entire level 833 // - length-prefixed signed data (if previous level exists) 834 // * length-prefixed X509 Certificate 835 // * uint32 signature algorithm ID describing how this signed data was signed 836 // - uint32 flags describing how to treat the cert contained in this level 837 // - uint32 signature algorithm ID to use to verify the signature of the next level. The 838 // algorithm here must match the one in the signed data section of the next level. 839 // - length-prefixed signature over the signed data in this level. The signature here 840 // is verified using the certificate from the previous level. 841 // The linking is provided by the certificate of each level signing the one of the next. 842 843 try { 844 845 // get the version code, but don't do anything with it: creator knew about all our flags 846 porBuf.getInt(); 847 HashSet<X509Certificate> certHistorySet = new HashSet<>(); 848 while (porBuf.hasRemaining()) { 849 levelCount++; 850 ByteBuffer level = getLengthPrefixedSlice(porBuf); 851 ByteBuffer signedData = getLengthPrefixedSlice(level); 852 int flags = level.getInt(); 853 int sigAlgorithm = level.getInt(); 854 byte[] signature = readLengthPrefixedByteArray(level); 855 856 if (lastCert != null) { 857 // Use previous level cert to verify current level 858 Pair<String, ? extends AlgorithmParameterSpec> sigAlgParams = 859 getSignatureAlgorithmJcaSignatureAlgorithm(lastSigAlgorithm); 860 PublicKey publicKey = lastCert.getPublicKey(); 861 Signature sig = Signature.getInstance(sigAlgParams.first); 862 sig.initVerify(publicKey); 863 if (sigAlgParams.second != null) { 864 sig.setParameter(sigAlgParams.second); 865 } 866 sig.update(signedData); 867 if (!sig.verify(signature)) { 868 throw new SecurityException("Unable to verify signature of certificate #" 869 + levelCount + " using " + sigAlgParams.first + " when verifying" 870 + " Proof-of-rotation record"); 871 } 872 } 873 874 signedData.rewind(); 875 byte[] encodedCert = readLengthPrefixedByteArray(signedData); 876 int signedSigAlgorithm = signedData.getInt(); 877 if (lastCert != null && lastSigAlgorithm != signedSigAlgorithm) { 878 throw new SecurityException("Signing algorithm ID mismatch for certificate #" 879 + levelCount + " when verifying Proof-of-rotation record"); 880 } 881 lastCert = (X509Certificate) 882 certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); 883 lastCert = new VerbatimX509Certificate(lastCert, encodedCert); 884 885 lastSigAlgorithm = sigAlgorithm; 886 if (certHistorySet.contains(lastCert)) { 887 throw new SecurityException("Encountered duplicate entries in " 888 + "Proof-of-rotation record at certificate #" + levelCount + ". All " 889 + "signing certificates should be unique"); 890 } 891 certHistorySet.add(lastCert); 892 certs.add(lastCert); 893 flagsList.add(flags); 894 } 895 } catch (IOException | BufferUnderflowException e) { 896 throw new IOException("Failed to parse Proof-of-rotation record", e); 897 } catch (NoSuchAlgorithmException | InvalidKeyException 898 | InvalidAlgorithmParameterException | SignatureException e) { 899 throw new SecurityException( 900 "Failed to verify signature over signed data for certificate #" 901 + levelCount + " when verifying Proof-of-rotation record", e); 902 } catch (CertificateException e) { 903 throw new SecurityException("Failed to decode certificate #" + levelCount 904 + " when verifying Proof-of-rotation record", e); 905 } 906 return new VerifiedProofOfRotation(certs, flagsList); 907 } 908 909 /** 910 * Verified processed proof of rotation. 911 * 912 * @hide for internal use only. 913 */ 914 public static class VerifiedProofOfRotation { 915 public final List<X509Certificate> certs; 916 public final List<Integer> flagsList; 917 VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList)918 public VerifiedProofOfRotation(List<X509Certificate> certs, List<Integer> flagsList) { 919 this.certs = certs; 920 this.flagsList = flagsList; 921 } 922 } 923 } 924