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 com.android.apksig.internal.apk; 18 19 import com.android.apksig.ApkVerifier; 20 import com.android.apksig.SigningCertificateLineage; 21 import com.android.apksig.apk.ApkFormatException; 22 import com.android.apksig.apk.ApkSigningBlockNotFoundException; 23 import com.android.apksig.apk.ApkUtils; 24 import com.android.apksig.internal.util.ByteBufferDataSource; 25 import com.android.apksig.internal.util.ChainedDataSource; 26 import com.android.apksig.internal.util.Pair; 27 import com.android.apksig.internal.util.VerityTreeBuilder; 28 import com.android.apksig.internal.zip.ZipUtils; 29 import com.android.apksig.util.DataSink; 30 import com.android.apksig.util.DataSinks; 31 import com.android.apksig.util.DataSource; 32 import com.android.apksig.util.DataSources; 33 34 import com.android.apksig.util.RunnablesExecutor; 35 import java.io.IOException; 36 import java.nio.BufferUnderflowException; 37 import java.nio.ByteBuffer; 38 import java.nio.ByteOrder; 39 import java.security.DigestException; 40 import java.security.InvalidAlgorithmParameterException; 41 import java.security.InvalidKeyException; 42 import java.security.KeyFactory; 43 import java.security.MessageDigest; 44 import java.security.NoSuchAlgorithmException; 45 import java.security.PrivateKey; 46 import java.security.PublicKey; 47 import java.security.Signature; 48 import java.security.SignatureException; 49 import java.security.cert.CertificateEncodingException; 50 import java.security.cert.X509Certificate; 51 import java.security.spec.AlgorithmParameterSpec; 52 import java.security.spec.InvalidKeySpecException; 53 import java.security.spec.X509EncodedKeySpec; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.HashMap; 57 import java.util.HashSet; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.concurrent.ExecutionException; 62 import java.util.concurrent.ForkJoinPool; 63 import java.util.concurrent.Future; 64 import java.util.concurrent.atomic.AtomicInteger; 65 import java.util.function.Function; 66 import java.util.function.Supplier; 67 import java.util.stream.Collectors; 68 69 public class ApkSigningBlockUtils { 70 71 private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray(); 72 private static final long CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024; 73 public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096; 74 public static final byte[] APK_SIGNING_BLOCK_MAGIC = 75 new byte[] { 76 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20, 77 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32, 78 }; 79 private static final int VERITY_PADDING_BLOCK_ID = 0x42726577; 80 81 public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; 82 public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; 83 public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3; 84 85 86 /** 87 * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if 88 * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference. 89 */ compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2)90 public static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) { 91 ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm(); 92 ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm(); 93 return compareContentDigestAlgorithm(digestAlg1, digestAlg2); 94 } 95 96 /** 97 * Returns a positive number if {@code alg1} is preferred over {@code alg2}, a negative number 98 * if {@code alg2} is preferred over {@code alg1}, or {@code 0} if there is no preference. 99 */ compareContentDigestAlgorithm( ContentDigestAlgorithm alg1, ContentDigestAlgorithm alg2)100 private static int compareContentDigestAlgorithm( 101 ContentDigestAlgorithm alg1, 102 ContentDigestAlgorithm alg2) { 103 switch (alg1) { 104 case CHUNKED_SHA256: 105 switch (alg2) { 106 case CHUNKED_SHA256: 107 return 0; 108 case CHUNKED_SHA512: 109 case VERITY_CHUNKED_SHA256: 110 return -1; 111 default: 112 throw new IllegalArgumentException("Unknown alg2: " + alg2); 113 } 114 case CHUNKED_SHA512: 115 switch (alg2) { 116 case CHUNKED_SHA256: 117 case VERITY_CHUNKED_SHA256: 118 return 1; 119 case CHUNKED_SHA512: 120 return 0; 121 default: 122 throw new IllegalArgumentException("Unknown alg2: " + alg2); 123 } 124 case VERITY_CHUNKED_SHA256: 125 switch (alg2) { 126 case CHUNKED_SHA256: 127 return 1; 128 case VERITY_CHUNKED_SHA256: 129 return 0; 130 case CHUNKED_SHA512: 131 return -1; 132 default: 133 throw new IllegalArgumentException("Unknown alg2: " + alg2); 134 } 135 default: 136 throw new IllegalArgumentException("Unknown alg1: " + alg1); 137 } 138 } 139 140 141 142 /** 143 * Verifies integrity of the APK outside of the APK Signing Block by computing digests of the 144 * APK and comparing them against the digests listed in APK Signing Block. The expected digests 145 * are taken from {@code SignerInfos} of the provided {@code result}. 146 * 147 * <p>This method adds one or more errors to the {@code result} if a verification error is 148 * expected to be encountered on Android. No errors are added to the {@code result} if the APK's 149 * integrity is expected to verify on Android for each algorithm in 150 * {@code contentDigestAlgorithms}. 151 * 152 * <p>The reason this method is currently not parameterized by a 153 * {@code [minSdkVersion, maxSdkVersion]} range is that up until now content digest algorithms 154 * exhibit the same behavior on all Android platform versions. 155 */ verifyIntegrity( RunnablesExecutor executor, DataSource beforeApkSigningBlock, DataSource centralDir, ByteBuffer eocd, Set<ContentDigestAlgorithm> contentDigestAlgorithms, Result result)156 public static void verifyIntegrity( 157 RunnablesExecutor executor, 158 DataSource beforeApkSigningBlock, 159 DataSource centralDir, 160 ByteBuffer eocd, 161 Set<ContentDigestAlgorithm> contentDigestAlgorithms, 162 Result result) throws IOException, NoSuchAlgorithmException { 163 if (contentDigestAlgorithms.isEmpty()) { 164 // This should never occur because this method is invoked once at least one signature 165 // is verified, meaning at least one content digest is known. 166 throw new RuntimeException("No content digests found"); 167 } 168 169 // For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be 170 // treated as though its Central Directory offset points to the start of APK Signing Block. 171 // We thus modify the EoCD accordingly. 172 ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining()); 173 int eocdSavedPos = eocd.position(); 174 modifiedEocd.order(ByteOrder.LITTLE_ENDIAN); 175 modifiedEocd.put(eocd); 176 modifiedEocd.flip(); 177 178 // restore eocd to position prior to modification in case it is to be used elsewhere 179 eocd.position(eocdSavedPos); 180 ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size()); 181 Map<ContentDigestAlgorithm, byte[]> actualContentDigests; 182 try { 183 actualContentDigests = 184 computeContentDigests( 185 executor, 186 contentDigestAlgorithms, 187 beforeApkSigningBlock, 188 centralDir, 189 new ByteBufferDataSource(modifiedEocd)); 190 // Special checks for the verity algorithm requirements. 191 if (actualContentDigests.containsKey(ContentDigestAlgorithm.VERITY_CHUNKED_SHA256)) { 192 if ((beforeApkSigningBlock.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) { 193 throw new RuntimeException( 194 "APK Signing Block is not aligned on 4k boundary: " + 195 beforeApkSigningBlock.size()); 196 } 197 198 long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); 199 long signingBlockSize = centralDirOffset - beforeApkSigningBlock.size(); 200 if (signingBlockSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) { 201 throw new RuntimeException( 202 "APK Signing Block size is not multiple of page size: " + 203 signingBlockSize); 204 } 205 } 206 } catch (DigestException e) { 207 throw new RuntimeException("Failed to compute content digests", e); 208 } 209 if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) { 210 throw new RuntimeException( 211 "Mismatch between sets of requested and computed content digests" 212 + " . Requested: " + contentDigestAlgorithms 213 + ", computed: " + actualContentDigests.keySet()); 214 } 215 216 // Compare digests computed over the rest of APK against the corresponding expected digests 217 // in signer blocks. 218 for (Result.SignerInfo signerInfo : result.signers) { 219 for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) { 220 SignatureAlgorithm signatureAlgorithm = 221 SignatureAlgorithm.findById(expected.getSignatureAlgorithmId()); 222 if (signatureAlgorithm == null) { 223 continue; 224 } 225 ContentDigestAlgorithm contentDigestAlgorithm = 226 signatureAlgorithm.getContentDigestAlgorithm(); 227 // if the current digest algorithm is not in the list provided by the caller then 228 // ignore it; the signer may contain digests not recognized by the specified SDK 229 // range. 230 if (!contentDigestAlgorithms.contains(contentDigestAlgorithm)) { 231 continue; 232 } 233 byte[] expectedDigest = expected.getValue(); 234 byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm); 235 if (!Arrays.equals(expectedDigest, actualDigest)) { 236 if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2) { 237 signerInfo.addError( 238 ApkVerifier.Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY, 239 contentDigestAlgorithm, 240 toHex(expectedDigest), 241 toHex(actualDigest)); 242 } else if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3) { 243 signerInfo.addError( 244 ApkVerifier.Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY, 245 contentDigestAlgorithm, 246 toHex(expectedDigest), 247 toHex(actualDigest)); 248 } 249 continue; 250 } 251 signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest); 252 } 253 } 254 } 255 findApkSignatureSchemeBlock( ByteBuffer apkSigningBlock, int blockId, Result result)256 public static ByteBuffer findApkSignatureSchemeBlock( 257 ByteBuffer apkSigningBlock, 258 int blockId, 259 Result result) throws SignatureNotFoundException { 260 checkByteOrderLittleEndian(apkSigningBlock); 261 // FORMAT: 262 // OFFSET DATA TYPE DESCRIPTION 263 // * @+0 bytes uint64: size in bytes (excluding this field) 264 // * @+8 bytes pairs 265 // * @-24 bytes uint64: size in bytes (same as the one above) 266 // * @-16 bytes uint128: magic 267 ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); 268 269 int entryCount = 0; 270 while (pairs.hasRemaining()) { 271 entryCount++; 272 if (pairs.remaining() < 8) { 273 throw new SignatureNotFoundException( 274 "Insufficient data to read size of APK Signing Block entry #" + entryCount); 275 } 276 long lenLong = pairs.getLong(); 277 if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { 278 throw new SignatureNotFoundException( 279 "APK Signing Block entry #" + entryCount 280 + " size out of range: " + lenLong); 281 } 282 int len = (int) lenLong; 283 int nextEntryPos = pairs.position() + len; 284 if (len > pairs.remaining()) { 285 throw new SignatureNotFoundException( 286 "APK Signing Block entry #" + entryCount + " size out of range: " + len 287 + ", available: " + pairs.remaining()); 288 } 289 int id = pairs.getInt(); 290 if (id == blockId) { 291 return getByteBuffer(pairs, len - 4); 292 } 293 pairs.position(nextEntryPos); 294 } 295 296 throw new SignatureNotFoundException( 297 "No APK Signature Scheme block in APK Signing Block with ID: " + blockId); 298 } 299 checkByteOrderLittleEndian(ByteBuffer buffer)300 public static void checkByteOrderLittleEndian(ByteBuffer buffer) { 301 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 302 throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 303 } 304 } 305 306 /** 307 * Returns new byte buffer whose content is a shared subsequence of this buffer's content 308 * between the specified start (inclusive) and end (exclusive) positions. As opposed to 309 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source 310 * buffer's byte order. 311 */ sliceFromTo(ByteBuffer source, int start, int end)312 private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { 313 if (start < 0) { 314 throw new IllegalArgumentException("start: " + start); 315 } 316 if (end < start) { 317 throw new IllegalArgumentException("end < start: " + end + " < " + start); 318 } 319 int capacity = source.capacity(); 320 if (end > source.capacity()) { 321 throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); 322 } 323 int originalLimit = source.limit(); 324 int originalPosition = source.position(); 325 try { 326 source.position(0); 327 source.limit(end); 328 source.position(start); 329 ByteBuffer result = source.slice(); 330 result.order(source.order()); 331 return result; 332 } finally { 333 source.position(0); 334 source.limit(originalLimit); 335 source.position(originalPosition); 336 } 337 } 338 339 /** 340 * Relative <em>get</em> method for reading {@code size} number of bytes from the current 341 * position of this buffer. 342 * 343 * <p>This method reads the next {@code size} bytes at this buffer's current position, 344 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to 345 * {@code size}, byte order set to this buffer's byte order; and then increments the position by 346 * {@code size}. 347 */ getByteBuffer(ByteBuffer source, int size)348 private static ByteBuffer getByteBuffer(ByteBuffer source, int size) { 349 if (size < 0) { 350 throw new IllegalArgumentException("size: " + size); 351 } 352 int originalLimit = source.limit(); 353 int position = source.position(); 354 int limit = position + size; 355 if ((limit < position) || (limit > originalLimit)) { 356 throw new BufferUnderflowException(); 357 } 358 source.limit(limit); 359 try { 360 ByteBuffer result = source.slice(); 361 result.order(source.order()); 362 source.position(limit); 363 return result; 364 } finally { 365 source.limit(originalLimit); 366 } 367 } 368 getLengthPrefixedSlice(ByteBuffer source)369 public static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException { 370 if (source.remaining() < 4) { 371 throw new ApkFormatException( 372 "Remaining buffer too short to contain length of length-prefixed field" 373 + ". Remaining: " + source.remaining()); 374 } 375 int len = source.getInt(); 376 if (len < 0) { 377 throw new IllegalArgumentException("Negative length"); 378 } else if (len > source.remaining()) { 379 throw new ApkFormatException( 380 "Length-prefixed field longer than remaining buffer" 381 + ". Field length: " + len + ", remaining: " + source.remaining()); 382 } 383 return getByteBuffer(source, len); 384 } 385 readLengthPrefixedByteArray(ByteBuffer buf)386 public static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException { 387 int len = buf.getInt(); 388 if (len < 0) { 389 throw new ApkFormatException("Negative length"); 390 } else if (len > buf.remaining()) { 391 throw new ApkFormatException( 392 "Underflow while reading length-prefixed value. Length: " + len 393 + ", available: " + buf.remaining()); 394 } 395 byte[] result = new byte[len]; 396 buf.get(result); 397 return result; 398 } 399 toHex(byte[] value)400 public static String toHex(byte[] value) { 401 StringBuilder sb = new StringBuilder(value.length * 2); 402 int len = value.length; 403 for (int i = 0; i < len; i++) { 404 int hi = (value[i] & 0xff) >>> 4; 405 int lo = value[i] & 0x0f; 406 sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]); 407 } 408 return sb.toString(); 409 } 410 computeContentDigests( RunnablesExecutor executor, Set<ContentDigestAlgorithm> digestAlgorithms, DataSource beforeCentralDir, DataSource centralDir, DataSource eocd)411 public static Map<ContentDigestAlgorithm, byte[]> computeContentDigests( 412 RunnablesExecutor executor, 413 Set<ContentDigestAlgorithm> digestAlgorithms, 414 DataSource beforeCentralDir, 415 DataSource centralDir, 416 DataSource eocd) throws IOException, NoSuchAlgorithmException, DigestException { 417 Map<ContentDigestAlgorithm, byte[]> contentDigests = new HashMap<>(); 418 Set<ContentDigestAlgorithm> oneMbChunkBasedAlgorithm = digestAlgorithms.stream() 419 .filter(a -> a == ContentDigestAlgorithm.CHUNKED_SHA256 || 420 a == ContentDigestAlgorithm.CHUNKED_SHA512) 421 .collect(Collectors.toSet()); 422 computeOneMbChunkContentDigests( 423 executor, 424 oneMbChunkBasedAlgorithm, 425 new DataSource[] { beforeCentralDir, centralDir, eocd }, 426 contentDigests); 427 428 if (digestAlgorithms.contains(ContentDigestAlgorithm.VERITY_CHUNKED_SHA256)) { 429 computeApkVerityDigest(beforeCentralDir, centralDir, eocd, contentDigests); 430 } 431 return contentDigests; 432 } 433 computeOneMbChunkContentDigests( Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)434 static void computeOneMbChunkContentDigests( 435 Set<ContentDigestAlgorithm> digestAlgorithms, 436 DataSource[] contents, 437 Map<ContentDigestAlgorithm, byte[]> outputContentDigests) 438 throws IOException, NoSuchAlgorithmException, DigestException { 439 // For each digest algorithm the result is computed as follows: 440 // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. 441 // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. 442 // No chunks are produced for empty (zero length) segments. 443 // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's 444 // length in bytes (uint32 little-endian) and the chunk's contents. 445 // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of 446 // chunks (uint32 little-endian) and the concatenation of digests of chunks of all 447 // segments in-order. 448 449 long chunkCountLong = 0; 450 for (DataSource input : contents) { 451 chunkCountLong += 452 getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 453 } 454 if (chunkCountLong > Integer.MAX_VALUE) { 455 throw new DigestException("Input too long: " + chunkCountLong + " chunks"); 456 } 457 int chunkCount = (int) chunkCountLong; 458 459 ContentDigestAlgorithm[] digestAlgorithmsArray = 460 digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); 461 MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; 462 byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; 463 int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; 464 for (int i = 0; i < digestAlgorithmsArray.length; i++) { 465 ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; 466 int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); 467 digestOutputSizes[i] = digestOutputSizeBytes; 468 byte[] concatenationOfChunkCountAndChunkDigests = 469 new byte[5 + chunkCount * digestOutputSizeBytes]; 470 concatenationOfChunkCountAndChunkDigests[0] = 0x5a; 471 setUnsignedInt32LittleEndian( 472 chunkCount, concatenationOfChunkCountAndChunkDigests, 1); 473 digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; 474 String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); 475 mds[i] = MessageDigest.getInstance(jcaAlgorithm); 476 } 477 478 DataSink mdSink = DataSinks.asDataSink(mds); 479 byte[] chunkContentPrefix = new byte[5]; 480 chunkContentPrefix[0] = (byte) 0xa5; 481 int chunkIndex = 0; 482 // Optimization opportunity: digests of chunks can be computed in parallel. However, 483 // determining the number of computations to be performed in parallel is non-trivial. This 484 // depends on a wide range of factors, such as data source type (e.g., in-memory or fetched 485 // from file), CPU/memory/disk cache bandwidth and latency, interconnect architecture of CPU 486 // cores, load on the system from other threads of execution and other processes, size of 487 // input. 488 // For now, we compute these digests sequentially and thus have the luxury of improving 489 // performance by writing the digest of each chunk into a pre-allocated buffer at exactly 490 // the right position. This avoids unnecessary allocations, copying, and enables the final 491 // digest to be more efficient because it's presented with all of its input in one go. 492 for (DataSource input : contents) { 493 long inputOffset = 0; 494 long inputRemaining = input.size(); 495 while (inputRemaining > 0) { 496 int chunkSize = 497 (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 498 setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); 499 for (int i = 0; i < mds.length; i++) { 500 mds[i].update(chunkContentPrefix); 501 } 502 try { 503 input.feed(inputOffset, chunkSize, mdSink); 504 } catch (IOException e) { 505 throw new IOException("Failed to read chunk #" + chunkIndex, e); 506 } 507 for (int i = 0; i < digestAlgorithmsArray.length; i++) { 508 MessageDigest md = mds[i]; 509 byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; 510 int expectedDigestSizeBytes = digestOutputSizes[i]; 511 int actualDigestSizeBytes = 512 md.digest( 513 concatenationOfChunkCountAndChunkDigests, 514 5 + chunkIndex * expectedDigestSizeBytes, 515 expectedDigestSizeBytes); 516 if (actualDigestSizeBytes != expectedDigestSizeBytes) { 517 throw new RuntimeException( 518 "Unexpected output size of " + md.getAlgorithm() 519 + " digest: " + actualDigestSizeBytes); 520 } 521 } 522 inputOffset += chunkSize; 523 inputRemaining -= chunkSize; 524 chunkIndex++; 525 } 526 } 527 528 for (int i = 0; i < digestAlgorithmsArray.length; i++) { 529 ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; 530 byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; 531 MessageDigest md = mds[i]; 532 byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); 533 outputContentDigests.put(digestAlgorithm, digest); 534 } 535 } 536 computeOneMbChunkContentDigests( RunnablesExecutor executor, Set<ContentDigestAlgorithm> digestAlgorithms, DataSource[] contents, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)537 static void computeOneMbChunkContentDigests( 538 RunnablesExecutor executor, 539 Set<ContentDigestAlgorithm> digestAlgorithms, 540 DataSource[] contents, 541 Map<ContentDigestAlgorithm, byte[]> outputContentDigests) 542 throws NoSuchAlgorithmException, DigestException { 543 long chunkCountLong = 0; 544 for (DataSource input : contents) { 545 chunkCountLong += 546 getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 547 } 548 if (chunkCountLong > Integer.MAX_VALUE) { 549 throw new DigestException("Input too long: " + chunkCountLong + " chunks"); 550 } 551 int chunkCount = (int) chunkCountLong; 552 553 List<ChunkDigests> chunkDigestsList = new ArrayList<>(digestAlgorithms.size()); 554 for (ContentDigestAlgorithm algorithms : digestAlgorithms) { 555 chunkDigestsList.add(new ChunkDigests(algorithms, chunkCount)); 556 } 557 558 ChunkSupplier chunkSupplier = new ChunkSupplier(contents); 559 executor.execute(() -> new ChunkDigester(chunkSupplier, chunkDigestsList)); 560 561 // Compute and write out final digest for each algorithm. 562 for (ChunkDigests chunkDigests : chunkDigestsList) { 563 MessageDigest messageDigest = chunkDigests.createMessageDigest(); 564 outputContentDigests.put( 565 chunkDigests.algorithm, 566 messageDigest.digest(chunkDigests.concatOfDigestsOfChunks)); 567 } 568 } 569 570 private static class ChunkDigests { 571 private final ContentDigestAlgorithm algorithm; 572 private final int digestOutputSize; 573 private final byte[] concatOfDigestsOfChunks; 574 ChunkDigests(ContentDigestAlgorithm algorithm, int chunkCount)575 private ChunkDigests(ContentDigestAlgorithm algorithm, int chunkCount) { 576 this.algorithm = algorithm; 577 digestOutputSize = this.algorithm.getChunkDigestOutputSizeBytes(); 578 concatOfDigestsOfChunks = new byte[1 + 4 + chunkCount * digestOutputSize]; 579 580 // Fill the initial values of the concatenated digests of chunks, which is 581 // {0x5a, 4-bytes-of-little-endian-chunk-count, digests*...}. 582 concatOfDigestsOfChunks[0] = 0x5a; 583 setUnsignedInt32LittleEndian(chunkCount, concatOfDigestsOfChunks, 1); 584 } 585 createMessageDigest()586 private MessageDigest createMessageDigest() throws NoSuchAlgorithmException { 587 return MessageDigest.getInstance(algorithm.getJcaMessageDigestAlgorithm()); 588 } 589 getOffset(int chunkIndex)590 private int getOffset(int chunkIndex) { 591 return 1 + 4 + chunkIndex * digestOutputSize; 592 } 593 } 594 595 /** 596 * A per-thread digest worker. 597 */ 598 private static class ChunkDigester implements Runnable { 599 private final ChunkSupplier dataSupplier; 600 private final List<ChunkDigests> chunkDigests; 601 private final List<MessageDigest> messageDigests; 602 private final DataSink mdSink; 603 ChunkDigester(ChunkSupplier dataSupplier, List<ChunkDigests> chunkDigests)604 private ChunkDigester(ChunkSupplier dataSupplier, List<ChunkDigests> chunkDigests) { 605 this.dataSupplier = dataSupplier; 606 this.chunkDigests = chunkDigests; 607 messageDigests = new ArrayList<>(chunkDigests.size()); 608 for (ChunkDigests chunkDigest : chunkDigests) { 609 try { 610 messageDigests.add(chunkDigest.createMessageDigest()); 611 } catch (NoSuchAlgorithmException ex) { 612 throw new RuntimeException(ex); 613 } 614 } 615 mdSink = DataSinks.asDataSink(messageDigests.toArray(new MessageDigest[0])); 616 } 617 618 @Override run()619 public void run() { 620 byte[] chunkContentPrefix = new byte[5]; 621 chunkContentPrefix[0] = (byte) 0xa5; 622 623 try { 624 for (ChunkSupplier.Chunk chunk = dataSupplier.get(); 625 chunk != null; 626 chunk = dataSupplier.get()) { 627 long size = chunk.dataSource.size(); 628 if (size > CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES) { 629 throw new RuntimeException("Chunk size greater than expected: " + size); 630 } 631 632 // First update with the chunk prefix. 633 setUnsignedInt32LittleEndian((int)size, chunkContentPrefix, 1); 634 mdSink.consume(chunkContentPrefix, 0, chunkContentPrefix.length); 635 636 // Then update with the chunk data. 637 chunk.dataSource.feed(0, size, mdSink); 638 639 // Now finalize chunk for all algorithms. 640 for (int i = 0; i < chunkDigests.size(); i++) { 641 ChunkDigests chunkDigest = chunkDigests.get(i); 642 int actualDigestSize = messageDigests.get(i).digest( 643 chunkDigest.concatOfDigestsOfChunks, 644 chunkDigest.getOffset(chunk.chunkIndex), 645 chunkDigest.digestOutputSize); 646 if (actualDigestSize != chunkDigest.digestOutputSize) { 647 throw new RuntimeException( 648 "Unexpected output size of " + chunkDigest.algorithm 649 + " digest: " + actualDigestSize); 650 } 651 } 652 } 653 } catch (IOException | DigestException e) { 654 throw new RuntimeException(e); 655 } 656 } 657 } 658 659 /** 660 * Thread-safe 1MB DataSource chunk supplier. When bounds are met in a 661 * supplied {@link DataSource}, the data from the next {@link DataSource} 662 * are NOT concatenated. Only the next call to get() will fetch from the 663 * next {@link DataSource} in the input {@link DataSource} array. 664 */ 665 private static class ChunkSupplier implements Supplier<ChunkSupplier.Chunk> { 666 private final DataSource[] dataSources; 667 private final int[] chunkCounts; 668 private final int totalChunkCount; 669 private final AtomicInteger nextIndex; 670 ChunkSupplier(DataSource[] dataSources)671 private ChunkSupplier(DataSource[] dataSources) { 672 this.dataSources = dataSources; 673 chunkCounts = new int[dataSources.length]; 674 int totalChunkCount = 0; 675 for (int i = 0; i < dataSources.length; i++) { 676 long chunkCount = getChunkCount(dataSources[i].size(), 677 CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 678 if (chunkCount > Integer.MAX_VALUE) { 679 throw new RuntimeException( 680 String.format( 681 "Number of chunks in dataSource[%d] is greater than max int.", 682 i)); 683 } 684 chunkCounts[i] = (int)chunkCount; 685 totalChunkCount += chunkCount; 686 } 687 this.totalChunkCount = totalChunkCount; 688 nextIndex = new AtomicInteger(0); 689 } 690 691 /** 692 * We map an integer index to the termination-adjusted dataSources 1MB chunks. 693 * Note that {@link Chunk}s could be less than 1MB, namely the last 1MB-aligned 694 * blocks in each input {@link DataSource} (unless the DataSource itself is 695 * 1MB-aligned). 696 */ 697 @Override get()698 public ChunkSupplier.Chunk get() { 699 int index = nextIndex.getAndIncrement(); 700 if (index < 0 || index >= totalChunkCount) { 701 return null; 702 } 703 704 int dataSourceIndex = 0; 705 int dataSourceChunkOffset = index; 706 for (; dataSourceIndex < dataSources.length; dataSourceIndex++) { 707 if (dataSourceChunkOffset < chunkCounts[dataSourceIndex]) { 708 break; 709 } 710 dataSourceChunkOffset -= chunkCounts[dataSourceIndex]; 711 } 712 713 long remainingSize = Math.min( 714 dataSources[dataSourceIndex].size() - 715 dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES, 716 CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 717 // Note that slicing may involve its own locking. We may wish to reimplement the 718 // underlying mechanism to get rid of that lock (e.g. ByteBufferDataSource should 719 // probably get reimplemented to a delegate model, such that grabbing a slice 720 // doesn't incur a lock). 721 return new Chunk( 722 dataSources[dataSourceIndex].slice( 723 dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES, 724 remainingSize), 725 index); 726 } 727 728 static class Chunk { 729 private final int chunkIndex; 730 private final DataSource dataSource; 731 Chunk(DataSource parentSource, int chunkIndex)732 private Chunk(DataSource parentSource, int chunkIndex) { 733 this.chunkIndex = chunkIndex; 734 dataSource = parentSource; 735 } 736 } 737 } 738 computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, Map<ContentDigestAlgorithm, byte[]> outputContentDigests)739 private static void computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir, 740 DataSource eocd, Map<ContentDigestAlgorithm, byte[]> outputContentDigests) 741 throws IOException, NoSuchAlgorithmException { 742 // FORMAT: 743 // OFFSET DATA TYPE DESCRIPTION 744 // * @+0 bytes uint8[32] Merkle tree root hash of SHA-256 745 // * @+32 bytes int64 Length of source data 746 int backBufferSize = 747 ContentDigestAlgorithm.VERITY_CHUNKED_SHA256.getChunkDigestOutputSizeBytes() + 748 Long.SIZE / Byte.SIZE; 749 ByteBuffer encoded = ByteBuffer.allocate(backBufferSize); 750 encoded.order(ByteOrder.LITTLE_ENDIAN); 751 752 // Use 0s as salt for now. This also needs to be consistent in the fsverify header for 753 // kernel to use. 754 VerityTreeBuilder builder = new VerityTreeBuilder(new byte[8]); 755 byte[] rootHash = builder.generateVerityTreeRootHash(beforeCentralDir, centralDir, eocd); 756 encoded.put(rootHash); 757 encoded.putLong(beforeCentralDir.size() + centralDir.size() + eocd.size()); 758 759 outputContentDigests.put(ContentDigestAlgorithm.VERITY_CHUNKED_SHA256, encoded.array()); 760 } 761 getChunkCount(long inputSize, long chunkSize)762 private static long getChunkCount(long inputSize, long chunkSize) { 763 return (inputSize + chunkSize - 1) / chunkSize; 764 } 765 setUnsignedInt32LittleEndian(int value, byte[] result, int offset)766 private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { 767 result[offset] = (byte) (value & 0xff); 768 result[offset + 1] = (byte) ((value >> 8) & 0xff); 769 result[offset + 2] = (byte) ((value >> 16) & 0xff); 770 result[offset + 3] = (byte) ((value >> 24) & 0xff); 771 } 772 encodePublicKey(PublicKey publicKey)773 public static byte[] encodePublicKey(PublicKey publicKey) 774 throws InvalidKeyException, NoSuchAlgorithmException { 775 byte[] encodedPublicKey = null; 776 if ("X.509".equals(publicKey.getFormat())) { 777 encodedPublicKey = publicKey.getEncoded(); 778 } 779 if (encodedPublicKey == null) { 780 try { 781 encodedPublicKey = 782 KeyFactory.getInstance(publicKey.getAlgorithm()) 783 .getKeySpec(publicKey, X509EncodedKeySpec.class) 784 .getEncoded(); 785 } catch (InvalidKeySpecException e) { 786 throw new InvalidKeyException( 787 "Failed to obtain X.509 encoded form of public key " + publicKey 788 + " of class " + publicKey.getClass().getName(), 789 e); 790 } 791 } 792 if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) { 793 throw new InvalidKeyException( 794 "Failed to obtain X.509 encoded form of public key " + publicKey 795 + " of class " + publicKey.getClass().getName()); 796 } 797 return encodedPublicKey; 798 } 799 encodeCertificates(List<X509Certificate> certificates)800 public static List<byte[]> encodeCertificates(List<X509Certificate> certificates) 801 throws CertificateEncodingException { 802 List<byte[]> result = new ArrayList<>(certificates.size()); 803 for (X509Certificate certificate : certificates) { 804 result.add(certificate.getEncoded()); 805 } 806 return result; 807 } 808 encodeAsLengthPrefixedElement(byte[] bytes)809 public static byte[] encodeAsLengthPrefixedElement(byte[] bytes) { 810 byte[][] adapterBytes = new byte[1][]; 811 adapterBytes[0] = bytes; 812 return encodeAsSequenceOfLengthPrefixedElements(adapterBytes); 813 } 814 encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence)815 public static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) { 816 return encodeAsSequenceOfLengthPrefixedElements( 817 sequence.toArray(new byte[sequence.size()][])); 818 } 819 encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence)820 public static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) { 821 int payloadSize = 0; 822 for (byte[] element : sequence) { 823 payloadSize += 4 + element.length; 824 } 825 ByteBuffer result = ByteBuffer.allocate(payloadSize); 826 result.order(ByteOrder.LITTLE_ENDIAN); 827 for (byte[] element : sequence) { 828 result.putInt(element.length); 829 result.put(element); 830 } 831 return result.array(); 832 } 833 encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( List<Pair<Integer, byte[]>> sequence)834 public static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( 835 List<Pair<Integer, byte[]>> sequence) { 836 int resultSize = 0; 837 for (Pair<Integer, byte[]> element : sequence) { 838 resultSize += 12 + element.getSecond().length; 839 } 840 ByteBuffer result = ByteBuffer.allocate(resultSize); 841 result.order(ByteOrder.LITTLE_ENDIAN); 842 for (Pair<Integer, byte[]> element : sequence) { 843 byte[] second = element.getSecond(); 844 result.putInt(8 + second.length); 845 result.putInt(element.getFirst()); 846 result.putInt(second.length); 847 result.put(second); 848 } 849 return result.array(); 850 } 851 852 /** 853 * Returns the APK Signature Scheme block contained in the provided APK file for the given ID 854 * and the additional information relevant for verifying the block against the file. 855 * 856 * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs 857 * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 858 * block ID. 859 * 860 * @throws SignatureNotFoundException if the APK is not signed using given APK Signature Scheme 861 * @throws IOException if an I/O error occurs while reading the APK 862 */ findSignature( DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result)863 public static SignatureInfo findSignature( 864 DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result) 865 throws IOException, SignatureNotFoundException { 866 // Find the APK Signing Block. 867 DataSource apkSigningBlock; 868 long apkSigningBlockOffset; 869 try { 870 ApkUtils.ApkSigningBlock apkSigningBlockInfo = 871 ApkUtils.findApkSigningBlock(apk, zipSections); 872 apkSigningBlockOffset = apkSigningBlockInfo.getStartOffset(); 873 apkSigningBlock = apkSigningBlockInfo.getContents(); 874 } catch (ApkSigningBlockNotFoundException e) { 875 throw new SignatureNotFoundException(e.getMessage(), e); 876 } 877 ByteBuffer apkSigningBlockBuf = 878 apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size()); 879 apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN); 880 881 // Find the APK Signature Scheme Block inside the APK Signing Block. 882 ByteBuffer apkSignatureSchemeBlock = 883 findApkSignatureSchemeBlock(apkSigningBlockBuf, blockId, result); 884 return new SignatureInfo( 885 apkSignatureSchemeBlock, 886 apkSigningBlockOffset, 887 zipSections.getZipCentralDirectoryOffset(), 888 zipSections.getZipEndOfCentralDirectoryOffset(), 889 zipSections.getZipEndOfCentralDirectory()); 890 } 891 892 /** 893 * Generates a new DataSource representing the APK contents before the Central Directory with 894 * padding, if padding is requested. If the existing data entries before the Central Directory 895 * are already aligned, or no padding is requested, the original DataSource is used. This 896 * padding is used to allow for verity-based APK verification. 897 * 898 * @return {@code Pair} containing the potentially new {@code DataSource} and the amount of 899 * padding used. 900 */ generateApkSigningBlockPadding( DataSource beforeCentralDir, boolean apkSigningBlockPaddingSupported)901 public static Pair<DataSource, Integer> generateApkSigningBlockPadding( 902 DataSource beforeCentralDir, 903 boolean apkSigningBlockPaddingSupported) { 904 905 // Ensure APK Signing Block starts from page boundary. 906 int padSizeBeforeSigningBlock = 0; 907 if (apkSigningBlockPaddingSupported && 908 (beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) { 909 padSizeBeforeSigningBlock = (int) ( 910 ANDROID_COMMON_PAGE_ALIGNMENT_BYTES - 911 beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES); 912 beforeCentralDir = new ChainedDataSource( 913 beforeCentralDir, 914 DataSources.asDataSource( 915 ByteBuffer.allocate(padSizeBeforeSigningBlock))); 916 } 917 return Pair.of(beforeCentralDir, padSizeBeforeSigningBlock); 918 } 919 copyWithModifiedCDOffset( DataSource beforeCentralDir, DataSource eocd)920 public static DataSource copyWithModifiedCDOffset( 921 DataSource beforeCentralDir, DataSource eocd) throws IOException { 922 923 // Ensure that, when digesting, ZIP End of Central Directory record's Central Directory 924 // offset field is treated as pointing to the offset at which the APK Signing Block will 925 // start. 926 long centralDirOffsetForDigesting = beforeCentralDir.size(); 927 ByteBuffer eocdBuf = ByteBuffer.allocate((int) eocd.size()); 928 eocdBuf.order(ByteOrder.LITTLE_ENDIAN); 929 eocd.copyTo(0, (int) eocd.size(), eocdBuf); 930 eocdBuf.flip(); 931 ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting); 932 return DataSources.asDataSource(eocdBuf); 933 } 934 generateApkSigningBlock( List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs)935 public static byte[] generateApkSigningBlock( 936 List<Pair<byte[], Integer>> apkSignatureSchemeBlockPairs) { 937 // FORMAT: 938 // uint64: size (excluding this field) 939 // repeated ID-value pairs: 940 // uint64: size (excluding this field) 941 // uint32: ID 942 // (size - 4) bytes: value 943 // (extra dummy ID-value for padding to make block size a multiple of 4096 bytes) 944 // uint64: size (same as the one above) 945 // uint128: magic 946 947 int blocksSize = 0; 948 for (Pair<byte[], Integer> schemeBlockPair : apkSignatureSchemeBlockPairs) { 949 blocksSize += 8 + 4 + schemeBlockPair.getFirst().length; // size + id + value 950 } 951 952 int resultSize = 953 8 // size 954 + blocksSize 955 + 8 // size 956 + 16 // magic 957 ; 958 ByteBuffer paddingPair = null; 959 if (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) { 960 int padding = ANDROID_COMMON_PAGE_ALIGNMENT_BYTES - 961 (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES); 962 if (padding < 12) { // minimum size of an ID-value pair 963 padding += ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; 964 } 965 paddingPair = ByteBuffer.allocate(padding).order(ByteOrder.LITTLE_ENDIAN); 966 paddingPair.putLong(padding - 8); 967 paddingPair.putInt(VERITY_PADDING_BLOCK_ID); 968 paddingPair.rewind(); 969 resultSize += padding; 970 } 971 972 ByteBuffer result = ByteBuffer.allocate(resultSize); 973 result.order(ByteOrder.LITTLE_ENDIAN); 974 long blockSizeFieldValue = resultSize - 8L; 975 result.putLong(blockSizeFieldValue); 976 977 978 for (Pair<byte[], Integer> schemeBlockPair : apkSignatureSchemeBlockPairs) { 979 byte[] apkSignatureSchemeBlock = schemeBlockPair.getFirst(); 980 int apkSignatureSchemeId = schemeBlockPair.getSecond(); 981 long pairSizeFieldValue = 4L + apkSignatureSchemeBlock.length; 982 result.putLong(pairSizeFieldValue); 983 result.putInt(apkSignatureSchemeId); 984 result.put(apkSignatureSchemeBlock); 985 } 986 987 if (paddingPair != null) { 988 result.put(paddingPair); 989 } 990 991 result.putLong(blockSizeFieldValue); 992 result.put(APK_SIGNING_BLOCK_MAGIC); 993 994 return result.array(); 995 } 996 997 /** 998 * Computes the digests of the given APK components according to the algorithms specified in the 999 * given SignerConfigs. 1000 * 1001 * @param signerConfigs signer configurations, one for each signer At least one signer config 1002 * must be provided. 1003 * 1004 * @throws IOException if an I/O error occurs 1005 * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is 1006 * missing 1007 * @throws SignatureException if an error occurs when computing digests of generating 1008 * signatures 1009 */ 1010 public static Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>> computeContentDigests( RunnablesExecutor executor, DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List<SignerConfig> signerConfigs)1011 computeContentDigests( 1012 RunnablesExecutor executor, 1013 DataSource beforeCentralDir, 1014 DataSource centralDir, 1015 DataSource eocd, 1016 List<SignerConfig> signerConfigs) 1017 throws IOException, NoSuchAlgorithmException, SignatureException { 1018 if (signerConfigs.isEmpty()) { 1019 throw new IllegalArgumentException( 1020 "No signer configs provided. At least one is required"); 1021 } 1022 1023 // Figure out which digest(s) to use for APK contents. 1024 Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<>(1); 1025 for (SignerConfig signerConfig : signerConfigs) { 1026 for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { 1027 contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm()); 1028 } 1029 } 1030 1031 // Compute digests of APK contents. 1032 Map<ContentDigestAlgorithm, byte[]> contentDigests; // digest algorithm ID -> digest 1033 try { 1034 contentDigests = 1035 computeContentDigests( 1036 executor, 1037 contentDigestAlgorithms, 1038 beforeCentralDir, 1039 centralDir, 1040 eocd); 1041 } catch (IOException e) { 1042 throw new IOException("Failed to read APK being signed", e); 1043 } catch (DigestException e) { 1044 throw new SignatureException("Failed to compute digests of APK", e); 1045 } 1046 1047 // Sign the digests and wrap the signatures and signer info into an APK Signing Block. 1048 return Pair.of(signerConfigs, contentDigests); 1049 } 1050 1051 /** 1052 * Returns the subset of signatures which are expected to be verified by at least one Android 1053 * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is 1054 * guaranteed to contain at least one signature. 1055 * 1056 * <p>Each Android platform version typically verifies exactly one signature from the provided 1057 * {@code signatures} set. This method returns the set of these signatures collected over all 1058 * requested platform versions. As a result, the result may contain more than one signature. 1059 * 1060 * @throws NoSupportedSignaturesException if no supported signatures were 1061 * found for an Android platform version in the range. 1062 */ getSignaturesToVerify( List<SupportedSignature> signatures, int minSdkVersion, int maxSdkVersion)1063 public static List<SupportedSignature> getSignaturesToVerify( 1064 List<SupportedSignature> signatures, int minSdkVersion, int maxSdkVersion) 1065 throws NoSupportedSignaturesException { 1066 // Pick the signature with the strongest algorithm at all required SDK versions, to mimic 1067 // Android's behavior on those versions. 1068 // 1069 // Here we assume that, once introduced, a signature algorithm continues to be supported in 1070 // all future Android versions. We also assume that the better-than relationship between 1071 // algorithms is exactly the same on all Android platform versions (except that older 1072 // platforms might support fewer algorithms). If these assumption are no longer true, the 1073 // logic here will need to change accordingly. 1074 Map<Integer, SupportedSignature> bestSigAlgorithmOnSdkVersion = new HashMap<>(); 1075 int minProvidedSignaturesVersion = Integer.MAX_VALUE; 1076 for (SupportedSignature sig : signatures) { 1077 SignatureAlgorithm sigAlgorithm = sig.algorithm; 1078 int sigMinSdkVersion = sigAlgorithm.getMinSdkVersion(); 1079 if (sigMinSdkVersion > maxSdkVersion) { 1080 continue; 1081 } 1082 if (sigMinSdkVersion < minProvidedSignaturesVersion) { 1083 minProvidedSignaturesVersion = sigMinSdkVersion; 1084 } 1085 1086 SupportedSignature candidate = bestSigAlgorithmOnSdkVersion.get(sigMinSdkVersion); 1087 if ((candidate == null) 1088 || (compareSignatureAlgorithm( 1089 sigAlgorithm, candidate.algorithm) > 0)) { 1090 bestSigAlgorithmOnSdkVersion.put(sigMinSdkVersion, sig); 1091 } 1092 } 1093 1094 // Must have some supported signature algorithms for minSdkVersion. 1095 if (minSdkVersion < minProvidedSignaturesVersion) { 1096 throw new NoSupportedSignaturesException( 1097 "Minimum provided signature version " + minProvidedSignaturesVersion + 1098 " < minSdkVersion " + minSdkVersion); 1099 } 1100 if (bestSigAlgorithmOnSdkVersion.isEmpty()) { 1101 throw new NoSupportedSignaturesException("No supported signature"); 1102 } 1103 return bestSigAlgorithmOnSdkVersion.values().stream() 1104 .sorted((sig1, sig2) -> Integer.compare( 1105 sig1.algorithm.getId(), sig2.algorithm.getId())) 1106 .collect(Collectors.toList()); 1107 } 1108 1109 public static class NoSupportedSignaturesException extends Exception { 1110 private static final long serialVersionUID = 1L; 1111 NoSupportedSignaturesException(String message)1112 public NoSupportedSignaturesException(String message) { 1113 super(message); 1114 } 1115 } 1116 1117 public static class SignatureNotFoundException extends Exception { 1118 private static final long serialVersionUID = 1L; 1119 SignatureNotFoundException(String message)1120 public SignatureNotFoundException(String message) { 1121 super(message); 1122 } 1123 SignatureNotFoundException(String message, Throwable cause)1124 public SignatureNotFoundException(String message, Throwable cause) { 1125 super(message, cause); 1126 } 1127 } 1128 1129 /** 1130 * uses the SignatureAlgorithms in the provided signerConfig to sign the provided data 1131 * 1132 * @return list of signature algorithm IDs and their corresponding signatures over the data. 1133 */ generateSignaturesOverData( SignerConfig signerConfig, byte[] data)1134 public static List<Pair<Integer, byte[]>> generateSignaturesOverData( 1135 SignerConfig signerConfig, byte[] data) 1136 throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { 1137 List<Pair<Integer, byte[]>> signatures = 1138 new ArrayList<>(signerConfig.signatureAlgorithms.size()); 1139 PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); 1140 for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { 1141 Pair<String, ? extends AlgorithmParameterSpec> sigAlgAndParams = 1142 signatureAlgorithm.getJcaSignatureAlgorithmAndParams(); 1143 String jcaSignatureAlgorithm = sigAlgAndParams.getFirst(); 1144 AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond(); 1145 byte[] signatureBytes; 1146 try { 1147 Signature signature = Signature.getInstance(jcaSignatureAlgorithm); 1148 signature.initSign(signerConfig.privateKey); 1149 if (jcaSignatureAlgorithmParams != null) { 1150 signature.setParameter(jcaSignatureAlgorithmParams); 1151 } 1152 signature.update(data); 1153 signatureBytes = signature.sign(); 1154 } catch (InvalidKeyException e) { 1155 throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e); 1156 } catch (InvalidAlgorithmParameterException | SignatureException e) { 1157 throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e); 1158 } 1159 1160 try { 1161 Signature signature = Signature.getInstance(jcaSignatureAlgorithm); 1162 signature.initVerify(publicKey); 1163 if (jcaSignatureAlgorithmParams != null) { 1164 signature.setParameter(jcaSignatureAlgorithmParams); 1165 } 1166 signature.update(data); 1167 if (!signature.verify(signatureBytes)) { 1168 throw new SignatureException("Failed to verify generated " 1169 + jcaSignatureAlgorithm 1170 + " signature using public key from certificate"); 1171 } 1172 } catch (InvalidKeyException e) { 1173 throw new InvalidKeyException( 1174 "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" 1175 + " public key from certificate", e); 1176 } catch (InvalidAlgorithmParameterException | SignatureException e) { 1177 throw new SignatureException( 1178 "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" 1179 + " public key from certificate", e); 1180 } 1181 1182 signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes)); 1183 } 1184 return signatures; 1185 } 1186 1187 /** 1188 * Signer configuration. 1189 */ 1190 public static class SignerConfig { 1191 /** Private key. */ 1192 public PrivateKey privateKey; 1193 1194 /** 1195 * Certificates, with the first certificate containing the public key corresponding to 1196 * {@link #privateKey}. 1197 */ 1198 public List<X509Certificate> certificates; 1199 1200 /** 1201 * List of signature algorithms with which to sign. 1202 */ 1203 public List<SignatureAlgorithm> signatureAlgorithms; 1204 1205 public int minSdkVersion; 1206 public int maxSdkVersion; 1207 public SigningCertificateLineage mSigningCertificateLineage; 1208 } 1209 1210 public static class Result { 1211 public final int signatureSchemeVersion; 1212 1213 /** Whether the APK's APK Signature Scheme signature verifies. */ 1214 public boolean verified; 1215 1216 public final List<SignerInfo> signers = new ArrayList<>(); 1217 public SigningCertificateLineage signingCertificateLineage = null; 1218 private final List<ApkVerifier.IssueWithParams> mWarnings = new ArrayList<>(); 1219 private final List<ApkVerifier.IssueWithParams> mErrors = new ArrayList<>(); 1220 Result(int signatureSchemeVersion)1221 public Result(int signatureSchemeVersion) { 1222 this.signatureSchemeVersion = signatureSchemeVersion; 1223 } 1224 containsErrors()1225 public boolean containsErrors() { 1226 if (!mErrors.isEmpty()) { 1227 return true; 1228 } 1229 if (!signers.isEmpty()) { 1230 for (SignerInfo signer : signers) { 1231 if (signer.containsErrors()) { 1232 return true; 1233 } 1234 } 1235 } 1236 return false; 1237 } 1238 addError(ApkVerifier.Issue msg, Object... parameters)1239 public void addError(ApkVerifier.Issue msg, Object... parameters) { 1240 mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1241 } 1242 addWarning(ApkVerifier.Issue msg, Object... parameters)1243 public void addWarning(ApkVerifier.Issue msg, Object... parameters) { 1244 mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1245 } 1246 getErrors()1247 public List<ApkVerifier.IssueWithParams> getErrors() { 1248 return mErrors; 1249 } 1250 getWarnings()1251 public List<ApkVerifier.IssueWithParams> getWarnings() { 1252 return mWarnings; 1253 } 1254 1255 public static class SignerInfo { 1256 public int index; 1257 public List<X509Certificate> certs = new ArrayList<>(); 1258 public List<ContentDigest> contentDigests = new ArrayList<>(); 1259 public Map<ContentDigestAlgorithm, byte[]> verifiedContentDigests = new HashMap<>(); 1260 public List<Signature> signatures = new ArrayList<>(); 1261 public Map<SignatureAlgorithm, byte[]> verifiedSignatures = new HashMap<>(); 1262 public List<AdditionalAttribute> additionalAttributes = new ArrayList<>(); 1263 public byte[] signedData; 1264 public int minSdkVersion; 1265 public int maxSdkVersion; 1266 public SigningCertificateLineage signingCertificateLineage; 1267 1268 private final List<ApkVerifier.IssueWithParams> mWarnings = new ArrayList<>(); 1269 private final List<ApkVerifier.IssueWithParams> mErrors = new ArrayList<>(); 1270 addError(ApkVerifier.Issue msg, Object... parameters)1271 public void addError(ApkVerifier.Issue msg, Object... parameters) { 1272 mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1273 } 1274 addWarning(ApkVerifier.Issue msg, Object... parameters)1275 public void addWarning(ApkVerifier.Issue msg, Object... parameters) { 1276 mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters)); 1277 } 1278 containsErrors()1279 public boolean containsErrors() { 1280 return !mErrors.isEmpty(); 1281 } 1282 getErrors()1283 public List<ApkVerifier.IssueWithParams> getErrors() { 1284 return mErrors; 1285 } 1286 getWarnings()1287 public List<ApkVerifier.IssueWithParams> getWarnings() { 1288 return mWarnings; 1289 } 1290 1291 public static class ContentDigest { 1292 private final int mSignatureAlgorithmId; 1293 private final byte[] mValue; 1294 ContentDigest(int signatureAlgorithmId, byte[] value)1295 public ContentDigest(int signatureAlgorithmId, byte[] value) { 1296 mSignatureAlgorithmId = signatureAlgorithmId; 1297 mValue = value; 1298 } 1299 getSignatureAlgorithmId()1300 public int getSignatureAlgorithmId() { 1301 return mSignatureAlgorithmId; 1302 } 1303 getValue()1304 public byte[] getValue() { 1305 return mValue; 1306 } 1307 } 1308 1309 public static class Signature { 1310 private final int mAlgorithmId; 1311 private final byte[] mValue; 1312 Signature(int algorithmId, byte[] value)1313 public Signature(int algorithmId, byte[] value) { 1314 mAlgorithmId = algorithmId; 1315 mValue = value; 1316 } 1317 getAlgorithmId()1318 public int getAlgorithmId() { 1319 return mAlgorithmId; 1320 } 1321 getValue()1322 public byte[] getValue() { 1323 return mValue; 1324 } 1325 } 1326 1327 public static class AdditionalAttribute { 1328 private final int mId; 1329 private final byte[] mValue; 1330 AdditionalAttribute(int id, byte[] value)1331 public AdditionalAttribute(int id, byte[] value) { 1332 mId = id; 1333 mValue = value.clone(); 1334 } 1335 getId()1336 public int getId() { 1337 return mId; 1338 } 1339 getValue()1340 public byte[] getValue() { 1341 return mValue.clone(); 1342 } 1343 } 1344 } 1345 } 1346 1347 public static class SupportedSignature { 1348 public final SignatureAlgorithm algorithm; 1349 public final byte[] signature; 1350 SupportedSignature(SignatureAlgorithm algorithm, byte[] signature)1351 public SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) { 1352 this.algorithm = algorithm; 1353 this.signature = signature; 1354 } 1355 } 1356 } 1357