Add support for deterministic DSA signing

This commit adds support for deterministic DSA signing (RFC6979)
using the Java signature algorithm SHA256withDetDSA. Note that this
signature algorithm may not be supported by the default Security
Providers and may require adding an additional Provider. The
deterministic signature is still a valid signature and can be
verified using the same process to verify a standard DSA signature.

Bug: 169266066
Test: gradlew test
Change-Id: I80bd17302814411c0feaea8a7e6a08e80476d7ef
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);
+    }
 }