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