ApkSignerV2.java revision dd910c5945272e9820dfd9d7798ba32aa7dfc73f
1/* 2 * Copyright (C) 2016 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 17package com.android.signapk; 18 19import java.nio.BufferUnderflowException; 20import java.nio.ByteBuffer; 21import java.nio.ByteOrder; 22import java.security.DigestException; 23import java.security.InvalidAlgorithmParameterException; 24import java.security.InvalidKeyException; 25import java.security.KeyFactory; 26import java.security.MessageDigest; 27import java.security.NoSuchAlgorithmException; 28import java.security.PrivateKey; 29import java.security.PublicKey; 30import java.security.Signature; 31import java.security.SignatureException; 32import java.security.cert.CertificateEncodingException; 33import java.security.cert.X509Certificate; 34import java.security.spec.AlgorithmParameterSpec; 35import java.security.spec.InvalidKeySpecException; 36import java.security.spec.MGF1ParameterSpec; 37import java.security.spec.PSSParameterSpec; 38import java.security.spec.X509EncodedKeySpec; 39import java.util.ArrayList; 40import java.util.HashMap; 41import java.util.HashSet; 42import java.util.List; 43import java.util.Map; 44import java.util.Set; 45 46/** 47 * APK Signature Scheme v2 signer. 48 * 49 * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single 50 * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and 51 * uncompressed contents of ZIP entries. 52 */ 53public abstract class ApkSignerV2 { 54 /* 55 * The two main goals of APK Signature Scheme v2 are: 56 * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature 57 * cover every byte of the APK being signed. 58 * 2. Enable much faster signature and integrity verification. This is achieved by requiring 59 * only a minimal amount of APK parsing before the signature is verified, thus completely 60 * bypassing ZIP entry decompression and by making integrity verification parallelizable by 61 * employing a hash tree. 62 * 63 * The generated signature block is wrapped into an APK Signing Block and inserted into the 64 * original APK immediately before the start of ZIP Central Directory. This is to ensure that 65 * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for 66 * extensibility. For example, a future signature scheme could insert its signatures there as 67 * well. The contract of the APK Signing Block is that all contents outside of the block must be 68 * protected by signatures inside the block. 69 */ 70 71 public static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101; 72 public static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102; 73 public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103; 74 public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104; 75 public static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201; 76 public static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202; 77 public static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301; 78 public static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302; 79 80 /** 81 * {@code .SF} file header section attribute indicating that the APK is signed not just with 82 * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute 83 * facilitates v2 signature stripping detection. 84 * 85 * <p>The attribute contains a comma-separated set of signature scheme IDs. 86 */ 87 public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed"; 88 // TODO: Adjust the value when signing scheme finalized. 89 public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE = "1234567890"; 90 91 private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 0; 92 private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 1; 93 94 private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024; 95 96 private static final byte[] APK_SIGNING_BLOCK_MAGIC = 97 new byte[] { 98 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20, 99 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32, 100 }; 101 private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; 102 103 private ApkSignerV2() {} 104 105 /** 106 * Signer configuration. 107 */ 108 public static final class SignerConfig { 109 /** Private key. */ 110 public PrivateKey privateKey; 111 112 /** 113 * Certificates, with the first certificate containing the public key corresponding to 114 * {@link #privateKey}. 115 */ 116 public List<X509Certificate> certificates; 117 118 /** 119 * List of signature algorithms with which to sign (see {@code SIGNATURE_...} constants). 120 */ 121 public List<Integer> signatureAlgorithms; 122 } 123 124 /** 125 * Signs the provided APK using APK Signature Scheme v2 and returns the signed APK as a list of 126 * consecutive chunks. 127 * 128 * <p>NOTE: To enable APK signature verifier to detect v2 signature stripping, header sections 129 * of META-INF/*.SF files of APK being signed must contain the 130 * {@code X-Android-APK-Signed: true} attribute. 131 * 132 * @param inputApk contents of the APK to be signed. The APK starts at the current position 133 * of the buffer and ends at the limit of the buffer. 134 * @param signerConfigs signer configurations, one for each signer. 135 * 136 * @throws ApkParseException if the APK cannot be parsed. 137 * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or 138 * cannot be used in general. 139 * @throws SignatureException if an error occurs when computing digests of generating 140 * signatures. 141 */ 142 public static ByteBuffer[] sign( 143 ByteBuffer inputApk, 144 List<SignerConfig> signerConfigs) 145 throws ApkParseException, InvalidKeyException, SignatureException { 146 // Slice/create a view in the inputApk to make sure that: 147 // 1. inputApk is what's between position and limit of the original inputApk, and 148 // 2. changes to position, limit, and byte order are not reflected in the original. 149 ByteBuffer originalInputApk = inputApk; 150 inputApk = originalInputApk.slice(); 151 inputApk.order(ByteOrder.LITTLE_ENDIAN); 152 153 // Locate ZIP End of Central Directory (EoCD), Central Directory, and check that Central 154 // Directory is immediately followed by the ZIP End of Central Directory. 155 int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(inputApk); 156 if (eocdOffset == -1) { 157 throw new ApkParseException("Failed to locate ZIP End of Central Directory"); 158 } 159 if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(inputApk, eocdOffset)) { 160 throw new ApkParseException("ZIP64 format not supported"); 161 } 162 inputApk.position(eocdOffset); 163 long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(inputApk); 164 if (centralDirSizeLong > Integer.MAX_VALUE) { 165 throw new ApkParseException( 166 "ZIP Central Directory size out of range: " + centralDirSizeLong); 167 } 168 int centralDirSize = (int) centralDirSizeLong; 169 long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(inputApk); 170 if (centralDirOffsetLong > Integer.MAX_VALUE) { 171 throw new ApkParseException( 172 "ZIP Central Directory offset in file out of range: " + centralDirOffsetLong); 173 } 174 int centralDirOffset = (int) centralDirOffsetLong; 175 int expectedEocdOffset = centralDirOffset + centralDirSize; 176 if (expectedEocdOffset < centralDirOffset) { 177 throw new ApkParseException( 178 "ZIP Central Directory extent too large. Offset: " + centralDirOffset 179 + ", size: " + centralDirSize); 180 } 181 if (eocdOffset != expectedEocdOffset) { 182 throw new ApkParseException( 183 "ZIP Central Directory not immeiately followed by ZIP End of" 184 + " Central Directory. CD end: " + expectedEocdOffset 185 + ", EoCD start: " + eocdOffset); 186 } 187 188 // Create ByteBuffers holding the contents of everything before ZIP Central Directory, 189 // ZIP Central Directory, and ZIP End of Central Directory. 190 inputApk.clear(); 191 ByteBuffer beforeCentralDir = getByteBuffer(inputApk, centralDirOffset); 192 ByteBuffer centralDir = getByteBuffer(inputApk, eocdOffset - centralDirOffset); 193 // Create a copy of End of Central Directory because we'll need modify its contents later. 194 byte[] eocdBytes = new byte[inputApk.remaining()]; 195 inputApk.get(eocdBytes); 196 ByteBuffer eocd = ByteBuffer.wrap(eocdBytes); 197 eocd.order(inputApk.order()); 198 199 // Figure which which digests to use for APK contents. 200 Set<Integer> contentDigestAlgorithms = new HashSet<>(); 201 for (SignerConfig signerConfig : signerConfigs) { 202 for (int signatureAlgorithm : signerConfig.signatureAlgorithms) { 203 contentDigestAlgorithms.add( 204 getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm)); 205 } 206 } 207 208 // Compute digests of APK contents. 209 Map<Integer, byte[]> contentDigests; // digest algorithm ID -> digest 210 try { 211 contentDigests = 212 computeContentDigests( 213 contentDigestAlgorithms, 214 new ByteBuffer[] {beforeCentralDir, centralDir, eocd}); 215 } catch (DigestException e) { 216 throw new SignatureException("Failed to compute digests of APK", e); 217 } 218 219 // Sign the digests and wrap the signatures and signer info into an APK Signing Block. 220 ByteBuffer apkSigningBlock = 221 ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests)); 222 223 // Update Central Directory Offset in End of Central Directory Record. Central Directory 224 // follows the APK Signing Block and thus is shifted by the size of the APK Signing Block. 225 centralDirOffset += apkSigningBlock.remaining(); 226 eocd.clear(); 227 ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset); 228 229 // Follow the Java NIO pattern for ByteBuffer whose contents have been consumed. 230 originalInputApk.position(originalInputApk.limit()); 231 232 // Reset positions (to 0) and limits (to capacity) in the ByteBuffers below to follow the 233 // Java NIO pattern for ByteBuffers which are ready for their contents to be read by caller. 234 // Contrary to the name, this does not clear the contents of these ByteBuffer. 235 beforeCentralDir.clear(); 236 centralDir.clear(); 237 eocd.clear(); 238 239 // Insert APK Signing Block immediately before the ZIP Central Directory. 240 return new ByteBuffer[] { 241 beforeCentralDir, 242 apkSigningBlock, 243 centralDir, 244 eocd, 245 }; 246 } 247 248 private static Map<Integer, byte[]> computeContentDigests( 249 Set<Integer> digestAlgorithms, 250 ByteBuffer[] contents) throws DigestException { 251 // For each digest algorithm the result is computed as follows: 252 // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. 253 // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. 254 // No chunks are produced for empty (zero length) segments. 255 // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's 256 // length in bytes (uint32 little-endian) and the chunk's contents. 257 // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of 258 // chunks (uint32 little-endian) and the concatenation of digests of chunks of all 259 // segments in-order. 260 261 int chunkCount = 0; 262 for (ByteBuffer input : contents) { 263 chunkCount += getChunkCount(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 264 } 265 266 final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size()); 267 for (int digestAlgorithm : digestAlgorithms) { 268 int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); 269 byte[] concatenationOfChunkCountAndChunkDigests = 270 new byte[5 + chunkCount * digestOutputSizeBytes]; 271 concatenationOfChunkCountAndChunkDigests[0] = 0x5a; 272 setUnsignedInt32LittleEngian( 273 chunkCount, concatenationOfChunkCountAndChunkDigests, 1); 274 digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests); 275 } 276 277 int chunkIndex = 0; 278 byte[] chunkContentPrefix = new byte[5]; 279 chunkContentPrefix[0] = (byte) 0xa5; 280 // Optimization opportunity: digests of chunks can be computed in parallel. 281 for (ByteBuffer input : contents) { 282 while (input.hasRemaining()) { 283 int chunkSize = 284 Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); 285 final ByteBuffer chunk = getByteBuffer(input, chunkSize); 286 for (int digestAlgorithm : digestAlgorithms) { 287 String jcaAlgorithmName = 288 getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); 289 MessageDigest md; 290 try { 291 md = MessageDigest.getInstance(jcaAlgorithmName); 292 } catch (NoSuchAlgorithmException e) { 293 throw new DigestException( 294 jcaAlgorithmName + " MessageDigest not supported", e); 295 } 296 // Reset position to 0 and limit to capacity. Position would've been modified 297 // by the preceding iteration of this loop. NOTE: Contrary to the method name, 298 // this does not modify the contents of the chunk. 299 chunk.clear(); 300 setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1); 301 md.update(chunkContentPrefix); 302 md.update(chunk); 303 byte[] concatenationOfChunkCountAndChunkDigests = 304 digestsOfChunks.get(digestAlgorithm); 305 int expectedDigestSizeBytes = 306 getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm); 307 int actualDigestSizeBytes = 308 md.digest( 309 concatenationOfChunkCountAndChunkDigests, 310 5 + chunkIndex * expectedDigestSizeBytes, 311 expectedDigestSizeBytes); 312 if (actualDigestSizeBytes != expectedDigestSizeBytes) { 313 throw new DigestException( 314 "Unexpected output size of " + md.getAlgorithm() 315 + " digest: " + actualDigestSizeBytes); 316 } 317 } 318 chunkIndex++; 319 } 320 } 321 322 Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size()); 323 for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) { 324 int digestAlgorithm = entry.getKey(); 325 byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue(); 326 String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm); 327 MessageDigest md; 328 try { 329 md = MessageDigest.getInstance(jcaAlgorithmName); 330 } catch (NoSuchAlgorithmException e) { 331 throw new DigestException(jcaAlgorithmName + " MessageDigest not supported", e); 332 } 333 result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests)); 334 } 335 return result; 336 } 337 338 private static final int getChunkCount(int inputSize, int chunkSize) { 339 return (inputSize + chunkSize - 1) / chunkSize; 340 } 341 342 private static void setUnsignedInt32LittleEngian(int value, byte[] result, int offset) { 343 result[offset] = (byte) (value & 0xff); 344 result[offset + 1] = (byte) ((value >> 8) & 0xff); 345 result[offset + 2] = (byte) ((value >> 16) & 0xff); 346 result[offset + 3] = (byte) ((value >> 24) & 0xff); 347 } 348 349 private static byte[] generateApkSigningBlock( 350 List<SignerConfig> signerConfigs, 351 Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException { 352 byte[] apkSignatureSchemeV2Block = 353 generateApkSignatureSchemeV2Block(signerConfigs, contentDigests); 354 return generateApkSigningBlock(apkSignatureSchemeV2Block); 355 } 356 357 private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) { 358 // FORMAT: 359 // uint64: size (excluding this field) 360 // repeated ID-value pairs: 361 // uint64: size (excluding this field) 362 // uint32: ID 363 // (size - 4) bytes: value 364 // uint64: size (same as the one above) 365 // uint128: magic 366 367 int resultSize = 368 8 // size 369 + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair 370 + 8 // size 371 + 16 // magic 372 ; 373 ByteBuffer result = ByteBuffer.allocate(resultSize); 374 result.order(ByteOrder.LITTLE_ENDIAN); 375 long blockSizeFieldValue = resultSize - 8; 376 result.putLong(blockSizeFieldValue); 377 378 long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length; 379 result.putLong(pairSizeFieldValue); 380 result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID); 381 result.put(apkSignatureSchemeV2Block); 382 383 result.putLong(blockSizeFieldValue); 384 result.put(APK_SIGNING_BLOCK_MAGIC); 385 386 return result.array(); 387 } 388 389 private static byte[] generateApkSignatureSchemeV2Block( 390 List<SignerConfig> signerConfigs, 391 Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException { 392 // FORMAT: 393 // * length-prefixed sequence of length-prefixed signer blocks. 394 395 List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size()); 396 int signerNumber = 0; 397 for (SignerConfig signerConfig : signerConfigs) { 398 signerNumber++; 399 byte[] signerBlock; 400 try { 401 signerBlock = generateSignerBlock(signerConfig, contentDigests); 402 } catch (InvalidKeyException e) { 403 throw new InvalidKeyException("Signer #" + signerNumber + " failed", e); 404 } catch (SignatureException e) { 405 throw new SignatureException("Signer #" + signerNumber + " failed", e); 406 } 407 signerBlocks.add(signerBlock); 408 } 409 410 return encodeAsSequenceOfLengthPrefixedElements( 411 new byte[][] { 412 encodeAsSequenceOfLengthPrefixedElements(signerBlocks), 413 }); 414 } 415 416 private static byte[] generateSignerBlock( 417 SignerConfig signerConfig, 418 Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException { 419 if (signerConfig.certificates.isEmpty()) { 420 throw new SignatureException("No certificates configured for signer"); 421 } 422 PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); 423 424 byte[] encodedPublicKey = encodePublicKey(publicKey); 425 426 V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData(); 427 try { 428 signedData.certificates = encodeCertificates(signerConfig.certificates); 429 } catch (CertificateEncodingException e) { 430 throw new SignatureException("Failed to encode certificates", e); 431 } 432 433 List<Pair<Integer, byte[]>> digests = 434 new ArrayList<>(signerConfig.signatureAlgorithms.size()); 435 for (int signatureAlgorithm : signerConfig.signatureAlgorithms) { 436 int contentDigestAlgorithm = 437 getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm); 438 byte[] contentDigest = contentDigests.get(contentDigestAlgorithm); 439 if (contentDigest == null) { 440 throw new RuntimeException( 441 getContentDigestAlgorithmJcaDigestAlgorithm(contentDigestAlgorithm) 442 + " content digest for " 443 + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm) 444 + " not computed"); 445 } 446 digests.add(Pair.create(signatureAlgorithm, contentDigest)); 447 } 448 signedData.digests = digests; 449 450 V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer(); 451 // FORMAT: 452 // * length-prefixed sequence of length-prefixed digests: 453 // * uint32: signature algorithm ID 454 // * length-prefixed bytes: digest of contents 455 // * length-prefixed sequence of certificates: 456 // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded). 457 // * length-prefixed sequence of length-prefixed additional attributes: 458 // * uint32: ID 459 // * (length - 4) bytes: value 460 signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] { 461 encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests), 462 encodeAsSequenceOfLengthPrefixedElements(signedData.certificates), 463 // additional attributes 464 new byte[0], 465 }); 466 signer.publicKey = encodedPublicKey; 467 signer.signatures = new ArrayList<>(); 468 for (int signatureAlgorithm : signerConfig.signatureAlgorithms) { 469 Pair<String, ? extends AlgorithmParameterSpec> signatureParams = 470 getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm); 471 String jcaSignatureAlgorithm = signatureParams.getFirst(); 472 AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureParams.getSecond(); 473 byte[] signatureBytes; 474 try { 475 Signature signature = Signature.getInstance(jcaSignatureAlgorithm); 476 signature.initSign(signerConfig.privateKey); 477 if (jcaSignatureAlgorithmParams != null) { 478 signature.setParameter(jcaSignatureAlgorithmParams); 479 } 480 signature.update(signer.signedData); 481 signatureBytes = signature.sign(); 482 } catch (InvalidKeyException e) { 483 throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e); 484 } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException 485 | SignatureException e) { 486 throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e); 487 } 488 489 try { 490 Signature signature = Signature.getInstance(jcaSignatureAlgorithm); 491 signature.initVerify(publicKey); 492 if (jcaSignatureAlgorithmParams != null) { 493 signature.setParameter(jcaSignatureAlgorithmParams); 494 } 495 signature.update(signer.signedData); 496 if (!signature.verify(signatureBytes)) { 497 throw new SignatureException("Signature did not verify"); 498 } 499 } catch (InvalidKeyException e) { 500 throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm 501 + " signature using public key from certificate", e); 502 } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException 503 | SignatureException e) { 504 throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm 505 + " signature using public key from certificate", e); 506 } 507 508 signer.signatures.add(Pair.create(signatureAlgorithm, signatureBytes)); 509 } 510 511 // FORMAT: 512 // * length-prefixed signed data 513 // * length-prefixed sequence of length-prefixed signatures: 514 // * uint32: signature algorithm ID 515 // * length-prefixed bytes: signature of signed data 516 // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded) 517 return encodeAsSequenceOfLengthPrefixedElements( 518 new byte[][] { 519 signer.signedData, 520 encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( 521 signer.signatures), 522 signer.publicKey, 523 }); 524 } 525 526 private static final class V2SignatureSchemeBlock { 527 private static final class Signer { 528 public byte[] signedData; 529 public List<Pair<Integer, byte[]>> signatures; 530 public byte[] publicKey; 531 } 532 533 private static final class SignedData { 534 public List<Pair<Integer, byte[]>> digests; 535 public List<byte[]> certificates; 536 } 537 } 538 539 private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException { 540 byte[] encodedPublicKey = null; 541 if ("X.509".equals(publicKey.getFormat())) { 542 encodedPublicKey = publicKey.getEncoded(); 543 } 544 if (encodedPublicKey == null) { 545 try { 546 encodedPublicKey = 547 KeyFactory.getInstance(publicKey.getAlgorithm()) 548 .getKeySpec(publicKey, X509EncodedKeySpec.class) 549 .getEncoded(); 550 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 551 throw new InvalidKeyException( 552 "Failed to obtain X.509 encoded form of public key " + publicKey 553 + " of class " + publicKey.getClass().getName(), 554 e); 555 } 556 } 557 if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) { 558 throw new InvalidKeyException( 559 "Failed to obtain X.509 encoded form of public key " + publicKey 560 + " of class " + publicKey.getClass().getName()); 561 } 562 return encodedPublicKey; 563 } 564 565 public static List<byte[]> encodeCertificates(List<X509Certificate> certificates) 566 throws CertificateEncodingException { 567 List<byte[]> result = new ArrayList<>(); 568 for (X509Certificate certificate : certificates) { 569 result.add(certificate.getEncoded()); 570 } 571 return result; 572 } 573 574 private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) { 575 return encodeAsSequenceOfLengthPrefixedElements( 576 sequence.toArray(new byte[sequence.size()][])); 577 } 578 579 private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) { 580 int payloadSize = 0; 581 for (byte[] element : sequence) { 582 payloadSize += 4 + element.length; 583 } 584 ByteBuffer result = ByteBuffer.allocate(payloadSize); 585 result.order(ByteOrder.LITTLE_ENDIAN); 586 for (byte[] element : sequence) { 587 result.putInt(element.length); 588 result.put(element); 589 } 590 return result.array(); 591 } 592 593 private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( 594 List<Pair<Integer, byte[]>> sequence) { 595 int resultSize = 0; 596 for (Pair<Integer, byte[]> element : sequence) { 597 resultSize += 12 + element.getSecond().length; 598 } 599 ByteBuffer result = ByteBuffer.allocate(resultSize); 600 result.order(ByteOrder.LITTLE_ENDIAN); 601 for (Pair<Integer, byte[]> element : sequence) { 602 byte[] second = element.getSecond(); 603 result.putInt(8 + second.length); 604 result.putInt(element.getFirst()); 605 result.putInt(second.length); 606 result.put(second); 607 } 608 return result.array(); 609 } 610 611 /** 612 * Relative <em>get</em> method for reading {@code size} number of bytes from the current 613 * position of this buffer. 614 * 615 * <p>This method reads the next {@code size} bytes at this buffer's current position, 616 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to 617 * {@code size}, byte order set to this buffer's byte order; and then increments the position by 618 * {@code size}. 619 */ 620 private static ByteBuffer getByteBuffer(ByteBuffer source, int size) { 621 if (size < 0) { 622 throw new IllegalArgumentException("size: " + size); 623 } 624 int originalLimit = source.limit(); 625 int position = source.position(); 626 int limit = position + size; 627 if ((limit < position) || (limit > originalLimit)) { 628 throw new BufferUnderflowException(); 629 } 630 source.limit(limit); 631 try { 632 ByteBuffer result = source.slice(); 633 result.order(source.order()); 634 source.position(limit); 635 return result; 636 } finally { 637 source.limit(originalLimit); 638 } 639 } 640 641 private static Pair<String, ? extends AlgorithmParameterSpec> 642 getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) { 643 switch (sigAlgorithm) { 644 case SIGNATURE_RSA_PSS_WITH_SHA256: 645 return Pair.create( 646 "SHA256withRSA/PSS", 647 new PSSParameterSpec( 648 "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1)); 649 case SIGNATURE_RSA_PSS_WITH_SHA512: 650 return Pair.create( 651 "SHA512withRSA/PSS", 652 new PSSParameterSpec( 653 "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1)); 654 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 655 return Pair.create("SHA256withRSA", null); 656 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 657 return Pair.create("SHA512withRSA", null); 658 case SIGNATURE_ECDSA_WITH_SHA256: 659 return Pair.create("SHA256withECDSA", null); 660 case SIGNATURE_ECDSA_WITH_SHA512: 661 return Pair.create("SHA512withECDSA", null); 662 case SIGNATURE_DSA_WITH_SHA256: 663 return Pair.create("SHA256withDSA", null); 664 case SIGNATURE_DSA_WITH_SHA512: 665 return Pair.create("SHA512withDSA", null); 666 default: 667 throw new IllegalArgumentException( 668 "Unknown signature algorithm: 0x" 669 + Long.toHexString(sigAlgorithm & 0xffffffff)); 670 } 671 } 672 673 private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) { 674 switch (sigAlgorithm) { 675 case SIGNATURE_RSA_PSS_WITH_SHA256: 676 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256: 677 case SIGNATURE_ECDSA_WITH_SHA256: 678 case SIGNATURE_DSA_WITH_SHA256: 679 return CONTENT_DIGEST_CHUNKED_SHA256; 680 case SIGNATURE_RSA_PSS_WITH_SHA512: 681 case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512: 682 case SIGNATURE_ECDSA_WITH_SHA512: 683 case SIGNATURE_DSA_WITH_SHA512: 684 return CONTENT_DIGEST_CHUNKED_SHA512; 685 default: 686 throw new IllegalArgumentException( 687 "Unknown signature algorithm: 0x" 688 + Long.toHexString(sigAlgorithm & 0xffffffff)); 689 } 690 } 691 692 private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) { 693 switch (digestAlgorithm) { 694 case CONTENT_DIGEST_CHUNKED_SHA256: 695 return "SHA-256"; 696 case CONTENT_DIGEST_CHUNKED_SHA512: 697 return "SHA-512"; 698 default: 699 throw new IllegalArgumentException( 700 "Unknown content digest algorthm: " + digestAlgorithm); 701 } 702 } 703 704 private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) { 705 switch (digestAlgorithm) { 706 case CONTENT_DIGEST_CHUNKED_SHA256: 707 return 256 / 8; 708 case CONTENT_DIGEST_CHUNKED_SHA512: 709 return 512 / 8; 710 default: 711 throw new IllegalArgumentException( 712 "Unknown content digest algorthm: " + digestAlgorithm); 713 } 714 } 715 716 /** 717 * Indicates that APK file could not be parsed. 718 */ 719 public static class ApkParseException extends Exception { 720 private static final long serialVersionUID = 1L; 721 722 public ApkParseException(String message) { 723 super(message); 724 } 725 726 public ApkParseException(String message, Throwable cause) { 727 super(message, cause); 728 } 729 } 730} 731