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