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