Loading tools/signapk/SignApk.java +235 −147 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,7 @@ import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64; import java.io.BufferedReader; import java.io.BufferedReader; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataInputStream; import java.io.File; import java.io.File; Loading Loading @@ -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'. */ /** 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, Loading Loading @@ -395,24 +371,171 @@ class SignApk { dos.writeObject(asn1.readObject()); dos.writeObject(asn1.readObject()); } } private static void signWholeOutputFile(byte[] zipData, /** OutputStream outputStream, * Copy all the files in a manifest from input to output. We set X509Certificate publicKey, * the modification times in the output to a fixed time, so as to PrivateKey privateKey) * 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, throws IOException, CertificateEncodingException, CertificateEncodingException, OperatorCreationException, OperatorCreationException, CMSException { CMSException { // For a zip with no archive comment, the SignApk.writeSignatureBlock(this, publicKey, privateKey, temp); // 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"); } } 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(); ByteArrayOutputStream temp = new ByteArrayOutputStream(); // put a readable message and a null char at the start of the // put a readable message and a null char at the start of the Loading @@ -423,8 +546,20 @@ class SignApk { temp.write(message); temp.write(message); temp.write(0); temp.write(0); writeSignatureBlock(new CMSByteArraySlice(zipData, 0, zipData.length-2), cmsOut.writeSignatureBlock(temp); publicKey, privateKey, 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; int total_size = temp.size() + 6; if (total_size > 0xffff) { if (total_size > 0xffff) { throw new IllegalArgumentException("signature is too big for ZIP file comment"); throw new IllegalArgumentException("signature is too big for ZIP file comment"); Loading Loading @@ -458,44 +593,48 @@ class SignApk { } } } } outputStream.write(zipData, 0, zipData.length-2); outputStream.write(total_size & 0xff); outputStream.write(total_size & 0xff); outputStream.write((total_size >> 8) & 0xff); outputStream.write((total_size >> 8) & 0xff); temp.writeTo(outputStream); temp.writeTo(outputStream); } } /** private static void signFile(Manifest manifest, JarFile inputJar, * Copy all the files in a manifest from input to output. We set X509Certificate[] publicKey, PrivateKey[] privateKey, * the modification times in the output to a fixed time, so as to JarOutputStream outputJar) * reduce variation in the output file and make incremental OTAs throws Exception { * more efficient. // Assume the certificate is valid for at least an hour. */ long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000; 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(); JarEntry je; 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); // Everything else while ((num = data.read(buffer)) > 0) { copyFiles(manifest, inputJar, outputJar, timestamp); out.write(buffer, 0, num); } // MANIFEST.MF out.flush(); 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); } } } } Loading Loading @@ -531,7 +670,6 @@ class SignApk { String outputFilename = args[args.length-1]; String outputFilename = args[args.length-1]; JarFile inputJar = null; JarFile inputJar = null; JarOutputStream outputJar = null; FileOutputStream outputFile = null; FileOutputStream outputFile = null; try { try { Loading @@ -555,13 +693,14 @@ class SignApk { } } inputJar = new JarFile(new File(inputFilename), false); // Don't verify. inputJar = new JarFile(new File(inputFilename), false); // Don't verify. OutputStream outputStream = null; outputFile = new FileOutputStream(outputFilename); if (signWholeFile) { if (signWholeFile) { outputStream = new ByteArrayOutputStream(); SignApk.signWholeFile(inputJar, firstPublicKeyFile, publicKey[0], privateKey[0], outputFile); } else { } else { outputStream = outputFile = new FileOutputStream(outputFilename); JarOutputStream outputJar = new JarOutputStream(outputFile); } outputJar = new JarOutputStream(outputStream); // For signing .apks, use the maximum compression to make // For signing .apks, use the maximum compression to make // them as small as possible (since they live forever on // them as small as possible (since they live forever on Loading @@ -569,62 +708,11 @@ class SignApk { // default compression level, which is much much faster // default compression level, which is much much faster // and produces output that is only a tiny bit larger // and produces output that is only a tiny bit larger // (~0.1% on full OTA packages I tested). // (~0.1% on full OTA packages I tested). if (!signWholeFile) { outputJar.setLevel(9); outputJar.setLevel(9); } JarEntry je; Manifest manifest = addDigestsToManifest(inputJar); // Everything else copyFiles(manifest, inputJar, outputJar, timestamp); // otacert if (signWholeFile) { addOtacert(outputJar, firstPublicKeyFile, timestamp, manifest); } // MANIFEST.MF je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); manifest.write(outputJar); // In the case of multiple keys, all the .SF files will be // identical, but as far as I can tell the jarsigner docs // don't allow there to be just one copy in the zipfile; // there hase to be one per .RSA file. ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeSignatureFile(manifest, baos); byte[] signedData = baos.toByteArray(); 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); 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); } signFile(addDigestsToManifest(inputJar), inputJar, publicKey, privateKey, outputJar); outputJar.close(); outputJar.close(); outputJar = null; outputStream.flush(); if (signWholeFile) { outputFile = new FileOutputStream(outputFilename); signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(), outputFile, publicKey[0], privateKey[0]); } } } catch (Exception e) { } catch (Exception e) { e.printStackTrace(); e.printStackTrace(); Loading Loading
tools/signapk/SignApk.java +235 −147 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,7 @@ import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64; import java.io.BufferedReader; import java.io.BufferedReader; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataInputStream; import java.io.File; import java.io.File; Loading Loading @@ -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'. */ /** 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, Loading Loading @@ -395,24 +371,171 @@ class SignApk { dos.writeObject(asn1.readObject()); dos.writeObject(asn1.readObject()); } } private static void signWholeOutputFile(byte[] zipData, /** OutputStream outputStream, * Copy all the files in a manifest from input to output. We set X509Certificate publicKey, * the modification times in the output to a fixed time, so as to PrivateKey privateKey) * 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, throws IOException, CertificateEncodingException, CertificateEncodingException, OperatorCreationException, OperatorCreationException, CMSException { CMSException { // For a zip with no archive comment, the SignApk.writeSignatureBlock(this, publicKey, privateKey, temp); // 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"); } } 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(); ByteArrayOutputStream temp = new ByteArrayOutputStream(); // put a readable message and a null char at the start of the // put a readable message and a null char at the start of the Loading @@ -423,8 +546,20 @@ class SignApk { temp.write(message); temp.write(message); temp.write(0); temp.write(0); writeSignatureBlock(new CMSByteArraySlice(zipData, 0, zipData.length-2), cmsOut.writeSignatureBlock(temp); publicKey, privateKey, 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; int total_size = temp.size() + 6; if (total_size > 0xffff) { if (total_size > 0xffff) { throw new IllegalArgumentException("signature is too big for ZIP file comment"); throw new IllegalArgumentException("signature is too big for ZIP file comment"); Loading Loading @@ -458,44 +593,48 @@ class SignApk { } } } } outputStream.write(zipData, 0, zipData.length-2); outputStream.write(total_size & 0xff); outputStream.write(total_size & 0xff); outputStream.write((total_size >> 8) & 0xff); outputStream.write((total_size >> 8) & 0xff); temp.writeTo(outputStream); temp.writeTo(outputStream); } } /** private static void signFile(Manifest manifest, JarFile inputJar, * Copy all the files in a manifest from input to output. We set X509Certificate[] publicKey, PrivateKey[] privateKey, * the modification times in the output to a fixed time, so as to JarOutputStream outputJar) * reduce variation in the output file and make incremental OTAs throws Exception { * more efficient. // Assume the certificate is valid for at least an hour. */ long timestamp = publicKey[0].getNotBefore().getTime() + 3600L * 1000; 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(); JarEntry je; 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); // Everything else while ((num = data.read(buffer)) > 0) { copyFiles(manifest, inputJar, outputJar, timestamp); out.write(buffer, 0, num); } // MANIFEST.MF out.flush(); 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); } } } } Loading Loading @@ -531,7 +670,6 @@ class SignApk { String outputFilename = args[args.length-1]; String outputFilename = args[args.length-1]; JarFile inputJar = null; JarFile inputJar = null; JarOutputStream outputJar = null; FileOutputStream outputFile = null; FileOutputStream outputFile = null; try { try { Loading @@ -555,13 +693,14 @@ class SignApk { } } inputJar = new JarFile(new File(inputFilename), false); // Don't verify. inputJar = new JarFile(new File(inputFilename), false); // Don't verify. OutputStream outputStream = null; outputFile = new FileOutputStream(outputFilename); if (signWholeFile) { if (signWholeFile) { outputStream = new ByteArrayOutputStream(); SignApk.signWholeFile(inputJar, firstPublicKeyFile, publicKey[0], privateKey[0], outputFile); } else { } else { outputStream = outputFile = new FileOutputStream(outputFilename); JarOutputStream outputJar = new JarOutputStream(outputFile); } outputJar = new JarOutputStream(outputStream); // For signing .apks, use the maximum compression to make // For signing .apks, use the maximum compression to make // them as small as possible (since they live forever on // them as small as possible (since they live forever on Loading @@ -569,62 +708,11 @@ class SignApk { // default compression level, which is much much faster // default compression level, which is much much faster // and produces output that is only a tiny bit larger // and produces output that is only a tiny bit larger // (~0.1% on full OTA packages I tested). // (~0.1% on full OTA packages I tested). if (!signWholeFile) { outputJar.setLevel(9); outputJar.setLevel(9); } JarEntry je; Manifest manifest = addDigestsToManifest(inputJar); // Everything else copyFiles(manifest, inputJar, outputJar, timestamp); // otacert if (signWholeFile) { addOtacert(outputJar, firstPublicKeyFile, timestamp, manifest); } // MANIFEST.MF je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); manifest.write(outputJar); // In the case of multiple keys, all the .SF files will be // identical, but as far as I can tell the jarsigner docs // don't allow there to be just one copy in the zipfile; // there hase to be one per .RSA file. ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeSignatureFile(manifest, baos); byte[] signedData = baos.toByteArray(); 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); 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); } signFile(addDigestsToManifest(inputJar), inputJar, publicKey, privateKey, outputJar); outputJar.close(); outputJar.close(); outputJar = null; outputStream.flush(); if (signWholeFile) { outputFile = new FileOutputStream(outputFilename); signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(), outputFile, publicKey[0], privateKey[0]); } } } catch (Exception e) { } catch (Exception e) { e.printStackTrace(); e.printStackTrace(); Loading