Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1fb96c3f authored by Alex Klyubin's avatar Alex Klyubin Committed by Gerrit Code Review
Browse files

Merge "Don't depend on Bouncy Castle."

parents 98b4f07d 7f770c09
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -20,7 +20,10 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := apksigner-core
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES = \
  bouncycastle-host \
  bouncycastle-bcpkix-host

# Disable warnnings about our use of internal proprietary OpenJDK API.
# TODO: Remove this workaround by moving to our own implementation of PKCS #7
# SignedData block generation, parsing, and verification.
LOCAL_JAVACFLAGS := -XDignore.symbol.file

include $(BUILD_HOST_JAVA_LIBRARY)
+3 −3
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
@@ -342,7 +342,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
                                mOutputJarEntryDigests,
                                apkSigningSchemeIds,
                                inputJarManifest);
            } catch (CertificateEncodingException e) {
            } catch (CertificateException e) {
                throw new SignatureException("Failed to generate v1 signature", e);
            }
        } else {
@@ -360,7 +360,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
                                    mV1ContentDigestAlgorithm,
                                    apkSigningSchemeIds,
                                    newManifest);
                } catch (CertificateEncodingException e) {
                } catch (CertificateException e) {
                    throw new SignatureException("Failed to generate v1 signature", e);
                }
            } else {
+150 −101
Original line number Diff line number Diff line
@@ -24,8 +24,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
@@ -40,25 +42,11 @@ import java.util.TreeMap;
import java.util.jar.Attributes;
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.DefaultCMSSignatureEncryptionAlgorithmFinder;
import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;

import com.android.apksigner.core.internal.jar.ManifestWriter;
import com.android.apksigner.core.internal.jar.SignatureFileWriter;
@@ -238,7 +226,7 @@ public abstract class V1SchemeSigner {
            Map<String, byte[]> jarEntryDigests,
            List<Integer> apkSigningSchemeIds,
            byte[] sourceManifestBytes)
                    throws InvalidKeyException, CertificateEncodingException, SignatureException {
                    throws InvalidKeyException, CertificateException, SignatureException {
        if (signerConfigs.isEmpty()) {
            throw new IllegalArgumentException("At least one signer config must be provided");
        }
@@ -265,7 +253,7 @@ public abstract class V1SchemeSigner {
            DigestAlgorithm digestAlgorithm,
            List<Integer> apkSigningSchemeIds,
            OutputManifestFile manifest)
                    throws InvalidKeyException, CertificateEncodingException, SignatureException {
                    throws InvalidKeyException, CertificateException, SignatureException {
        if (signerConfigs.isEmpty()) {
            throw new IllegalArgumentException("At least one signer config must be provided");
        }
@@ -283,8 +271,8 @@ public abstract class V1SchemeSigner {
            } catch (InvalidKeyException e) {
                throw new InvalidKeyException(
                        "Failed to sign using signer \"" + signerName + "\"", e);
            } catch (CertificateEncodingException e) {
                throw new CertificateEncodingException(
            } catch (CertificateException e) {
                throw new CertificateException(
                        "Failed to sign using signer \"" + signerName + "\"", e);
            } catch (SignatureException e) {
                throw new SignatureException(
@@ -456,69 +444,156 @@ public abstract class V1SchemeSigner {
        return out.toByteArray();
    }

    @SuppressWarnings("restriction")
    private static byte[] generateSignatureBlock(
            SignerConfig signerConfig, byte[] signatureFileBytes)
                    throws InvalidKeyException, CertificateEncodingException, SignatureException {
        JcaCertStore certs = new JcaCertStore(signerConfig.certificates);
        X509Certificate signerCert = signerConfig.certificates.get(0);
        String jcaSignatureAlgorithm =
                getJcaSignatureAlgorithm(
                        signerCert.getPublicKey(), signerConfig.signatureDigestAlgorithm);
                    throws InvalidKeyException, CertificateException, SignatureException {
        List<X509Certificate> signerCerts = signerConfig.certificates;
        X509Certificate signerCert = signerCerts.get(0);
        PublicKey signerPublicKey = signerCert.getPublicKey();
        DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm;
        Pair<String, AlgorithmId> signatureAlgs =
                getSignerInfoSignatureAlgorithm(signerPublicKey, digestAlgorithm);
        String jcaSignatureAlgorithm = signatureAlgs.getFirst();
        byte[] signatureBytes;
        try {
            ContentSigner signer =
                    new JcaContentSignerBuilder(jcaSignatureAlgorithm)
                    .build(signerConfig.privateKey);
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            gen.addSignerInfoGenerator(
                    new SignerInfoGeneratorBuilder(
                            new JcaDigestCalculatorProviderBuilder().build(),
                            SignerInfoSignatureAlgorithmFinder.INSTANCE)
                            .setDirectSignature(true)
                            .build(signer, new JcaX509CertificateHolder(signerCert)));
            gen.addCertificates(certs);

            CMSSignedData sigData =
                    gen.generate(new CMSProcessableByteArray(signatureFileBytes), false);

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) {
                DEROutputStream dos = new DEROutputStream(out);
                dos.writeObject(asn1.readObject());
            Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
            signature.initSign(signerConfig.privateKey);
            signature.update(signatureFileBytes);
            signatureBytes = signature.sign();
        } catch (NoSuchAlgorithmException e) {
            throw new SignatureException(
                    jcaSignatureAlgorithm + " Signature implementation not found", e);
        }
            return out.toByteArray();
        } catch (OperatorCreationException | CMSException | IOException e) {
            throw new SignatureException("Failed to generate signature", e);

        X500Name issuerName;
        try {
            issuerName = new X500Name(signerCert.getIssuerX500Principal().getName());
        } catch (IOException e) {
            throw new CertificateParsingException(
                    "Failed to parse signer certificate issuer name", e);
        }

        AlgorithmId digestAlgorithmId = getSignerInfoDigestAlgorithm(digestAlgorithm);
        SignerInfo signerInfo =
                new SignerInfo(
                        issuerName,
                        signerCert.getSerialNumber(),
                        digestAlgorithmId,
                        signatureAlgs.getSecond(),
                        signatureBytes);
        PKCS7 pkcs7 =
                new PKCS7(
                        new AlgorithmId[] {digestAlgorithmId},
                        new ContentInfo(ContentInfo.DATA_OID, null),
                        signerCerts.toArray(new X509Certificate[signerCerts.size()]),
                        new SignerInfo[] {signerInfo});

        ByteArrayOutputStream result = new ByteArrayOutputStream();
        try {
            pkcs7.encodeSignedData(result);
        } catch (IOException e) {
            throw new SignatureException("Failed to encode PKCS#7 signed data", e);
        }
        return result.toByteArray();
    }

    @SuppressWarnings("restriction")
    private static final AlgorithmId OID_DIGEST_SHA1 = getSupportedAlgorithmId("1.3.14.3.2.26");
    @SuppressWarnings("restriction")
    private static final AlgorithmId OID_DIGEST_SHA256 =
            getSupportedAlgorithmId("2.16.840.1.101.3.4.2.1");

    /**
     * Chooser of SignatureAlgorithm for PKCS #7 CMS SignerInfo.
     * Returns the {@code SignerInfo} {@code DigestAlgorithm} to use for {@code SignerInfo} signing
     * using the specified digest algorithm.
     */
    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);
    @SuppressWarnings("restriction")
    private static AlgorithmId getSignerInfoDigestAlgorithm(DigestAlgorithm digestAlgorithm) {
        switch (digestAlgorithm) {
            case SHA1:
                return OID_DIGEST_SHA1;
            case SHA256:
                return OID_DIGEST_SHA256;
            default:
                throw new RuntimeException("Unsupported digest algorithm: " + digestAlgorithm);
        }
    }

        private final CMSSignatureEncryptionAlgorithmFinder mDefault =
                new DefaultCMSSignatureEncryptionAlgorithmFinder();
    /**
     * Returns the JCA {@link Signature} algorithm and {@code SignerInfo} {@code SignatureAlgorithm}
     * to use for {@code SignerInfo} which signs with the specified key and digest algorithms.
     */
    @SuppressWarnings("restriction")
    private static Pair<String, AlgorithmId> getSignerInfoSignatureAlgorithm(
            PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
        // NOTE: This method on purpose uses hard-coded OIDs instead of
        // Algorithm.getId(JCA Signature Algorithm). This is to ensure that the generated SignedData
        // is compatible with all targeted Android platforms and is not dependent on changes in the
        // JCA Signature Algorithm -> OID mappings maintained by AlgorithmId.get(String).

        @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;
        String keyAlgorithm = publicKey.getAlgorithm();
        String digestPrefixForSigAlg;
        switch (digestAlgorithm) {
            case SHA1:
                digestPrefixForSigAlg = "SHA1";
                break;
            case SHA256:
                digestPrefixForSigAlg = "SHA256";
                break;
            default:
                throw new IllegalArgumentException(
                        "Unexpected digest algorithm: " + digestAlgorithm);
        }
        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
            return Pair.of(
                    digestPrefixForSigAlg + "withRSA",
                    getSupportedAlgorithmId("1.2.840.113549.1.1.1") // RSA encryption
                    );
        } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
            AlgorithmId sigAlgId;
            switch (digestAlgorithm) {
                case SHA1:
                    sigAlgId = getSupportedAlgorithmId("1.2.840.10040.4.1"); // DSA
                    break;
                case SHA256:
                    // DSA signatures with SHA-256 in SignedData are accepted by Android API Level
                    // 21 and higher. However, there are two ways to specify their SignedData
                    // SignatureAlgorithm: dsaWithSha256 (2.16.840.1.101.3.4.3.2) and
                    // dsa (1.2.840.10040.4.1). The latter works only on API Level 22+. Thus, we use
                    // the former.
                    sigAlgId = getSupportedAlgorithmId("2.16.840.1.101.3.4.3.2"); // DSA with SHA-256
                    break;
                default:
                    throw new IllegalArgumentException(
                            "Unexpected digest algorithm: " + digestAlgorithm);
            }
            return Pair.of(digestPrefixForSigAlg + "withDSA", sigAlgId);
        } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
            AlgorithmId sigAlgId;
            switch (digestAlgorithm) {
                case SHA1:
                    sigAlgId = getSupportedAlgorithmId("1.2.840.10045.4.1"); // ECDSA with SHA-1
                    break;
                case SHA256:
                    sigAlgId = getSupportedAlgorithmId("1.2.840.10045.4.3.2"); // ECDSA with SHA-256
                    break;
                default:
                    throw new IllegalArgumentException(
                            "Unexpected digest algorithm: " + digestAlgorithm);
            }
            return Pair.of(digestPrefixForSigAlg + "withECDSA", sigAlgId);
        } else {
            throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
        }
    }

            return id;
    @SuppressWarnings("restriction")
    private static AlgorithmId getSupportedAlgorithmId(String oid) {
        try {
            return AlgorithmId.get(oid);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Unsupported OID: " + oid, e);
        }
    }

@@ -545,30 +620,4 @@ public abstract class V1SchemeSigner {
                        "Unexpected content digest algorithm: " + digestAlgorithm);
        }
    }

    private static String getJcaSignatureAlgorithm(
            PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
        String keyAlgorithm = publicKey.getAlgorithm();
        String digestPrefixForSigAlg;
        switch (digestAlgorithm) {
            case SHA1:
                digestPrefixForSigAlg = "SHA1";
                break;
            case SHA256:
                digestPrefixForSigAlg = "SHA256";
                break;
            default:
                throw new IllegalArgumentException(
                        "Unexpected digest algorithm: " + digestAlgorithm);
        }
        if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
            return digestPrefixForSigAlg + "withRSA";
        } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
            return digestPrefixForSigAlg + "withDSA";
        } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
            return digestPrefixForSigAlg + "withECDSA";
        } else {
            throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
        }
    }
}