Merge "Unconditionally use SHA-256 when minSdkVersion is 18 or higher."

This commit is contained in:
Alex Klyubin 2016-01-13 17:15:34 +00:00 committed by Gerrit Code Review
commit fbee4f2e42
1 changed files with 65 additions and 23 deletions

View File

@ -67,6 +67,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.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
@ -115,15 +116,25 @@ class SignApk {
private static final int USE_SHA1 = 1; private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2; private static final int USE_SHA256 = 2;
/**
* Minimum Android SDK API Level which accepts JAR signatures which use SHA-256. Older platform
* versions accept only SHA-1 signatures.
*/
private static final int MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES = 18;
/** /**
* 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 getDigestAlgorithm(X509Certificate cert) { private static int getDigestAlgorithm(X509Certificate cert, int minSdkVersion) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US); String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
if ("SHA1WITHRSA".equals(sigAlg) || if ("SHA1WITHRSA".equals(sigAlg) || "MD5WITHRSA".equals(sigAlg)) {
"MD5WITHRSA".equals(sigAlg)) { // see "HISTORICAL NOTE" above. // see "HISTORICAL NOTE" above.
return USE_SHA1; if (minSdkVersion < MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES) {
return USE_SHA1;
} else {
return USE_SHA256;
}
} else if (sigAlg.startsWith("SHA256WITH")) { } else if (sigAlg.startsWith("SHA256WITH")) {
return USE_SHA256; return USE_SHA256;
} else { } else {
@ -133,10 +144,11 @@ class SignApk {
} }
/** Returns the expected signature algorithm for this key type. */ /** Returns the expected signature algorithm for this key type. */
private static String getSignatureAlgorithm(X509Certificate cert) { private static String getSignatureAlgorithm(X509Certificate cert, int minSdkVersion) {
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US); String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
if ("RSA".equalsIgnoreCase(keyType)) { if ("RSA".equalsIgnoreCase(keyType)) {
if (getDigestAlgorithm(cert) == USE_SHA256) { if ((minSdkVersion >= MIN_API_LEVEL_FOR_SHA256_JAR_SIGNATURES)
|| (getDigestAlgorithm(cert, minSdkVersion) == USE_SHA256)) {
return "SHA256withRSA"; return "SHA256withRSA";
} else { } else {
return "SHA1withRSA"; return "SHA1withRSA";
@ -309,10 +321,24 @@ class SignApk {
Attributes attr = null; Attributes attr = null;
if (input != null) attr = input.getAttributes(name); if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes(); attr = attr != null ? new Attributes(attr) : new Attributes();
// Remove any previously computed digests from this entry's attributes.
for (Iterator<Object> i = attr.keySet().iterator(); i.hasNext();) {
Object key = i.next();
if (!(key instanceof Attributes.Name)) {
continue;
}
String attributeNameLowerCase =
((Attributes.Name) key).toString().toLowerCase(Locale.US);
if (attributeNameLowerCase.endsWith("-digest")) {
i.remove();
}
}
// Add SHA-1 digest if requested
if (md_sha1 != null) { if (md_sha1 != null) {
attr.putValue("SHA1-Digest", attr.putValue("SHA1-Digest",
new String(Base64.encode(md_sha1.digest()), "ASCII")); new String(Base64.encode(md_sha1.digest()), "ASCII"));
} }
// Add SHA-256 digest if requested
if (md_sha256 != null) { if (md_sha256 != null) {
attr.putValue("SHA-256-Digest", attr.putValue("SHA-256-Digest",
new String(Base64.encode(md_sha256.digest()), "ASCII")); new String(Base64.encode(md_sha256.digest()), "ASCII"));
@ -438,7 +464,7 @@ class SignApk {
/** Sign data and write the digital signature to 'out'. */ /** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock( private static void writeSignatureBlock(
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, int minSdkVersion,
OutputStream out) OutputStream out)
throws IOException, throws IOException,
CertificateEncodingException, CertificateEncodingException,
@ -449,8 +475,9 @@ class SignApk {
JcaCertStore certs = new JcaCertStore(certList); JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey)) ContentSigner signer =
.build(privateKey); new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey, minSdkVersion))
.build(privateKey);
gen.addSignerInfoGenerator( gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder( new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder() new JcaDigestCalculatorProviderBuilder()
@ -631,21 +658,23 @@ class SignApk {
} }
private static class CMSSigner implements CMSTypedData { private static class CMSSigner implements CMSTypedData {
private JarFile inputJar; private final JarFile inputJar;
private File publicKeyFile; private final File publicKeyFile;
private X509Certificate publicKey; private final X509Certificate publicKey;
private PrivateKey privateKey; private final PrivateKey privateKey;
private OutputStream outputStream; private final int minSdkVersion;
private final OutputStream outputStream;
private final ASN1ObjectIdentifier type; private final ASN1ObjectIdentifier type;
private WholeFileSignerOutputStream signer; private WholeFileSignerOutputStream signer;
public CMSSigner(JarFile inputJar, File publicKeyFile, public CMSSigner(JarFile inputJar, File publicKeyFile,
X509Certificate publicKey, PrivateKey privateKey, X509Certificate publicKey, PrivateKey privateKey, int minSdkVersion,
OutputStream outputStream) { OutputStream outputStream) {
this.inputJar = inputJar; this.inputJar = inputJar;
this.publicKeyFile = publicKeyFile; this.publicKeyFile = publicKeyFile;
this.publicKey = publicKey; this.publicKey = publicKey;
this.privateKey = privateKey; this.privateKey = privateKey;
this.minSdkVersion = minSdkVersion;
this.outputStream = outputStream; this.outputStream = outputStream;
this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()); this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
} }
@ -670,7 +699,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 = getDigestAlgorithm(publicKey); int hash = getDigestAlgorithm(publicKey, minSdkVersion);
// 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;
@ -682,6 +711,7 @@ class SignApk {
signFile(manifest, signFile(manifest,
new X509Certificate[]{ publicKey }, new X509Certificate[]{ publicKey },
new PrivateKey[]{ privateKey }, new PrivateKey[]{ privateKey },
minSdkVersion,
outputJar); outputJar);
signer.notifyClosing(); signer.notifyClosing();
@ -698,7 +728,7 @@ class SignApk {
CertificateEncodingException, CertificateEncodingException,
OperatorCreationException, OperatorCreationException,
CMSException { CMSException {
SignApk.writeSignatureBlock(this, publicKey, privateKey, temp); SignApk.writeSignatureBlock(this, publicKey, privateKey, minSdkVersion, temp);
} }
public WholeFileSignerOutputStream getSigner() { public WholeFileSignerOutputStream getSigner() {
@ -708,9 +738,10 @@ class SignApk {
private static void signWholeFile(JarFile inputJar, File publicKeyFile, private static void signWholeFile(JarFile inputJar, File publicKeyFile,
X509Certificate publicKey, PrivateKey privateKey, X509Certificate publicKey, PrivateKey privateKey,
int minSdkVersion,
OutputStream outputStream) throws Exception { OutputStream outputStream) throws Exception {
CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile, CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile,
publicKey, privateKey, outputStream); publicKey, privateKey, minSdkVersion, outputStream);
ByteArrayOutputStream temp = new ByteArrayOutputStream(); ByteArrayOutputStream temp = new ByteArrayOutputStream();
@ -776,6 +807,7 @@ class SignApk {
private static void signFile(Manifest manifest, private static void signFile(Manifest manifest,
X509Certificate[] publicKey, PrivateKey[] privateKey, X509Certificate[] publicKey, PrivateKey[] privateKey,
int minSdkVersion,
JarOutputStream outputJar) JarOutputStream outputJar)
throws Exception { throws Exception {
// Assume the certificate is valid for at least an hour. // Assume the certificate is valid for at least an hour.
@ -795,7 +827,7 @@ 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, getDigestAlgorithm(publicKey[k])); writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k], minSdkVersion));
byte[] signedData = baos.toByteArray(); byte[] signedData = baos.toByteArray();
outputJar.write(signedData); outputJar.write(signedData);
@ -807,7 +839,7 @@ class SignApk {
je.setTime(timestamp); je.setTime(timestamp);
outputJar.putNextEntry(je); outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData), writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey[k], privateKey[k], outputJar); publicKey[k], privateKey[k], minSdkVersion, outputJar);
} }
} }
@ -887,6 +919,7 @@ class SignApk {
boolean signWholeFile = false; boolean signWholeFile = false;
String providerClass = null; String providerClass = null;
int alignment = 4; int alignment = 4;
int minSdkVersion = 0;
int argstart = 0; int argstart = 0;
while (argstart < args.length && args[argstart].startsWith("-")) { while (argstart < args.length && args[argstart].startsWith("-")) {
@ -902,6 +935,15 @@ class SignApk {
} else if ("-a".equals(args[argstart])) { } else if ("-a".equals(args[argstart])) {
alignment = Integer.parseInt(args[++argstart]); alignment = Integer.parseInt(args[++argstart]);
++argstart; ++argstart;
} else if ("--min-sdk-version".equals(args[argstart])) {
String minSdkVersionString = args[++argstart];
try {
minSdkVersion = Integer.parseInt(minSdkVersionString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"min-sdk-version must be a decimal number: " + minSdkVersionString);
}
++argstart;
} else { } else {
usage(); usage();
} }
@ -931,7 +973,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 |= getDigestAlgorithm(publicKey[i]); hashes |= getDigestAlgorithm(publicKey[i], minSdkVersion);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
System.err.println(e); System.err.println(e);
@ -955,7 +997,7 @@ class SignApk {
if (signWholeFile) { if (signWholeFile) {
SignApk.signWholeFile(inputJar, firstPublicKeyFile, SignApk.signWholeFile(inputJar, firstPublicKeyFile,
publicKey[0], privateKey[0], outputFile); publicKey[0], privateKey[0], minSdkVersion, outputFile);
} else { } else {
JarOutputStream outputJar = new JarOutputStream(outputFile); JarOutputStream outputJar = new JarOutputStream(outputFile);
@ -969,7 +1011,7 @@ class SignApk {
Manifest manifest = addDigestsToManifest(inputJar, hashes); Manifest manifest = addDigestsToManifest(inputJar, hashes);
copyFiles(manifest, inputJar, outputJar, timestamp, alignment); copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, publicKey, privateKey, outputJar); signFile(manifest, publicKey, privateKey, minSdkVersion, outputJar);
outputJar.close(); outputJar.close();
} }
} catch (Exception e) { } catch (Exception e) {