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
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
  * 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.
  */
 
 package com.android.builder.signing;
 
 
 import java.io.File;
 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) {
             super(out);
              = 0;
         }
 
         @Override
         public void write(int bthrows IOException {
             super.write(b);
             ++;
         }
 
         @Override
         public void write(byte[] bint offint lenthrows IOException {
             super.write(bofflen);
              += 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() {
                super();
            }
            public ZipAbortException(String formatObject... args) {
                super(String.format(formatargs));
            }
            public ZipAbortException(Throwable causeString formatObject... args) {
                super(String.format(formatargs), cause);
            }
            public ZipAbortException(Throwable cause) {
                super(cause);
            }
        }


        
Checks a file for inclusion in a Jar archive.

Parameters:
archivePath the archive file path of the entry
Returns:
true if the file should be included.
Throws:
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.

Parameters:
out the java.io.OutputStream where to write the Jar archive.
key the java.security.PrivateKey used to sign the archive, or null.
certificate the java.security.cert.X509Certificate used to sign the archive, or null.
Throws:
java.io.IOException
java.security.NoSuchAlgorithmException
    public SignedJarBuilder(@NonNull OutputStream out,
                            @Nullable PrivateKey key,
                            @Nullable X509Certificate certificate,
                            @Nullable String builtBy,
                            @Nullable String createdBy)
            throws IOExceptionNoSuchAlgorithmException {
         = new JarOutputStream(new BufferedOutputStream(out));
        .setLevel(9);
         = key;
         = certificate;
        if ( != null &&  != null) {
             = new Manifest();
            Attributes main = .getMainAttributes();
            main.putValue("Manifest-Version""1.0");
            if (builtBy != null) {
                main.putValue("Built-By"builtBy);
            }
            if (createdBy != null) {
                main.putValue("Created-By"createdBy);
            }
             = MessageDigest.getInstance();
        }
    }

    
Writes a new java.io.File into the archive.

Parameters:
inputFile the java.io.File to write.
jarPath the filepath inside the archive.
Throws:
java.io.IOException
    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);
            entry.setTime(inputFile.lastModified());
            writeEntry(fisentry);
        } finally {
            // close the file stream used to read the file
            fis.close();
        }
    }

    
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.

Parameters:
input the java.io.InputStream for the Jar/Zip to copy.
filter the filter or null
Throws:
java.io.IOException
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()) {
                    continue;
                }
                // 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)) {
                        continue;
                    }
                    // special case for Maven meta-data because we really don't care about them in apks.
                    if (name.startsWith("META-INF/maven/")) {
                        continue;
                    }
                    // 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")) {
                            continue;
                        }
                    }
                }
                // if we have a filter, we check the entry against it
                if (filter != null && !filter.checkEntry(name)) {
                    continue;
                }
                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);
                }
                writeEntry(zisnewEntry);
                zis.closeEntry();
            }
        } finally {
            zis.close();
        }
    }

    
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(.));
            .write();
            try {
                // CERT.SF
                Signature signature = Signature.getInstance("SHA1with" + .getAlgorithm());
                signature.initSign();
                .putNextEntry(new JarEntry("META-INF/CERT.SF"));
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                writeSignatureFile(baos);
                byte[] signedData = baos.toByteArray();
                .write(signedData);
                // CERT.*
                .putNextEntry(new JarEntry("META-INF/CERT." + .getAlgorithm()));
                writeSignatureBlock(new CMSProcessableByteArray(signedData), );
            } catch (Exception e) {
                throw new SigningException(e);
            }
        }
        .close();
         = null;
    }

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

    
Adds an entry to the output jar, and write its content from the java.io.InputStream

Parameters:
input The input stream from where to write the entry content.
entry the entry to write in the jar.
Throws:
java.io.IOException
    private void writeEntry(InputStream inputJarEntry entrythrows IOException {
        // add the entry to the jar archive
        .putNextEntry(entry);
        // read the content of the entry from the input stream, and write it into the archive.
        int count;
        while ((count = input.read()) != -1) {
            .write(, 0, count);
            // update the digest
            if ( != null) {
                .update(, 0, count);
            }
        }
        // close the entry for this file
        .closeEntry();
        if ( != null) {
            // update the manifest for this entry.
            Attributes attr = .getAttributes(entry.getName());
            if (attr == null) {
                attr = new Attributes();
                .getEntries().put(entry.getName(), attr);
            }
            attr.putValue(
                          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("Signature-Version""1.0");
        main.putValue("Created-By""1.0 (Android)");
        MessageDigest md = MessageDigest.getInstance();
        PrintStream print = new PrintStream(
                new DigestOutputStream(new ByteArrayOutputStream(), md),
                true.);
        // Digest of the entire manifest
        .write(print);
        print.flush();
        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");
            }
            print.print("\r\n");
            print.flush();
            Attributes sfAttr = new Attributes();
            sfAttr.putValue(new String(Base64.encode(md.digest()), "ASCII"));
            sf.getEntries().put(entry.getKey(), sfAttr);
        }
        CountOutputStream cout = new CountOutputStream(out);
        sf.write(cout);
        // 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) {
            cout.write('\r');
            cout.write('\n');
        }
    }

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