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