From 05f87de7a3bc62695c34d6adaa67c72bc4e7de8f Mon Sep 17 00:00:00 2001 From: Alex Klyubin Date: Thu, 16 Jun 2016 09:21:54 -0700 Subject: [PATCH] Let caller handle NoSuchAlgorithmException. This surfaces relevant NoSuchAlgorithmExceptions to the caller instead of rethrowing as other exception types. Some setups need to be able to distringuish issues due to their own misconfiguration (required crypto algorithm mising -- NoSuchAlgorithmException) from issues with the APK being signed or verified. Bug: 27461702 Change-Id: I993f73edb29b2cd4cc485734a89a924ec357ef19 --- .../apksigner/core/ApkSignerEngine.java | 17 ++++-- .../android/apksigner/core/ApkVerifier.java | 5 +- .../core/DefaultApkSignerEngine.java | 6 +- .../core/internal/apk/v1/V1SchemeSigner.java | 36 ++++++------ .../internal/apk/v1/V1SchemeVerifier.java | 56 ++++++++----------- .../core/internal/apk/v2/V2SchemeSigner.java | 47 +++++++--------- .../internal/apk/v2/V2SchemeVerifier.java | 20 +++++-- 7 files changed, 95 insertions(+), 92 deletions(-) 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 36f2a08e99..6a148ca2a3 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 @@ package com.android.apksigner.core; 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 @@ public interface ApkSignerEngine extends Closeable { * 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 @@ public interface ApkSignerEngine extends Closeable { * {@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 c3999b569a..d509a48dc7 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.util.DataSource; 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 @@ public class ApkVerifier { * * @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 52042ac9d8..75b0b20420 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 @@ public class DefaultApkSignerEngine implements ApkSignerEngine { @Override public OutputJarSignatureRequest outputJarEntries() - throws InvalidKeyException, SignatureException { + throws InvalidKeyException, SignatureException, NoSuchAlgorithmException { checkNotClosed(); if (!mV1SignaturePending) { @@ -413,7 +413,9 @@ public class DefaultApkSignerEngine implements ApkSignerEngine { 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 1a4a90bb83..f124d16976 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 @@ public abstract class V1SchemeSigner { /** * 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 @@ public abstract class V1SchemeSigner { * @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 @@ public abstract class V1SchemeSigner { Map jarEntryDigests, List 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 @@ public abstract class V1SchemeSigner { DigestAlgorithm digestAlgorithm, List 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 @@ public abstract class V1SchemeSigner { private static byte[] generateSignatureFile( List 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 @@ public abstract class V1SchemeSigner { @SuppressWarnings("restriction") private static byte[] generateSignatureBlock( SignerConfig signerConfig, byte[] signatureFileBytes) - throws InvalidKeyException, CertificateException, SignatureException { + throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, + SignatureException { List signerCerts = signerConfig.certificates; X509Certificate signerCert = signerCerts.get(0); PublicKey signerPublicKey = signerCert.getPublicKey(); @@ -455,16 +457,10 @@ public abstract class V1SchemeSigner { Pair 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 60a47b2a21..1bba313232 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 @@ public abstract class V1SchemeVerifier { * * @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 @@ public abstract class V1SchemeVerifier { Map supportedApkSigSchemeNames, Set 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 @@ public abstract class V1SchemeVerifier { Set 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 @@ public abstract class V1SchemeVerifier { cdRecords, entryNameToManifestSection, signers, + minSdkVersion, + maxSdkVersion, result); if (result.containsErrors()) { return; @@ -405,7 +409,7 @@ public abstract class V1SchemeVerifier { @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 @@ public abstract class V1SchemeVerifier { } 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 @@ public abstract class V1SchemeVerifier { Map supportedApkSigSchemeNames, Set 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 @@ public abstract class V1SchemeVerifier { boolean createdBySigntool, byte[] manifestBytes, int minSdkVersion, - int maxSdkVersion) { + int maxSdkVersion) throws NoSuchAlgorithmException { Collection expectedDigests = getDigestsToVerify( sfMainSection, @@ -1008,7 +1012,7 @@ public abstract class V1SchemeVerifier { ManifestParser.Section manifestMainSection, byte[] manifestBytes, int minSdkVersion, - int maxSdkVersion) { + int maxSdkVersion) throws NoSuchAlgorithmException { Collection expectedDigests = getDigestsToVerify( sfMainSection, @@ -1049,7 +1053,7 @@ public abstract class V1SchemeVerifier { ManifestParser.Section manifestIndividualSection, byte[] manifestBytes, int minSdkVersion, - int maxSdkVersion) { + int maxSdkVersion) throws NoSuchAlgorithmException { String entryName = sfIndividualSection.getName(); Collection expectedDigests = getDigestsToVerify( @@ -1344,7 +1348,9 @@ public abstract class V1SchemeVerifier { Collection cdRecords, Map entryNameToManifestSection, List 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 cdRecordsSortedByLocalFileHeaderOffset = new ArrayList<>(cdRecords); @@ -1391,22 +1397,8 @@ public abstract class V1SchemeVerifier { continue; } - List 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 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 @@ public abstract class V1SchemeVerifier { 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 aba390b2a2..06d31dd1f6 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 @@ public abstract class V2SchemeSigner { * 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 @@ public abstract class V2SchemeSigner { DataSource centralDir, DataSource eocd, List 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 @@ public abstract class V2SchemeSigner { static Map computeContentDigests( Set 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 @@ public abstract class V2SchemeSigner { 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 @@ public abstract class V2SchemeSigner { private static byte[] generateApkSigningBlock( List signerConfigs, Map contentDigests) - throws InvalidKeyException, SignatureException { + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { byte[] apkSignatureSchemeV2Block = generateApkSignatureSchemeV2Block(signerConfigs, contentDigests); return generateApkSigningBlock(apkSignatureSchemeV2Block); @@ -379,7 +378,7 @@ public abstract class V2SchemeSigner { private static byte[] generateApkSignatureSchemeV2Block( List signerConfigs, Map contentDigests) - throws InvalidKeyException, SignatureException { + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { // FORMAT: // * length-prefixed sequence of length-prefixed signer blocks. @@ -407,7 +406,7 @@ public abstract class V2SchemeSigner { private static byte[] generateSignerBlock( SignerConfig signerConfig, Map contentDigests) - throws InvalidKeyException, SignatureException { + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { if (signerConfig.certificates.isEmpty()) { throw new SignatureException("No certificates configured for signer"); } @@ -470,10 +469,9 @@ public abstract class V2SchemeSigner { 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 @@ public abstract class V2SchemeSigner { 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 @@ public abstract class V2SchemeSigner { } } - 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 @@ public abstract class V2SchemeSigner { 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 efefb0051d..0c303ee3dc 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.BufferUnderflowException; 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 @@ public abstract class V2SchemeVerifier { * 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 @@ public abstract class V2SchemeVerifier { ByteBuffer apkSignatureSchemeV2Block, DataSource centralDir, ByteBuffer eocd, - Result result) throws IOException { + Result result) throws IOException, NoSuchAlgorithmException { Set contentDigestsToVerify = new HashSet<>(1); parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result); if (result.containsErrors()) { @@ -131,7 +137,7 @@ public abstract class V2SchemeVerifier { private static void parseSigners( ByteBuffer apkSignatureSchemeV2Block, Set contentDigestsToVerify, - Result result) { + Result result) throws NoSuchAlgorithmException { ByteBuffer signers; try { signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block); @@ -178,7 +184,8 @@ public abstract class V2SchemeVerifier { ByteBuffer signerBlock, CertificateFactory certFactory, Result.SignerInfo result, - Set contentDigestsToVerify) throws IOException { + Set contentDigestsToVerify) + throws IOException, NoSuchAlgorithmException { ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); byte[] signedDataBytes = new byte[signedData.remaining()]; signedData.get(signedDataBytes); @@ -252,7 +259,8 @@ public abstract class V2SchemeVerifier { } 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 @@ public abstract class V2SchemeVerifier { DataSource centralDir, ByteBuffer eocd, Set 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.