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