forked from openkylin/platform_build
am abc0bf08: Merge "SignApk: add support for EC keys"
* commit 'abc0bf084beea3251c042b28576c8f398749c80b': SignApk: add support for EC keys
This commit is contained in:
commit
19ccb3a62a
|
@ -52,8 +52,10 @@ import java.security.GeneralSecurityException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
|
@ -64,6 +66,7 @@ import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.jar.Attributes;
|
import java.util.jar.Attributes;
|
||||||
|
@ -83,9 +86,9 @@ import javax.crypto.spec.PBEKeySpec;
|
||||||
* Prior to the keylimepie release, SignApk ignored the signature
|
* Prior to the keylimepie release, SignApk ignored the signature
|
||||||
* algorithm specified in the certificate and always used SHA1withRSA.
|
* algorithm specified in the certificate and always used SHA1withRSA.
|
||||||
*
|
*
|
||||||
* Starting with keylimepie, we support SHA256withRSA, and use the
|
* Starting with JB-MR2, the platform supports SHA256withRSA, so we use
|
||||||
* signature algorithm in the certificate to select which to use
|
* the signature algorithm in the certificate to select which to use
|
||||||
* (SHA256withRSA or SHA1withRSA).
|
* (SHA256withRSA or SHA1withRSA). Also in JB-MR2, EC keys are supported.
|
||||||
*
|
*
|
||||||
* Because there are old keys still in use whose certificate actually
|
* Because there are old keys still in use whose certificate actually
|
||||||
* says "MD5withRSA", we treat these as though they say "SHA1withRSA"
|
* says "MD5withRSA", we treat these as though they say "SHA1withRSA"
|
||||||
|
@ -95,15 +98,15 @@ import javax.crypto.spec.PBEKeySpec;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command line tool to sign JAR files (including APKs and OTA
|
* Command line tool to sign JAR files (including APKs and OTA updates) in a way
|
||||||
* updates) in a way compatible with the mincrypt verifier, using RSA
|
* compatible with the mincrypt verifier, using EC or RSA keys and SHA1 or
|
||||||
* keys and SHA1 or SHA-256.
|
* SHA-256 (see historical note).
|
||||||
*/
|
*/
|
||||||
class SignApk {
|
class SignApk {
|
||||||
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
|
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
|
||||||
private static final String CERT_RSA_NAME = "META-INF/CERT.RSA";
|
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
|
||||||
private static final String CERT_SF_MULTI_NAME = "META-INF/CERT%d.SF";
|
private static final String CERT_SF_MULTI_NAME = "META-INF/CERT%d.SF";
|
||||||
private static final String CERT_RSA_MULTI_NAME = "META-INF/CERT%d.RSA";
|
private static final String CERT_SIG_MULTI_NAME = "META-INF/CERT%d.%s";
|
||||||
|
|
||||||
private static final String OTACERT_NAME = "META-INF/com/android/otacert";
|
private static final String OTACERT_NAME = "META-INF/com/android/otacert";
|
||||||
|
|
||||||
|
@ -117,12 +120,12 @@ class SignApk {
|
||||||
* Return one of USE_SHA1 or USE_SHA256 according to the signature
|
* Return one of USE_SHA1 or USE_SHA256 according to the signature
|
||||||
* algorithm specified in the cert.
|
* algorithm specified in the cert.
|
||||||
*/
|
*/
|
||||||
private static int getAlgorithm(X509Certificate cert) {
|
private static int getDigestAlgorithm(X509Certificate cert) {
|
||||||
String sigAlg = cert.getSigAlgName();
|
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
|
||||||
if ("SHA1withRSA".equals(sigAlg) ||
|
if ("SHA1WITHRSA".equals(sigAlg) ||
|
||||||
"MD5withRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
|
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above.
|
||||||
return USE_SHA1;
|
return USE_SHA1;
|
||||||
} else if ("SHA256withRSA".equals(sigAlg)) {
|
} else if (sigAlg.startsWith("SHA256WITH")) {
|
||||||
return USE_SHA256;
|
return USE_SHA256;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
|
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
|
||||||
|
@ -130,9 +133,26 @@ class SignApk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the expected signature algorithm for this key type. */
|
||||||
|
private static String getSignatureAlgorithm(X509Certificate cert) {
|
||||||
|
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
|
||||||
|
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
|
||||||
|
if ("RSA".equalsIgnoreCase(keyType)) {
|
||||||
|
if (getDigestAlgorithm(cert) == USE_SHA256) {
|
||||||
|
return "SHA256withRSA";
|
||||||
|
} else {
|
||||||
|
return "SHA1withRSA";
|
||||||
|
}
|
||||||
|
} else if ("EC".equalsIgnoreCase(keyType)) {
|
||||||
|
return "SHA256withECDSA";
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unsupported key type: " + keyType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Files matching this pattern are not copied to the output.
|
// Files matching this pattern are not copied to the output.
|
||||||
private static Pattern stripPattern =
|
private static Pattern stripPattern =
|
||||||
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|(" +
|
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
|
||||||
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
|
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
|
||||||
|
|
||||||
private static X509Certificate readPublicKey(File file)
|
private static X509Certificate readPublicKey(File file)
|
||||||
|
@ -211,16 +231,32 @@ class SignApk {
|
||||||
spec = new PKCS8EncodedKeySpec(bytes);
|
spec = new PKCS8EncodedKeySpec(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
PrivateKey key;
|
||||||
return KeyFactory.getInstance("RSA").generatePrivate(spec);
|
key = decodeAsKeyType(spec, "RSA");
|
||||||
} catch (InvalidKeySpecException ex) {
|
if (key != null) {
|
||||||
return KeyFactory.getInstance("DSA").generatePrivate(spec);
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key = decodeAsKeyType(spec, "EC");
|
||||||
|
if (key != null) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NoSuchAlgorithmException("Must be an EC or RSA key");
|
||||||
} finally {
|
} finally {
|
||||||
input.close();
|
input.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PrivateKey decodeAsKeyType(KeySpec spec, String keyType)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
try {
|
||||||
|
return KeyFactory.getInstance(keyType).generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the hash(es) of every file to the manifest, creating it if
|
* Add the hash(es) of every file to the manifest, creating it if
|
||||||
* necessary.
|
* necessary.
|
||||||
|
@ -413,8 +449,7 @@ class SignApk {
|
||||||
JcaCertStore certs = new JcaCertStore(certList);
|
JcaCertStore certs = new JcaCertStore(certList);
|
||||||
|
|
||||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||||
ContentSigner signer = new JcaContentSignerBuilder(
|
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
|
||||||
getAlgorithm(publicKey) == USE_SHA256 ? "SHA256withRSA" : "SHA1withRSA")
|
|
||||||
.setProvider(sBouncyCastleProvider)
|
.setProvider(sBouncyCastleProvider)
|
||||||
.build(privateKey);
|
.build(privateKey);
|
||||||
gen.addSignerInfoGenerator(
|
gen.addSignerInfoGenerator(
|
||||||
|
@ -560,7 +595,7 @@ class SignApk {
|
||||||
signer = new WholeFileSignerOutputStream(out, outputStream);
|
signer = new WholeFileSignerOutputStream(out, outputStream);
|
||||||
JarOutputStream outputJar = new JarOutputStream(signer);
|
JarOutputStream outputJar = new JarOutputStream(signer);
|
||||||
|
|
||||||
int hash = getAlgorithm(publicKey);
|
int hash = getDigestAlgorithm(publicKey);
|
||||||
|
|
||||||
// Assume the certificate is valid for at least an hour.
|
// Assume the certificate is valid for at least an hour.
|
||||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
||||||
|
@ -685,13 +720,14 @@ class SignApk {
|
||||||
je.setTime(timestamp);
|
je.setTime(timestamp);
|
||||||
outputJar.putNextEntry(je);
|
outputJar.putNextEntry(je);
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
writeSignatureFile(manifest, baos, getAlgorithm(publicKey[k]));
|
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k]));
|
||||||
byte[] signedData = baos.toByteArray();
|
byte[] signedData = baos.toByteArray();
|
||||||
outputJar.write(signedData);
|
outputJar.write(signedData);
|
||||||
|
|
||||||
// CERT.RSA / CERT#.RSA
|
// CERT.{EC,RSA} / CERT#.{EC,RSA}
|
||||||
je = new JarEntry(numKeys == 1 ? CERT_RSA_NAME :
|
je = new JarEntry(numKeys == 1 ?
|
||||||
(String.format(CERT_RSA_MULTI_NAME, k)));
|
(String.format(CERT_SIG_NAME, privateKey[k].getAlgorithm())) :
|
||||||
|
(String.format(CERT_SIG_MULTI_NAME, k, privateKey[k].getAlgorithm())));
|
||||||
je.setTime(timestamp);
|
je.setTime(timestamp);
|
||||||
outputJar.putNextEntry(je);
|
outputJar.putNextEntry(je);
|
||||||
writeSignatureBlock(new CMSProcessableByteArray(signedData),
|
writeSignatureBlock(new CMSProcessableByteArray(signedData),
|
||||||
|
@ -742,7 +778,7 @@ class SignApk {
|
||||||
for (int i = 0; i < numKeys; ++i) {
|
for (int i = 0; i < numKeys; ++i) {
|
||||||
int argNum = argstart + i*2;
|
int argNum = argstart + i*2;
|
||||||
publicKey[i] = readPublicKey(new File(args[argNum]));
|
publicKey[i] = readPublicKey(new File(args[argNum]));
|
||||||
hashes |= getAlgorithm(publicKey[i]);
|
hashes |= getDigestAlgorithm(publicKey[i]);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
System.err.println(e);
|
System.err.println(e);
|
||||||
|
@ -765,6 +801,10 @@ class SignApk {
|
||||||
|
|
||||||
|
|
||||||
if (signWholeFile) {
|
if (signWholeFile) {
|
||||||
|
if (!"RSA".equalsIgnoreCase(privateKey[0].getAlgorithm())) {
|
||||||
|
System.err.println("Cannot sign OTA packages with non-RSA keys");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
SignApk.signWholeFile(inputJar, firstPublicKeyFile,
|
SignApk.signWholeFile(inputJar, firstPublicKeyFile,
|
||||||
publicKey[0], privateKey[0], outputFile);
|
publicKey[0], privateKey[0], outputFile);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue