Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   * Copyright (C) 2008 The Android Open Source Project
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
 import java.util.Map;
A Jar file builder with signature support.
 public class SignedJarBuilder {
     private static final String DIGEST_ALGORITHM = "SHA1";
     private static final String DIGEST_ATTR = "SHA1-Digest";
     private static final String DIGEST_MANIFEST_ATTR = "SHA1-Digest-Manifest";

Write to another stream and track how many bytes have been written.
     private static class CountOutputStream extends FilterOutputStream {
         private int mCount = 0;
         public CountOutputStream(OutputStream out) {
              = 0;
         public void write(int bthrows IOException {
         public void write(byte[] bint offint lenthrows IOException {
              += len;
         public int size() {
             return ;
    private JarOutputStream mOutputJar;
    private PrivateKey mKey;
    private X509Certificate mCertificate;
    private Manifest mManifest;
    private MessageDigest mMessageDigest;
    private byte[] mBuffer = new byte[4096];

Classes which implement this interface provides a method to check whether a file should be added to a Jar file.
    public interface IZipEntryFilter {

An exception thrown during packaging of a zip file into APK file. This is typically thrown by implementations of SignedJarBuilder.IZipEntryFilter.checkEntry(java.lang.String).
        public static class ZipAbortException extends Exception {
            private static final long serialVersionUID = 1L;
            public ZipAbortException() {
            public ZipAbortException(String formatObject... args) {
            public ZipAbortException(Throwable causeString formatObject... args) {
                super(String.format(formatargs), cause);
            public ZipAbortException(Throwable cause) {

Checks a file for inclusion in a Jar archive.

archivePath the archive file path of the entry
true if the file should be included.
SignedJarBuilder.IZipEntryFilter.ZipAbortException if writing the file should be aborted.
        public boolean checkEntry(String archivePaththrows ZipAbortException;

Creates a SignedJarBuilder with a given output stream, and signing information.

If either key or certificate is null then the archive will not be signed.

out the where to write the Jar archive.
key the used to sign the archive, or null.
certificate the used to sign the archive, or null.
    public SignedJarBuilder(@NonNull OutputStream out,
                            @Nullable PrivateKey key,
                            @Nullable X509Certificate certificate,
                            @Nullable String builtBy,
                            @Nullable String createdBy)
            throws IOExceptionNoSuchAlgorithmException {
         = new JarOutputStream(new BufferedOutputStream(out));
         = key;
         = certificate;
        if ( != null &&  != null) {
             = new Manifest();
            Attributes main = .getMainAttributes();
            if (builtBy != null) {
            if (createdBy != null) {
             = MessageDigest.getInstance();

Writes a new into the archive.

inputFile the to write.
jarPath the filepath inside the archive.
    public void writeFile(File inputFileString jarPaththrows IOException {
        // Get an input stream on the file.
        FileInputStream fis = new FileInputStream(inputFile);
        try {
            // create the zip entry
            JarEntry entry = new JarEntry(jarPath);
        } finally {
            // close the file stream used to read the file

Copies the content of a Jar/Zip archive into the receiver archive.

An optional SignedJarBuilder.IZipEntryFilter allows to selectively choose which files to copy over.

input the for the Jar/Zip to copy.
filter the filter or null
SignedJarBuilder.IZipEntryFilter.ZipAbortException if the SignedJarBuilder.IZipEntryFilter filter indicated that the write must be aborted.
    public void writeZip(InputStream inputIZipEntryFilter filter)
            throws IOExceptionZipAbortException {
        ZipInputStream zis = new ZipInputStream(input);
        try {
            // loop on the entries of the intermediary package and put them in the final package.
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                String name = entry.getName();
                // do not take directories or anything inside a potential META-INF folder.
                if (entry.isDirectory()) {
                // ignore some of the content in META-INF/ but not all
                if (name.startsWith("META-INF/")) {
                    // ignore the manifest file.
                    String subName = name.substring(9);
                    if ("MANIFEST.MF".equals(subName)) {
                    // special case for Maven meta-data because we really don't care about them in apks.
                    if (name.startsWith("META-INF/maven/")) {
                    // check for subfolder
                    int index = subName.indexOf('/');
                    if (index == -1) {
                        // no sub folder, ignores signature files.
                        if (subName.endsWith(".SF") || name.endsWith(".RSA") || name.endsWith(".DSA")) {
                // if we have a filter, we check the entry against it
                if (filter != null && !filter.checkEntry(name)) {
                JarEntry newEntry;
                // Preserve the STORED method of the input entry.
                if (entry.getMethod() == .) {
                    newEntry = new JarEntry(entry);
                } else {
                    // Create a new entry so that the compressed len is recomputed.
                    newEntry = new JarEntry(name);
        } finally {

Closes the Jar archive by creating the manifest, and signing the archive.

    public void close() throws IOExceptionSigningException {
        if ( != null) {
            // write the manifest to the jar file
            .putNextEntry(new JarEntry(.));
            try {
                // CERT.SF
                Signature signature = Signature.getInstance("SHA1with" + .getAlgorithm());
                .putNextEntry(new JarEntry("META-INF/CERT.SF"));
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] signedData = baos.toByteArray();
                // CERT.*
                .putNextEntry(new JarEntry("META-INF/CERT." + .getAlgorithm()));
                writeSignatureBlock(new CMSProcessableByteArray(signedData), );
            } catch (Exception e) {
                throw new SigningException(e);
         = null;

Clean up of the builder for interrupted workflow. This does nothing if close() was called successfully.
    public void cleanUp() {
        if ( != null) {
            try {
            } catch (IOException e) {
                // pass

Adds an entry to the output jar, and write its content from the

input The input stream from where to write the entry content.
entry the entry to write in the jar.
    private void writeEntry(InputStream inputJarEntry entrythrows IOException {
        // add the entry to the jar archive
        // read the content of the entry from the input stream, and write it into the archive.
        int count;
        while ((count = != -1) {
            .write(, 0, count);
            // update the digest
            if ( != null) {
                .update(, 0, count);
        // close the entry for this file
        if ( != null) {
            // update the manifest for this entry.
            Attributes attr = .getAttributes(entry.getName());
            if (attr == null) {
                attr = new Attributes();
                .getEntries().put(entry.getName(), attr);
                          new String(Base64.encode(.digest()), "ASCII"));

Writes a .SF file with a digest to the manifest.
    private void writeSignatureFile(OutputStream out)
            throws IOExceptionGeneralSecurityException {
        Manifest sf = new Manifest();
        Attributes main = sf.getMainAttributes();
        main.putValue("Created-By""1.0 (Android)");
        MessageDigest md = MessageDigest.getInstance();
        PrintStream print = new PrintStream(
                new DigestOutputStream(new ByteArrayOutputStream(), md),
        // Digest of the entire manifest
        main.putValue(new String(Base64.encode(md.digest()), "ASCII"));
        Map<StringAttributesentries = .getEntries();
        for (Map.Entry<StringAttributesentry : entries.entrySet()) {
            // Digest of the manifest stanza for this entry.
            print.print("Name: " + entry.getKey() + "\r\n");
            for (Map.Entry<ObjectObjectatt : entry.getValue().entrySet()) {
                print.print(att.getKey() + ": " + att.getValue() + "\r\n");
            Attributes sfAttr = new Attributes();
            sfAttr.putValue(new String(Base64.encode(md.digest()), "ASCII"));
            sf.getEntries().put(entry.getKey(), sfAttr);
        CountOutputStream cout = new CountOutputStream(out);
        // A bug in the java.util.jar implementation of Android platforms
        // up to version 1.6 will cause a spurious IOException to be thrown
        // if the length of the signature file is a multiple of 1024 bytes.
        // As a workaround, add an extra CRLF in this case.
        if ((cout.size() % 1024) == 0) {

Write the certificate file with a digital signature.
    private void writeSignatureBlock(CMSTypedData dataX509Certificate publicKey,
            PrivateKey privateKey)
                        throws IOException,
                        CMSException {
        ArrayList<X509CertificatecertList = new ArrayList<X509Certificate>();
        JcaCertStore certs = new JcaCertStore(certList);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        ContentSigner sha1Signer = new JcaContentSignerBuilder(
                                       "SHA1with" + privateKey.getAlgorithm())
            new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder()
        CMSSignedData sigData = gen.generate(datafalse);
        ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
        DEROutputStream dos = new DEROutputStream();
New to GrepCode? Check out our FAQ X