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