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