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