com.sun.enterprise.security.ssl.JarSigner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-embedded-all Show documentation
Show all versions of payara-embedded-all Show documentation
Payara-Embedded-All Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2014 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.Collections;
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 com.ibm.security.x509.AlgorithmId;
import com.ibm.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 hash(content.getBytes());
}
/**
* Hash the data.
*
* @param data
* @return
*/
private String hash(final byte[] data) {
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 {
signJar(input, output, alias, null);
}
/**
* Signs a JAR, adding caller-specified attributes to the manifest's main attrs.
*
* @param input input JAR file
* @param output output JAR file
* @param alias signing alias in the keystore
* @param additionalAttrs additional attributes to add to the manifest's main attrs (null if none)
*/
public void signJar(File input, File output, String alias, final Attributes additionalAttrs)
throws IOException, KeyStoreException, NoSuchAlgorithmException,
InvalidKeyException, UnrecoverableKeyException, SignatureException {
final ZipOutputStream zout = new ZipOutputStream(
new FileOutputStream(output));
try {
signJar(input, zout, alias, additionalAttrs, Collections.EMPTY_MAP);
} finally {
zout.close();
}
}
/**
* Signs a JAR, adding caller-specified attributes to the manifest's main attrs and also
* inserting (and signing) additional caller-supplied content as new entries in the
* zip output stream.
* @param input input JAR file
* @param zout Zip output stream created
* @param alias signing alias in the keystore
* @param additionalAttrs additional attributes to add to the manifest's main attrs (null if none)
* @param additionalEntries entry-name/byte[] pairs of additional content to add to the signed output
* @throws IOException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws UnrecoverableKeyException
* @throws SignatureException
*/
public void signJar(File input, ZipOutputStream zout, String alias, final Attributes additionalAttrs,
Map additionalEntries)
throws IOException, KeyStoreException, NoSuchAlgorithmException,
InvalidKeyException, UnrecoverableKeyException, SignatureException {
JarFile jf = new JarFile(input);
try {
Enumeration jes;
// manifestEntries is content of META-INF/MANIFEST.MF
StringBuilder manifestEntries = new StringBuilder();
byte[] manifestContent;
byte[] sigFileContent = getExistingSignatureFile(jf);
boolean signed = (sigFileContent != null);
if (!signed || ! additionalEntries.isEmpty()) {
jes = jf.entries();// manifestHeader is header of META-INF/MANIFEST.MF, initialized to default
Manifest manifest = retrieveManifest(jf);
StringBuilder manifestHeader = new StringBuilder();
Attributes mfAttrs = manifest.getMainAttributes();
if (additionalAttrs != null) {
mfAttrs.putAll(additionalAttrs);
}
appendAttributes(manifestHeader, mfAttrs);
// 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;
}
processMetadataForEntry(manifest, manifestEntries, sigFileEntries, name, readJarEntry(jf, je));
}
if (additionalEntries != null) {
for (Map.Entry entry : additionalEntries.entrySet()) {
processMetadataForEntry(manifest, manifestEntries, sigFileEntries, entry.getKey(), entry.getValue());
}
}
// 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
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);
}
}
if (additionalEntries != null) {
for (Map.Entry entry : additionalEntries.entrySet()) {
final ZipEntry newZipEntry = new ZipEntry(entry.getKey());
zout.putNextEntry(newZipEntry);
zout.write(entry.getValue());
}
}
} finally {
jf.close();
}
}
private void processMetadataForEntry(final Manifest manifest,
final StringBuilder manifestEntries,
final StringBuilder sigFileEntries,
final String name,
final byte[] content) {
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(content)).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);
}
/**
* 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 entriesMap = manifest.getEntries();
for (Iterator 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) {
return appendAttributes(manifestEntry, (entry == null)
? manifest.getMainAttributes() : manifest.getAttributes(entry));
}
private static StringBuilder appendAttributes(StringBuilder manifestEntry,
Attributes attributes) {
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 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;
}
}