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