diff options
author | Doug Zongker <dougz@google.com> | 2012-09-04 13:32:13 -0700 |
---|---|---|
committer | Brian Carlstrom <bdc@google.com> | 2012-09-18 23:29:10 -0700 |
commit | 147626e624ec0b709e6dc8f156ccb391fffef9c8 (patch) | |
tree | e79090fded96175c027b8eaaba1807fee778b316 /tools/signapk | |
parent | 8cf60ec166e32472801d203384f9de2e62004da1 (diff) | |
download | replicant_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.mk | 1 | ||||
-rw-r--r-- | tools/signapk/SignApk.java | 184 |
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; |