FileDocCategorySizeDatePackage
SignedJarBuilder.javaAPI DocAndroid 1.5 API12117Wed May 06 22:41:10 BST 2009com.android.jarutils

SignedJarBuilder

public class SignedJarBuilder extends Object
A Jar file builder with signature support.

Fields Summary
private static final String
DIGEST_ALGORITHM
private static final String
DIGEST_ATTR
private static final String
DIGEST_MANIFEST_ATTR
private JarOutputStream
mOutputJar
private PrivateKey
mKey
private X509Certificate
mCertificate
private Manifest
mManifest
private sun.misc.BASE64Encoder
mBase64Encoder
private MessageDigest
mMessageDigest
private byte[]
mBuffer
Constructors Summary
public SignedJarBuilder(OutputStream out, PrivateKey key, X509Certificate certificate)
Creates a {@link SignedJarBuilder} with a given output stream, and signing information.

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

param
out the {@link OutputStream} where to write the Jar archive.
param
key the {@link PrivateKey} used to sign the archive, or null.
param
certificate the {@link X509Certificate} used to sign the archive, or null.
throws
IOException
throws
NoSuchAlgorithmException


                             
       
                                           
           
    
    
                                                                          
          
               
        mOutputJar = new JarOutputStream(out);
        mOutputJar.setLevel(9);
        mKey = key;
        mCertificate = certificate;
        
        if (mKey != null && mCertificate != null) {
            mManifest = new Manifest();
            Attributes main = mManifest.getMainAttributes();
            main.putValue("Manifest-Version", "1.0");
            main.putValue("Created-By", "1.0 (Android)");
    
            mBase64Encoder = new BASE64Encoder();
            mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
        }
    
Methods Summary
public voidclose()
Closes the Jar archive by creating the manifest, and signing the archive.

throws
IOException
throws
GeneralSecurityException

        if (mManifest != null) {
            // write the manifest to the jar file
            mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
            mManifest.write(mOutputJar);
            
            // CERT.SF
            Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm());
            signature.initSign(mKey);
            mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
            writeSignatureFile(new SignatureOutputStream(mOutputJar, signature));
    
            // CERT.*
            mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
            writeSignatureBlock(signature, mCertificate, mKey);
        }
        
        mOutputJar.close();
    
private voidwriteEntry(java.io.InputStream input, java.util.jar.JarEntry entry)
Adds an entry to the output jar, and write its content from the {@link InputStream}

param
input The input stream from where to write the entry content.
param
entry the entry to write in the jar.
throws
IOException

        // add the entry to the jar archive
        mOutputJar.putNextEntry(entry);

        // read the content of the entry from the input stream, and write it into the archive.
        int count; 
        while ((count = input.read(mBuffer)) != -1) {
            mOutputJar.write(mBuffer, 0, count);
            
            // update the digest
            if (mMessageDigest != null) {
                mMessageDigest.update(mBuffer, 0, count);
            }
        }

        // close the entry for this file
        mOutputJar.closeEntry();
        
        if (mManifest != null) {
            // update the manifest for this entry.
            Attributes attr = mManifest.getAttributes(entry.getName());
            if (attr == null) {
                attr = new Attributes();
                mManifest.getEntries().put(entry.getName(), attr);
            }
            attr.putValue(DIGEST_ATTR, mBase64Encoder.encode(mMessageDigest.digest()));
        }
    
public voidwriteFile(java.io.File inputFile, java.lang.String jarPath)
Writes a new {@link File} into the archive.

param
inputFile the {@link File} to write.
param
jarPath the filepath inside the archive.
throws
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(fis, entry);
        } finally {
            // close the file stream used to read the file
            fis.close();
        }
    
private voidwriteSignatureBlock(java.security.Signature signature, java.security.cert.X509Certificate publicKey, java.security.PrivateKey privateKey)
Write the certificate file with a digital signature.

        SignerInfo signerInfo = new SignerInfo(
                new X500Name(publicKey.getIssuerX500Principal().getName()),
                publicKey.getSerialNumber(),
                AlgorithmId.get(DIGEST_ALGORITHM),
                AlgorithmId.get(privateKey.getAlgorithm()),
                signature.sign());

        PKCS7 pkcs7 = new PKCS7(
                new AlgorithmId[] { AlgorithmId.get(DIGEST_ALGORITHM) },
                new ContentInfo(ContentInfo.DATA_OID, null),
                new X509Certificate[] { publicKey },
                new SignerInfo[] { signerInfo });

        pkcs7.encodeSignedData(mOutputJar);
    
private voidwriteSignatureFile(java.io.OutputStream out)
Writes a .SF file with a digest to the manifest.

        Manifest sf = new Manifest();
        Attributes main = sf.getMainAttributes();
        main.putValue("Signature-Version", "1.0");
        main.putValue("Created-By", "1.0 (Android)");

        BASE64Encoder base64 = new BASE64Encoder();
        MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
        PrintStream print = new PrintStream(
                new DigestOutputStream(new ByteArrayOutputStream(), md),
                true, "UTF-8");

        // Digest of the entire manifest
        mManifest.write(print);
        print.flush();
        main.putValue(DIGEST_MANIFEST_ATTR, base64.encode(md.digest()));

        Map<String, Attributes> entries = mManifest.getEntries();
        for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
            // Digest of the manifest stanza for this entry.
            print.print("Name: " + entry.getKey() + "\r\n");
            for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
                print.print(att.getKey() + ": " + att.getValue() + "\r\n");
            }
            print.print("\r\n");
            print.flush();

            Attributes sfAttr = new Attributes();
            sfAttr.putValue(DIGEST_ATTR, base64.encode(md.digest()));
            sf.getEntries().put(entry.getKey(), sfAttr);
        }

        sf.write(out);
    
public voidwriteZip(java.io.InputStream input, com.android.jarutils.SignedJarBuilder$IZipEntryFilter filter)
Copies the content of a Jar/Zip archive into the receiver archive.

An optional {@link IZipEntryFilter} allows to selectively choose which files to copy over.

param
input the {@link InputStream} for the Jar/Zip to copy.
param
filter the filter or null
throws
IOException

        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() || name.startsWith("META-INF/")) {
                    continue;
                }
    
                // if we have a filter, we check the entry against it
                if (filter != null && filter.checkEntry(name) == false) {
                    continue;
                }
    
                JarEntry newEntry;
    
                // Preserve the STORED method of the input entry.
                if (entry.getMethod() == JarEntry.STORED) {
                    newEntry = new JarEntry(entry);
                } else {
                    // Create a new entry so that the compressed len is recomputed.
                    newEntry = new JarEntry(name);
                }
                
                writeEntry(zis, newEntry);
    
                zis.closeEntry();
            }
        } finally {
            zis.close();
        }