Merge "Let caller handle NoSuchAlgorithmException."

This commit is contained in:
Alex Klyubin
2016-06-17 20:36:00 +00:00
committed by Gerrit Code Review
7 changed files with 95 additions and 92 deletions

View File

@@ -19,6 +19,7 @@ package com.android.apksigner.core;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.List; import java.util.List;
@@ -182,13 +183,17 @@ public interface ApkSignerEngine extends Closeable {
* request must be fulfilled before * request must be fulfilled before
* {@link #outputZipSections(DataSource, DataSource, DataSource)} is invoked. * {@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 * @throws InvalidKeyException if a signature could not be generated because a signing key is
* not suitable for generating the signature * 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 * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
* entries, or if the engine is closed * 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. * 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. * {@link #outputDone()} is invoked.
* *
* @throws IOException if an I/O error occurs while reading the provided ZIP sections * @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 * @throws InvalidKeyException if a signature could not be generated because a signing key is
* not suitable for generating the signature * 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 * @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 * entries or to output JAR signature, or if the engine is closed
*/ */
OutputApkSigningBlockRequest outputZipSections( OutputApkSigningBlockRequest outputZipSections(
DataSource zipEntries, DataSource zipEntries,
DataSource zipCentralDirectory, 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. * Indicates to this engine that the signed APK was output.

View File

@@ -26,6 +26,7 @@ import com.android.apksigner.core.util.DataSource;
import com.android.apksigner.core.zip.ZipFormatException; import com.android.apksigner.core.zip.ZipFormatException;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; 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 IOException if an I/O error is encountered while reading the APK
* @throws ZipFormatException if the APK is malformed at ZIP format level * @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) public Result verify(DataSource apk, int minSdkVersion, int maxSdkVersion)
throws IOException, ZipFormatException { throws IOException, ZipFormatException, NoSuchAlgorithmException {
if (minSdkVersion < 0) { if (minSdkVersion < 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"minSdkVersion must not be negative: " + minSdkVersion); "minSdkVersion must not be negative: " + minSdkVersion);

View File

@@ -293,7 +293,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
@Override @Override
public OutputJarSignatureRequest outputJarEntries() public OutputJarSignatureRequest outputJarEntries()
throws InvalidKeyException, SignatureException { throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
checkNotClosed(); checkNotClosed();
if (!mV1SignaturePending) { if (!mV1SignaturePending) {
@@ -413,7 +413,9 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
public OutputApkSigningBlockRequest outputZipSections( public OutputApkSigningBlockRequest outputZipSections(
DataSource zipEntries, DataSource zipEntries,
DataSource zipCentralDirectory, DataSource zipCentralDirectory,
DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException { DataSource zipEocd)
throws IOException, InvalidKeyException, SignatureException,
NoSuchAlgorithmException {
checkNotClosed(); checkNotClosed();
checkV1SigningDoneIfEnabled(); checkV1SigningDoneIfEnabled();
if (!mV2SigningEnabled) { if (!mV2SigningEnabled) {

View File

@@ -155,13 +155,10 @@ public abstract class V1SchemeSigner {
/** /**
* Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm. * 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(); String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
try { return MessageDigest.getInstance(jcaAlgorithm);
return MessageDigest.getInstance(jcaAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to obtain " + jcaAlgorithm + " MessageDigest", e);
}
} }
/** /**
@@ -215,6 +212,8 @@ public abstract class V1SchemeSigner {
* @param signerConfigs signer configurations, one for each signer. At least one signer config * @param signerConfigs signer configurations, one for each signer. At least one signer config
* must be provided. * 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 * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
* cannot be used in general * cannot be used in general
* @throws SignatureException if an error occurs when computing digests of generating * @throws SignatureException if an error occurs when computing digests of generating
@@ -226,7 +225,8 @@ public abstract class V1SchemeSigner {
Map<String, byte[]> jarEntryDigests, Map<String, byte[]> jarEntryDigests,
List<Integer> apkSigningSchemeIds, List<Integer> apkSigningSchemeIds,
byte[] sourceManifestBytes) byte[] sourceManifestBytes)
throws InvalidKeyException, CertificateException, SignatureException { throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
SignatureException {
if (signerConfigs.isEmpty()) { if (signerConfigs.isEmpty()) {
throw new IllegalArgumentException("At least one signer config must be provided"); throw new IllegalArgumentException("At least one signer config must be provided");
} }
@@ -253,7 +253,8 @@ public abstract class V1SchemeSigner {
DigestAlgorithm digestAlgorithm, DigestAlgorithm digestAlgorithm,
List<Integer> apkSigningSchemeIds, List<Integer> apkSigningSchemeIds,
OutputManifestFile manifest) OutputManifestFile manifest)
throws InvalidKeyException, CertificateException, SignatureException { throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
SignatureException {
if (signerConfigs.isEmpty()) { if (signerConfigs.isEmpty()) {
throw new IllegalArgumentException("At least one signer config must be provided"); throw new IllegalArgumentException("At least one signer config must be provided");
} }
@@ -378,7 +379,7 @@ public abstract class V1SchemeSigner {
private static byte[] generateSignatureFile( private static byte[] generateSignatureFile(
List<Integer> apkSignatureSchemeIds, List<Integer> apkSignatureSchemeIds,
DigestAlgorithm manifestDigestAlgorithm, DigestAlgorithm manifestDigestAlgorithm,
OutputManifestFile manifest) { OutputManifestFile manifest) throws NoSuchAlgorithmException {
Manifest sf = new Manifest(); Manifest sf = new Manifest();
Attributes mainAttrs = sf.getMainAttributes(); Attributes mainAttrs = sf.getMainAttributes();
mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION); mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION);
@@ -447,7 +448,8 @@ public abstract class V1SchemeSigner {
@SuppressWarnings("restriction") @SuppressWarnings("restriction")
private static byte[] generateSignatureBlock( private static byte[] generateSignatureBlock(
SignerConfig signerConfig, byte[] signatureFileBytes) SignerConfig signerConfig, byte[] signatureFileBytes)
throws InvalidKeyException, CertificateException, SignatureException { throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
SignatureException {
List<X509Certificate> signerCerts = signerConfig.certificates; List<X509Certificate> signerCerts = signerConfig.certificates;
X509Certificate signerCert = signerCerts.get(0); X509Certificate signerCert = signerCerts.get(0);
PublicKey signerPublicKey = signerCert.getPublicKey(); PublicKey signerPublicKey = signerCert.getPublicKey();
@@ -455,16 +457,10 @@ public abstract class V1SchemeSigner {
Pair<String, AlgorithmId> signatureAlgs = Pair<String, AlgorithmId> signatureAlgs =
getSignerInfoSignatureAlgorithm(signerPublicKey, digestAlgorithm); getSignerInfoSignatureAlgorithm(signerPublicKey, digestAlgorithm);
String jcaSignatureAlgorithm = signatureAlgs.getFirst(); String jcaSignatureAlgorithm = signatureAlgs.getFirst();
byte[] signatureBytes; Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
try { signature.initSign(signerConfig.privateKey);
Signature signature = Signature.getInstance(jcaSignatureAlgorithm); signature.update(signatureFileBytes);
signature.initSign(signerConfig.privateKey); byte[] signatureBytes = signature.sign();
signature.update(signatureFileBytes);
signatureBytes = signature.sign();
} catch (NoSuchAlgorithmException e) {
throw new SignatureException(
jcaSignatureAlgorithm + " Signature implementation not found", e);
}
X500Name issuerName; X500Name issuerName;
try { try {

View File

@@ -72,6 +72,8 @@ public abstract class V1SchemeVerifier {
* *
* @throws ZipFormatException if the APK is malformed * @throws ZipFormatException if the APK is malformed
* @throws IOException if an I/O error occurs when reading the APK * @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( public static Result verify(
DataSource apk, DataSource apk,
@@ -79,7 +81,7 @@ public abstract class V1SchemeVerifier {
Map<Integer, String> supportedApkSigSchemeNames, Map<Integer, String> supportedApkSigSchemeNames,
Set<Integer> foundApkSigSchemeIds, Set<Integer> foundApkSigSchemeIds,
int minSdkVersion, int minSdkVersion,
int maxSdkVersion) throws IOException, ZipFormatException { int maxSdkVersion) throws IOException, ZipFormatException, NoSuchAlgorithmException {
if (minSdkVersion > maxSdkVersion) { if (minSdkVersion > maxSdkVersion) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion
@@ -152,7 +154,7 @@ public abstract class V1SchemeVerifier {
Set<Integer> foundApkSigSchemeIds, Set<Integer> foundApkSigSchemeIds,
int minSdkVersion, int minSdkVersion,
int maxSdkVersion, int maxSdkVersion,
Result result) throws ZipFormatException, IOException { Result result) throws ZipFormatException, IOException, NoSuchAlgorithmException {
// Find JAR manifest and signature block files. // Find JAR manifest and signature block files.
CentralDirectoryRecord manifestEntry = null; CentralDirectoryRecord manifestEntry = null;
@@ -312,6 +314,8 @@ public abstract class V1SchemeVerifier {
cdRecords, cdRecords,
entryNameToManifestSection, entryNameToManifestSection,
signers, signers,
minSdkVersion,
maxSdkVersion,
result); result);
if (result.containsErrors()) { if (result.containsErrors()) {
return; return;
@@ -405,7 +409,7 @@ public abstract class V1SchemeVerifier {
@SuppressWarnings("restriction") @SuppressWarnings("restriction")
public void verifySigBlockAgainstSigFile( public void verifySigBlockAgainstSigFile(
DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion)
throws IOException, ZipFormatException { throws IOException, ZipFormatException, NoSuchAlgorithmException {
byte[] sigBlockBytes = byte[] sigBlockBytes =
LocalFileHeader.getUncompressedData( LocalFileHeader.getUncompressedData(
apk, 0, apk, 0,
@@ -461,7 +465,7 @@ public abstract class V1SchemeVerifier {
} }
try { try {
verifiedSignerInfo = sigBlock.verify(unverifiedSignerInfo, mSigFileBytes); verifiedSignerInfo = sigBlock.verify(unverifiedSignerInfo, mSigFileBytes);
} catch (NoSuchAlgorithmException | SignatureException e) { } catch (SignatureException e) {
mResult.addError( mResult.addError(
Issue.JAR_SIG_VERIFY_EXCEPTION, Issue.JAR_SIG_VERIFY_EXCEPTION,
mSignatureBlockEntry.getName(), mSignatureBlockEntry.getName(),
@@ -856,7 +860,7 @@ public abstract class V1SchemeVerifier {
Map<Integer, String> supportedApkSigSchemeNames, Map<Integer, String> supportedApkSigSchemeNames,
Set<Integer> foundApkSigSchemeIds, Set<Integer> foundApkSigSchemeIds,
int minSdkVersion, int minSdkVersion,
int maxSdkVersion) { int maxSdkVersion) throws NoSuchAlgorithmException {
// Inspect the main section of the .SF file. // Inspect the main section of the .SF file.
ManifestParser sf = new ManifestParser(mSigFileBytes); ManifestParser sf = new ManifestParser(mSigFileBytes);
ManifestParser.Section sfMainSection = sf.readSection(); ManifestParser.Section sfMainSection = sf.readSection();
@@ -965,7 +969,7 @@ public abstract class V1SchemeVerifier {
boolean createdBySigntool, boolean createdBySigntool,
byte[] manifestBytes, byte[] manifestBytes,
int minSdkVersion, int minSdkVersion,
int maxSdkVersion) { int maxSdkVersion) throws NoSuchAlgorithmException {
Collection<NamedDigest> expectedDigests = Collection<NamedDigest> expectedDigests =
getDigestsToVerify( getDigestsToVerify(
sfMainSection, sfMainSection,
@@ -1008,7 +1012,7 @@ public abstract class V1SchemeVerifier {
ManifestParser.Section manifestMainSection, ManifestParser.Section manifestMainSection,
byte[] manifestBytes, byte[] manifestBytes,
int minSdkVersion, int minSdkVersion,
int maxSdkVersion) { int maxSdkVersion) throws NoSuchAlgorithmException {
Collection<NamedDigest> expectedDigests = Collection<NamedDigest> expectedDigests =
getDigestsToVerify( getDigestsToVerify(
sfMainSection, sfMainSection,
@@ -1049,7 +1053,7 @@ public abstract class V1SchemeVerifier {
ManifestParser.Section manifestIndividualSection, ManifestParser.Section manifestIndividualSection,
byte[] manifestBytes, byte[] manifestBytes,
int minSdkVersion, int minSdkVersion,
int maxSdkVersion) { int maxSdkVersion) throws NoSuchAlgorithmException {
String entryName = sfIndividualSection.getName(); String entryName = sfIndividualSection.getName();
Collection<NamedDigest> expectedDigests = Collection<NamedDigest> expectedDigests =
getDigestsToVerify( getDigestsToVerify(
@@ -1344,7 +1348,9 @@ public abstract class V1SchemeVerifier {
Collection<CentralDirectoryRecord> cdRecords, Collection<CentralDirectoryRecord> cdRecords,
Map<String, ManifestParser.Section> entryNameToManifestSection, Map<String, ManifestParser.Section> entryNameToManifestSection,
List<Signer> signers, 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. // Iterate over APK contents as sequentially as possible to improve performance.
List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset = List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset =
new ArrayList<>(cdRecords); new ArrayList<>(cdRecords);
@@ -1391,22 +1397,8 @@ public abstract class V1SchemeVerifier {
continue; continue;
} }
List<NamedDigest> expectedDigests = new ArrayList<>(); Collection<NamedDigest> expectedDigests =
for (ManifestParser.Attribute attr : manifestSection.getAttributes()) { getDigestsToVerify(manifestSection, "-Digest", minSdkVersion, maxSdkVersion);
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));
}
if (expectedDigests.isEmpty()) { if (expectedDigests.isEmpty()) {
result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
continue; continue;
@@ -1465,21 +1457,19 @@ public abstract class V1SchemeVerifier {
return result; return result;
} }
private static MessageDigest getMessageDigest(String algorithm) { private static MessageDigest getMessageDigest(String algorithm)
try { throws NoSuchAlgorithmException {
return MessageDigest.getInstance(algorithm); return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to obtain " + algorithm + " MessageDigest", e);
}
} }
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); MessageDigest md = getMessageDigest(algorithm);
md.update(data, offset, length); md.update(data, offset, length);
return md.digest(); 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); return getMessageDigest(algorithm).digest(data);
} }

View File

@@ -163,6 +163,8 @@ public abstract class V2SchemeSigner {
* must be provided. * must be provided.
* *
* @throws IOException if an I/O error occurs * @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 * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
* cannot be used in general * cannot be used in general
* @throws SignatureException if an error occurs when computing digests of generating * @throws SignatureException if an error occurs when computing digests of generating
@@ -173,7 +175,8 @@ public abstract class V2SchemeSigner {
DataSource centralDir, DataSource centralDir,
DataSource eocd, DataSource eocd,
List<SignerConfig> signerConfigs) List<SignerConfig> signerConfigs)
throws IOException, InvalidKeyException, SignatureException { throws IOException, NoSuchAlgorithmException, InvalidKeyException,
SignatureException {
if (signerConfigs.isEmpty()) { if (signerConfigs.isEmpty()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"No signer configs provided. At least one is required"); "No signer configs provided. At least one is required");
@@ -219,7 +222,7 @@ public abstract class V2SchemeSigner {
static Map<ContentDigestAlgorithm, byte[]> computeContentDigests( static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
Set<ContentDigestAlgorithm> digestAlgorithms, Set<ContentDigestAlgorithm> digestAlgorithms,
DataSource[] contents) throws IOException, DigestException { DataSource[] contents) throws IOException, NoSuchAlgorithmException, DigestException {
// For each digest algorithm the result is computed as follows: // 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. // 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. // 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); chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
try { mds[i] = MessageDigest.getInstance(jcaAlgorithm);
mds[i] = MessageDigest.getInstance(jcaAlgorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(jcaAlgorithm + " MessageDigest not supported", e);
}
} }
MessageDigestSink mdSink = new MessageDigestSink(mds); MessageDigestSink mdSink = new MessageDigestSink(mds);
@@ -338,7 +337,7 @@ public abstract class V2SchemeSigner {
private static byte[] generateApkSigningBlock( private static byte[] generateApkSigningBlock(
List<SignerConfig> signerConfigs, List<SignerConfig> signerConfigs,
Map<ContentDigestAlgorithm, byte[]> contentDigests) Map<ContentDigestAlgorithm, byte[]> contentDigests)
throws InvalidKeyException, SignatureException { throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
byte[] apkSignatureSchemeV2Block = byte[] apkSignatureSchemeV2Block =
generateApkSignatureSchemeV2Block(signerConfigs, contentDigests); generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
return generateApkSigningBlock(apkSignatureSchemeV2Block); return generateApkSigningBlock(apkSignatureSchemeV2Block);
@@ -379,7 +378,7 @@ public abstract class V2SchemeSigner {
private static byte[] generateApkSignatureSchemeV2Block( private static byte[] generateApkSignatureSchemeV2Block(
List<SignerConfig> signerConfigs, List<SignerConfig> signerConfigs,
Map<ContentDigestAlgorithm, byte[]> contentDigests) Map<ContentDigestAlgorithm, byte[]> contentDigests)
throws InvalidKeyException, SignatureException { throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// FORMAT: // FORMAT:
// * length-prefixed sequence of length-prefixed signer blocks. // * length-prefixed sequence of length-prefixed signer blocks.
@@ -407,7 +406,7 @@ public abstract class V2SchemeSigner {
private static byte[] generateSignerBlock( private static byte[] generateSignerBlock(
SignerConfig signerConfig, SignerConfig signerConfig,
Map<ContentDigestAlgorithm, byte[]> contentDigests) Map<ContentDigestAlgorithm, byte[]> contentDigests)
throws InvalidKeyException, SignatureException { throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
if (signerConfig.certificates.isEmpty()) { if (signerConfig.certificates.isEmpty()) {
throw new SignatureException("No certificates configured for signer"); throw new SignatureException("No certificates configured for signer");
} }
@@ -470,10 +469,9 @@ public abstract class V2SchemeSigner {
signature.update(signer.signedData); signature.update(signer.signedData);
signatureBytes = signature.sign(); signatureBytes = signature.sign();
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e); throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException } catch (InvalidAlgorithmParameterException | SignatureException e) {
| SignatureException e) { throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e);
throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
} }
try { try {
@@ -487,12 +485,13 @@ public abstract class V2SchemeSigner {
throw new SignatureException("Signature did not verify"); throw new SignatureException("Signature did not verify");
} }
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm throw new InvalidKeyException(
+ " signature using public key from certificate", e); "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException + " public key from certificate", e);
| SignatureException e) { } catch (InvalidAlgorithmParameterException | SignatureException e) {
throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm throw new SignatureException(
+ " signature using public key from certificate", e); "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
+ " public key from certificate", e);
} }
signer.signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes)); 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; byte[] encodedPublicKey = null;
if ("X.509".equals(publicKey.getFormat())) { if ("X.509".equals(publicKey.getFormat())) {
encodedPublicKey = publicKey.getEncoded(); encodedPublicKey = publicKey.getEncoded();
@@ -537,11 +537,6 @@ public abstract class V2SchemeSigner {
KeyFactory.getInstance(publicKey.getAlgorithm()) KeyFactory.getInstance(publicKey.getAlgorithm())
.getKeySpec(publicKey, X509EncodedKeySpec.class) .getKeySpec(publicKey, X509EncodedKeySpec.class)
.getEncoded(); .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) { } catch (InvalidKeySpecException e) {
throw new InvalidKeyException( throw new InvalidKeyException(
"Failed to obtain X.509 encoded form of public key " + publicKey "Failed to obtain X.509 encoded form of public key " + publicKey

View File

@@ -31,9 +31,13 @@ import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.security.DigestException; import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; 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. APK is considered verified only if {@link Result#verified} is {@code true}. If
* verification fails, the result will contain errors -- see {@link Result#getErrors()}. * 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 SignatureNotFoundException if no APK Signature Scheme v2 signatures are found
* @throws IOException if an I/O error occurs when reading the APK * @throws IOException if an I/O error occurs when reading the APK
*/ */
public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections) public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections)
throws IOException, SignatureNotFoundException { throws IOException, NoSuchAlgorithmException, SignatureNotFoundException {
Result result = new Result(); Result result = new Result();
SignatureInfo signatureInfo = findSignature(apk, zipSections, result); SignatureInfo signatureInfo = findSignature(apk, zipSections, result);
@@ -107,7 +113,7 @@ public abstract class V2SchemeVerifier {
ByteBuffer apkSignatureSchemeV2Block, ByteBuffer apkSignatureSchemeV2Block,
DataSource centralDir, DataSource centralDir,
ByteBuffer eocd, ByteBuffer eocd,
Result result) throws IOException { Result result) throws IOException, NoSuchAlgorithmException {
Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1); Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result); parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result);
if (result.containsErrors()) { if (result.containsErrors()) {
@@ -131,7 +137,7 @@ public abstract class V2SchemeVerifier {
private static void parseSigners( private static void parseSigners(
ByteBuffer apkSignatureSchemeV2Block, ByteBuffer apkSignatureSchemeV2Block,
Set<ContentDigestAlgorithm> contentDigestsToVerify, Set<ContentDigestAlgorithm> contentDigestsToVerify,
Result result) { Result result) throws NoSuchAlgorithmException {
ByteBuffer signers; ByteBuffer signers;
try { try {
signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block); signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block);
@@ -178,7 +184,8 @@ public abstract class V2SchemeVerifier {
ByteBuffer signerBlock, ByteBuffer signerBlock,
CertificateFactory certFactory, CertificateFactory certFactory,
Result.SignerInfo result, Result.SignerInfo result,
Set<ContentDigestAlgorithm> contentDigestsToVerify) throws IOException { Set<ContentDigestAlgorithm> contentDigestsToVerify)
throws IOException, NoSuchAlgorithmException {
ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
byte[] signedDataBytes = new byte[signedData.remaining()]; byte[] signedDataBytes = new byte[signedData.remaining()];
signedData.get(signedDataBytes); signedData.get(signedDataBytes);
@@ -252,7 +259,8 @@ public abstract class V2SchemeVerifier {
} }
result.verifiedSignatures.put(signatureAlgorithm, sigBytes); result.verifiedSignatures.put(signatureAlgorithm, sigBytes);
contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm()); contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm());
} catch (Exception e) { } catch (InvalidKeyException | InvalidAlgorithmParameterException
| SignatureException e) {
result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e); result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e);
return; return;
} }
@@ -440,7 +448,7 @@ public abstract class V2SchemeVerifier {
DataSource centralDir, DataSource centralDir,
ByteBuffer eocd, ByteBuffer eocd,
Set<ContentDigestAlgorithm> contentDigestAlgorithms, Set<ContentDigestAlgorithm> contentDigestAlgorithms,
Result result) throws IOException { Result result) throws IOException, NoSuchAlgorithmException {
if (contentDigestAlgorithms.isEmpty()) { if (contentDigestAlgorithms.isEmpty()) {
// This should never occur because this method is invoked once at least one signature // This should never occur because this method is invoked once at least one signature
// is verified, meaning at least one content digest is known. // is verified, meaning at least one content digest is known.