Fix DSA APK signatures for API Level 8 and lower.

This modifies JAR signing code to produce DSA signatures which are
accepted by all Android platforms rather than only API Level 9 and
higher.

The issue is that by default Bouncy Castle uses OID 1.2.840.10040.4.3
(dsaWithSha1) in PKCS #7 CMS SignerInfo whereas Android accepts that
only since API Level 9. However, OID 1.2.840.10040.4.1 (dsa) is
accepted by all Android platforms.

Bug: 27461702
Change-Id: I24256a255bcdc2108bdb447557af7568a2c096e3
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
index 8b59b8e..9f4ccce 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
@@ -41,13 +41,20 @@
 import java.util.jar.Manifest;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignatureEncryptionAlgorithmFinder;
 import org.bouncycastle.cms.CMSSignedData;
 import org.bouncycastle.cms.CMSSignedDataGenerator;
-import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.DefaultCMSSignatureEncryptionAlgorithmFinder;
+import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
 import org.bouncycastle.operator.ContentSigner;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -462,10 +469,11 @@
                     .build(signerConfig.privateKey);
             CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
             gen.addSignerInfoGenerator(
-                    new JcaSignerInfoGeneratorBuilder(
-                            new JcaDigestCalculatorProviderBuilder().build())
-                    .setDirectSignature(true)
-                    .build(signer, signerCert));
+                    new SignerInfoGeneratorBuilder(
+                            new JcaDigestCalculatorProviderBuilder().build(),
+                            SignerInfoSignatureAlgorithmFinder.INSTANCE)
+                            .setDirectSignature(true)
+                            .build(signer, new JcaX509CertificateHolder(signerCert)));
             gen.addCertificates(certs);
 
             CMSSignedData sigData =
@@ -482,6 +490,37 @@
         }
     }
 
+    /**
+     * Chooser of SignatureAlgorithm for PKCS #7 CMS SignerInfo.
+     */
+    private static class SignerInfoSignatureAlgorithmFinder
+            implements CMSSignatureEncryptionAlgorithmFinder {
+        private static final SignerInfoSignatureAlgorithmFinder INSTANCE =
+                new SignerInfoSignatureAlgorithmFinder();
+
+        private static final AlgorithmIdentifier DSA =
+                new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, DERNull.INSTANCE);
+
+        private final CMSSignatureEncryptionAlgorithmFinder mDefault =
+                new DefaultCMSSignatureEncryptionAlgorithmFinder();
+
+        @Override
+        public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier id) {
+            // Use the default chooser, but replace dsaWithSha1 with dsa. This is because "dsa" is
+            // accepted by any Android platform whereas "dsaWithSha1" is accepted only since
+            // API Level 9.
+            id = mDefault.findEncryptionAlgorithm(id);
+            if (id != null) {
+                ASN1ObjectIdentifier oid = id.getAlgorithm();
+                if (X9ObjectIdentifiers.id_dsa_with_sha1.equals(oid)) {
+                    return DSA;
+                }
+            }
+
+            return id;
+        }
+    }
+
     private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) {
         switch (digestAlgorithm) {
             case SHA1: