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