forked from openkylin/platform_build
am 13722ac8: Merge "SignApk: update SignApk to support SHA256withRSA signing"
* commit '13722ac89f5512b932f13895217a6f861b77f57e': SignApk: update SignApk to support SHA256withRSA signing
This commit is contained in:
commit
84f12ed9d8
|
@ -78,8 +78,26 @@ import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command line tool to sign JAR files (including APKs and OTA updates) in
|
* HISTORICAL NOTE:
|
||||||
* a way compatible with the mincrypt verifier, using SHA1 and RSA keys.
|
*
|
||||||
|
* 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).
|
||||||
|
*
|
||||||
|
* Because there are old keys still in use whose certificate actually
|
||||||
|
* says "MD5withRSA", we treat these as though they say "SHA1withRSA"
|
||||||
|
* for compatibility with older releases. This can be changed by
|
||||||
|
* altering the getAlgorithm() function below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
*/
|
*/
|
||||||
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";
|
||||||
|
@ -91,6 +109,27 @@ class SignApk {
|
||||||
|
|
||||||
private static Provider sBouncyCastleProvider;
|
private static Provider sBouncyCastleProvider;
|
||||||
|
|
||||||
|
// bitmasks for which hash algorithms we need the manifest to include.
|
||||||
|
private static final int USE_SHA1 = 1;
|
||||||
|
private static final int USE_SHA256 = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
return USE_SHA1;
|
||||||
|
} else if ("SHA256withRSA".equals(sigAlg)) {
|
||||||
|
return USE_SHA256;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
|
||||||
|
"\" in cert [" + cert.getSubjectDN());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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)|com/android/otacert))|(" +
|
||||||
|
@ -182,8 +221,11 @@ class SignApk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add the SHA1 of every file to the manifest, creating it if necessary. */
|
/**
|
||||||
private static Manifest addDigestsToManifest(JarFile jar)
|
* Add the hash(es) of every file to the manifest, creating it if
|
||||||
|
* necessary.
|
||||||
|
*/
|
||||||
|
private static Manifest addDigestsToManifest(JarFile jar, int hashes)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
Manifest input = jar.getManifest();
|
Manifest input = jar.getManifest();
|
||||||
Manifest output = new Manifest();
|
Manifest output = new Manifest();
|
||||||
|
@ -195,7 +237,15 @@ class SignApk {
|
||||||
main.putValue("Created-By", "1.0 (Android SignApk)");
|
main.putValue("Created-By", "1.0 (Android SignApk)");
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
MessageDigest md_sha1 = null;
|
||||||
|
MessageDigest md_sha256 = null;
|
||||||
|
if ((hashes & USE_SHA1) != 0) {
|
||||||
|
md_sha1 = MessageDigest.getInstance("SHA1");
|
||||||
|
}
|
||||||
|
if ((hashes & USE_SHA256) != 0) {
|
||||||
|
md_sha256 = MessageDigest.getInstance("SHA256");
|
||||||
|
}
|
||||||
|
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
int num;
|
int num;
|
||||||
|
|
||||||
|
@ -216,14 +266,21 @@ class SignApk {
|
||||||
(stripPattern == null || !stripPattern.matcher(name).matches())) {
|
(stripPattern == null || !stripPattern.matcher(name).matches())) {
|
||||||
InputStream data = jar.getInputStream(entry);
|
InputStream data = jar.getInputStream(entry);
|
||||||
while ((num = data.read(buffer)) > 0) {
|
while ((num = data.read(buffer)) > 0) {
|
||||||
md.update(buffer, 0, num);
|
if (md_sha1 != null) md_sha1.update(buffer, 0, num);
|
||||||
|
if (md_sha256 != null) md_sha256.update(buffer, 0, num);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
if (md_sha1 != null) {
|
||||||
attr.putValue("SHA1-Digest",
|
attr.putValue("SHA1-Digest",
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
new String(Base64.encode(md_sha1.digest()), "ASCII"));
|
||||||
|
}
|
||||||
|
if (md_sha256 != null) {
|
||||||
|
attr.putValue("SHA-256-Digest",
|
||||||
|
new String(Base64.encode(md_sha256.digest()), "ASCII"));
|
||||||
|
}
|
||||||
output.getEntries().put(name, attr);
|
output.getEntries().put(name, attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,9 +298,10 @@ class SignApk {
|
||||||
private static void addOtacert(JarOutputStream outputJar,
|
private static void addOtacert(JarOutputStream outputJar,
|
||||||
File publicKeyFile,
|
File publicKeyFile,
|
||||||
long timestamp,
|
long timestamp,
|
||||||
Manifest manifest)
|
Manifest manifest,
|
||||||
|
int hash)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
MessageDigest md = MessageDigest.getInstance(hash == USE_SHA1 ? "SHA1" : "SHA256");
|
||||||
|
|
||||||
JarEntry je = new JarEntry(OTACERT_NAME);
|
JarEntry je = new JarEntry(OTACERT_NAME);
|
||||||
je.setTime(timestamp);
|
je.setTime(timestamp);
|
||||||
|
@ -258,7 +316,7 @@ class SignApk {
|
||||||
input.close();
|
input.close();
|
||||||
|
|
||||||
Attributes attr = new Attributes();
|
Attributes attr = new Attributes();
|
||||||
attr.putValue("SHA1-Digest",
|
attr.putValue(hash == USE_SHA1 ? "SHA1-Digest" : "SHA-256-Digest",
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
new String(Base64.encode(md.digest()), "ASCII"));
|
||||||
manifest.getEntries().put(OTACERT_NAME, attr);
|
manifest.getEntries().put(OTACERT_NAME, attr);
|
||||||
}
|
}
|
||||||
|
@ -293,14 +351,16 @@ class SignApk {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Write a .SF file with a digest of the specified manifest. */
|
/** Write a .SF file with a digest of the specified manifest. */
|
||||||
private static void writeSignatureFile(Manifest manifest, OutputStream out)
|
private static void writeSignatureFile(Manifest manifest, OutputStream out,
|
||||||
|
int hash)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
Manifest sf = new Manifest();
|
Manifest sf = new Manifest();
|
||||||
Attributes main = sf.getMainAttributes();
|
Attributes main = sf.getMainAttributes();
|
||||||
main.putValue("Signature-Version", "1.0");
|
main.putValue("Signature-Version", "1.0");
|
||||||
main.putValue("Created-By", "1.0 (Android SignApk)");
|
main.putValue("Created-By", "1.0 (Android SignApk)");
|
||||||
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
MessageDigest md = MessageDigest.getInstance(
|
||||||
|
hash == USE_SHA256 ? "SHA256" : "SHA1");
|
||||||
PrintStream print = new PrintStream(
|
PrintStream print = new PrintStream(
|
||||||
new DigestOutputStream(new ByteArrayOutputStream(), md),
|
new DigestOutputStream(new ByteArrayOutputStream(), md),
|
||||||
true, "UTF-8");
|
true, "UTF-8");
|
||||||
|
@ -308,7 +368,7 @@ class SignApk {
|
||||||
// Digest of the entire manifest
|
// Digest of the entire manifest
|
||||||
manifest.write(print);
|
manifest.write(print);
|
||||||
print.flush();
|
print.flush();
|
||||||
main.putValue("SHA1-Digest-Manifest",
|
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
new String(Base64.encode(md.digest()), "ASCII"));
|
||||||
|
|
||||||
Map<String, Attributes> entries = manifest.getEntries();
|
Map<String, Attributes> entries = manifest.getEntries();
|
||||||
|
@ -322,7 +382,7 @@ class SignApk {
|
||||||
print.flush();
|
print.flush();
|
||||||
|
|
||||||
Attributes sfAttr = new Attributes();
|
Attributes sfAttr = new Attributes();
|
||||||
sfAttr.putValue("SHA1-Digest",
|
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
|
||||||
new String(Base64.encode(md.digest()), "ASCII"));
|
new String(Base64.encode(md.digest()), "ASCII"));
|
||||||
sf.getEntries().put(entry.getKey(), sfAttr);
|
sf.getEntries().put(entry.getKey(), sfAttr);
|
||||||
}
|
}
|
||||||
|
@ -353,7 +413,8 @@ class SignApk {
|
||||||
JcaCertStore certs = new JcaCertStore(certList);
|
JcaCertStore certs = new JcaCertStore(certList);
|
||||||
|
|
||||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||||
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA")
|
ContentSigner signer = new JcaContentSignerBuilder(
|
||||||
|
getAlgorithm(publicKey) == USE_SHA256 ? "SHA256withRSA" : "SHA1withRSA")
|
||||||
.setProvider(sBouncyCastleProvider)
|
.setProvider(sBouncyCastleProvider)
|
||||||
.build(privateKey);
|
.build(privateKey);
|
||||||
gen.addSignerInfoGenerator(
|
gen.addSignerInfoGenerator(
|
||||||
|
@ -362,7 +423,7 @@ class SignApk {
|
||||||
.setProvider(sBouncyCastleProvider)
|
.setProvider(sBouncyCastleProvider)
|
||||||
.build())
|
.build())
|
||||||
.setDirectSignature(true)
|
.setDirectSignature(true)
|
||||||
.build(sha1Signer, publicKey));
|
.build(signer, publicKey));
|
||||||
gen.addCertificates(certs);
|
gen.addCertificates(certs);
|
||||||
CMSSignedData sigData = gen.generate(data, false);
|
CMSSignedData sigData = gen.generate(data, false);
|
||||||
|
|
||||||
|
@ -499,14 +560,19 @@ class SignApk {
|
||||||
signer = new WholeFileSignerOutputStream(out, outputStream);
|
signer = new WholeFileSignerOutputStream(out, outputStream);
|
||||||
JarOutputStream outputJar = new JarOutputStream(signer);
|
JarOutputStream outputJar = new JarOutputStream(signer);
|
||||||
|
|
||||||
Manifest manifest = addDigestsToManifest(inputJar);
|
int hash = getAlgorithm(publicKey);
|
||||||
|
|
||||||
|
// Assume the certificate is valid for at least an hour.
|
||||||
|
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
||||||
|
|
||||||
|
Manifest manifest = addDigestsToManifest(inputJar, hash);
|
||||||
|
copyFiles(manifest, inputJar, outputJar, timestamp);
|
||||||
|
addOtacert(outputJar, publicKeyFile, timestamp, manifest, hash);
|
||||||
|
|
||||||
signFile(manifest, inputJar,
|
signFile(manifest, inputJar,
|
||||||
new X509Certificate[]{ publicKey },
|
new X509Certificate[]{ publicKey },
|
||||||
new PrivateKey[]{ privateKey },
|
new PrivateKey[]{ privateKey },
|
||||||
outputJar);
|
outputJar);
|
||||||
// Assume the certificate is valid for at least an hour.
|
|
||||||
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
|
|
||||||
addOtacert(outputJar, publicKeyFile, timestamp, manifest);
|
|
||||||
|
|
||||||
signer.notifyClosing();
|
signer.notifyClosing();
|
||||||
outputJar.close();
|
outputJar.close();
|
||||||
|
@ -605,13 +671,8 @@ class SignApk {
|
||||||
// Assume the certificate is valid for at least an hour.
|
// Assume the certificate is valid for at least an hour.
|
||||||
long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
|
long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
|
||||||
|
|
||||||
JarEntry je;
|
|
||||||
|
|
||||||
// Everything else
|
|
||||||
copyFiles(manifest, inputJar, outputJar, timestamp);
|
|
||||||
|
|
||||||
// MANIFEST.MF
|
// MANIFEST.MF
|
||||||
je = new JarEntry(JarFile.MANIFEST_NAME);
|
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
|
||||||
je.setTime(timestamp);
|
je.setTime(timestamp);
|
||||||
outputJar.putNextEntry(je);
|
outputJar.putNextEntry(je);
|
||||||
manifest.write(outputJar);
|
manifest.write(outputJar);
|
||||||
|
@ -624,7 +685,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);
|
writeSignatureFile(manifest, baos, getAlgorithm(publicKey[k]));
|
||||||
byte[] signedData = baos.toByteArray();
|
byte[] signedData = baos.toByteArray();
|
||||||
outputJar.write(signedData);
|
outputJar.write(signedData);
|
||||||
|
|
||||||
|
@ -671,14 +732,21 @@ class SignApk {
|
||||||
|
|
||||||
JarFile inputJar = null;
|
JarFile inputJar = null;
|
||||||
FileOutputStream outputFile = null;
|
FileOutputStream outputFile = null;
|
||||||
|
int hashes = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File firstPublicKeyFile = new File(args[argstart+0]);
|
File firstPublicKeyFile = new File(args[argstart+0]);
|
||||||
|
|
||||||
X509Certificate[] publicKey = new X509Certificate[numKeys];
|
X509Certificate[] publicKey = new X509Certificate[numKeys];
|
||||||
|
try {
|
||||||
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]);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
System.err.println(e);
|
||||||
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the ZIP file timestamp to the starting valid time
|
// Set the ZIP file timestamp to the starting valid time
|
||||||
|
@ -710,8 +778,9 @@ class SignApk {
|
||||||
// (~0.1% on full OTA packages I tested).
|
// (~0.1% on full OTA packages I tested).
|
||||||
outputJar.setLevel(9);
|
outputJar.setLevel(9);
|
||||||
|
|
||||||
signFile(addDigestsToManifest(inputJar), inputJar,
|
Manifest manifest = addDigestsToManifest(inputJar, hashes);
|
||||||
publicKey, privateKey, outputJar);
|
copyFiles(manifest, inputJar, outputJar, timestamp);
|
||||||
|
signFile(manifest, inputJar, publicKey, privateKey, outputJar);
|
||||||
outputJar.close();
|
outputJar.close();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
Loading…
Reference in New Issue