Merge \\"Don\\'t depend on Bouncy Castle.\\" am: 1fb96c3ff5

am: 3b36c7960d

Change-Id: Ib33cb3a8aaa94df96731a16f20a7ef5415f42a3f
This commit is contained in:
Alex Klyubin
2016-06-15 20:48:11 +00:00
committed by android-build-merger
3 changed files with 158 additions and 106 deletions

View File

@@ -20,7 +20,10 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := apksigner-core LOCAL_MODULE := apksigner-core
LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES = \
bouncycastle-host \ # Disable warnnings about our use of internal proprietary OpenJDK API.
bouncycastle-bcpkix-host # 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) include $(BUILD_HOST_JAVA_LIBRARY)

View File

@@ -33,7 +33,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SignatureException; import java.security.SignatureException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -342,7 +342,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
mOutputJarEntryDigests, mOutputJarEntryDigests,
apkSigningSchemeIds, apkSigningSchemeIds,
inputJarManifest); inputJarManifest);
} catch (CertificateEncodingException e) { } catch (CertificateException e) {
throw new SignatureException("Failed to generate v1 signature", e); throw new SignatureException("Failed to generate v1 signature", e);
} }
} else { } else {
@@ -360,7 +360,7 @@ public class DefaultApkSignerEngine implements ApkSignerEngine {
mV1ContentDigestAlgorithm, mV1ContentDigestAlgorithm,
apkSigningSchemeIds, apkSigningSchemeIds,
newManifest); newManifest);
} catch (CertificateEncodingException e) { } catch (CertificateException e) {
throw new SignatureException("Failed to generate v1 signature", e); throw new SignatureException("Failed to generate v1 signature", e);
} }
} else { } else {

View File

@@ -24,8 +24,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException; 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.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
@@ -40,25 +42,11 @@ import java.util.TreeMap;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import org.bouncycastle.asn1.ASN1InputStream; import sun.security.pkcs.ContentInfo;
import org.bouncycastle.asn1.ASN1ObjectIdentifier; import sun.security.pkcs.PKCS7;
import org.bouncycastle.asn1.DERNull; import sun.security.pkcs.SignerInfo;
import org.bouncycastle.asn1.DEROutputStream; import sun.security.x509.AlgorithmId;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import sun.security.x509.X500Name;
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 com.android.apksigner.core.internal.jar.ManifestWriter; import com.android.apksigner.core.internal.jar.ManifestWriter;
import com.android.apksigner.core.internal.jar.SignatureFileWriter; import com.android.apksigner.core.internal.jar.SignatureFileWriter;
@@ -238,7 +226,7 @@ public abstract class V1SchemeSigner {
Map<String, byte[]> jarEntryDigests, Map<String, byte[]> jarEntryDigests,
List<Integer> apkSigningSchemeIds, List<Integer> apkSigningSchemeIds,
byte[] sourceManifestBytes) byte[] sourceManifestBytes)
throws InvalidKeyException, CertificateEncodingException, SignatureException { throws InvalidKeyException, CertificateException, SignatureException {
if (signerConfigs.isEmpty()) { if (signerConfigs.isEmpty()) {
throw new IllegalArgumentException("At least one signer config must be provided"); throw new IllegalArgumentException("At least one signer config must be provided");
} }
@@ -265,7 +253,7 @@ public abstract class V1SchemeSigner {
DigestAlgorithm digestAlgorithm, DigestAlgorithm digestAlgorithm,
List<Integer> apkSigningSchemeIds, List<Integer> apkSigningSchemeIds,
OutputManifestFile manifest) OutputManifestFile manifest)
throws InvalidKeyException, CertificateEncodingException, SignatureException { throws InvalidKeyException, CertificateException, SignatureException {
if (signerConfigs.isEmpty()) { if (signerConfigs.isEmpty()) {
throw new IllegalArgumentException("At least one signer config must be provided"); throw new IllegalArgumentException("At least one signer config must be provided");
} }
@@ -283,8 +271,8 @@ public abstract class V1SchemeSigner {
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new InvalidKeyException( throw new InvalidKeyException(
"Failed to sign using signer \"" + signerName + "\"", e); "Failed to sign using signer \"" + signerName + "\"", e);
} catch (CertificateEncodingException e) { } catch (CertificateException e) {
throw new CertificateEncodingException( throw new CertificateException(
"Failed to sign using signer \"" + signerName + "\"", e); "Failed to sign using signer \"" + signerName + "\"", e);
} catch (SignatureException e) { } catch (SignatureException e) {
throw new SignatureException( throw new SignatureException(
@@ -456,69 +444,156 @@ public abstract class V1SchemeSigner {
return out.toByteArray(); return out.toByteArray();
} }
@SuppressWarnings("restriction")
private static byte[] generateSignatureBlock( private static byte[] generateSignatureBlock(
SignerConfig signerConfig, byte[] signatureFileBytes) SignerConfig signerConfig, byte[] signatureFileBytes)
throws InvalidKeyException, CertificateEncodingException, SignatureException { throws InvalidKeyException, CertificateException, SignatureException {
JcaCertStore certs = new JcaCertStore(signerConfig.certificates); List<X509Certificate> signerCerts = signerConfig.certificates;
X509Certificate signerCert = signerConfig.certificates.get(0); X509Certificate signerCert = signerCerts.get(0);
String jcaSignatureAlgorithm = PublicKey signerPublicKey = signerCert.getPublicKey();
getJcaSignatureAlgorithm( DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm;
signerCert.getPublicKey(), signerConfig.signatureDigestAlgorithm); Pair<String, AlgorithmId> signatureAlgs =
getSignerInfoSignatureAlgorithm(signerPublicKey, digestAlgorithm);
String jcaSignatureAlgorithm = signatureAlgs.getFirst();
byte[] signatureBytes;
try { try {
ContentSigner signer = Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
new JcaContentSignerBuilder(jcaSignatureAlgorithm) signature.initSign(signerConfig.privateKey);
.build(signerConfig.privateKey); signature.update(signatureFileBytes);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); signatureBytes = signature.sign();
gen.addSignerInfoGenerator( } catch (NoSuchAlgorithmException e) {
new SignerInfoGeneratorBuilder( throw new SignatureException(
new JcaDigestCalculatorProviderBuilder().build(), jcaSignatureAlgorithm + " Signature implementation not found", e);
SignerInfoSignatureAlgorithmFinder.INSTANCE) }
.setDirectSignature(true)
.build(signer, new JcaX509CertificateHolder(signerCert)));
gen.addCertificates(certs);
CMSSignedData sigData = X500Name issuerName;
gen.generate(new CMSProcessableByteArray(signatureFileBytes), false); try {
issuerName = new X500Name(signerCert.getIssuerX500Principal().getName());
} catch (IOException e) {
throw new CertificateParsingException(
"Failed to parse signer certificate issuer name", e);
}
ByteArrayOutputStream out = new ByteArrayOutputStream(); AlgorithmId digestAlgorithmId = getSignerInfoDigestAlgorithm(digestAlgorithm);
try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) { SignerInfo signerInfo =
DEROutputStream dos = new DEROutputStream(out); new SignerInfo(
dos.writeObject(asn1.readObject()); issuerName,
} signerCert.getSerialNumber(),
return out.toByteArray(); digestAlgorithmId,
} catch (OperatorCreationException | CMSException | IOException e) { signatureAlgs.getSecond(),
throw new SignatureException("Failed to generate signature", e); 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");
/**
* Returns the {@code SignerInfo} {@code DigestAlgorithm} to use for {@code SignerInfo} signing
* using the specified digest algorithm.
*/
@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);
} }
} }
/** /**
* Chooser of SignatureAlgorithm for PKCS #7 CMS SignerInfo. * 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.
*/ */
private static class SignerInfoSignatureAlgorithmFinder @SuppressWarnings("restriction")
implements CMSSignatureEncryptionAlgorithmFinder { private static Pair<String, AlgorithmId> getSignerInfoSignatureAlgorithm(
private static final SignerInfoSignatureAlgorithmFinder INSTANCE = PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
new SignerInfoSignatureAlgorithmFinder(); // 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).
private static final AlgorithmIdentifier DSA = String keyAlgorithm = publicKey.getAlgorithm();
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, DERNull.INSTANCE); String digestPrefixForSigAlg;
switch (digestAlgorithm) {
private final CMSSignatureEncryptionAlgorithmFinder mDefault = case SHA1:
new DefaultCMSSignatureEncryptionAlgorithmFinder(); digestPrefixForSigAlg = "SHA1";
break;
@Override case SHA256:
public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier id) { digestPrefixForSigAlg = "SHA256";
// Use the default chooser, but replace dsaWithSha1 with dsa. This is because "dsa" is break;
// accepted by any Android platform whereas "dsaWithSha1" is accepted only since default:
// API Level 9. throw new IllegalArgumentException(
id = mDefault.findEncryptionAlgorithm(id); "Unexpected digest algorithm: " + digestAlgorithm);
if (id != null) { }
ASN1ObjectIdentifier oid = id.getAlgorithm(); if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
if (X9ObjectIdentifiers.id_dsa_with_sha1.equals(oid)) { return Pair.of(
return DSA; 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); "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);
}
}
} }