diff options
author | Kenny Root <kroot@google.com> | 2013-09-19 12:49:36 -0700 |
---|---|---|
committer | Kenny Root <kroot@google.com> | 2013-09-20 15:00:49 -0700 |
commit | 3d2365c068539941bec258d461c149a63c4fbf98 (patch) | |
tree | 7d836457ad780ab516e5f52cd7a512b28ae49bd1 /tools/signapk | |
parent | 8562fd478d7f1b1b693de5db67928f1993522c0a (diff) | |
download | build-3d2365c068539941bec258d461c149a63c4fbf98.zip build-3d2365c068539941bec258d461c149a63c4fbf98.tar.gz build-3d2365c068539941bec258d461c149a63c4fbf98.tar.bz2 |
SignApk: add support for EC keys
Change-Id: I09bc0e52fc84ec321b997016a8f97de1bfd2770d
Diffstat (limited to 'tools/signapk')
-rw-r--r-- | tools/signapk/SignApk.java | 92 |
1 files changed, 66 insertions, 26 deletions
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java index 716ea3b..a1e422c 100644 --- a/tools/signapk/SignApk.java +++ b/tools/signapk/SignApk.java @@ -52,8 +52,10 @@ import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyFactory; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.security.Security; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; @@ -64,6 +66,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.jar.Attributes; @@ -83,9 +86,9 @@ import javax.crypto.spec.PBEKeySpec; * Prior to the keylimepie release, SignApk ignored the signature * algorithm specified in the certificate and always used SHA1withRSA. * - * Starting with keylimepie, we support SHA256withRSA, and use the - * signature algorithm in the certificate to select which to use - * (SHA256withRSA or SHA1withRSA). + * Starting with JB-MR2, the platform supports SHA256withRSA, so we use + * the signature algorithm in the certificate to select which to use + * (SHA256withRSA or SHA1withRSA). Also in JB-MR2, EC keys are supported. * * Because there are old keys still in use whose certificate actually * 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 - * updates) in a way compatible with the mincrypt verifier, using RSA - * keys and SHA1 or SHA-256. + * Command line tool to sign JAR files (including APKs and OTA updates) in a way + * compatible with the mincrypt verifier, using EC or RSA keys and SHA1 or + * SHA-256 (see historical note). */ class SignApk { 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_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"; @@ -117,12 +120,12 @@ class SignApk { * Return one of USE_SHA1 or USE_SHA256 according to the signature * algorithm specified in the cert. */ - private static int getAlgorithm(X509Certificate cert) { - String sigAlg = cert.getSigAlgName(); - if ("SHA1withRSA".equals(sigAlg) || - "MD5withRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above. + private static int getDigestAlgorithm(X509Certificate cert) { + String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US); + if ("SHA1WITHRSA".equals(sigAlg) || + "MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above. return USE_SHA1; - } else if ("SHA256withRSA".equals(sigAlg)) { + } else if (sigAlg.startsWith("SHA256WITH")) { return USE_SHA256; } else { 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. 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) + ")$"); private static X509Certificate readPublicKey(File file) @@ -211,16 +231,32 @@ class SignApk { spec = new PKCS8EncodedKeySpec(bytes); } - try { - return KeyFactory.getInstance("RSA").generatePrivate(spec); - } catch (InvalidKeySpecException ex) { - return KeyFactory.getInstance("DSA").generatePrivate(spec); + PrivateKey key; + key = decodeAsKeyType(spec, "RSA"); + if (key != null) { + return key; + } + + key = decodeAsKeyType(spec, "EC"); + if (key != null) { + return key; } + + throw new NoSuchAlgorithmException("Must be an EC or RSA key"); } finally { 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 * necessary. @@ -413,8 +449,7 @@ class SignApk { JcaCertStore certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); - ContentSigner signer = new JcaContentSignerBuilder( - getAlgorithm(publicKey) == USE_SHA256 ? "SHA256withRSA" : "SHA1withRSA") + ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey)) .setProvider(sBouncyCastleProvider) .build(privateKey); gen.addSignerInfoGenerator( @@ -560,7 +595,7 @@ class SignApk { signer = new WholeFileSignerOutputStream(out, outputStream); JarOutputStream outputJar = new JarOutputStream(signer); - int hash = getAlgorithm(publicKey); + int hash = getDigestAlgorithm(publicKey); // Assume the certificate is valid for at least an hour. long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; @@ -685,13 +720,14 @@ class SignApk { je.setTime(timestamp); outputJar.putNextEntry(je); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - writeSignatureFile(manifest, baos, getAlgorithm(publicKey[k])); + writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k])); byte[] signedData = baos.toByteArray(); outputJar.write(signedData); - // CERT.RSA / CERT#.RSA - je = new JarEntry(numKeys == 1 ? CERT_RSA_NAME : - (String.format(CERT_RSA_MULTI_NAME, k))); + // CERT.{EC,RSA} / CERT#.{EC,RSA} + je = new JarEntry(numKeys == 1 ? + (String.format(CERT_SIG_NAME, privateKey[k].getAlgorithm())) : + (String.format(CERT_SIG_MULTI_NAME, k, privateKey[k].getAlgorithm()))); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureBlock(new CMSProcessableByteArray(signedData), @@ -742,7 +778,7 @@ class SignApk { for (int i = 0; i < numKeys; ++i) { int argNum = argstart + i*2; publicKey[i] = readPublicKey(new File(args[argNum])); - hashes |= getAlgorithm(publicKey[i]); + hashes |= getDigestAlgorithm(publicKey[i]); } } catch (IllegalArgumentException e) { System.err.println(e); @@ -765,6 +801,10 @@ class SignApk { 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, publicKey[0], privateKey[0], outputFile); } else { |