alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Glassfish example source code file (JarSigner.java)

This example Glassfish source code file (JarSigner.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Glassfish tags/keywords

attributes, file, io, ioexception, ioexception, jar, jarentry, jarentry, jarsigner, manifest, meta-inf, security, string, string, stringbuilder, stringbuilder, util, zip, zipentry

The Glassfish JarSigner.java source code

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.enterprise.security.ssl;

import com.sun.enterprise.server.pluggable.SecuritySupport;
import com.sun.enterprise.universal.GFBase64Encoder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;

/**
 * A utility class to sign jar files.
 *
 * @author Sudarsan Sridhar
 */
public class JarSigner {

    private static final GFBase64Encoder b64encoder = new GFBase64Encoder();
    private final MessageDigest md;
    private final String digestAlgorithm;
    private final String keyAlgorithm;
    private static final SecuritySupport secSupp = SecuritySupport.getDefaultInstance();

    public JarSigner(String digestAlgorithm, String keyAlgorithm)
            throws NoSuchAlgorithmException {
        this.digestAlgorithm = digestAlgorithm;
        this.keyAlgorithm = keyAlgorithm;
        this.md = MessageDigest.getInstance(digestAlgorithm);
    }

    public static void main(String[] args) throws Exception {
        File in = new File(args[0]);
        File out = new File(args[1]);
        new JarSigner("SHA1", "RSA").signJar(in, out, "s1as");
    }

    /**
     * Hash the string completely.
     *
     * @param content String to be hashed.
     * @return the hash.
     */
    private String hash(String content) {
        return b64encoder.encodeBuffer(md.digest(content.getBytes())).trim();
    }

    /**
     * Hash the JarEntry completely.
     *
     * @param jf the JarFile
     * @param je the JarEntry
     * @throws IOException
     * @return resulting hash
     */
    private String hash(JarFile jf, JarEntry je) throws IOException {
        byte[] data = readJarEntry(jf, je);
        return b64encoder.encodeBuffer(md.digest(data)).trim();
    }

    /**
     * Signs a jar.
     * 
     * @param input input jar file
     * @param output output jar file
     * @param alias signing alias in the keystore
     */
    public void signJar(File input, File output, String alias)
            throws IOException, KeyStoreException, NoSuchAlgorithmException,
            InvalidKeyException, UnrecoverableKeyException, SignatureException {

        JarFile jf = new JarFile(input);
        try {
            Enumeration<JarEntry> jes;
            // manifestEntries is content of META-INF/MANIFEST.MF
            StringBuilder manifestEntries = new StringBuilder();

            byte[] manifestContent = null;
            byte[] sigFileContent = getExistingSignatureFile(jf);
            boolean signed = (sigFileContent != null);

            if (!signed) {
                jes = jf.entries();// manifestHeader is header of META-INF/MANIFEST.MF, initialized to default
                Manifest manifest = retrieveManifest(jf);
                StringBuilder manifestHeader = new StringBuilder();
                appendAttributes(manifestHeader, manifest, null);
                // sigFileEntries is content of META-INF/ME.SF
                StringBuilder sigFileEntries = new StringBuilder();
                while (jes.hasMoreElements()) {
                    JarEntry je = jes.nextElement();
                    String name = je.getName();
                    if ((je.isDirectory() && manifest.getAttributes(name) == null)
                            || name.equals(JarFile.MANIFEST_NAME)) {
                        continue;
                    }

                    StringBuilder me = new StringBuilder();
                    StringBuilder currentLine = new StringBuilder();
                    // Create digest lines in MANIFEST.MF
                    currentLine.append("Name: ").append(name);
                    appendLine(me, currentLine);
                    currentLine.setLength(0);
                    me.append(digestAlgorithm).append("-Digest: ").append(hash(jf, je)).append("\r\n");
                    appendAttributes(me, manifest, name);
                    // Create digest lines in ME.SF
                    currentLine.append("Name: ").append(name);
                    appendLine(sigFileEntries, currentLine);
                    currentLine.setLength(0);
                    sigFileEntries.append(digestAlgorithm).append("-Digest: ").append(hash(me.toString())).append("\r\n\r\n");
                    manifestEntries.append(me);
                }


                // META-INF/ME.SF
                StringBuilder sigFile = new StringBuilder("Signature-Version: 1.0\r\n").append(digestAlgorithm).append("-Digest-Manifest-Main-Attributes: ").append(hash(manifestHeader.toString())).append("\r\n").append("Created-By: ").append(System.getProperty("java.version")).append(" (").append(System.getProperty("java.vendor")).append(")\r\n");
                // Combine header and content of MANIFEST.MF, and rehash
                manifestHeader.append(manifestEntries);
                sigFile.append(digestAlgorithm).append("-Digest-Manifest: ").append(hash(manifestHeader.toString())).append("\r\n\r\n");

                // Combine header and content of ME.SF
                sigFile.append(sigFileEntries);
                manifestContent = manifestHeader.toString().getBytes();
                sigFileContent = sigFile.toString().getBytes();
            } else {
                manifestContent = readJarEntry(jf,
                        jf.getJarEntry(JarFile.MANIFEST_NAME));
            }
            X509Certificate[] certChain = null;
            PrivateKey privKey = null;
            KeyStore[] ks = secSupp.getKeyStores();
            for (int i = 0; i < ks.length; i++) {
                privKey = secSupp.getPrivateKeyForAlias(alias, i);
                if (privKey != null) {
                    Certificate[] cs = ks[i].getCertificateChain(alias);
                    certChain = new X509Certificate[cs.length];
                    for (int j = 0; j < cs.length; j++) {
                        certChain[j] = (X509Certificate) cs[j];
                    }
                }
            }

            // Sign ME.SF
            Signature sig = Signature.getInstance(digestAlgorithm + "with" + keyAlgorithm);
            sig.initSign(privKey);
            sig.update(sigFileContent);

            // Create PKCS7 block
            PKCS7 pkcs7 = new PKCS7(
                    new AlgorithmId[]{AlgorithmId.get(digestAlgorithm)},
                    new ContentInfo(sigFileContent),
                    certChain,
                    new SignerInfo[]{new SignerInfo(
                        (X500Name) certChain[0].getIssuerDN(),
                        certChain[0].getSerialNumber(),
                        AlgorithmId.get(digestAlgorithm),
                        AlgorithmId.get(keyAlgorithm),
                        sig.sign())
                    });
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            pkcs7.encodeSignedData(bout);

            // Write output
            ZipOutputStream zout = new ZipOutputStream(
                    new FileOutputStream(output));
            zout.putNextEntry((signed)
                    ? getZipEntry(jf.getJarEntry(JarFile.MANIFEST_NAME))
                    : new ZipEntry(JarFile.MANIFEST_NAME));
            zout.write(manifestContent);

            zout.putNextEntry(new ZipEntry("META-INF/"
                    + alias.toUpperCase(Locale.US) + ".SF"));
            zout.write(sigFileContent);

            zout.putNextEntry(new ZipEntry("META-INF/"
                    + alias.toUpperCase(Locale.US) + "." + keyAlgorithm));
            zout.write(bout.toByteArray());

            jes = jf.entries();
            while (jes.hasMoreElements()) {
                JarEntry je = jes.nextElement();
                String name = je.getName();
                if (!name.equals(JarFile.MANIFEST_NAME)) {
                    zout.putNextEntry(getZipEntry(je));
                    byte[] data = readJarEntry(jf, je);
                    zout.write(data);
                }
            }
            zout.close();
        } finally {
            jf.close();
        }
    }

    /**
     * Retrieve manifest from jar, create a default template if none exists.
     *
     * @param jf The jar file
     * @return The Manifest
     * @throws IOException
     */
    private Manifest retrieveManifest(JarFile jf) throws IOException {
        Manifest manifest = jf.getManifest();
        if (manifest == null) {
            manifest = new Manifest();
            Attributes mainAttributes = manifest.getMainAttributes();
            mainAttributes.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
            mainAttributes.putValue("Created-By", System.getProperty("java.version")
                    + " (" + System.getProperty("java.vendor") + ")");
        }
        Map<String, Attributes> entriesMap = manifest.getEntries();
        for (Iterator<String> entries = entriesMap.keySet().iterator(); entries.hasNext();) {
            if (jf.getJarEntry(entries.next()) == null) {
                entries.remove();
            }
        }
        return manifest;
    }

    /**
     * Add attributes for the current entry.
     *
     * @param manifestEntry - The StringBuilder to which the attributes are to be added.
     * @param manifest - The Jar Manifest Entry
     * @param entry - The named entry in the manifest. null means the Main Attribute section.
     * @return manifestEntry with attributes added.
     */
    private static StringBuilder appendAttributes(StringBuilder manifestEntry,
            Manifest manifest, String entry) {
        Attributes attributes = (entry == null)
                ? manifest.getMainAttributes() : manifest.getAttributes(entry);
        StringBuilder line = new StringBuilder();
        if (attributes != null) {
            for (Map.Entry attr : attributes.entrySet()) {
                line.append(attr.getKey().toString()).append(": ").append((String) attr.getValue());
                appendLine(manifestEntry, line);
                line.setLength(0);
            }
        }
        return manifestEntry.append("\r\n");
    }

    /**
     * Process a long manifest line and add continuation if required
     *
     * @param sb - The output string
     * @param line - The line to be processed.
     * @return sb with the line added.
     */
    private static StringBuilder appendLine(StringBuilder sb, StringBuilder line) {
        int begin = 0;
        for (int end = 70; line.length() - begin > 70; end += 69) {
            sb.append(line.subSequence(begin, end)).append("\r\n ");
            begin = end;
        }
        return sb.append(line.subSequence(begin, line.length())).append("\r\n");
    }

    /**
     * If jar is signed, return existing Signature file, else return null.
     *
     * @param jf The jar file
     * @return Signature file
     * @throws IOException
     */
    private static byte[] getExistingSignatureFile(JarFile jf) throws IOException {
        Enumeration<JarEntry> entries = jf.entries();
        JarEntry je = null;
        while (entries.hasMoreElements()) {
            JarEntry cje = entries.nextElement();
            if (cje.getName().startsWith("META-INF/") && cje.getName().endsWith(".SF")) {
                je = cje;
                break;
            }
        }
        return readJarEntry(jf, je);
    }

    /**
     * Read completely the bytes from Entry je of jarfile jf.
     * 
     * @param jf the jar file
     * @param je the jar entry
     * @return bytes from je.
     * @throws IOException
     */
    private static byte[] readJarEntry(JarFile jf, JarEntry je) throws IOException {
        if (je == null) {
            return null;
        }
        byte[] data = new byte[(int) je.getSize()];
        InputStream jis = jf.getInputStream(je);
        int current;
        int idx = 0;
        while ((current = jis.read()) > -1) {
            data[idx++] = (byte) current;
        }
        return data;
    }

    /**
     * Get the ZipEntry for the given JarEntry. Added in order to suppress the
     * compressedSize field as it was causing errors
     *
     * @param je The jar entry.
     * @return ZipEntry with fields populated from the JarEntry.
     */
    private static ZipEntry getZipEntry(JarEntry je) {
        ZipEntry ze = new ZipEntry(je.getName());

        ze.setComment(je.getComment());
        ze.setCrc(je.getCrc());
        ze.setExtra(je.getExtra());
        ze.setMethod(je.getMethod());
        ze.setSize(je.getSize());
        ze.setTime(je.getTime());

        return ze;
    }
}

Other Glassfish examples (source code examples)

Here is a short list of links related to this Glassfish JarSigner.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.