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