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.apksig.internal.apk.v1; 18 19 import static com.android.apksig.Constants.MAX_APK_SIGNERS; 20 import static com.android.apksig.Constants.OID_RSA_ENCRYPTION; 21 import static com.android.apksig.internal.pkcs7.AlgorithmIdentifier.getSignerInfoDigestAlgorithmOid; 22 import static com.android.apksig.internal.pkcs7.AlgorithmIdentifier.getSignerInfoSignatureAlgorithm; 23 24 import com.android.apksig.apk.ApkFormatException; 25 import com.android.apksig.internal.apk.ApkSigningBlockUtils; 26 import com.android.apksig.internal.asn1.Asn1EncodingException; 27 import com.android.apksig.internal.jar.ManifestWriter; 28 import com.android.apksig.internal.jar.SignatureFileWriter; 29 import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; 30 import com.android.apksig.internal.util.Pair; 31 32 import java.io.ByteArrayInputStream; 33 import java.io.ByteArrayOutputStream; 34 import java.io.IOException; 35 import java.security.InvalidKeyException; 36 import java.security.MessageDigest; 37 import java.security.NoSuchAlgorithmException; 38 import java.security.PrivateKey; 39 import java.security.PublicKey; 40 import java.security.Signature; 41 import java.security.SignatureException; 42 import java.security.cert.CertificateEncodingException; 43 import java.security.cert.CertificateException; 44 import java.security.cert.X509Certificate; 45 import java.util.ArrayList; 46 import java.util.Base64; 47 import java.util.Collections; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Locale; 51 import java.util.Map; 52 import java.util.Set; 53 import java.util.SortedMap; 54 import java.util.TreeMap; 55 import java.util.jar.Attributes; 56 import java.util.jar.Manifest; 57 58 /** 59 * APK signer which uses JAR signing (aka v1 signing scheme). 60 * 61 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a> 62 */ 63 public abstract class V1SchemeSigner { 64 public static final String MANIFEST_ENTRY_NAME = V1SchemeConstants.MANIFEST_ENTRY_NAME; 65 66 private static final Attributes.Name ATTRIBUTE_NAME_CREATED_BY = 67 new Attributes.Name("Created-By"); 68 private static final String ATTRIBUTE_VALUE_MANIFEST_VERSION = "1.0"; 69 private static final String ATTRIBUTE_VALUE_SIGNATURE_VERSION = "1.0"; 70 71 private static final Attributes.Name SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME = 72 new Attributes.Name(V1SchemeConstants.SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR); 73 74 /** 75 * Signer configuration. 76 */ 77 public static class SignerConfig { 78 /** Name. */ 79 public String name; 80 81 /** Private key. */ 82 public PrivateKey privateKey; 83 84 /** 85 * Certificates, with the first certificate containing the public key corresponding to 86 * {@link #privateKey}. 87 */ 88 public List<X509Certificate> certificates; 89 90 /** 91 * Digest algorithm used for the signature. 92 */ 93 public DigestAlgorithm signatureDigestAlgorithm; 94 95 /** 96 * If DSA is the signing algorithm, whether or not deterministic DSA signing should be used. 97 */ 98 public boolean deterministicDsaSigning; 99 } 100 101 /** Hidden constructor to prevent instantiation. */ V1SchemeSigner()102 private V1SchemeSigner() {} 103 104 /** 105 * Gets the JAR signing digest algorithm to be used for signing an APK using the provided key. 106 * 107 * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see 108 * AndroidManifest.xml minSdkVersion attribute) 109 * 110 * @throws InvalidKeyException if the provided key is not suitable for signing APKs using 111 * JAR signing (aka v1 signature scheme) 112 */ getSuggestedSignatureDigestAlgorithm( PublicKey signingKey, int minSdkVersion)113 public static DigestAlgorithm getSuggestedSignatureDigestAlgorithm( 114 PublicKey signingKey, int minSdkVersion) throws InvalidKeyException { 115 String keyAlgorithm = signingKey.getAlgorithm(); 116 if ("RSA".equalsIgnoreCase(keyAlgorithm) || OID_RSA_ENCRYPTION.equals((keyAlgorithm))) { 117 // Prior to API Level 18, only SHA-1 can be used with RSA. 118 if (minSdkVersion < 18) { 119 return DigestAlgorithm.SHA1; 120 } 121 return DigestAlgorithm.SHA256; 122 } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { 123 // Prior to API Level 21, only SHA-1 can be used with DSA 124 if (minSdkVersion < 21) { 125 return DigestAlgorithm.SHA1; 126 } else { 127 return DigestAlgorithm.SHA256; 128 } 129 } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { 130 if (minSdkVersion < 18) { 131 throw new InvalidKeyException( 132 "ECDSA signatures only supported for minSdkVersion 18 and higher"); 133 } 134 return DigestAlgorithm.SHA256; 135 } else { 136 throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); 137 } 138 } 139 140 /** 141 * Returns a safe version of the provided signer name. 142 */ getSafeSignerName(String name)143 public static String getSafeSignerName(String name) { 144 if (name.isEmpty()) { 145 throw new IllegalArgumentException("Empty name"); 146 } 147 148 // According to https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html, the 149 // name must not be longer than 8 characters and may contain only A-Z, 0-9, _, and -. 150 StringBuilder result = new StringBuilder(); 151 char[] nameCharsUpperCase = name.toUpperCase(Locale.US).toCharArray(); 152 for (int i = 0; i < Math.min(nameCharsUpperCase.length, 8); i++) { 153 char c = nameCharsUpperCase[i]; 154 if (((c >= 'A') && (c <= 'Z')) 155 || ((c >= '0') && (c <= '9')) 156 || (c == '-') 157 || (c == '_')) { 158 result.append(c); 159 } else { 160 result.append('_'); 161 } 162 } 163 return result.toString(); 164 } 165 166 /** 167 * Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm. 168 */ getMessageDigestInstance(DigestAlgorithm digestAlgorithm)169 private static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm) 170 throws NoSuchAlgorithmException { 171 String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); 172 return MessageDigest.getInstance(jcaAlgorithm); 173 } 174 175 /** 176 * Returns the JCA {@link MessageDigest} algorithm corresponding to the provided digest 177 * algorithm. 178 */ getJcaMessageDigestAlgorithm(DigestAlgorithm digestAlgorithm)179 public static String getJcaMessageDigestAlgorithm(DigestAlgorithm digestAlgorithm) { 180 return digestAlgorithm.getJcaMessageDigestAlgorithm(); 181 } 182 183 /** 184 * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's 185 * manifest. 186 */ isJarEntryDigestNeededInManifest(String entryName)187 public static boolean isJarEntryDigestNeededInManifest(String entryName) { 188 // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File 189 190 // Entries which represent directories sould not be listed in the manifest. 191 if (entryName.endsWith("/")) { 192 return false; 193 } 194 195 // Entries outside of META-INF must be listed in the manifest. 196 if (!entryName.startsWith("META-INF/")) { 197 return true; 198 } 199 // Entries in subdirectories of META-INF must be listed in the manifest. 200 if (entryName.indexOf('/', "META-INF/".length()) != -1) { 201 return true; 202 } 203 204 // Ignored file names (case-insensitive) in META-INF directory: 205 // MANIFEST.MF 206 // *.SF 207 // *.RSA 208 // *.DSA 209 // *.EC 210 // SIG-* 211 String fileNameLowerCase = 212 entryName.substring("META-INF/".length()).toLowerCase(Locale.US); 213 if (("manifest.mf".equals(fileNameLowerCase)) 214 || (fileNameLowerCase.endsWith(".sf")) 215 || (fileNameLowerCase.endsWith(".rsa")) 216 || (fileNameLowerCase.endsWith(".dsa")) 217 || (fileNameLowerCase.endsWith(".ec")) 218 || (fileNameLowerCase.startsWith("sig-"))) { 219 return false; 220 } 221 return true; 222 } 223 224 /** 225 * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of 226 * JAR entries which need to be added to the APK as part of the signature. 227 * 228 * @param signerConfigs signer configurations, one for each signer. At least one signer config 229 * must be provided. 230 * 231 * @throws ApkFormatException if the source manifest is malformed 232 * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is 233 * missing 234 * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or 235 * cannot be used in general 236 * @throws SignatureException if an error occurs when computing digests of generating 237 * signatures 238 */ sign( List<SignerConfig> signerConfigs, DigestAlgorithm jarEntryDigestAlgorithm, Map<String, byte[]> jarEntryDigests, List<Integer> apkSigningSchemeIds, byte[] sourceManifestBytes, String createdBy)239 public static List<Pair<String, byte[]>> sign( 240 List<SignerConfig> signerConfigs, 241 DigestAlgorithm jarEntryDigestAlgorithm, 242 Map<String, byte[]> jarEntryDigests, 243 List<Integer> apkSigningSchemeIds, 244 byte[] sourceManifestBytes, 245 String createdBy) 246 throws NoSuchAlgorithmException, ApkFormatException, InvalidKeyException, 247 CertificateException, SignatureException { 248 if (signerConfigs.isEmpty()) { 249 throw new IllegalArgumentException("At least one signer config must be provided"); 250 } 251 if (signerConfigs.size() > MAX_APK_SIGNERS) { 252 throw new IllegalArgumentException( 253 "APK Signature Scheme v1 only supports a maximum of " + MAX_APK_SIGNERS + ", " 254 + signerConfigs.size() + " provided"); 255 } 256 OutputManifestFile manifest = 257 generateManifestFile( 258 jarEntryDigestAlgorithm, jarEntryDigests, sourceManifestBytes); 259 260 return signManifest( 261 signerConfigs, jarEntryDigestAlgorithm, apkSigningSchemeIds, createdBy, manifest); 262 } 263 264 /** 265 * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of 266 * JAR entries which need to be added to the APK as part of the signature. 267 * 268 * @param signerConfigs signer configurations, one for each signer. At least one signer config 269 * must be provided. 270 * 271 * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or 272 * cannot be used in general 273 * @throws SignatureException if an error occurs when computing digests of generating 274 * signatures 275 */ signManifest( List<SignerConfig> signerConfigs, DigestAlgorithm digestAlgorithm, List<Integer> apkSigningSchemeIds, String createdBy, OutputManifestFile manifest)276 public static List<Pair<String, byte[]>> signManifest( 277 List<SignerConfig> signerConfigs, 278 DigestAlgorithm digestAlgorithm, 279 List<Integer> apkSigningSchemeIds, 280 String createdBy, 281 OutputManifestFile manifest) 282 throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, 283 SignatureException { 284 if (signerConfigs.isEmpty()) { 285 throw new IllegalArgumentException("At least one signer config must be provided"); 286 } 287 288 // For each signer output .SF and .(RSA|DSA|EC) file, then output MANIFEST.MF. 289 List<Pair<String, byte[]>> signatureJarEntries = 290 new ArrayList<>(2 * signerConfigs.size() + 1); 291 byte[] sfBytes = 292 generateSignatureFile(apkSigningSchemeIds, digestAlgorithm, createdBy, manifest); 293 for (SignerConfig signerConfig : signerConfigs) { 294 String signerName = signerConfig.name; 295 byte[] signatureBlock; 296 try { 297 signatureBlock = generateSignatureBlock(signerConfig, sfBytes); 298 } catch (InvalidKeyException e) { 299 throw new InvalidKeyException( 300 "Failed to sign using signer \"" + signerName + "\"", e); 301 } catch (CertificateException e) { 302 throw new CertificateException( 303 "Failed to sign using signer \"" + signerName + "\"", e); 304 } catch (SignatureException e) { 305 throw new SignatureException( 306 "Failed to sign using signer \"" + signerName + "\"", e); 307 } 308 signatureJarEntries.add(Pair.of("META-INF/" + signerName + ".SF", sfBytes)); 309 PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); 310 String signatureBlockFileName = 311 "META-INF/" + signerName + "." 312 + publicKey.getAlgorithm().toUpperCase(Locale.US); 313 signatureJarEntries.add( 314 Pair.of(signatureBlockFileName, signatureBlock)); 315 } 316 signatureJarEntries.add(Pair.of(V1SchemeConstants.MANIFEST_ENTRY_NAME, manifest.contents)); 317 return signatureJarEntries; 318 } 319 320 /** 321 * Returns the names of JAR entries which this signer will produce as part of v1 signature. 322 */ getOutputEntryNames(List<SignerConfig> signerConfigs)323 public static Set<String> getOutputEntryNames(List<SignerConfig> signerConfigs) { 324 Set<String> result = new HashSet<>(2 * signerConfigs.size() + 1); 325 for (SignerConfig signerConfig : signerConfigs) { 326 String signerName = signerConfig.name; 327 result.add("META-INF/" + signerName + ".SF"); 328 PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); 329 String signatureBlockFileName = 330 "META-INF/" + signerName + "." 331 + publicKey.getAlgorithm().toUpperCase(Locale.US); 332 result.add(signatureBlockFileName); 333 } 334 result.add(V1SchemeConstants.MANIFEST_ENTRY_NAME); 335 return result; 336 } 337 338 /** 339 * Generated and returns the {@code META-INF/MANIFEST.MF} file based on the provided (optional) 340 * input {@code MANIFEST.MF} and digests of JAR entries covered by the manifest. 341 */ generateManifestFile( DigestAlgorithm jarEntryDigestAlgorithm, Map<String, byte[]> jarEntryDigests, byte[] sourceManifestBytes)342 public static OutputManifestFile generateManifestFile( 343 DigestAlgorithm jarEntryDigestAlgorithm, 344 Map<String, byte[]> jarEntryDigests, 345 byte[] sourceManifestBytes) throws ApkFormatException { 346 Manifest sourceManifest = null; 347 if (sourceManifestBytes != null) { 348 try { 349 sourceManifest = new Manifest(new ByteArrayInputStream(sourceManifestBytes)); 350 } catch (IOException e) { 351 throw new ApkFormatException("Malformed source META-INF/MANIFEST.MF", e); 352 } 353 } 354 ByteArrayOutputStream manifestOut = new ByteArrayOutputStream(); 355 Attributes mainAttrs = new Attributes(); 356 // Copy the main section from the source manifest (if provided). Otherwise use defaults. 357 // NOTE: We don't output our own Created-By header because this signer did not create the 358 // JAR/APK being signed -- the signer only adds signatures to the already existing 359 // JAR/APK. 360 if (sourceManifest != null) { 361 mainAttrs.putAll(sourceManifest.getMainAttributes()); 362 } else { 363 mainAttrs.put(Attributes.Name.MANIFEST_VERSION, ATTRIBUTE_VALUE_MANIFEST_VERSION); 364 } 365 366 try { 367 ManifestWriter.writeMainSection(manifestOut, mainAttrs); 368 } catch (IOException e) { 369 throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); 370 } 371 372 List<String> sortedEntryNames = new ArrayList<>(jarEntryDigests.keySet()); 373 Collections.sort(sortedEntryNames); 374 SortedMap<String, byte[]> invidualSectionsContents = new TreeMap<>(); 375 String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm); 376 for (String entryName : sortedEntryNames) { 377 checkEntryNameValid(entryName); 378 byte[] entryDigest = jarEntryDigests.get(entryName); 379 Attributes entryAttrs = new Attributes(); 380 entryAttrs.putValue( 381 entryDigestAttributeName, 382 Base64.getEncoder().encodeToString(entryDigest)); 383 ByteArrayOutputStream sectionOut = new ByteArrayOutputStream(); 384 byte[] sectionBytes; 385 try { 386 ManifestWriter.writeIndividualSection(sectionOut, entryName, entryAttrs); 387 sectionBytes = sectionOut.toByteArray(); 388 manifestOut.write(sectionBytes); 389 } catch (IOException e) { 390 throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); 391 } 392 invidualSectionsContents.put(entryName, sectionBytes); 393 } 394 395 OutputManifestFile result = new OutputManifestFile(); 396 result.contents = manifestOut.toByteArray(); 397 result.mainSectionAttributes = mainAttrs; 398 result.individualSectionsContents = invidualSectionsContents; 399 return result; 400 } 401 checkEntryNameValid(String name)402 private static void checkEntryNameValid(String name) throws ApkFormatException { 403 // JAR signing spec says CR, LF, and NUL are not permitted in entry names 404 // CR or LF in entry names will result in malformed MANIFEST.MF and .SF files because there 405 // is no way to escape characters in MANIFEST.MF and .SF files. NUL can, presumably, cause 406 // issues when parsing using C and C++ like languages. 407 for (char c : name.toCharArray()) { 408 if ((c == '\r') || (c == '\n') || (c == 0)) { 409 throw new ApkFormatException( 410 String.format( 411 "Unsupported character 0x%1$02x in ZIP entry name \"%2$s\"", 412 (int) c, 413 name)); 414 } 415 } 416 } 417 418 public static class OutputManifestFile { 419 public byte[] contents; 420 public SortedMap<String, byte[]> individualSectionsContents; 421 public Attributes mainSectionAttributes; 422 } 423 generateSignatureFile( List<Integer> apkSignatureSchemeIds, DigestAlgorithm manifestDigestAlgorithm, String createdBy, OutputManifestFile manifest)424 private static byte[] generateSignatureFile( 425 List<Integer> apkSignatureSchemeIds, 426 DigestAlgorithm manifestDigestAlgorithm, 427 String createdBy, 428 OutputManifestFile manifest) throws NoSuchAlgorithmException { 429 Manifest sf = new Manifest(); 430 Attributes mainAttrs = sf.getMainAttributes(); 431 mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION); 432 mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, createdBy); 433 if (!apkSignatureSchemeIds.isEmpty()) { 434 // Add APK Signature Scheme v2 (and newer) signature stripping protection. 435 // This attribute indicates that this APK is supposed to have been signed using one or 436 // more APK-specific signature schemes in addition to the standard JAR signature scheme 437 // used by this code. APK signature verifier should reject the APK if it does not 438 // contain a signature for the signature scheme the verifier prefers out of this set. 439 StringBuilder attrValue = new StringBuilder(); 440 for (int id : apkSignatureSchemeIds) { 441 if (attrValue.length() > 0) { 442 attrValue.append(", "); 443 } 444 attrValue.append(String.valueOf(id)); 445 } 446 mainAttrs.put( 447 SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME, 448 attrValue.toString()); 449 } 450 451 // Add main attribute containing the digest of MANIFEST.MF. 452 MessageDigest md = getMessageDigestInstance(manifestDigestAlgorithm); 453 mainAttrs.putValue( 454 getManifestDigestAttributeName(manifestDigestAlgorithm), 455 Base64.getEncoder().encodeToString(md.digest(manifest.contents))); 456 ByteArrayOutputStream out = new ByteArrayOutputStream(); 457 try { 458 SignatureFileWriter.writeMainSection(out, mainAttrs); 459 } catch (IOException e) { 460 throw new RuntimeException("Failed to write in-memory .SF file", e); 461 } 462 String entryDigestAttributeName = getEntryDigestAttributeName(manifestDigestAlgorithm); 463 for (Map.Entry<String, byte[]> manifestSection 464 : manifest.individualSectionsContents.entrySet()) { 465 String sectionName = manifestSection.getKey(); 466 byte[] sectionContents = manifestSection.getValue(); 467 byte[] sectionDigest = md.digest(sectionContents); 468 Attributes attrs = new Attributes(); 469 attrs.putValue( 470 entryDigestAttributeName, 471 Base64.getEncoder().encodeToString(sectionDigest)); 472 473 try { 474 SignatureFileWriter.writeIndividualSection(out, sectionName, attrs); 475 } catch (IOException e) { 476 throw new RuntimeException("Failed to write in-memory .SF file", e); 477 } 478 } 479 480 // A bug in the java.util.jar implementation of Android platforms up to version 1.6 will 481 // cause a spurious IOException to be thrown if the length of the signature file is a 482 // multiple of 1024 bytes. As a workaround, add an extra CRLF in this case. 483 if ((out.size() > 0) && ((out.size() % 1024) == 0)) { 484 try { 485 SignatureFileWriter.writeSectionDelimiter(out); 486 } catch (IOException e) { 487 throw new RuntimeException("Failed to write to ByteArrayOutputStream", e); 488 } 489 } 490 491 return out.toByteArray(); 492 } 493 494 495 496 /** 497 * Generates the CMS PKCS #7 signature block corresponding to the provided signature file and 498 * signing configuration. 499 */ generateSignatureBlock( SignerConfig signerConfig, byte[] signatureFileBytes)500 private static byte[] generateSignatureBlock( 501 SignerConfig signerConfig, byte[] signatureFileBytes) 502 throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, 503 SignatureException { 504 // Obtain relevant bits of signing configuration 505 List<X509Certificate> signerCerts = signerConfig.certificates; 506 X509Certificate signingCert = signerCerts.get(0); 507 PublicKey publicKey = signingCert.getPublicKey(); 508 DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm; 509 Pair<String, AlgorithmIdentifier> signatureAlgs = 510 getSignerInfoSignatureAlgorithm(publicKey, digestAlgorithm, 511 signerConfig.deterministicDsaSigning); 512 String jcaSignatureAlgorithm = signatureAlgs.getFirst(); 513 514 // Generate the cryptographic signature of the signature file 515 byte[] signatureBytes; 516 try { 517 Signature signature = Signature.getInstance(jcaSignatureAlgorithm); 518 signature.initSign(signerConfig.privateKey); 519 signature.update(signatureFileBytes); 520 signatureBytes = signature.sign(); 521 } catch (InvalidKeyException e) { 522 throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e); 523 } catch (SignatureException e) { 524 throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e); 525 } 526 527 // Verify the signature against the public key in the signing certificate 528 try { 529 Signature signature = Signature.getInstance(jcaSignatureAlgorithm); 530 signature.initVerify(publicKey); 531 signature.update(signatureFileBytes); 532 if (!signature.verify(signatureBytes)) { 533 throw new SignatureException("Signature did not verify"); 534 } 535 } catch (InvalidKeyException e) { 536 throw new InvalidKeyException( 537 "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" 538 + " public key from certificate", 539 e); 540 } catch (SignatureException e) { 541 throw new SignatureException( 542 "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" 543 + " public key from certificate", 544 e); 545 } 546 547 AlgorithmIdentifier digestAlgorithmId = 548 getSignerInfoDigestAlgorithmOid(digestAlgorithm); 549 AlgorithmIdentifier signatureAlgorithmId = signatureAlgs.getSecond(); 550 try { 551 return ApkSigningBlockUtils.generatePkcs7DerEncodedMessage( 552 signatureBytes, 553 null, 554 signerCerts, digestAlgorithmId, 555 signatureAlgorithmId); 556 } catch (Asn1EncodingException | CertificateEncodingException ex) { 557 throw new SignatureException("Failed to encode signature block"); 558 } 559 } 560 561 562 getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm)563 private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) { 564 switch (digestAlgorithm) { 565 case SHA1: 566 return "SHA1-Digest"; 567 case SHA256: 568 return "SHA-256-Digest"; 569 default: 570 throw new IllegalArgumentException( 571 "Unexpected content digest algorithm: " + digestAlgorithm); 572 } 573 } 574 getManifestDigestAttributeName(DigestAlgorithm digestAlgorithm)575 private static String getManifestDigestAttributeName(DigestAlgorithm digestAlgorithm) { 576 switch (digestAlgorithm) { 577 case SHA1: 578 return "SHA1-Digest-Manifest"; 579 case SHA256: 580 return "SHA-256-Digest-Manifest"; 581 default: 582 throw new IllegalArgumentException( 583 "Unexpected content digest algorithm: " + digestAlgorithm); 584 } 585 } 586 } 587