No need to JAR-sign OTA update packages.

This removes the logic for JAR signing from -w (whole-file signing)
mode. This mode is designed specifically for OTA update packages. When
such packages are verified, their JAR signatures are ignored. Thus,
there is no need to JAR-sign in -w mode.

For context, OTA update packages are protected by a special signature
residing in the ZIP End of Central Directory record (at the very end
of the file). This is the signature verified when update packages are
being applied to Android.

Change-Id: Ia852a11ed6774ce746087cdd7f028b191ef6bc8b
This commit is contained in:
Alex Klyubin
2016-05-11 17:19:07 -07:00
parent 648ea82b04
commit 0caa16a6d1

View File

@@ -200,10 +200,10 @@ class SignApk {
} }
} }
// 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 final Pattern STRIP_PATTERN =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|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)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
@@ -313,8 +313,9 @@ class SignApk {
* 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.
*/ */
private static Manifest addDigestsToManifest(JarFile jar, int hashes) private static Manifest addDigestsToManifest(
throws IOException, GeneralSecurityException { JarFile jar, Pattern ignoredFilenamePattern, int hashes)
throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest(); Manifest input = jar.getManifest();
Manifest output = new Manifest(); Manifest output = new Manifest();
Attributes main = output.getMainAttributes(); Attributes main = output.getMainAttributes();
@@ -350,8 +351,9 @@ class SignApk {
for (JarEntry entry: byName.values()) { for (JarEntry entry: byName.values()) {
String name = entry.getName(); String name = entry.getName();
if (!entry.isDirectory() && if (!entry.isDirectory()
(stripPattern == null || !stripPattern.matcher(name).matches())) { && (ignoredFilenamePattern == null
|| !ignoredFilenamePattern.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) {
if (md_sha1 != null) md_sha1.update(buffer, 0, num); if (md_sha1 != null) md_sha1.update(buffer, 0, num);
@@ -394,16 +396,13 @@ class SignApk {
* Add a copy of the public key to the archive; this should * Add a copy of the public key to the archive; this should
* exactly match one of the files in * exactly match one of the files in
* /system/etc/security/otacerts.zip on the device. (The same * /system/etc/security/otacerts.zip on the device. (The same
* cert can be extracted from the CERT.RSA file but this is much * cert can be extracted from the OTA update package's signature
* easier to get at.) * block but this is much easier to get at.)
*/ */
private static void addOtacert(JarOutputStream outputJar, private static void addOtacert(JarOutputStream outputJar,
File publicKeyFile, File publicKeyFile,
long timestamp, long timestamp)
Manifest manifest,
int hash)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
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);
@@ -413,14 +412,8 @@ class SignApk {
int read; int read;
while ((read = input.read(b)) != -1) { while ((read = input.read(b)) != -1) {
outputJar.write(b, 0, read); outputJar.write(b, 0, read);
md.update(b, 0, read);
} }
input.close(); input.close();
Attributes attr = new Attributes();
attr.putValue(hash == USE_SHA1 ? "SHA1-Digest" : "SHA-256-Digest",
new String(Base64.encode(md.digest()), "ASCII"));
manifest.getEntries().put(OTACERT_NAME, attr);
} }
@@ -545,18 +538,31 @@ class SignApk {
} }
/** /**
* Copy all the files in a manifest from input to output. We set * Copy all JAR entries from input to output. We set the modification times in the output to a
* the modification times in the output to a fixed time, so as to * fixed time, so as to reduce variation in the output file and make incremental OTAs more
* reduce variation in the output file and make incremental OTAs * efficient.
* more efficient.
*/ */
private static void copyFiles(Manifest manifest, JarFile in, JarOutputStream out, private static void copyFiles(JarFile in,
long timestamp, int defaultAlignment) throws IOException { Pattern ignoredFilenamePattern,
JarOutputStream out,
long timestamp,
int defaultAlignment) throws IOException {
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int num; int num;
Map<String, Attributes> entries = manifest.getEntries(); ArrayList<String> names = new ArrayList<String>();
ArrayList<String> names = new ArrayList<String>(entries.keySet()); for (Enumeration<JarEntry> e = in.entries(); e.hasMoreElements();) {
JarEntry entry = e.nextElement();
if (entry.isDirectory()) {
continue;
}
String entryName = entry.getName();
if ((ignoredFilenamePattern != null)
&& (ignoredFilenamePattern.matcher(entryName).matches())) {
continue;
}
names.add(entryName);
}
Collections.sort(names); Collections.sort(names);
boolean firstEntry = true; boolean firstEntry = true;
@@ -757,17 +763,8 @@ 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, hash); copyFiles(inputJar, STRIP_PATTERN, outputJar, timestamp, 0);
copyFiles(manifest, inputJar, outputJar, timestamp, 0); addOtacert(outputJar, publicKeyFile, timestamp);
addOtacert(outputJar, publicKeyFile, timestamp, manifest, hash);
signFile(manifest,
new X509Certificate[]{ publicKey },
new PrivateKey[]{ privateKey },
new int[] { hash },
timestamp,
false, // Don't sign using APK Signature Scheme v2
outputJar);
signer.notifyClosing(); signer.notifyClosing();
outputJar.close(); outputJar.close();
@@ -1156,8 +1153,9 @@ class SignApk {
v1DigestAlgorithm[i] = getV1DigestAlgorithmForApk(publicKey[i], minSdkVersion); v1DigestAlgorithm[i] = getV1DigestAlgorithmForApk(publicKey[i], minSdkVersion);
v1DigestAlgorithmBitSet |= v1DigestAlgorithm[i]; v1DigestAlgorithmBitSet |= v1DigestAlgorithm[i];
} }
Manifest manifest = addDigestsToManifest(inputJar, v1DigestAlgorithmBitSet); Manifest manifest =
copyFiles(manifest, inputJar, outputJar, timestamp, alignment); addDigestsToManifest(inputJar, STRIP_PATTERN, v1DigestAlgorithmBitSet);
copyFiles(inputJar, STRIP_PATTERN, outputJar, timestamp, alignment);
signFile( signFile(
manifest, manifest,
publicKey, privateKey, v1DigestAlgorithm, publicKey, privateKey, v1DigestAlgorithm,