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);
+    }
 }