summaryrefslogtreecommitdiffstats
path: root/tools/signapk
diff options
context:
space:
mode:
authorDoug Zongker <dougz@google.com>2012-09-04 13:32:13 -0700
committerBrian Carlstrom <bdc@google.com>2012-09-18 23:29:10 -0700
commit147626e624ec0b709e6dc8f156ccb391fffef9c8 (patch)
treee79090fded96175c027b8eaaba1807fee778b316 /tools/signapk
parent8cf60ec166e32472801d203384f9de2e62004da1 (diff)
downloadreplicant_build-147626e624ec0b709e6dc8f156ccb391fffef9c8.zip
replicant_build-147626e624ec0b709e6dc8f156ccb391fffef9c8.tar.gz
replicant_build-147626e624ec0b709e6dc8f156ccb391fffef9c8.tar.bz2
change SignApk.java to use bouncy castle for signing
Remove use of the private sun.security.* classes for generating pkcs7 signatures and use bouncy castle instead. Change-Id: Ie8213575461975085d119e000e764d2a28c26715
Diffstat (limited to 'tools/signapk')
-rw-r--r--tools/signapk/Android.mk1
-rw-r--r--tools/signapk/SignApk.java184
2 files changed, 114 insertions, 71 deletions
diff --git a/tools/signapk/Android.mk b/tools/signapk/Android.mk
index b2de21c..620ccb1 100644
--- a/tools/signapk/Android.mk
+++ b/tools/signapk/Android.mk
@@ -21,6 +21,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := signapk
LOCAL_SRC_FILES := SignApk.java
LOCAL_JAR_MANIFEST := SignApk.mf
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host bouncycastle-bcpkix-host
include $(BUILD_HOST_JAVA_LIBRARY)
ifeq ($(TARGET_BUILD_APPS),)
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java
index cb19296..07aefa7 100644
--- a/tools/signapk/SignApk.java
+++ b/tools/signapk/SignApk.java
@@ -16,12 +16,23 @@
package com.android.signapk;
-import sun.misc.BASE64Encoder;
-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 org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -35,16 +46,15 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
-import java.security.AlgorithmParameters;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.Certificate;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
@@ -52,9 +62,7 @@ import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.Enumeration;
-import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.Attributes;
@@ -78,6 +86,8 @@ class SignApk {
private static final String OTACERT_NAME = "META-INF/com/android/otacert";
+ private static Provider sBouncyCastleProvider;
+
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
@@ -181,7 +191,6 @@ class SignApk {
main.putValue("Created-By", "1.0 (Android SignApk)");
}
- BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] buffer = new byte[4096];
int num;
@@ -212,7 +221,8 @@ class SignApk {
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
- attr.putValue("SHA1-Digest", base64.encode(md.digest()));
+ attr.putValue("SHA1-Digest",
+ new String(Base64.encode(md.digest()), "ASCII"));
output.getEntries().put(name, attr);
}
}
@@ -232,7 +242,6 @@ class SignApk {
long timestamp,
Manifest manifest)
throws IOException, GeneralSecurityException {
- BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance("SHA1");
JarEntry je = new JarEntry(OTACERT_NAME);
@@ -248,40 +257,31 @@ class SignApk {
input.close();
Attributes attr = new Attributes();
- attr.putValue("SHA1-Digest", base64.encode(md.digest()));
+ attr.putValue("SHA1-Digest",
+ new String(Base64.encode(md.digest()), "ASCII"));
manifest.getEntries().put(OTACERT_NAME, attr);
}
- /** Write to another stream and also feed it to the Signature object. */
- private static class SignatureOutputStream extends FilterOutputStream {
- private Signature mSignature;
+ /** Write to another stream and track how many bytes have been
+ * written.
+ */
+ private static class CountOutputStream extends FilterOutputStream {
private int mCount;
- public SignatureOutputStream(OutputStream out, Signature sig) {
+ public CountOutputStream(OutputStream out) {
super(out);
- mSignature = sig;
mCount = 0;
}
@Override
public void write(int b) throws IOException {
- try {
- mSignature.update((byte) b);
- } catch (SignatureException e) {
- throw new IOException("SignatureException: " + e);
- }
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
- try {
- mSignature.update(b, off, len);
- } catch (SignatureException e) {
- throw new IOException("SignatureException: " + e);
- }
super.write(b, off, len);
mCount += len;
}
@@ -292,14 +292,13 @@ class SignApk {
}
/** Write a .SF file with a digest of the specified manifest. */
- private static void writeSignatureFile(Manifest manifest, SignatureOutputStream out)
- throws IOException, GeneralSecurityException {
+ private static void writeSignatureFile(Manifest manifest, OutputStream out)
+ throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
- BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance("SHA1");
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
@@ -308,7 +307,8 @@ class SignApk {
// Digest of the entire manifest
manifest.write(print);
print.flush();
- main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));
+ main.putValue("SHA1-Digest-Manifest",
+ new String(Base64.encode(md.digest()), "ASCII"));
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
@@ -321,48 +321,88 @@ class SignApk {
print.flush();
Attributes sfAttr = new Attributes();
- sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));
+ sfAttr.putValue("SHA1-Digest",
+ new String(Base64.encode(md.digest()), "ASCII"));
sf.getEntries().put(entry.getKey(), sfAttr);
}
- sf.write(out);
+ CountOutputStream cout = new CountOutputStream(out);
+ sf.write(cout);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
// As a workaround, add an extra CRLF in this case.
- if ((out.size() % 1024) == 0) {
- out.write('\r');
- out.write('\n');
+ if ((cout.size() % 1024) == 0) {
+ cout.write('\r');
+ cout.write('\n');
}
}
- /** Write a .RSA file with a digital signature. */
+ private static class CMSByteArraySlice implements CMSTypedData {
+ private final ASN1ObjectIdentifier type;
+ private final byte[] data;
+ private final int offset;
+ private final int length;
+ public CMSByteArraySlice(byte[] data, int offset, int length) {
+ this.data = data;
+ this.offset = offset;
+ this.length = length;
+ this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
+ }
+
+ public Object getContent() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ASN1ObjectIdentifier getContentType() {
+ return type;
+ }
+
+ public void write(OutputStream out) throws IOException {
+ out.write(data, offset, length);
+ }
+ }
+
+ /** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock(
- Signature signature, X509Certificate publicKey, OutputStream out)
- throws IOException, GeneralSecurityException {
- SignerInfo signerInfo = new SignerInfo(
- new X500Name(publicKey.getIssuerX500Principal().getName()),
- publicKey.getSerialNumber(),
- AlgorithmId.get("SHA1"),
- AlgorithmId.get("RSA"),
- signature.sign());
-
- PKCS7 pkcs7 = new PKCS7(
- new AlgorithmId[] { AlgorithmId.get("SHA1") },
- new ContentInfo(ContentInfo.DATA_OID, null),
- new X509Certificate[] { publicKey },
- new SignerInfo[] { signerInfo });
-
- pkcs7.encodeSignedData(out);
+ CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
+ OutputStream out)
+ throws IOException,
+ CertificateEncodingException,
+ OperatorCreationException,
+ CMSException {
+ ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1);
+ certList.add(publicKey);
+ JcaCertStore certs = new JcaCertStore(certList);
+
+ CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA")
+ .setProvider(sBouncyCastleProvider)
+ .build(privateKey);
+ gen.addSignerInfoGenerator(
+ new JcaSignerInfoGeneratorBuilder(
+ new JcaDigestCalculatorProviderBuilder()
+ .setProvider(sBouncyCastleProvider)
+ .build())
+ .setDirectSignature(true)
+ .build(sha1Signer, publicKey));
+ gen.addCertificates(certs);
+ CMSSignedData sigData = gen.generate(data, false);
+
+ ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
+ DEROutputStream dos = new DEROutputStream(out);
+ dos.writeObject(asn1.readObject());
}
private static void signWholeOutputFile(byte[] zipData,
OutputStream outputStream,
X509Certificate publicKey,
PrivateKey privateKey)
- throws IOException, GeneralSecurityException {
-
+ throws IOException,
+ CertificateEncodingException,
+ OperatorCreationException,
+ CMSException {
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
@@ -373,10 +413,6 @@ class SignApk {
throw new IllegalArgumentException("zip data already has an archive comment");
}
- Signature signature = Signature.getInstance("SHA1withRSA");
- signature.initSign(privateKey);
- signature.update(zipData, 0, zipData.length-2);
-
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
@@ -386,7 +422,9 @@ class SignApk {
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);
temp.write(0);
- writeSignatureBlock(signature, publicKey, temp);
+
+ writeSignatureBlock(new CMSByteArraySlice(zipData, 0, zipData.length-2),
+ publicKey, privateKey, temp);
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
@@ -399,7 +437,7 @@ class SignApk {
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
- // be nearly 4GB in side. So it's unlikely that a real
+ // be nearly 4GB in size. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp.write(0xff);
@@ -438,7 +476,7 @@ class SignApk {
int num;
Map<String, Attributes> entries = manifest.getEntries();
- List<String> names = new ArrayList(entries.keySet());
+ ArrayList<String> names = new ArrayList<String>(entries.keySet());
Collections.sort(names);
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
@@ -469,6 +507,9 @@ class SignApk {
System.exit(2);
}
+ sBouncyCastleProvider = new BouncyCastleProvider();
+ Security.addProvider(sBouncyCastleProvider);
+
boolean signWholeFile = false;
int argstart = 0;
if (args[0].equals("-w")) {
@@ -527,19 +568,20 @@ class SignApk {
manifest.write(outputJar);
// CERT.SF
- Signature signature = Signature.getInstance("SHA1withRSA");
- signature.initSign(privateKey);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
- writeSignatureFile(manifest,
- new SignatureOutputStream(outputJar, signature));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeSignatureFile(manifest, baos);
+ byte[] signedData = baos.toByteArray();
+ outputJar.write(signedData);
// CERT.RSA
je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
- writeSignatureBlock(signature, publicKey, outputJar);
+ writeSignatureBlock(new CMSProcessableByteArray(signedData),
+ publicKey, privateKey, outputJar);
outputJar.close();
outputJar = null;