forked from openkylin/platform_build
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
This commit is contained in:
parent
8cf60ec166
commit
147626e624
|
@ -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),)
|
||||
|
|
|
@ -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());
|
||||
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);
|
||||
|
||||
PKCS7 pkcs7 = new PKCS7(
|
||||
new AlgorithmId[] { AlgorithmId.get("SHA1") },
|
||||
new ContentInfo(ContentInfo.DATA_OID, null),
|
||||
new X509Certificate[] { publicKey },
|
||||
new SignerInfo[] { signerInfo });
|
||||
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);
|
||||
|
||||
pkcs7.encodeSignedData(out);
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue