Merge "Let caller handle NoSuchAlgorithmException."
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
index 36f2a08..6a148ca 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
@@ -19,6 +19,7 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
 import java.security.SignatureException;
 import java.util.List;
 
@@ -182,13 +183,17 @@
      *         request must be fulfilled before
      *         {@link #outputZipSections(DataSource, DataSource, DataSource)} is invoked.
      *
+     * @throws NoSuchAlgorithmException if a signature could not be generated because a required
+     *         cryptographic algorithm implementation is missing
      * @throws InvalidKeyException if a signature could not be generated because a signing key is
      *         not suitable for generating the signature
-     * @throws SignatureException if an error occurred while generating the JAR signature
+     * @throws SignatureException if an error occurred while generating a signature
      * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
      *         entries, or if the engine is closed
      */
-    OutputJarSignatureRequest outputJarEntries() throws InvalidKeyException, SignatureException;
+    OutputJarSignatureRequest outputJarEntries()
+            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
+                    IllegalStateException;
 
     /**
      * Indicates to this engine that the ZIP sections comprising the output APK have been output.
@@ -207,16 +212,20 @@
      *         {@link #outputDone()} is invoked.
      *
      * @throws IOException if an I/O error occurs while reading the provided ZIP sections
+     * @throws NoSuchAlgorithmException if a signature could not be generated because a required
+     *         cryptographic algorithm implementation is missing
      * @throws InvalidKeyException if a signature could not be generated because a signing key is
      *         not suitable for generating the signature
-     * @throws SignatureException if an error occurred while generating the APK's signature
+     * @throws SignatureException if an error occurred while generating a signature
      * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
      *         entries or to output JAR signature, or if the engine is closed
      */
     OutputApkSigningBlockRequest outputZipSections(
             DataSource zipEntries,
             DataSource zipCentralDirectory,
-            DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException;
+            DataSource zipEocd)
+                    throws IOException, NoSuchAlgorithmException, InvalidKeyException,
+                            SignatureException, IllegalStateException;
 
     /**
      * Indicates to this engine that the signed APK was output.
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
index c3999b5..d509a48 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
@@ -26,6 +26,7 @@
 import com.android.apksigner.core.zip.ZipFormatException;
 
 import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -61,9 +62,11 @@
      *
      * @throws IOException if an I/O error is encountered while reading the APK
      * @throws ZipFormatException if the APK is malformed at ZIP format level
+     * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
+     *         required cryptographic algorithm implementation is missing
      */
     public Result verify(DataSource apk, int minSdkVersion, int maxSdkVersion)
-            throws IOException, ZipFormatException {
+            throws IOException, ZipFormatException, NoSuchAlgorithmException {
         if (minSdkVersion < 0) {
             throw new IllegalArgumentException(
                     "minSdkVersion must not be negative: " + minSdkVersion);
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
index 52042ac..75b0b20 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
@@ -293,7 +293,7 @@
 
     @Override
     public OutputJarSignatureRequest outputJarEntries()
-            throws InvalidKeyException, SignatureException {
+            throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
         checkNotClosed();
 
         if (!mV1SignaturePending) {
@@ -413,7 +413,9 @@
     public OutputApkSigningBlockRequest outputZipSections(
             DataSource zipEntries,
             DataSource zipCentralDirectory,
-            DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException {
+            DataSource zipEocd)
+                    throws IOException, InvalidKeyException, SignatureException,
+                            NoSuchAlgorithmException {
         checkNotClosed();
         checkV1SigningDoneIfEnabled();
         if (!mV2SigningEnabled) {
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
index 1a4a90b..f124d16 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
@@ -155,13 +155,10 @@
     /**
      * Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm.
      */
-    public static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm) {
+    private static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm)
+            throws NoSuchAlgorithmException {
         String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
-        try {
-            return MessageDigest.getInstance(jcaAlgorithm);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("Failed to obtain " + jcaAlgorithm + " MessageDigest", e);
-        }
+        return MessageDigest.getInstance(jcaAlgorithm);
     }
 
     /**
@@ -215,6 +212,8 @@
      * @param signerConfigs signer configurations, one for each signer. At least one signer config
      *        must be provided.
      *
+     * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
+     *         missing
      * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
      *         cannot be used in general
      * @throws SignatureException if an error occurs when computing digests of generating
@@ -226,7 +225,8 @@
             Map<String, byte[]> jarEntryDigests,
             List<Integer> apkSigningSchemeIds,
             byte[] sourceManifestBytes)
-                    throws InvalidKeyException, CertificateException, SignatureException {
+                    throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
+                            SignatureException {
         if (signerConfigs.isEmpty()) {
             throw new IllegalArgumentException("At least one signer config must be provided");
         }
@@ -253,7 +253,8 @@
             DigestAlgorithm digestAlgorithm,
             List<Integer> apkSigningSchemeIds,
             OutputManifestFile manifest)
-                    throws InvalidKeyException, CertificateException, SignatureException {
+                    throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
+                            SignatureException {
         if (signerConfigs.isEmpty()) {
             throw new IllegalArgumentException("At least one signer config must be provided");
         }
@@ -378,7 +379,7 @@
     private static byte[] generateSignatureFile(
             List<Integer> apkSignatureSchemeIds,
             DigestAlgorithm manifestDigestAlgorithm,
-            OutputManifestFile manifest) {
+            OutputManifestFile manifest) throws NoSuchAlgorithmException {
         Manifest sf = new Manifest();
         Attributes mainAttrs = sf.getMainAttributes();
         mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION);
@@ -447,7 +448,8 @@
     @SuppressWarnings("restriction")
     private static byte[] generateSignatureBlock(
             SignerConfig signerConfig, byte[] signatureFileBytes)
-                    throws InvalidKeyException, CertificateException, SignatureException {
+                    throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
+                            SignatureException {
         List<X509Certificate> signerCerts = signerConfig.certificates;
         X509Certificate signerCert = signerCerts.get(0);
         PublicKey signerPublicKey = signerCert.getPublicKey();
@@ -455,16 +457,10 @@
         Pair<String, AlgorithmId> signatureAlgs =
                 getSignerInfoSignatureAlgorithm(signerPublicKey, digestAlgorithm);
         String jcaSignatureAlgorithm = signatureAlgs.getFirst();
-        byte[] signatureBytes;
-        try {
-            Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
-            signature.initSign(signerConfig.privateKey);
-            signature.update(signatureFileBytes);
-            signatureBytes = signature.sign();
-        } catch (NoSuchAlgorithmException e) {
-            throw new SignatureException(
-                    jcaSignatureAlgorithm + " Signature implementation not found", e);
-        }
+        Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
+        signature.initSign(signerConfig.privateKey);
+        signature.update(signatureFileBytes);
+        byte[] signatureBytes = signature.sign();
 
         X500Name issuerName;
         try {
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
index 60a47b2..1bba313 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
@@ -72,6 +72,8 @@
      *
      * @throws ZipFormatException if the APK is malformed
      * @throws IOException if an I/O error occurs when reading the APK
+     * @throws NoSuchAlgorithmException if the APK's JAR signatures cannot be verified because a
+     *         required cryptographic algorithm implementation is missing
      */
     public static Result verify(
             DataSource apk,
@@ -79,7 +81,7 @@
             Map<Integer, String> supportedApkSigSchemeNames,
             Set<Integer> foundApkSigSchemeIds,
             int minSdkVersion,
-            int maxSdkVersion) throws IOException, ZipFormatException {
+            int maxSdkVersion) throws IOException, ZipFormatException, NoSuchAlgorithmException {
         if (minSdkVersion > maxSdkVersion) {
             throw new IllegalArgumentException(
                     "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion
@@ -152,7 +154,7 @@
                 Set<Integer> foundApkSigSchemeIds,
                 int minSdkVersion,
                 int maxSdkVersion,
-                Result result) throws ZipFormatException, IOException {
+                Result result) throws ZipFormatException, IOException, NoSuchAlgorithmException {
 
             // Find JAR manifest and signature block files.
             CentralDirectoryRecord manifestEntry = null;
@@ -312,6 +314,8 @@
                             cdRecords,
                             entryNameToManifestSection,
                             signers,
+                            minSdkVersion,
+                            maxSdkVersion,
                             result);
             if (result.containsErrors()) {
                 return;
@@ -405,7 +409,7 @@
         @SuppressWarnings("restriction")
         public void verifySigBlockAgainstSigFile(
                 DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion)
-                        throws IOException, ZipFormatException {
+                        throws IOException, ZipFormatException, NoSuchAlgorithmException {
             byte[] sigBlockBytes =
                     LocalFileHeader.getUncompressedData(
                             apk, 0,
@@ -461,7 +465,7 @@
                     }
                     try {
                         verifiedSignerInfo = sigBlock.verify(unverifiedSignerInfo, mSigFileBytes);
-                    } catch (NoSuchAlgorithmException | SignatureException e) {
+                    } catch (SignatureException e) {
                         mResult.addError(
                                 Issue.JAR_SIG_VERIFY_EXCEPTION,
                                 mSignatureBlockEntry.getName(),
@@ -856,7 +860,7 @@
                 Map<Integer, String> supportedApkSigSchemeNames,
                 Set<Integer> foundApkSigSchemeIds,
                 int minSdkVersion,
-                int maxSdkVersion) {
+                int maxSdkVersion) throws NoSuchAlgorithmException {
             // Inspect the main section of the .SF file.
             ManifestParser sf = new ManifestParser(mSigFileBytes);
             ManifestParser.Section sfMainSection = sf.readSection();
@@ -965,7 +969,7 @@
                 boolean createdBySigntool,
                 byte[] manifestBytes,
                 int minSdkVersion,
-                int maxSdkVersion) {
+                int maxSdkVersion) throws NoSuchAlgorithmException {
             Collection<NamedDigest> expectedDigests =
                     getDigestsToVerify(
                             sfMainSection,
@@ -1008,7 +1012,7 @@
                 ManifestParser.Section manifestMainSection,
                 byte[] manifestBytes,
                 int minSdkVersion,
-                int maxSdkVersion) {
+                int maxSdkVersion) throws NoSuchAlgorithmException {
             Collection<NamedDigest> expectedDigests =
                     getDigestsToVerify(
                             sfMainSection,
@@ -1049,7 +1053,7 @@
                 ManifestParser.Section manifestIndividualSection,
                 byte[] manifestBytes,
                 int minSdkVersion,
-                int maxSdkVersion) {
+                int maxSdkVersion) throws NoSuchAlgorithmException {
             String entryName = sfIndividualSection.getName();
             Collection<NamedDigest> expectedDigests =
                     getDigestsToVerify(
@@ -1344,7 +1348,9 @@
             Collection<CentralDirectoryRecord> cdRecords,
             Map<String, ManifestParser.Section> entryNameToManifestSection,
             List<Signer> signers,
-            Result result) throws ZipFormatException, IOException {
+            int minSdkVersion,
+            int maxSdkVersion,
+            Result result) throws ZipFormatException, IOException, NoSuchAlgorithmException {
         // Iterate over APK contents as sequentially as possible to improve performance.
         List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset =
                 new ArrayList<>(cdRecords);
@@ -1391,22 +1397,8 @@
                 continue;
             }
 
-            List<NamedDigest> expectedDigests = new ArrayList<>();
-            for (ManifestParser.Attribute attr : manifestSection.getAttributes()) {
-                String name = attr.getName();
-                String nameUpperCase = name.toUpperCase(Locale.US);
-                if (!nameUpperCase.endsWith("-DIGEST")) {
-                    continue;
-                }
-                String jcaDigestAlgorithm =
-                        nameUpperCase.substring(0, nameUpperCase.length() - "-DIGEST".length());
-                if ("SHA1".equals(jcaDigestAlgorithm)) {
-                    jcaDigestAlgorithm = "SHA-1";
-                }
-                byte[] digest = Base64.getDecoder().decode(attr.getValue());
-                expectedDigests.add(new NamedDigest(jcaDigestAlgorithm, digest));
-            }
-
+            Collection<NamedDigest> expectedDigests =
+                    getDigestsToVerify(manifestSection, "-Digest", minSdkVersion, maxSdkVersion);
             if (expectedDigests.isEmpty()) {
                 result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
                 continue;
@@ -1465,21 +1457,19 @@
         return result;
     }
 
-    private static MessageDigest getMessageDigest(String algorithm) {
-        try {
-            return MessageDigest.getInstance(algorithm);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("Failed to obtain " + algorithm + " MessageDigest", e);
-        }
+    private static MessageDigest getMessageDigest(String algorithm)
+            throws NoSuchAlgorithmException {
+        return MessageDigest.getInstance(algorithm);
     }
 
-    private static byte[] digest(String algorithm, byte[] data, int offset, int length) {
+    private static byte[] digest(String algorithm, byte[] data, int offset, int length)
+            throws NoSuchAlgorithmException {
         MessageDigest md = getMessageDigest(algorithm);
         md.update(data, offset, length);
         return md.digest();
     }
 
-    private static byte[] digest(String algorithm, byte[] data) {
+    private static byte[] digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
         return getMessageDigest(algorithm).digest(data);
     }
 
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
index aba390b..06d31dd 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
@@ -163,6 +163,8 @@
      *        must be provided.
      *
      * @throws IOException if an I/O error occurs
+     * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
+     *         missing
      * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
      *         cannot be used in general
      * @throws SignatureException if an error occurs when computing digests of generating
@@ -173,7 +175,8 @@
             DataSource centralDir,
             DataSource eocd,
             List<SignerConfig> signerConfigs)
-                        throws IOException, InvalidKeyException, SignatureException {
+                        throws IOException, NoSuchAlgorithmException, InvalidKeyException,
+                                SignatureException {
         if (signerConfigs.isEmpty()) {
             throw new IllegalArgumentException(
                     "No signer configs provided. At least one is required");
@@ -219,7 +222,7 @@
 
     static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
             Set<ContentDigestAlgorithm> digestAlgorithms,
-            DataSource[] contents) throws IOException, DigestException {
+            DataSource[] contents) throws IOException, NoSuchAlgorithmException, DigestException {
         // For each digest algorithm the result is computed as follows:
         // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
         //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
@@ -256,11 +259,7 @@
                     chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
             digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
             String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
-            try {
-                mds[i] = MessageDigest.getInstance(jcaAlgorithm);
-            } catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException(jcaAlgorithm + " MessageDigest not supported", e);
-            }
+            mds[i] = MessageDigest.getInstance(jcaAlgorithm);
         }
 
         MessageDigestSink mdSink = new MessageDigestSink(mds);
@@ -338,7 +337,7 @@
     private static byte[] generateApkSigningBlock(
             List<SignerConfig> signerConfigs,
             Map<ContentDigestAlgorithm, byte[]> contentDigests)
-                    throws InvalidKeyException, SignatureException {
+                    throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
         byte[] apkSignatureSchemeV2Block =
                 generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
         return generateApkSigningBlock(apkSignatureSchemeV2Block);
@@ -379,7 +378,7 @@
     private static byte[] generateApkSignatureSchemeV2Block(
             List<SignerConfig> signerConfigs,
             Map<ContentDigestAlgorithm, byte[]> contentDigests)
-                    throws InvalidKeyException, SignatureException {
+                    throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
         // FORMAT:
         // * length-prefixed sequence of length-prefixed signer blocks.
 
@@ -407,7 +406,7 @@
     private static byte[] generateSignerBlock(
             SignerConfig signerConfig,
             Map<ContentDigestAlgorithm, byte[]> contentDigests)
-                    throws InvalidKeyException, SignatureException {
+                    throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
         if (signerConfig.certificates.isEmpty()) {
             throw new SignatureException("No certificates configured for signer");
         }
@@ -470,10 +469,9 @@
                 signature.update(signer.signedData);
                 signatureBytes = signature.sign();
             } catch (InvalidKeyException e) {
-                throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e);
-            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
-                    | SignatureException e) {
-                throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
+                throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e);
+            } catch (InvalidAlgorithmParameterException | SignatureException e) {
+                throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e);
             }
 
             try {
@@ -487,12 +485,13 @@
                     throw new SignatureException("Signature did not verify");
                 }
             } catch (InvalidKeyException e) {
-                throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm
-                        + " signature using public key from certificate", e);
-            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
-                    | SignatureException e) {
-                throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm
-                        + " signature using public key from certificate", e);
+                throw new InvalidKeyException(
+                        "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
+                                + " public key from certificate", e);
+            } catch (InvalidAlgorithmParameterException | SignatureException e) {
+                throw new SignatureException(
+                        "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
+                                + " public key from certificate", e);
             }
 
             signer.signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes));
@@ -526,7 +525,8 @@
         }
     }
 
-    private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException {
+    private static byte[] encodePublicKey(PublicKey publicKey)
+            throws InvalidKeyException, NoSuchAlgorithmException {
         byte[] encodedPublicKey = null;
         if ("X.509".equals(publicKey.getFormat())) {
             encodedPublicKey = publicKey.getEncoded();
@@ -537,11 +537,6 @@
                         KeyFactory.getInstance(publicKey.getAlgorithm())
                                 .getKeySpec(publicKey, X509EncodedKeySpec.class)
                                 .getEncoded();
-            } catch (NoSuchAlgorithmException e) {
-                throw new InvalidKeyException(
-                        "Failed to obtain X.509 encoded form of public key " + publicKey
-                                + " of class " + publicKey.getClass().getName(),
-                        e);
             } catch (InvalidKeySpecException e) {
                 throw new InvalidKeyException(
                         "Failed to obtain X.509 encoded form of public key " + publicKey
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
index efefb00..0c303ee 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
@@ -31,9 +31,13 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
 import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.Signature;
+import java.security.SignatureException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -74,11 +78,13 @@
      * verification. APK is considered verified only if {@link Result#verified} is {@code true}. If
      * verification fails, the result will contain errors -- see {@link Result#getErrors()}.
      *
+     * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
+     *         required cryptographic algorithm implementation is missing
      * @throws SignatureNotFoundException if no APK Signature Scheme v2 signatures are found
      * @throws IOException if an I/O error occurs when reading the APK
      */
     public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections)
-            throws IOException, SignatureNotFoundException {
+            throws IOException, NoSuchAlgorithmException, SignatureNotFoundException {
         Result result = new Result();
         SignatureInfo signatureInfo = findSignature(apk, zipSections, result);
 
@@ -107,7 +113,7 @@
             ByteBuffer apkSignatureSchemeV2Block,
             DataSource centralDir,
             ByteBuffer eocd,
-            Result result) throws IOException {
+            Result result) throws IOException, NoSuchAlgorithmException {
         Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
         parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result);
         if (result.containsErrors()) {
@@ -131,7 +137,7 @@
     private static void parseSigners(
             ByteBuffer apkSignatureSchemeV2Block,
             Set<ContentDigestAlgorithm> contentDigestsToVerify,
-            Result result) {
+            Result result) throws NoSuchAlgorithmException {
         ByteBuffer signers;
         try {
             signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block);
@@ -178,7 +184,8 @@
             ByteBuffer signerBlock,
             CertificateFactory certFactory,
             Result.SignerInfo result,
-            Set<ContentDigestAlgorithm> contentDigestsToVerify) throws IOException {
+            Set<ContentDigestAlgorithm> contentDigestsToVerify)
+                    throws IOException, NoSuchAlgorithmException {
         ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
         byte[] signedDataBytes = new byte[signedData.remaining()];
         signedData.get(signedDataBytes);
@@ -252,7 +259,8 @@
                 }
                 result.verifiedSignatures.put(signatureAlgorithm, sigBytes);
                 contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm());
-            } catch (Exception e) {
+            } catch (InvalidKeyException | InvalidAlgorithmParameterException
+                    | SignatureException e) {
                 result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e);
                 return;
             }
@@ -440,7 +448,7 @@
             DataSource centralDir,
             ByteBuffer eocd,
             Set<ContentDigestAlgorithm> contentDigestAlgorithms,
-            Result result) throws IOException {
+            Result result) throws IOException, NoSuchAlgorithmException {
         if (contentDigestAlgorithms.isEmpty()) {
             // This should never occur because this method is invoked once at least one signature
             // is verified, meaning at least one content digest is known.