Add support for deterministic DSA signing am: 87d6acee83
Original change: https://android-review.googlesource.com/c/platform/tools/apksig/+/1666084
Change-Id: Ib6377e3496ec6d56178e7172929f2b5add0de468
diff --git a/build.gradle b/build.gradle
index 12c0d32..4c05a77 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,6 +21,7 @@
dependencies {
implementation 'com.google.protobuf:protobuf-javalite:3.8.0'
testImplementation 'junit:junit:4.13'
+ testImplementation 'org.bouncycastle:bcprov-jdk15on:1.68'
}
protobuf {
diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
index 3e346f5..2c1c7fb 100644
--- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
+++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java
@@ -157,6 +157,7 @@
String optionOriginalForm = null;
boolean v4SigningFlagFound = false;
boolean sourceStampFlagFound = false;
+ boolean deterministicDsaSigning = false;
while ((optionName = optionsParser.nextOption()) != null) {
optionOriginalForm = optionsParser.getOptionOriginalForm();
if (("help".equals(optionName)) || ("h".equals(optionName))) {
@@ -257,6 +258,8 @@
File stampLineageFile = new File(
optionsParser.getRequiredValue("Stamp Lineage File"));
sourceStampLineage = getLineageFromInputFile(stampLineageFile);
+ } else if ("deterministic-dsa-signing".equals(optionName)) {
+ deterministicDsaSigning = optionsParser.getOptionalBooleanValue(false);
} else {
throw new ParameterException(
"Unsupported option: " + optionOriginalForm + ". See --help for supported"
@@ -313,7 +316,8 @@
for (SignerParams signer : signers) {
signerNumber++;
signer.setName("signer #" + signerNumber);
- ApkSigner.SignerConfig signerConfig = getSignerConfig(signer, passwordRetriever);
+ ApkSigner.SignerConfig signerConfig = getSignerConfig(signer, passwordRetriever,
+ deterministicDsaSigning);
if (signerConfig == null) {
return;
}
@@ -322,7 +326,8 @@
if (sourceStampFlagFound) {
sourceStampSignerParams.setName("stamp signer");
sourceStampSignerConfig =
- getSignerConfig(sourceStampSignerParams, passwordRetriever);
+ getSignerConfig(sourceStampSignerParams, passwordRetriever,
+ deterministicDsaSigning);
if (sourceStampSignerConfig == null) {
return;
}
@@ -389,8 +394,8 @@
}
}
- private static ApkSigner.SignerConfig getSignerConfig(
- SignerParams signer, PasswordRetriever passwordRetriever) {
+ private static ApkSigner.SignerConfig getSignerConfig(SignerParams signer,
+ PasswordRetriever passwordRetriever, boolean deterministicDsaSigning) {
try {
signer.loadPrivateKeyAndCerts(passwordRetriever);
} catch (ParameterException e) {
@@ -422,7 +427,8 @@
}
ApkSigner.SignerConfig signerConfig =
new ApkSigner.SignerConfig.Builder(
- v1SigBasename, signer.getPrivateKey(), signer.getCerts())
+ v1SigBasename, signer.getPrivateKey(), signer.getCerts(),
+ deterministicDsaSigning)
.build();
return signerConfig;
}
diff --git a/src/apksigner/java/com/android/apksigner/help_sign.txt b/src/apksigner/java/com/android/apksigner/help_sign.txt
index 1285810..88c27a1 100644
--- a/src/apksigner/java/com/android/apksigner/help_sign.txt
+++ b/src/apksigner/java/com/android/apksigner/help_sign.txt
@@ -90,6 +90,10 @@
can also be specified; the lineage will then be read from
the signed data in the APK.
+--deterministic-dsa-signing When signing with the DSA signature algorithm,
+ whether to use the deterministic version as specified in
+ RFC 6979.
+
-h, --help Show help about this command and exit
diff --git a/src/main/java/com/android/apksig/ApkSigner.java b/src/main/java/com/android/apksig/ApkSigner.java
index 8cca444..ca792c4 100644
--- a/src/main/java/com/android/apksig/ApkSigner.java
+++ b/src/main/java/com/android/apksig/ApkSigner.java
@@ -284,7 +284,8 @@
new DefaultApkSignerEngine.SignerConfig.Builder(
signerConfig.getName(),
signerConfig.getPrivateKey(),
- signerConfig.getCertificates())
+ signerConfig.getCertificates(),
+ signerConfig.getDeterministicDsaSigning())
.build());
}
DefaultApkSignerEngine.Builder signerEngineBuilder =
@@ -304,7 +305,8 @@
new DefaultApkSignerEngine.SignerConfig.Builder(
mSourceStampSignerConfig.getName(),
mSourceStampSignerConfig.getPrivateKey(),
- mSourceStampSignerConfig.getCertificates())
+ mSourceStampSignerConfig.getCertificates(),
+ mSourceStampSignerConfig.getDeterministicDsaSigning())
.build());
}
if (mSourceStampSigningCertificateLineage != null) {
@@ -967,14 +969,18 @@
private final String mName;
private final PrivateKey mPrivateKey;
private final List<X509Certificate> mCertificates;
+ private boolean mDeterministicDsaSigning;
private SignerConfig(
- String name, PrivateKey privateKey, List<X509Certificate> certificates) {
+ String name,
+ PrivateKey privateKey,
+ List<X509Certificate> certificates,
+ boolean deterministicDsaSigning) {
mName = name;
mPrivateKey = privateKey;
mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
+ mDeterministicDsaSigning = deterministicDsaSigning;
}
-
/** Returns the name of this signer. */
public String getName() {
return mName;
@@ -993,11 +999,20 @@
return mCertificates;
}
+
+ /**
+ * If this signer is a DSA signer, whether or not the signing is done deterministically.
+ */
+ public boolean getDeterministicDsaSigning() {
+ return mDeterministicDsaSigning;
+ }
+
/** Builder of {@link SignerConfig} instances. */
public static class Builder {
private final String mName;
private final PrivateKey mPrivateKey;
private final List<X509Certificate> mCertificates;
+ private final boolean mDeterministicDsaSigning;
/**
* Constructs a new {@code Builder}.
@@ -1008,13 +1023,36 @@
* @param certificates list of one or more X.509 certificates. The subject public key of
* the first certificate must correspond to the {@code privateKey}.
*/
- public Builder(String name, PrivateKey privateKey, List<X509Certificate> certificates) {
+ public Builder(
+ String name,
+ PrivateKey privateKey,
+ List<X509Certificate> certificates) {
+ this(name, privateKey, certificates, false);
+ }
+
+ /**
+ * Constructs a new {@code Builder}.
+ *
+ * @param name signer's name. The name is reflected in the name of files comprising the
+ * JAR signature of the APK.
+ * @param privateKey signing key
+ * @param certificates list of one or more X.509 certificates. The subject public key of
+ * the first certificate must correspond to the {@code privateKey}.
+ * @param deterministicDsaSigning When signing using DSA, whether or not the
+ * deterministic variant (RFC6979) should be used.
+ */
+ public Builder(
+ String name,
+ PrivateKey privateKey,
+ List<X509Certificate> certificates,
+ boolean deterministicDsaSigning) {
if (name.isEmpty()) {
throw new IllegalArgumentException("Empty name");
}
mName = name;
mPrivateKey = privateKey;
mCertificates = new ArrayList<>(certificates);
+ mDeterministicDsaSigning = deterministicDsaSigning;
}
/**
@@ -1022,7 +1060,8 @@
* this builder.
*/
public SignerConfig build() {
- return new SignerConfig(mName, mPrivateKey, mCertificates);
+ return new SignerConfig(mName, mPrivateKey, mCertificates,
+ mDeterministicDsaSigning);
}
}
}
diff --git a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
index 90f2a6d..ceb8bb5 100644
--- a/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ b/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
@@ -255,6 +255,7 @@
v1SignerConfig.privateKey = signerConfig.getPrivateKey();
v1SignerConfig.certificates = certificates;
v1SignerConfig.signatureDigestAlgorithm = v1SignatureDigestAlgorithm;
+ v1SignerConfig.deterministicDsaSigning = signerConfig.getDeterministicDsaSigning();
// For digesting contents of APK entries and of MANIFEST.MF, pick the algorithm
// of comparable strength to the digest algorithm used for computing the signature.
// When there are multiple signers, pick the strongest digest algorithm out of their
@@ -441,7 +442,8 @@
V2SchemeSigner.getSuggestedSignatureAlgorithms(
publicKey,
mMinSdkVersion,
- apkSigningBlockPaddingSupported && mVerityEnabled);
+ apkSigningBlockPaddingSupported && mVerityEnabled,
+ signerConfig.getDeterministicDsaSigning());
break;
case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3:
try {
@@ -449,7 +451,8 @@
V3SchemeSigner.getSuggestedSignatureAlgorithms(
publicKey,
mMinSdkVersion,
- apkSigningBlockPaddingSupported && mVerityEnabled);
+ apkSigningBlockPaddingSupported && mVerityEnabled,
+ signerConfig.getDeterministicDsaSigning());
} catch (InvalidKeyException e) {
// It is possible for a signer used for v1/v2 signing to not be allowed for use
@@ -463,7 +466,8 @@
try {
newSignerConfig.signatureAlgorithms =
V4SchemeSigner.getSuggestedSignatureAlgorithms(
- publicKey, mMinSdkVersion, apkSigningBlockPaddingSupported);
+ publicKey, mMinSdkVersion, apkSigningBlockPaddingSupported,
+ signerConfig.getDeterministicDsaSigning());
} catch (InvalidKeyException e) {
// V4 is an optional signing schema, ok to proceed without.
newSignerConfig.signatureAlgorithms = null;
@@ -1422,12 +1426,15 @@
private final String mName;
private final PrivateKey mPrivateKey;
private final List<X509Certificate> mCertificates;
+ private final boolean mDeterministicDsaSigning;
private SignerConfig(
- String name, PrivateKey privateKey, List<X509Certificate> certificates) {
+ String name, PrivateKey privateKey, List<X509Certificate> certificates,
+ boolean deterministicDsaSigning) {
mName = name;
mPrivateKey = privateKey;
mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
+ mDeterministicDsaSigning = deterministicDsaSigning;
}
/** Returns the name of this signer. */
@@ -1448,11 +1455,19 @@
return mCertificates;
}
+ /**
+ * If this signer is a DSA signer, whether or not the signing is done deterministically.
+ */
+ public boolean getDeterministicDsaSigning() {
+ return mDeterministicDsaSigning;
+ }
+
/** Builder of {@link SignerConfig} instances. */
public static class Builder {
private final String mName;
private final PrivateKey mPrivateKey;
private final List<X509Certificate> mCertificates;
+ private final boolean mDeterministicDsaSigning;
/**
* Constructs a new {@code Builder}.
@@ -1464,12 +1479,29 @@
* the first certificate must correspond to the {@code privateKey}.
*/
public Builder(String name, PrivateKey privateKey, List<X509Certificate> certificates) {
+ this(name, privateKey, certificates, false);
+ }
+
+ /**
+ * Constructs a new {@code Builder}.
+ *
+ * @param name signer's name. The name is reflected in the name of files comprising the
+ * JAR signature of the APK.
+ * @param privateKey signing key
+ * @param certificates list of one or more X.509 certificates. The subject public key of
+ * the first certificate must correspond to the {@code privateKey}.
+ * @param deterministicDsaSigning When signing using DSA, whether or not the
+ * deterministic signing algorithm variant (RFC6979) should be used.
+ */
+ public Builder(String name, PrivateKey privateKey, List<X509Certificate> certificates,
+ boolean deterministicDsaSigning) {
if (name.isEmpty()) {
throw new IllegalArgumentException("Empty name");
}
mName = name;
mPrivateKey = privateKey;
mCertificates = new ArrayList<>(certificates);
+ mDeterministicDsaSigning = deterministicDsaSigning;
}
/**
@@ -1477,7 +1509,8 @@
* this builder.
*/
public SignerConfig build() {
- return new SignerConfig(mName, mPrivateKey, mCertificates);
+ return new SignerConfig(mName, mPrivateKey, mCertificates,
+ mDeterministicDsaSigning);
}
}
}
diff --git a/src/main/java/com/android/apksig/SigningCertificateLineage.java b/src/main/java/com/android/apksig/SigningCertificateLineage.java
index b8f1f8b..767a1f6 100644
--- a/src/main/java/com/android/apksig/SigningCertificateLineage.java
+++ b/src/main/java/com/android/apksig/SigningCertificateLineage.java
@@ -402,7 +402,8 @@
// TODO switch to one signature algorithm selection, or add support for multiple algorithms
List<SignatureAlgorithm> algorithms = V3SchemeSigner.getSuggestedSignatureAlgorithms(
- publicKey, mMinSdkVersion, false /* padding support */);
+ publicKey, mMinSdkVersion, false /* verityEnabled */,
+ false /* deterministicDsaSigning */);
return algorithms.get(0);
}
diff --git a/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java b/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java
index d54f1e0..804eb37 100644
--- a/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java
+++ b/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java
@@ -102,6 +102,18 @@
AndroidSdkVersion.INITIAL_RELEASE),
/**
+ * DSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. Signing is done
+ * deterministically according to RFC 6979.
+ */
+ DETDSA_WITH_SHA256(
+ 0x0301,
+ ContentDigestAlgorithm.CHUNKED_SHA256,
+ "DSA",
+ Pair.of("SHA256withDetDSA", null),
+ AndroidSdkVersion.N,
+ AndroidSdkVersion.INITIAL_RELEASE),
+
+ /**
* RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 4 KB chunks, in
* the same way fsverity operates. This digest and the content length (before digestion, 8 bytes
* in little endian) construct the final digest.
diff --git a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
index 6e9e0c3..85301ca 100644
--- a/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java
@@ -89,6 +89,11 @@
* Digest algorithm used for the signature.
*/
public DigestAlgorithm signatureDigestAlgorithm;
+
+ /**
+ * If DSA is the signing algorithm, whether or not deterministic DSA signing should be used.
+ */
+ public boolean deterministicDsaSigning;
}
/** Hidden constructor to prevent instantiation. */
@@ -495,7 +500,8 @@
PublicKey publicKey = signingCert.getPublicKey();
DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm;
Pair<String, AlgorithmIdentifier> signatureAlgs =
- getSignerInfoSignatureAlgorithm(publicKey, digestAlgorithm);
+ getSignerInfoSignatureAlgorithm(publicKey, digestAlgorithm,
+ signerConfig.deterministicDsaSigning);
String jcaSignatureAlgorithm = signatureAlgs.getFirst();
// Generate the cryptographic signature of the signature file
diff --git a/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java
index c870a9e..156a163 100644
--- a/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java
@@ -86,7 +86,8 @@
* Signature Scheme v2
*/
public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(PublicKey signingKey,
- int minSdkVersion, boolean verityEnabled) throws InvalidKeyException {
+ int minSdkVersion, boolean verityEnabled, boolean deterministicDsaSigning)
+ throws InvalidKeyException {
String keyAlgorithm = signingKey.getAlgorithm();
if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
// Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
@@ -111,7 +112,10 @@
} else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
// DSA is supported only with SHA-256.
List<SignatureAlgorithm> algorithms = new ArrayList<>();
- algorithms.add(SignatureAlgorithm.DSA_WITH_SHA256);
+ algorithms.add(
+ deterministicDsaSigning ?
+ SignatureAlgorithm.DETDSA_WITH_SHA256 :
+ SignatureAlgorithm.DSA_WITH_SHA256);
if (verityEnabled) {
algorithms.add(SignatureAlgorithm.VERITY_DSA_WITH_SHA256);
}
diff --git a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
index cab2a47..04260d5 100644
--- a/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java
@@ -74,7 +74,8 @@
* Signature Scheme v3
*/
public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(PublicKey signingKey,
- int minSdkVersion, boolean verityEnabled) throws InvalidKeyException {
+ int minSdkVersion, boolean verityEnabled, boolean deterministicDsaSigning)
+ throws InvalidKeyException {
String keyAlgorithm = signingKey.getAlgorithm();
if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
// Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
@@ -99,7 +100,10 @@
} else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
// DSA is supported only with SHA-256.
List<SignatureAlgorithm> algorithms = new ArrayList<>();
- algorithms.add(SignatureAlgorithm.DSA_WITH_SHA256);
+ algorithms.add(
+ deterministicDsaSigning ?
+ SignatureAlgorithm.DETDSA_WITH_SHA256 :
+ SignatureAlgorithm.DSA_WITH_SHA256);
if (verityEnabled) {
algorithms.add(SignatureAlgorithm.VERITY_DSA_WITH_SHA256);
}
diff --git a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
index 1a1ad93..0a8b7ee 100644
--- a/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
+++ b/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java
@@ -74,11 +74,12 @@
* Based on a public key, return a signing algorithm that supports verity.
*/
public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(PublicKey signingKey,
- int minSdkVersion, boolean apkSigningBlockPaddingSupported)
+ int minSdkVersion, boolean apkSigningBlockPaddingSupported,
+ boolean deterministicDsaSigning)
throws InvalidKeyException {
List<SignatureAlgorithm> algorithms = V3SchemeSigner.getSuggestedSignatureAlgorithms(
signingKey, minSdkVersion,
- apkSigningBlockPaddingSupported);
+ apkSigningBlockPaddingSupported, deterministicDsaSigning);
// Keeping only supported algorithms.
for (Iterator<SignatureAlgorithm> iter = algorithms.listIterator(); iter.hasNext(); ) {
final SignatureAlgorithm algorithm = iter.next();
diff --git a/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java b/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java
index c27c487..4185dbc 100644
--- a/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java
+++ b/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java
@@ -77,7 +77,8 @@
* when signing with the specified key and digest algorithm.
*/
public static Pair<String, AlgorithmIdentifier> getSignerInfoSignatureAlgorithm(
- PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
+ PublicKey publicKey, DigestAlgorithm digestAlgorithm, boolean deterministicDsaSigning)
+ throws InvalidKeyException {
String keyAlgorithm = publicKey.getAlgorithm();
String jcaDigestPrefixForSigAlg;
switch (digestAlgorithm) {
@@ -115,7 +116,9 @@
throw new IllegalArgumentException(
"Unexpected digest algorithm: " + digestAlgorithm);
}
- return Pair.of(jcaDigestPrefixForSigAlg + "withDSA", sigAlgId);
+ String signingAlgorithmName =
+ jcaDigestPrefixForSigAlg + (deterministicDsaSigning ? "withDetDSA" : "withDSA");
+ return Pair.of(signingAlgorithmName, sigAlgId);
} else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
return Pair.of(
jcaDigestPrefixForSigAlg + "withECDSA",
diff --git a/src/test/java/com/android/apksig/ApkSignerTest.java b/src/test/java/com/android/apksig/ApkSignerTest.java
index 67f339f..830d571 100644
--- a/src/test/java/com/android/apksig/ApkSignerTest.java
+++ b/src/test/java/com/android/apksig/ApkSignerTest.java
@@ -48,6 +48,7 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -61,9 +62,11 @@
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
+import java.security.Security;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
@@ -716,6 +719,48 @@
verifyForMinSdkVersion(out, 20), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG);
}
+
+ @Test
+ public void testDeterministicDsaSignedVerifies() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ try {
+ List<ApkSigner.SignerConfig> signers =
+ Collections.singletonList(getDeterministicDsaSignerConfigFromResources("dsa-2048"));
+ String in = "original.apk";
+
+ // Sign so that the APK is guaranteed to verify on API Level 1+
+ File out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1));
+ assertVerified(verifyForMinSdkVersion(out, 1));
+
+ // Sign so that the APK is guaranteed to verify on API Level 21+
+ out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(21));
+ assertVerified(verifyForMinSdkVersion(out, 21));
+ // Does not verify on API Level 20 because DSA with SHA-256 not supported
+ assertVerificationFailure(
+ verifyForMinSdkVersion(out, 20), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG);
+ } finally {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ }
+ }
+
+ @Test
+ public void testDeterministicDsaSigningIsDeterministic() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ try {
+ List<ApkSigner.SignerConfig> signers =
+ Collections.singletonList(getDeterministicDsaSignerConfigFromResources("dsa-2048"));
+ String in = "original.apk";
+
+ ApkSigner.Builder apkSignerBuilder = new ApkSigner.Builder(signers).setMinSdkVersion(1);
+ File first = sign(in, apkSignerBuilder);
+ File second = sign(in, apkSignerBuilder);
+
+ assertFileContentsEqual(first, second);
+ } finally {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ }
+ }
+
@Test
public void testEcSignedVerifies() throws Exception {
List<ApkSigner.SignerConfig> signers =
@@ -1458,13 +1503,24 @@
ApkVerifierTest.assertVerificationFailure(result, expectedIssue);
}
+ private void assertFileContentsEqual(File first, File second) throws IOException {
+ assertArrayEquals(Files.readAllBytes(Paths.get(first.getPath())),
+ Files.readAllBytes(Paths.get(second.getPath())));
+ }
+
private static ApkSigner.SignerConfig getDefaultSignerConfigFromResources(
String keyNameInResources) throws Exception {
+ return getDefaultSignerConfigFromResources(keyNameInResources, false);
+ }
+
+ private static ApkSigner.SignerConfig getDefaultSignerConfigFromResources(
+ String keyNameInResources, boolean deterministicDsaSigning) throws Exception {
PrivateKey privateKey =
Resources.toPrivateKey(ApkSignerTest.class, keyNameInResources + ".pk8");
List<X509Certificate> certs =
Resources.toCertificateChain(ApkSignerTest.class, keyNameInResources + ".x509.pem");
- return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs).build();
+ return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs,
+ deterministicDsaSigning).build();
}
private static ApkSigner.SignerConfig getDefaultSignerConfigFromResources(
@@ -1475,4 +1531,9 @@
Resources.toCertificateChain(ApkSignerTest.class, certNameInResources);
return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs).build();
}
+
+ private static ApkSigner.SignerConfig getDeterministicDsaSignerConfigFromResources(
+ String keyNameInResources) throws Exception {
+ return getDefaultSignerConfigFromResources(keyNameInResources, true);
+ }
}