summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/adbs6
-rw-r--r--tools/fs_config/fs_config.c3
-rw-r--r--tools/fs_get_stats/fs_get_stats.c3
-rw-r--r--tools/signapk/SignApk.java428
4 files changed, 283 insertions, 157 deletions
diff --git a/tools/adbs b/tools/adbs
index 598af85..37c520c 100755
--- a/tools/adbs
+++ b/tools/adbs
@@ -140,13 +140,15 @@ def SetupToolsPath():
uname = "darwin-x86"
elif uname == "Linux":
uname = "linux-x86"
- prefix = "./prebuilts/gcc/" + uname + "/arm/arm-linux-androideabi-4.6/bin/"
+ gcc_version = os.environ["TARGET_GCC_VERSION"]
+ prefix = "./prebuilts/gcc/" + uname + "/arm/arm-linux-androideabi-" + \
+ gcc_version + "/bin/"
addr2line_cmd = prefix + "arm-linux-androideabi-addr2line"
if (not os.path.exists(addr2line_cmd)):
try:
prefix = os.environ['ANDROID_BUILD_TOP'] + "/prebuilts/gcc/" + \
- uname + "/arm/arm-linux-androideabi-4.6/bin/"
+ uname + "/arm/arm-linux-androideabi-" + gcc_version + "/bin/"
except:
prefix = "";
diff --git a/tools/fs_config/fs_config.c b/tools/fs_config/fs_config.c
index 5b99b30..f6760cc 100644
--- a/tools/fs_config/fs_config.c
+++ b/tools/fs_config/fs_config.c
@@ -62,7 +62,8 @@ int main(int argc, char** argv) {
}
unsigned uid = 0, gid = 0, mode = 0;
- fs_config(buffer, is_dir, &uid, &gid, &mode);
+ uint64_t capabilities;
+ fs_config(buffer, is_dir, &uid, &gid, &mode, &capabilities);
printf("%s %d %d %o\n", buffer, uid, gid, mode);
}
return 0;
diff --git a/tools/fs_get_stats/fs_get_stats.c b/tools/fs_get_stats/fs_get_stats.c
index 356f6f9..a9814b9 100644
--- a/tools/fs_get_stats/fs_get_stats.c
+++ b/tools/fs_get_stats/fs_get_stats.c
@@ -57,7 +57,8 @@ main(int argc, const char *argv[])
if (!strcmp(argv[2], "1"))
is_dir = 1;
- fs_config(argv[3], is_dir, &uid, &gid, &perms);
+ uint64_t capabilities;
+ fs_config(argv[3], is_dir, &uid, &gid, &perms, &capabilities);
fprintf(stdout, "%d %d 0%o\n", uid, gid, perms);
return 0;
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java
index 07aefa7..adfe9a3 100644
--- a/tools/signapk/SignApk.java
+++ b/tools/signapk/SignApk.java
@@ -35,6 +35,7 @@ import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedReader;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
@@ -83,6 +84,8 @@ import javax.crypto.spec.PBEKeySpec;
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_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 OTACERT_NAME = "META-INF/com/android/otacert";
@@ -90,10 +93,11 @@ class SignApk {
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
- Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
+ Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|(" +
+ Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
private static X509Certificate readPublicKey(File file)
- throws IOException, GeneralSecurityException {
+ throws IOException, GeneralSecurityException {
FileInputStream input = new FileInputStream(file);
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
@@ -130,7 +134,7 @@ class SignApk {
* @param keyFile The file containing the private key
*/
private static KeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)
- throws GeneralSecurityException {
+ throws GeneralSecurityException {
EncryptedPrivateKeyInfo epkInfo;
try {
epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
@@ -157,7 +161,7 @@ class SignApk {
/** Read a PKCS 8 format private key. */
private static PrivateKey readPrivateKey(File file)
- throws IOException, GeneralSecurityException {
+ throws IOException, GeneralSecurityException {
DataInputStream input = new DataInputStream(new FileInputStream(file));
try {
byte[] bytes = new byte[(int) file.length()];
@@ -180,7 +184,7 @@ class SignApk {
/** Add the SHA1 of every file to the manifest, creating it if necessary. */
private static Manifest addDigestsToManifest(JarFile jar)
- throws IOException, GeneralSecurityException {
+ throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest();
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
@@ -208,11 +212,8 @@ class SignApk {
for (JarEntry entry: byName.values()) {
String name = entry.getName();
- if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
- !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
- !name.equals(OTACERT_NAME) &&
- (stripPattern == null ||
- !stripPattern.matcher(name).matches())) {
+ if (!entry.isDirectory() &&
+ (stripPattern == null || !stripPattern.matcher(name).matches())) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
md.update(buffer, 0, num);
@@ -301,8 +302,8 @@ class SignApk {
MessageDigest md = MessageDigest.getInstance("SHA1");
PrintStream print = new PrintStream(
- new DigestOutputStream(new ByteArrayOutputStream(), md),
- true, "UTF-8");
+ new DigestOutputStream(new ByteArrayOutputStream(), md),
+ true, "UTF-8");
// Digest of the entire manifest
manifest.write(print);
@@ -339,31 +340,6 @@ class SignApk {
}
}
- private static class CMSByteArraySlice implements CMSTypedData {
- private final ASN1ObjectIdentifier type;
- private final byte[] data;
- private final int offset;
- private final int length;
- public CMSByteArraySlice(byte[] data, int offset, int length) {
- this.data = data;
- this.offset = offset;
- this.length = length;
- this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
- }
-
- public Object getContent() {
- throw new UnsupportedOperationException();
- }
-
- public ASN1ObjectIdentifier getContentType() {
- return type;
- }
-
- public void write(OutputStream out) throws IOException {
- out.write(data, offset, length);
- }
- }
-
/** Sign data and write the digital signature to 'out'. */
private static void writeSignatureBlock(
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
@@ -395,24 +371,171 @@ class SignApk {
dos.writeObject(asn1.readObject());
}
- private static void signWholeOutputFile(byte[] zipData,
- OutputStream outputStream,
- X509Certificate publicKey,
- PrivateKey privateKey)
- throws IOException,
- CertificateEncodingException,
- OperatorCreationException,
- CMSException {
- // For a zip with no archive comment, the
- // end-of-central-directory record will be 22 bytes long, so
- // we expect to find the EOCD marker 22 bytes from the end.
- if (zipData[zipData.length-22] != 0x50 ||
- zipData[zipData.length-21] != 0x4b ||
- zipData[zipData.length-20] != 0x05 ||
- zipData[zipData.length-19] != 0x06) {
- throw new IllegalArgumentException("zip data already has an archive comment");
+ /**
+ * Copy all the files in a manifest from input to output. We set
+ * the modification times in the output to a fixed time, so as to
+ * reduce variation in the output file and make incremental OTAs
+ * more efficient.
+ */
+ private static void copyFiles(Manifest manifest,
+ JarFile in, JarOutputStream out, long timestamp) throws IOException {
+ byte[] buffer = new byte[4096];
+ int num;
+
+ Map<String, Attributes> entries = manifest.getEntries();
+ ArrayList<String> names = new ArrayList<String>(entries.keySet());
+ Collections.sort(names);
+ for (String name : names) {
+ JarEntry inEntry = in.getJarEntry(name);
+ JarEntry outEntry = null;
+ if (inEntry.getMethod() == JarEntry.STORED) {
+ // Preserve the STORED method of the input entry.
+ outEntry = new JarEntry(inEntry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ outEntry = new JarEntry(name);
+ }
+ outEntry.setTime(timestamp);
+ out.putNextEntry(outEntry);
+
+ InputStream data = in.getInputStream(inEntry);
+ while ((num = data.read(buffer)) > 0) {
+ out.write(buffer, 0, num);
+ }
+ out.flush();
+ }
+ }
+
+ private static class WholeFileSignerOutputStream extends FilterOutputStream {
+ private boolean closing = false;
+ private ByteArrayOutputStream footer = new ByteArrayOutputStream();
+ private OutputStream tee;
+
+ public WholeFileSignerOutputStream(OutputStream out, OutputStream tee) {
+ super(out);
+ this.tee = tee;
+ }
+
+ public void notifyClosing() {
+ closing = true;
+ }
+
+ public void finish() throws IOException {
+ closing = false;
+
+ byte[] data = footer.toByteArray();
+ if (data.length < 2)
+ throw new IOException("Less than two bytes written to footer");
+ write(data, 0, data.length - 2);
}
+ public byte[] getTail() {
+ return footer.toByteArray();
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (closing) {
+ // if the jar is about to close, save the footer that will be written
+ footer.write(b, off, len);
+ }
+ else {
+ // write to both output streams. out is the CMSTypedData signer and tee is the file.
+ out.write(b, off, len);
+ tee.write(b, off, len);
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (closing) {
+ // if the jar is about to close, save the footer that will be written
+ footer.write(b);
+ }
+ else {
+ // write to both output streams. out is the CMSTypedData signer and tee is the file.
+ out.write(b);
+ tee.write(b);
+ }
+ }
+ }
+
+ private static class CMSSigner implements CMSTypedData {
+ private JarFile inputJar;
+ private File publicKeyFile;
+ private X509Certificate publicKey;
+ private PrivateKey privateKey;
+ private String outputFile;
+ private OutputStream outputStream;
+ private final ASN1ObjectIdentifier type;
+ private WholeFileSignerOutputStream signer;
+
+ public CMSSigner(JarFile inputJar, File publicKeyFile,
+ X509Certificate publicKey, PrivateKey privateKey,
+ OutputStream outputStream) {
+ this.inputJar = inputJar;
+ this.publicKeyFile = publicKeyFile;
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+ this.outputStream = outputStream;
+ this.type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
+ }
+
+ public Object getContent() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ASN1ObjectIdentifier getContentType() {
+ return type;
+ }
+
+ public void write(OutputStream out) throws IOException {
+ try {
+ signer = new WholeFileSignerOutputStream(out, outputStream);
+ JarOutputStream outputJar = new JarOutputStream(signer);
+
+ Manifest manifest = addDigestsToManifest(inputJar);
+ signFile(manifest, inputJar,
+ new X509Certificate[]{ publicKey },
+ new PrivateKey[]{ privateKey },
+ 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();
+ outputJar.close();
+ signer.finish();
+ }
+ catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ public void writeSignatureBlock(ByteArrayOutputStream temp)
+ throws IOException,
+ CertificateEncodingException,
+ OperatorCreationException,
+ CMSException {
+ SignApk.writeSignatureBlock(this, publicKey, privateKey, temp);
+ }
+
+ public WholeFileSignerOutputStream getSigner() {
+ return signer;
+ }
+ }
+
+ private static void signWholeFile(JarFile inputJar, File publicKeyFile,
+ X509Certificate publicKey, PrivateKey privateKey,
+ OutputStream outputStream) throws Exception {
+ CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile,
+ publicKey, privateKey, outputStream);
+
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
@@ -423,8 +546,20 @@ class SignApk {
temp.write(message);
temp.write(0);
- writeSignatureBlock(new CMSByteArraySlice(zipData, 0, zipData.length-2),
- publicKey, privateKey, temp);
+ cmsOut.writeSignatureBlock(temp);
+
+ byte[] zipData = cmsOut.getSigner().getTail();
+
+ // For a zip with no archive comment, the
+ // end-of-central-directory record will be 22 bytes long, so
+ // we expect to find the EOCD marker 22 bytes from the end.
+ if (zipData[zipData.length-22] != 0x50 ||
+ zipData[zipData.length-21] != 0x4b ||
+ zipData[zipData.length-20] != 0x05 ||
+ zipData[zipData.length-19] != 0x06) {
+ throw new IllegalArgumentException("zip data already has an archive comment");
+ }
+
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
@@ -458,54 +593,61 @@ class SignApk {
}
}
- outputStream.write(zipData, 0, zipData.length-2);
outputStream.write(total_size & 0xff);
outputStream.write((total_size >> 8) & 0xff);
temp.writeTo(outputStream);
}
- /**
- * Copy all the files in a manifest from input to output. We set
- * the modification times in the output to a fixed time, so as to
- * reduce variation in the output file and make incremental OTAs
- * more efficient.
- */
- private static void copyFiles(Manifest manifest,
- JarFile in, JarOutputStream out, long timestamp) throws IOException {
- byte[] buffer = new byte[4096];
- int num;
+ private static void signFile(Manifest manifest, JarFile inputJar,
+ X509Certificate[] publicKey, PrivateKey[] privateKey,
+ JarOutputStream outputJar)
+ throws Exception {
+ // Assume the certificate is valid for at least an hour.
+ long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
- Map<String, Attributes> entries = manifest.getEntries();
- ArrayList<String> names = new ArrayList<String>(entries.keySet());
- Collections.sort(names);
- for (String name : names) {
- JarEntry inEntry = in.getJarEntry(name);
- JarEntry outEntry = null;
- if (inEntry.getMethod() == JarEntry.STORED) {
- // Preserve the STORED method of the input entry.
- outEntry = new JarEntry(inEntry);
- } else {
- // Create a new entry so that the compressed len is recomputed.
- outEntry = new JarEntry(name);
- }
- outEntry.setTime(timestamp);
- out.putNextEntry(outEntry);
+ JarEntry je;
- InputStream data = in.getInputStream(inEntry);
- while ((num = data.read(buffer)) > 0) {
- out.write(buffer, 0, num);
- }
- out.flush();
+ // Everything else
+ copyFiles(manifest, inputJar, outputJar, timestamp);
+
+ // MANIFEST.MF
+ je = new JarEntry(JarFile.MANIFEST_NAME);
+ je.setTime(timestamp);
+ outputJar.putNextEntry(je);
+ manifest.write(outputJar);
+
+ int numKeys = publicKey.length;
+ for (int k = 0; k < numKeys; ++k) {
+ // CERT.SF / CERT#.SF
+ je = new JarEntry(numKeys == 1 ? CERT_SF_NAME :
+ (String.format(CERT_SF_MULTI_NAME, k)));
+ je.setTime(timestamp);
+ outputJar.putNextEntry(je);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeSignatureFile(manifest, baos);
+ 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)));
+ je.setTime(timestamp);
+ outputJar.putNextEntry(je);
+ writeSignatureBlock(new CMSProcessableByteArray(signedData),
+ publicKey[k], privateKey[k], outputJar);
}
}
+ private static void usage() {
+ System.err.println("Usage: signapk [-w] " +
+ "publickey.x509[.pem] privatekey.pk8 " +
+ "[publickey2.x509[.pem] privatekey2.pk8 ...] " +
+ "input.jar output.jar");
+ System.exit(2);
+ }
+
public static void main(String[] args) {
- if (args.length != 4 && args.length != 5) {
- System.err.println("Usage: signapk [-w] " +
- "publickey.x509[.pem] privatekey.pk8 " +
- "input.jar output.jar");
- System.exit(2);
- }
+ if (args.length < 4) usage();
sBouncyCastleProvider = new BouncyCastleProvider();
Security.addProvider(sBouncyCastleProvider);
@@ -517,80 +659,60 @@ class SignApk {
argstart = 1;
}
+ if ((args.length - argstart) % 2 == 1) usage();
+ int numKeys = ((args.length - argstart) / 2) - 1;
+ if (signWholeFile && numKeys > 1) {
+ System.err.println("Only one key may be used with -w.");
+ System.exit(2);
+ }
+
+ String inputFilename = args[args.length-2];
+ String outputFilename = args[args.length-1];
+
JarFile inputJar = null;
- JarOutputStream outputJar = null;
FileOutputStream outputFile = null;
try {
- File publicKeyFile = new File(args[argstart+0]);
- X509Certificate publicKey = readPublicKey(publicKeyFile);
-
- // Assume the certificate is valid for at least an hour.
- long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
+ File firstPublicKeyFile = new File(args[argstart+0]);
- PrivateKey privateKey = readPrivateKey(new File(args[argstart+1]));
- inputJar = new JarFile(new File(args[argstart+2]), false); // Don't verify.
-
- OutputStream outputStream = null;
- if (signWholeFile) {
- outputStream = new ByteArrayOutputStream();
- } else {
- outputStream = outputFile = new FileOutputStream(args[argstart+3]);
+ X509Certificate[] publicKey = new X509Certificate[numKeys];
+ for (int i = 0; i < numKeys; ++i) {
+ int argNum = argstart + i*2;
+ publicKey[i] = readPublicKey(new File(args[argNum]));
}
- outputJar = new JarOutputStream(outputStream);
-
- // For signing .apks, use the maximum compression to make
- // them as small as possible (since they live forever on
- // the system partition). For OTA packages, use the
- // default compression level, which is much much faster
- // and produces output that is only a tiny bit larger
- // (~0.1% on full OTA packages I tested).
- if (!signWholeFile) {
- outputJar.setLevel(9);
- }
-
- JarEntry je;
-
- Manifest manifest = addDigestsToManifest(inputJar);
- // Everything else
- copyFiles(manifest, inputJar, outputJar, timestamp);
+ // Set the ZIP file timestamp to the starting valid time
+ // of the 0th certificate plus one hour (to match what
+ // we've historically done).
+ long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000;
- // otacert
- if (signWholeFile) {
- addOtacert(outputJar, publicKeyFile, timestamp, manifest);
+ PrivateKey[] privateKey = new PrivateKey[numKeys];
+ for (int i = 0; i < numKeys; ++i) {
+ int argNum = argstart + i*2 + 1;
+ privateKey[i] = readPrivateKey(new File(args[argNum]));
}
+ inputJar = new JarFile(new File(inputFilename), false); // Don't verify.
- // MANIFEST.MF
- je = new JarEntry(JarFile.MANIFEST_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- manifest.write(outputJar);
+ outputFile = new FileOutputStream(outputFilename);
- // CERT.SF
- je = new JarEntry(CERT_SF_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeSignatureFile(manifest, baos);
- byte[] signedData = baos.toByteArray();
- outputJar.write(signedData);
-
- // CERT.RSA
- je = new JarEntry(CERT_RSA_NAME);
- je.setTime(timestamp);
- outputJar.putNextEntry(je);
- writeSignatureBlock(new CMSProcessableByteArray(signedData),
- publicKey, privateKey, outputJar);
-
- outputJar.close();
- outputJar = null;
- outputStream.flush();
if (signWholeFile) {
- outputFile = new FileOutputStream(args[argstart+3]);
- signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),
- outputFile, publicKey, privateKey);
+ SignApk.signWholeFile(inputJar, firstPublicKeyFile,
+ publicKey[0], privateKey[0], outputFile);
+ } else {
+ JarOutputStream outputJar = new JarOutputStream(outputFile);
+
+ // For signing .apks, use the maximum compression to make
+ // them as small as possible (since they live forever on
+ // the system partition). For OTA packages, use the
+ // default compression level, which is much much faster
+ // and produces output that is only a tiny bit larger
+ // (~0.1% on full OTA packages I tested).
+ outputJar.setLevel(9);
+
+ signFile(addDigestsToManifest(inputJar), inputJar,
+ publicKey, privateKey, outputJar);
+ outputJar.close();
}
} catch (Exception e) {
e.printStackTrace();