Merge "Let caller handle NoSuchAlgorithmException."
This commit is contained in:
@@ -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.
|
||||||
|
@@ -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);
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
|
Reference in New Issue
Block a user