# HG changeset patch
# User weijun
# Date 1427117812 -28800
# Mon Mar 23 21:36:52 2015 +0800
# Node ID 961486d09695b6190fc2df9dea603462e6773ee2
# Parent 9c230f39a86c19953edff95ee44ae82c349b894f
8056174: New APIs for jar signing
diff --git a/src/java.base/share/classes/sun/security/x509/AlgorithmId.java b/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
--- a/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
+++ b/src/java.base/share/classes/sun/security/x509/AlgorithmId.java
@@ -977,4 +977,30 @@
}
return null;
}
+
+ // Both arg cannot be null
+ public static void checKeyAlgSigAlgkMatch(String keyAlgorithm, String signatureAlgorithm) {
+ String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.US);
+ if ((sigAlgUpperCase.endsWith("WITHRSA") &&
+ !keyAlgorithm.equalsIgnoreCase("RSA")) ||
+ (sigAlgUpperCase.endsWith("WITHECDSA") &&
+ !keyAlgorithm.equalsIgnoreCase("EC")) ||
+ (sigAlgUpperCase.endsWith("WITHDSA") &&
+ !keyAlgorithm.equalsIgnoreCase("DSA"))) {
+ throw new IllegalArgumentException(
+ "key algorithm not compatible with signature algorithm");
+ }
+ }
+
+ // Arg cannot be null, might return null
+ public static String getDefaultSigAlgForKeyAlg(String keyAlgorithm) {
+ if (keyAlgorithm.equalsIgnoreCase("DSA"))
+ return "SHA256withDSA";
+ else if (keyAlgorithm.equalsIgnoreCase("RSA"))
+ return "SHA256withRSA";
+ else if (keyAlgorithm.equalsIgnoreCase("EC"))
+ return "SHA256withECDSA";
+ else
+ return null;
+ }
}
diff --git a/src/jdk.jartool/share/classes/jdk/security/jarsigner/Action.java b/src/jdk.jartool/share/classes/jdk/security/jarsigner/Action.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/jdk/security/jarsigner/Action.java
@@ -0,0 +1,743 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.security.jarsigner;
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import sun.misc.IOUtils;
+import sun.security.util.ManifestDigester;
+import sun.security.util.SignatureFileVerifier;
+import sun.security.x509.AlgorithmId;
+import sun.security.tools.jarsigner.TimestampedSigner;
+
+import static jdk.security.jarsigner.JarSignerException.ErrorCode.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Vector;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * The class that actually signs a jar.
+ *
+ * This class is separated from Builder to make sure the sign() method here
+ * does not modify any internal status of a JarSigner. All fields in Builder
+ * are duplicated here as final.
+ */
+class Action {
+
+ private static final String META_INF = "META-INF/";
+
+ // Signer materials
+ private final PrivateKey privateKey;
+ private final X509Certificate[] certChain;
+
+ // Below are all option settings
+ // Common:
+ private final String[] digestalg;
+ private final String sigalg;
+ private final Provider digestProvider;
+ private final Provider sigProvider;
+ private final URI tsaUrl;
+ private final String signerName;
+ // Seldom:
+ private final String tSAPolicyID;
+ private final String tSADigestAlg;
+ private final X509Certificate tsaCert;
+ // Useless:
+ private final boolean signManifest; // "sign" the whole manifest
+ private final boolean externalSF; // leave the .SF out of the PKCS7 block
+ private final ContentSigner signingMechanism;
+
+ private final Builder.EventHandler handler;
+
+ public Action(Builder builder) {
+
+ this.privateKey = builder.privateKey;
+ this.certChain = builder.certChain;
+ this.digestalg = builder.digestalg;
+ this.digestProvider = builder.digestProvider;
+ this.sigalg = builder.sigalg;
+ this.sigProvider = builder.sigProvider;
+ this.tsaUrl = builder.tsaUrl;
+ this.signerName = builder.signerName;
+ this.tSAPolicyID = builder.tSAPolicyID;
+ this.tSADigestAlg = builder.tSADigestAlg;
+ this.tsaCert = builder.tsaCert;
+ this.signManifest = builder.signManifest;
+ this.externalSF = builder.externalSF;
+ this.signingMechanism = builder.signingMechanism;
+ this.handler = builder.handler;
+ }
+
+ public void sign(ZipFile zipFile, OutputStream os)
+ throws JarSignerException {
+ try {
+ sign0(zipFile, os);
+ } catch (SocketTimeoutException | CertificateException e) {
+ throw new JarSignerException(TSA_ERROR, e);
+ } catch (IOException ioe) {
+ throw new JarSignerException(IO_ERROR, ioe);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new JarSignerException(SIGNER_ERROR, e);
+ } catch (SignatureException se) {
+ throw new JarSignerException(SIGNING_ERROR, se);
+ }
+ }
+
+ private void sign0(ZipFile zipFile, OutputStream os)
+ throws IOException, CertificateException, NoSuchAlgorithmException,
+ SignatureException, InvalidKeyException {
+ String name;
+ if (signerName == null) {
+ name = "SIGNER";
+ } else {
+ name = signerName;
+ }
+
+ MessageDigest[] digests;
+ try {
+ if (digestalg == null) {
+ digests = new MessageDigest[]{
+ MessageDigest.getInstance(Builder.getDefaultDigestAlg())};
+ } else {
+ digests = new MessageDigest[digestalg.length];
+ for (int i = 0; i < digestalg.length; i++) {
+ if (digestProvider == null) {
+ digests[i] = MessageDigest.getInstance(digestalg[i]);
+ } else {
+ digests[i] = MessageDigest.getInstance(
+ digestalg[i], digestProvider);
+ }
+ }
+ }
+ } catch (NoSuchAlgorithmException asae) {
+ // Should not happen. User provided alg were checked, and default
+ // alg should always be available.
+ throw new AssertionError(asae);
+ }
+
+ PrintStream ps = new PrintStream(os);
+ ZipOutputStream zos = new ZipOutputStream(ps);
+
+ Manifest manifest = new Manifest();
+ Map<String, Attributes> mfEntries = manifest.getEntries();
+
+ // The Attributes of manifest before updating
+ Attributes oldAttr = null;
+
+ boolean mfModified = false;
+ boolean mfCreated = false;
+ byte[] mfRawBytes = null;
+
+ // Check if manifest exists
+ ZipEntry mfFile;
+ if ((mfFile = getManifestFile(zipFile)) != null) {
+ // Manifest exists. Read its raw bytes.
+ mfRawBytes = IOUtils.readFully(
+ zipFile.getInputStream(mfFile), -1, true);
+ manifest.read(new ByteArrayInputStream(mfRawBytes));
+ oldAttr = (Attributes) (manifest.getMainAttributes().clone());
+ } else {
+ // Create new manifest
+ Attributes mattr = manifest.getMainAttributes();
+ mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
+ "1.0");
+ String javaVendor = System.getProperty("java.vendor");
+ String jdkVersion = System.getProperty("java.version");
+ mattr.putValue("Created-By", jdkVersion + " (" + javaVendor
+ + ")");
+ mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+ mfCreated = true;
+ }
+
+ /*
+ * For each entry in jar
+ * (except for signature-related META-INF entries),
+ * do the following:
+ *
+ * - if entry is not contained in manifest, add it to manifest;
+ * - if entry is contained in manifest, calculate its hash and
+ * compare it with the one in the manifest; if they are
+ * different, replace the hash in the manifest with the newly
+ * generated one. (This may invalidate existing signatures!)
+ */
+ Vector<ZipEntry> mfFiles = new Vector<>();
+
+ boolean wasSigned = false;
+
+ for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
+ enum_.hasMoreElements(); ) {
+ ZipEntry ze = enum_.nextElement();
+
+ if (ze.getName().startsWith(META_INF)) {
+ // Store META-INF files in vector, so they can be written
+ // out first
+ mfFiles.addElement(ze);
+
+ if (SignatureFileVerifier.isBlockOrSF(
+ ze.getName().toUpperCase(Locale.ENGLISH))) {
+ wasSigned = true;
+ }
+
+ if (SignatureFileVerifier.isSigningRelated(ze.getName())) {
+ // ignore signature-related and manifest files
+ continue;
+ }
+ }
+
+ if (manifest.getAttributes(ze.getName()) != null) {
+ // jar entry is contained in manifest, check and
+ // possibly update its digest attributes
+ if (updateDigests(ze, zipFile, digests,
+ manifest) == true) {
+ mfModified = true;
+ }
+ } else if (!ze.isDirectory()) {
+ // Add entry to manifest
+ Attributes attrs = getDigestAttributes(ze, zipFile, digests);
+ mfEntries.put(ze.getName(), attrs);
+ mfModified = true;
+ }
+ }
+
+ // Recalculate the manifest raw bytes if necessary
+ if (mfModified) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ manifest.write(baos);
+ if (wasSigned) {
+ byte[] newBytes = baos.toByteArray();
+ if (mfRawBytes != null
+ && oldAttr.equals(manifest.getMainAttributes())) {
+
+ /*
+ * Note:
+ *
+ * The Attributes object is based on HashMap and can handle
+ * continuation columns. Therefore, even if the contents are
+ * not changed (in a Map view), the bytes that it write()
+ * may be different from the original bytes that it read()
+ * from. Since the signature on the main attributes is based
+ * on raw bytes, we must retain the exact bytes.
+ */
+
+ int newPos = findHeaderEnd(newBytes);
+ int oldPos = findHeaderEnd(mfRawBytes);
+
+ if (newPos == oldPos) {
+ System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
+ } else {
+ // cat oldHead newTail > newBytes
+ byte[] lastBytes = new byte[oldPos +
+ newBytes.length - newPos];
+ System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
+ System.arraycopy(newBytes, newPos, lastBytes, oldPos,
+ newBytes.length - newPos);
+ newBytes = lastBytes;
+ }
+ }
+ mfRawBytes = newBytes;
+ } else {
+ mfRawBytes = baos.toByteArray();
+ }
+ }
+
+ // Write out the manifest
+ if (mfModified) {
+ // manifest file has new length
+ mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+ }
+ if (mfCreated) {
+ handler.adding(mfFile.getName());
+ } else if (mfModified) {
+ handler.updating(mfFile.getName());
+ }
+
+ zos.putNextEntry(mfFile);
+ zos.write(mfRawBytes);
+
+ // Calculate SignatureFile (".SF") and SignatureBlockFile
+ ManifestDigester manDig = new ManifestDigester(mfRawBytes);
+ SignatureFile sf = new SignatureFile(digests, manifest, manDig,
+ name, signManifest);
+
+ byte[] block = null;
+
+ Signature signer;
+ if (sigalg == null) {
+ String alg = JarSigner.getDefaultSigAlg(privateKey.getAlgorithm());
+ if (alg == null) {
+ throw new NoSuchAlgorithmException(
+ "No signature alg for " + privateKey.getAlgorithm());
+ }
+ signer = Signature.getInstance(alg);
+ } else if (sigProvider == null ) {
+ signer = Signature.getInstance(sigalg);
+ } else {
+ signer = Signature.getInstance(sigalg, sigProvider);
+ }
+ signer.initSign(privateKey);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ sf.write(baos);
+
+ byte[] content = baos.toByteArray();
+
+ signer.update(content);
+ byte[] signature = signer.sign();
+ ContentSignerParameters params =
+ new JarSignerParameters(null, tsaUrl, tsaCert, tSAPolicyID,
+ tSADigestAlg, signature,
+ signer.getAlgorithm(), certChain, content, zipFile);
+ block = sf.generateBlock(params, externalSF, signingMechanism);
+
+ String sfFilename = sf.getMetaName();
+ String bkFilename = sf.getBlockName(privateKey);
+
+ ZipEntry sfFile = new ZipEntry(sfFilename);
+ ZipEntry bkFile = new ZipEntry(bkFilename);
+
+ long time = System.currentTimeMillis();
+ sfFile.setTime(time);
+ bkFile.setTime(time);
+
+ // signature file
+ zos.putNextEntry(sfFile);
+ sf.write(zos);
+
+ if (zipFile.getEntry(sfFilename) != null) {
+ handler.updating(sfFilename);
+ } else {
+ handler.adding(sfFilename);
+ }
+
+ // signature block file
+ zos.putNextEntry(bkFile);
+ zos.write(block);
+
+ if (zipFile.getEntry(bkFilename) != null) {
+ handler.updating(bkFilename);
+ } else {
+ handler.adding(bkFilename);
+ }
+
+ // Write out all other META-INF files that we stored in the
+ // vector
+ for (int i = 0; i < mfFiles.size(); i++) {
+ ZipEntry ze = mfFiles.elementAt(i);
+ if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
+ && !ze.getName().equalsIgnoreCase(sfFilename)
+ && !ze.getName().equalsIgnoreCase(bkFilename)) {
+ if (manifest.getAttributes(ze.getName()) != null) {
+ handler.signing(ze.getName());
+ } else if (!ze.isDirectory()) {
+ handler.adding(ze.getName());
+ }
+ writeEntry(zipFile, zos, ze);
+ }
+ }
+
+ // Write out all other files
+ for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
+ enum_.hasMoreElements(); ) {
+ ZipEntry ze = enum_.nextElement();
+
+ if (!ze.getName().startsWith(META_INF)) {
+ if (manifest.getAttributes(ze.getName()) != null) {
+ handler.signing(ze.getName());
+ } else {
+ handler.adding(ze.getName());
+ }
+ writeEntry(zipFile, zos, ze);
+ }
+ }
+ zipFile.close();
+ zos.close();
+ }
+
+ private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
+ throws IOException {
+ ZipEntry ze2 = new ZipEntry(ze.getName());
+ ze2.setMethod(ze.getMethod());
+ ze2.setTime(ze.getTime());
+ ze2.setComment(ze.getComment());
+ ze2.setExtra(ze.getExtra());
+ if (ze.getMethod() == ZipEntry.STORED) {
+ ze2.setSize(ze.getSize());
+ ze2.setCrc(ze.getCrc());
+ }
+ os.putNextEntry(ze2);
+ writeBytes(zf, ze, os);
+ }
+
+ private void writeBytes
+ (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
+ try (InputStream is = zf.getInputStream(ze)) {
+ is.transferTo(os);
+ }
+ }
+
+ private boolean updateDigests(ZipEntry ze, ZipFile zf,
+ MessageDigest[] digests,
+ Manifest mf) throws IOException {
+ boolean update = false;
+
+ Attributes attrs = mf.getAttributes(ze.getName());
+ String[] base64Digests = getDigests(ze, zf, digests);
+
+ for (int i = 0; i < digests.length; i++) {
+ // The entry name to be written into attrs
+ String name = null;
+ try {
+ // Find if the digest already exists. An algorithm could have
+ // different names. For example, last time it was SHA, and this
+ // time it's SHA-1.
+ AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
+ for (Object key : attrs.keySet()) {
+ if (key instanceof Attributes.Name) {
+ String n = ((Attributes.Name) key).toString();
+ if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
+ String tmp = n.substring(0, n.length() - 7);
+ if (AlgorithmId.get(tmp).equals(aid)) {
+ name = n;
+ break;
+ }
+ }
+ }
+ }
+ } catch (NoSuchAlgorithmException nsae) {
+ // Ignored. Writing new digest entry.
+ }
+
+ if (name == null) {
+ name = digests[i].getAlgorithm() + "-Digest";
+ attrs.putValue(name, base64Digests[i]);
+ update = true;
+ } else {
+ // compare digests, and replace the one in the manifest
+ // if they are different
+ String mfDigest = attrs.getValue(name);
+ if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
+ attrs.putValue(name, base64Digests[i]);
+ update = true;
+ }
+ }
+ }
+ return update;
+ }
+
+ private Attributes getDigestAttributes(
+ ZipEntry ze, ZipFile zf, MessageDigest[] digests)
+ throws IOException {
+
+ String[] base64Digests = getDigests(ze, zf, digests);
+ Attributes attrs = new Attributes();
+
+ for (int i = 0; i < digests.length; i++) {
+ attrs.putValue(digests[i].getAlgorithm() + "-Digest",
+ base64Digests[i]);
+ }
+ return attrs;
+ }
+
+ /*
+ * Returns manifest entry from given jar file, or null if given jar file
+ * does not have a manifest entry.
+ */
+ private ZipEntry getManifestFile(ZipFile zf) {
+ ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
+ if (ze == null) {
+ // Check all entries for matching name
+ Enumeration<? extends ZipEntry> enum_ = zf.entries();
+ while (enum_.hasMoreElements() && ze == null) {
+ ze = enum_.nextElement();
+ if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
+ (ze.getName())) {
+ ze = null;
+ }
+ }
+ }
+ return ze;
+ }
+
+ private String[] getDigests(
+ ZipEntry ze, ZipFile zf, MessageDigest[] digests)
+ throws IOException {
+
+ int n, i;
+ try (InputStream is = zf.getInputStream(ze)) {
+ long left = ze.getSize();
+ byte[] buffer = new byte[8192];
+ while ((left > 0)
+ && (n = is.read(buffer, 0, buffer.length)) != -1) {
+ for (i = 0; i < digests.length; i++) {
+ digests[i].update(buffer, 0, n);
+ }
+ left -= n;
+ }
+ }
+
+ // complete the digests
+ String[] base64Digests = new String[digests.length];
+ for (i = 0; i < digests.length; i++) {
+ base64Digests[i] = Base64.getEncoder()
+ .encodeToString(digests[i].digest());
+ }
+ return base64Digests;
+ }
+
+ @SuppressWarnings("fallthrough")
+ private int findHeaderEnd(byte[] bs) {
+ // Initial state true to deal with empty header
+ boolean newline = true; // just met a newline
+ int len = bs.length;
+ for (int i = 0; i < len; i++) {
+ switch (bs[i]) {
+ case '\r':
+ if (i < len - 1 && bs[i + 1] == '\n') i++;
+ // fallthrough
+ case '\n':
+ if (newline) return i + 1; //+1 to get length
+ newline = true;
+ break;
+ default:
+ newline = false;
+ }
+ }
+ // If header end is not found, it means the MANIFEST.MF has only
+ // the main attributes section and it does not end with 2 newlines.
+ // Returns the whole length so that it can be completely replaced.
+ return len;
+ }
+
+ static class SignatureFile {
+
+ /**
+ * SignatureFile
+ */
+ Manifest sf;
+
+ /**
+ * .SF base name
+ */
+ String baseName;
+
+ public SignatureFile(MessageDigest digests[],
+ Manifest mf,
+ ManifestDigester md,
+ String baseName,
+ boolean signManifest) {
+
+ this.baseName = baseName;
+
+ String version = System.getProperty("java.version");
+ String javaVendor = System.getProperty("java.vendor");
+
+ sf = new Manifest();
+ Attributes mattr = sf.getMainAttributes();
+
+ mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
+ mattr.putValue("Created-By", version + " (" + javaVendor + ")");
+
+ if (signManifest) {
+ for (int i = 0; i < digests.length; i++) {
+ mattr.putValue(digests[i].getAlgorithm() + "-Digest-Manifest",
+ Base64.getEncoder().encodeToString(md.manifestDigest(digests[i])));
+ }
+ }
+
+ // create digest of the manifest main attributes
+ ManifestDigester.Entry mde =
+ md.get(ManifestDigester.MF_MAIN_ATTRS, false);
+ if (mde != null) {
+ for (int i = 0; i < digests.length; i++) {
+ mattr.putValue(digests[i].getAlgorithm() +
+ "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
+ Base64.getEncoder().encodeToString(mde.digest(digests[i])));
+ }
+ } else {
+ throw new IllegalStateException
+ ("ManifestDigester failed to create " +
+ "Manifest-Main-Attribute entry");
+ }
+
+ // go through the manifest entries and create the digests
+ Map<String, Attributes> entries = sf.getEntries();
+ Iterator<Map.Entry<String, Attributes>> mit =
+ mf.getEntries().entrySet().iterator();
+ while (mit.hasNext()) {
+ Map.Entry<String, Attributes> e = mit.next();
+ String name = e.getKey();
+ mde = md.get(name, false);
+ if (mde != null) {
+ Attributes attr = new Attributes();
+ for (int i = 0; i < digests.length; i++) {
+ attr.putValue(digests[i].getAlgorithm() + "-Digest",
+ Base64.getEncoder().encodeToString(mde.digest(digests[i])));
+ }
+ entries.put(name, attr);
+ }
+ }
+ }
+
+ // Write .SF file
+ public void write(OutputStream out) throws IOException {
+ sf.write(out);
+ }
+
+ // get .SF file name
+ public String getMetaName() {
+ return "META-INF/" + baseName + ".SF";
+ }
+
+ // get .DSA (or .DSA, .EC) file name
+ public String getBlockName(PrivateKey privateKey) {
+ String keyAlgorithm = privateKey.getAlgorithm();
+ return "META-INF/" + baseName + "." + keyAlgorithm;
+ }
+
+ // Generates the PKCS#7 content of block file
+ public byte[] generateBlock(ContentSignerParameters params,
+ boolean externalSF,
+ ContentSigner signingMechanism)
+ throws NoSuchAlgorithmException, IOException, CertificateException {
+
+ if (signingMechanism == null) {
+ signingMechanism = new TimestampedSigner();
+ }
+ return signingMechanism.generateSignedData(
+ params, externalSF,
+ params.getTimestampingAuthority() != null
+ || params.getTimestampingAuthorityCertificate() != null);
+ }
+ }
+
+ class JarSignerParameters implements ContentSignerParameters {
+
+ private String[] args;
+ private URI tsa;
+ private X509Certificate tsaCertificate;
+ private byte[] signature;
+ private String signatureAlgorithm;
+ private X509Certificate[] signerCertificateChain;
+ private byte[] content;
+ private ZipFile source;
+ private String tSAPolicyID;
+ private String tSADigestAlg;
+
+ JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
+ String tSAPolicyID, String tSADigestAlg,
+ byte[] signature, String signatureAlgorithm,
+ X509Certificate[] signerCertificateChain, byte[] content,
+ ZipFile source) {
+
+ Objects.requireNonNull(signature);
+ Objects.requireNonNull(signatureAlgorithm);
+ Objects.requireNonNull(signerCertificateChain);
+
+ if (tSADigestAlg == null) {
+ tSADigestAlg = Builder.getDefaultDigestAlg();
+ }
+
+ this.args = args;
+ this.tsa = tsa;
+ this.tsaCertificate = tsaCertificate;
+ this.tSAPolicyID = tSAPolicyID;
+ this.tSADigestAlg = tSADigestAlg;
+ this.signature = signature;
+ this.signatureAlgorithm = signatureAlgorithm;
+ this.signerCertificateChain = signerCertificateChain;
+ this.content = content;
+ this.source = source;
+ }
+
+ public String[] getCommandLine() {
+ return args;
+ }
+
+ public URI getTimestampingAuthority() {
+ return tsa;
+ }
+
+ public X509Certificate getTimestampingAuthorityCertificate() {
+ return tsaCertificate;
+ }
+
+ public String getTSAPolicyID() {
+ return tSAPolicyID;
+ }
+
+ public String getTSADigestAlg() {
+ return tSADigestAlg;
+ }
+
+ public byte[] getSignature() {
+ return signature;
+ }
+
+ public String getSignatureAlgorithm() {
+ return signatureAlgorithm;
+ }
+
+ public X509Certificate[] getSignerCertificateChain() {
+ return signerCertificateChain;
+ }
+
+ public byte[] getContent() {
+ return content;
+ }
+
+ public ZipFile getSource() {
+ return source;
+ }
+ }
+}
diff --git a/src/jdk.jartool/share/classes/jdk/security/jarsigner/Builder.java b/src/jdk.jartool/share/classes/jdk/security/jarsigner/Builder.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/jdk/security/jarsigner/Builder.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.security.jarsigner;
+
+import com.sun.jarsigner.ContentSigner;
+import sun.security.x509.AlgorithmId;
+
+import java.net.URI;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Locale;
+
+/**
+ * A utility to build an Action object that contains the actual sign method.
+ *
+ * This class exposes more public methods than JarSigner so that the jarsigner
+ * tool can be based on it. Null check, array cloning, and other checks that
+ * are usually not necessary when called internally are implemented in
+ * JarSigner. Read each method carefully.
+ */
+class Builder {
+
+ // Signer materials
+ final PrivateKey privateKey;
+ final X509Certificate[] certChain;
+
+ // Below are all option settings
+ // Common:
+ String[] digestalg;
+ String sigalg;
+ Provider digestProvider;
+ Provider sigProvider;
+ URI tsaUrl;
+ String signerName;
+ // Seldom:
+ String tSAPolicyID;
+ String tSADigestAlg;
+ X509Certificate tsaCert;
+ // Useless:
+ boolean signManifest = true;
+ boolean externalSF = true;
+ ContentSigner signingMechanism;
+
+ EventHandler handler = new EventHandler() {};
+
+ interface EventHandler {
+ default public void updating(String s) {}
+ default public void adding(String s) {}
+ default public void signing(String s) {}
+ }
+
+ public Builder digestAlg(String alg, Provider provider)
+ throws NoSuchAlgorithmException {
+ // Check availability
+ if (provider == null) {
+ MessageDigest.getInstance(alg);
+ } else {
+ MessageDigest.getInstance(alg, provider);
+ }
+ digestalg = new String[]{alg};
+ digestProvider = provider;
+ return this;
+ }
+
+ public Builder sigAlg(String signatureAlgorithm, Provider provider)
+ throws NoSuchAlgorithmException, IllegalArgumentException {
+
+ // Check availability
+ if (provider == null) {
+ Signature.getInstance(signatureAlgorithm);
+ } else {
+ Signature.getInstance(signatureAlgorithm, provider);
+ }
+ AlgorithmId.checKeyAlgSigAlgkMatch(
+ privateKey.getAlgorithm(), signatureAlgorithm);
+ this.sigalg = signatureAlgorithm;
+ this.sigProvider = provider;
+ return this;
+ }
+
+ public Builder tsa(URI uri) {
+ this.tsaUrl = uri;
+ return this;
+ }
+
+ public Builder signerName(String name) {
+ if (name.isEmpty() || name.length() > 8) {
+ throw new IllegalArgumentException("Name too long");
+ }
+
+ name = name.toUpperCase(Locale.ENGLISH);
+
+ for (int j = 0; j < name.length(); j++) {
+ char c = name.charAt(j);
+ if (!
+ ((c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ (c == '-') ||
+ (c == '_'))) {
+ throw new IllegalArgumentException("Invalid characters in name");
+ }
+ }
+ this.signerName = name;
+ return this;
+ }
+
+ public static String getDefaultDigestAlg() {
+ return "SHA-256";
+ }
+
+ // Methods not exposed by JarSigner (yet)
+ public Builder tsaCert(X509Certificate tsaCert) {
+ this.tsaCert = tsaCert;
+ return this;
+ }
+
+ public Builder tsaPolicyId(String oid) {
+ this.tSAPolicyID = oid;
+ return this;
+ }
+
+ public Builder tsaDigestAlg(String alg) throws NoSuchAlgorithmException {
+ if (alg != null) {
+ MessageDigest.getInstance(alg);
+ }
+ this.tSADigestAlg = alg;
+ return this;
+ }
+
+ public Builder altsigner(com.sun.jarsigner.ContentSigner signer) {
+ signingMechanism = signer;
+ return this;
+ }
+
+ public Builder sectionsOnly(boolean v) {
+ signManifest = !v;
+ return this;
+ }
+
+ public Builder internalSF(boolean v) {
+ externalSF = !v;
+ return this;
+ }
+
+ public Builder setEventHandler(EventHandler a) {
+ this.handler = a == null ? new EventHandler() {}: a;
+ return this;
+ }
+
+ public String getDefaultTsaDigestAlg() {
+ return getDefaultDigestAlg();
+ }
+
+ // Constructor
+
+ public Builder(PrivateKey key, Certificate... certs) {
+ this.privateKey = key;
+ this.certChain = new X509Certificate[certs.length];
+ for (int i=0; i<certs.length; i++) {
+ certChain[i] = (X509Certificate)certs[i];
+ }
+ }
+
+ // Builder
+
+ public Action build() {
+ return new Action(this);
+ }
+}
diff --git a/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.security.jarsigner;
+
+import sun.security.x509.AlgorithmId;
+
+import java.io.OutputStream;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Objects;
+import java.util.zip.ZipFile;
+
+/**
+ * A utility class to sign a jar file.
+ * <p>
+ * A caller first creates a {@code JarSigner} object, then calls various
+ * mutators (methods except for {@link #sign}) to specify the options of
+ * signing, and finally calls {@link #sign} to sign a given file.
+ * <p>
+ * The {@link #sign} method does not change the internal state of a
+ * {@code JarSigner} object. Therefore it's safe to call multiple {@link #sign}
+ * methods on different jar files in parallel.
+ * <p>
+ * Unless otherwise stated, calling a method with a null argument will throw
+ * a {@link NullPointerException}.
+ * <p>
+ * Example:
+ * <pre>
+ * JarSigner signer = new JarSigner(key, certPath)
+ * .digestAlg("SHA-1")
+ * .sigAlg("SHA1withDSA");
+ * try (ZipFile in = new ZipFile(inputFile);
+ * FileOutputStream out = new FileOutputStream(outFile)) {
+ * signer.sign(in, out);
+ * }
+ * </pre>
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public final class JarSigner {
+
+ private final Builder builder;
+
+ /**
+ * Creates a {@code JarSigner} object with
+ * a {@link KeyStore.PrivateKeyEntry} object.
+ *
+ * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.
+ */
+ public JarSigner(KeyStore.PrivateKeyEntry entry) {
+ builder = new Builder(
+ entry.getPrivateKey(), entry.getCertificateChain());
+ }
+
+ /**
+ * Creates a {@code JarSigner} object with a private key and
+ * a certificate path.
+ *
+ * @param privateKey the private key of the signer.
+ * @param certPath the certificate path of the signer.
+ * @throws IllegalArgumentException if {@code certPath} is empty, or
+ * the {@code privateKey} algorithm does not match the algorithm
+ * of the {@code PublicKey} in the end entity certificate
+ * (the first certificate in {@code certPath}).
+ */
+ public JarSigner(PrivateKey privateKey, CertPath certPath) {
+ List<? extends Certificate> certs = certPath.getCertificates();
+ if (certs.isEmpty()) {
+ throw new IllegalArgumentException("certPath cannot be empty");
+ }
+ if (!privateKey.getAlgorithm().equals
+ (certs.get(0).getPublicKey().getAlgorithm())) {
+ throw new IllegalArgumentException
+ ("private key algorithm does not match " +
+ "algorithm of public key in end entity " +
+ "certificate (1st in certPath)");
+ }
+ builder = new Builder(
+ privateKey, certs.toArray(new Certificate[certs.size()]));
+ }
+
+ /**
+ * Sets the digest algorithm. If no digest algorithm is specified,
+ * the default algorithm returned by {@link #getDefaultDigestAlg}
+ * will be used.
+ *
+ * @param alg the digest algorithm.
+ * @return the {@code JarSigner} itself.
+ * @throws NoSuchAlgorithmException if {@code alg} is not available.
+ */
+ public JarSigner digestAlg(String alg) throws NoSuchAlgorithmException {
+ builder.digestAlg(Objects.requireNonNull(alg), null);
+ return this;
+ }
+
+ /**
+ * Sets the digest algorithm from the specified provider.
+ * If no digest algorithm is specified, the default algorithm
+ * returned by {@link #getDefaultDigestAlg} will be used.
+ *
+ * @param alg the digest algorithm.
+ * @param provider the provider.
+ * @return the {@code JarSigner} itself.
+ * @throws NoSuchAlgorithmException if {@code alg} is not available in
+ * the specified provider.
+ */
+ public JarSigner digestAlg(String alg, Provider provider)
+ throws NoSuchAlgorithmException {
+ builder.digestAlg(
+ Objects.requireNonNull(alg),
+ Objects.requireNonNull(provider));
+ return this;
+ }
+
+ /**
+ * Sets the signature algorithm. If no signature algorithm
+ * is specified, the default signature algorithm returned by
+ * {@link #getDefaultSigAlg} for the private key algorithm will be used.
+ *
+ * @param alg the signature algorithm.
+ * @return the {@code JarSigner} itself.
+ * @throws NoSuchAlgorithmException if {@code alg} is not available.
+ * @throws IllegalArgumentException if {@code alg} is not compatible with
+ * the algorithm of the signer's private key.
+ */
+ public JarSigner sigAlg(String alg) throws NoSuchAlgorithmException {
+ builder.sigAlg(Objects.requireNonNull(alg), null);
+ return this;
+ }
+
+ /**
+ * Sets the signature algorithm from the specified provider.
+ * If no signature algorithm is specified, the default signature algorithm
+ * returned by {@link #getDefaultSigAlg} for the private key algorithm
+ * will be used.
+ *
+ * @param alg the signature algorithm.
+ * @param provider the provider.
+ * @return the {@code JarSigner} itself.
+ * @throws NoSuchAlgorithmException if {@code alg} is not available in
+ * the specified provider.
+ * @throws IllegalArgumentException if {@code alg} is not compatible with
+ * the algorithm of the signer's private key.
+ */
+ public JarSigner sigAlg(String alg, Provider provider)
+ throws NoSuchAlgorithmException {
+ builder.sigAlg(
+ Objects.requireNonNull(alg),
+ Objects.requireNonNull(provider));
+ return this;
+ }
+
+ /**
+ * Sets the URI of the Time Stamping Authority (TSA).
+ *
+ * @param uri the URI.
+ * @return the {@code JarSigner} itself.
+ */
+ public JarSigner tsa(URI uri) {
+ builder.tsa(Objects.requireNonNull(uri));
+ return this;
+ }
+
+ /**
+ * Sets the signer name. The name will be used as the base name for
+ * the signature files. All lowercase characters will be converted to
+ * uppercase for signature file names. If a signer name is not
+ * specified, the string "SIGNER" will be used.
+ *
+ * @param name the signer name.
+ * @return the {@code JarSigner} itself.
+ * @throws IllegalArgumentException if {@code name} is empty or has a size
+ * bigger than 8, or it contains characters not from the set
+ * "a-zA-Z0-9_-".
+ */
+ public JarSigner signerName(String name) {
+ builder.signerName(Objects.requireNonNull(name));
+ return this;
+ }
+
+ /**
+ * Gets the default digest algorithm.
+ *
+ * @return the default digest algorithm.
+ */
+ public static String getDefaultDigestAlg() {
+ return Builder.getDefaultDigestAlg();
+ }
+
+ /**
+ * Gets the default signature algorithm for a private key algorithm.
+ * For example, SHA256withRSA for RSA, and SHA256withDSA for DSA.
+ *
+ * @param keyAlg the key algorithm.
+ * @return the default signature algorithm. Returns null if a default
+ * signature algorithm cannot be found. In this case, {@link #sigAlg}
+ * must be called to specify a signature algorithm. Otherwise,
+ * the {@link #sign} method will throw a {@link JarSignerException}
+ * with {@link JarSignerException.ErrorCode#SIGNER_ERROR SIGNER_ERROR}.
+ * @throws IllegalArgumentException if {@code keyAlg} is empty.
+ */
+ public static String getDefaultSigAlg(String keyAlg) {
+ if (keyAlg.isEmpty()) {
+ throw new IllegalArgumentException("key algorithm cannot be empty");
+ }
+ return AlgorithmId.getDefaultSigAlgForKeyAlg(keyAlg);
+ }
+
+ /**
+ * Signs a file into an {@link OutputStream}. This method will not close
+ * {@code file} or {@code os}.
+ *
+ * @param file the file to sign.
+ * @param os the output stream.
+ * @throws JarSignerException if the signing fails.
+ */
+ public void sign(ZipFile file, OutputStream os)
+ throws JarSignerException {
+ builder.build().sign(
+ Objects.requireNonNull(file),
+ Objects.requireNonNull(os));
+ }
+
+}
diff --git a/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java
new file mode 100644
--- /dev/null
+++ b/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.security.jarsigner;
+
+/**
+ * This exception is thrown when {@link JarSigner#sign} fails.
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public class JarSignerException extends Exception {
+
+ public static enum ErrorCode {
+ /** An I/O error occurs. */
+ IO_ERROR,
+ /** An invalid name is set. */
+ NAME_ERROR,
+ /** An unspecified error. */
+ OTHER_ERROR,
+ /** The signer's key or certificate chain is invalid. */
+ SIGNER_ERROR,
+ /** The signing process fails. */
+ SIGNING_ERROR,
+ /** The TSA signing process fails. */
+ TSA_ERROR,
+ }
+
+ private static final long serialVersionUID = 9L;
+ private final ErrorCode code;
+
+ /**
+ * Returns the error code.
+ *
+ * @return the error code.
+ */
+ public ErrorCode getCode() {
+ return code;
+ }
+
+ /**
+ * Creates a {@code JarSignerException} object.
+ *
+ * @param code the error code.
+ * @param cause the cause, if exists.
+ */
+ public JarSignerException(ErrorCode code, Throwable cause) {
+ this.code = code;
+ initCause(cause);
+ }
+}
+
diff --git a/test/sun/security/tools/jarsigner/API.java b/test/sun/security/tools/jarsigner/API.java
new file mode 100644
--- /dev/null
+++ b/test/sun/security/tools/jarsigner/API.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8056174
+ * @summary New APIs for jar signing
+ */
+
+import jdk.security.jarsigner.JarSigner;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class API {
+ public static void main(String[] args) throws Exception {
+
+ try (FileOutputStream fout =new FileOutputStream("src.zip");
+ ZipOutputStream zout = new ZipOutputStream(fout)) {
+ zout.putNextEntry(new ZipEntry("x"));
+ zout.write(new byte[10]);
+ zout.closeEntry();
+ }
+
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(new FileInputStream(
+ new File(System.getProperty("test.src"), "JarSigning.keystore")),
+ "bbbbbb".toCharArray());
+ PrivateKey key = (PrivateKey)ks.getKey("c", "bbbbbb".toCharArray());
+ Certificate cert = ks.getCertificate("c");
+ JarSigner js = new JarSigner(key,
+ CertificateFactory.getInstance("X.509").generateCertPath(
+ Collections.singletonList(cert)));
+
+ try {
+ js.digestAlg("FUNNY");
+ throw new Exception("Should have failed");
+ } catch (NoSuchAlgorithmException e) {
+ // Good
+ }
+
+ try {
+ js.sigAlg("SHA1withDSA");
+ throw new Exception("Should have failed");
+ } catch (IllegalArgumentException e) {
+ // Good
+ }
+
+ try {
+ js.sigAlg("FUNwithJOY");
+ throw new Exception("Should have failed");
+ } catch (NoSuchAlgorithmException e) {
+ // Good
+ }
+
+ js.digestAlg("SHA1");
+ js.sigAlg("SHA1withRSA");
+
+ try {
+ js.signerName("");
+ throw new Exception("Should have failed");
+ } catch (IllegalArgumentException e) {
+ // I knew that
+ }
+
+ try {
+ js.signerName("this_is_long");
+ throw new Exception("Should have failed");
+ } catch (IllegalArgumentException e) {
+ // I knew that
+ }
+
+ try {
+ js.signerName("illegal!");
+ throw new Exception("Should have failed");
+ } catch (IllegalArgumentException e) {
+ // I knew that
+ }
+
+ OutputStream blackHole = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ return;
+ }
+ };
+
+ try (ZipFile src = new ZipFile("src.zip")) {
+ js.sign(src, blackHole);
+ }
+
+
+ Provider p = new MyProvider();
+ js.digestAlg("Five", p);
+ js.sigAlg("SHA1WithRSA", p);
+ try (ZipFile src = new ZipFile("src.zip")) {
+ js.sign(src, blackHole);
+ }
+ }
+
+ public static class MyProvider extends Provider {
+ MyProvider() {
+ super("MY", 1.0d, null);
+ put("MessageDigest.Five", Five.class.getName());
+ put("Signature.SHA1WithRSA", SHA1WithRSA.class.getName());
+ }
+ }
+
+ public static class Five extends MessageDigest {
+ static final byte[] dig = {0x14, 0x02, (byte)0x84}; //base64 -> FAKE
+ public Five() { super("Five"); }
+ protected void engineUpdate(byte input) { }
+ protected void engineUpdate(byte[] input, int offset, int len) { }
+ protected byte[] engineDigest() { return dig; }
+ protected void engineReset() { }
+ }
+
+ public static class SHA1WithRSA extends Signature {
+ static final byte[] sig = "FAKEFAKE".getBytes();
+ public SHA1WithRSA() { super("SHA1WithRSA"); }
+ protected void engineInitVerify(PublicKey publicKey)
+ throws InvalidKeyException { }
+ protected void engineInitSign(PrivateKey privateKey)
+ throws InvalidKeyException { }
+ protected void engineUpdate(byte b) throws SignatureException { }
+ protected void engineUpdate(byte[] b, int off, int len)
+ throws SignatureException { }
+ protected byte[] engineSign() throws SignatureException { return sig; }
+ protected boolean engineVerify(byte[] sigBytes)
+ throws SignatureException {
+ return Arrays.equals(sigBytes, sig);
+ }
+ protected void engineSetParameter(String param, Object value)
+ throws InvalidParameterException { }
+ protected Object engineGetParameter(String param)
+ throws InvalidParameterException { return null; }
+ }
+}
diff --git a/test/sun/security/tools/jarsigner/options.sh b/test/sun/security/tools/jarsigner/options.sh
new file mode 100644
--- /dev/null
+++ b/test/sun/security/tools/jarsigner/options.sh
@@ -0,0 +1,74 @@
+#
+# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+# @test
+# @bug 8056174
+# @summary confirm behaviors for different sig names
+#
+# set a few environment variables so that the shell-script can run stand-alone
+# in the source directory
+if [ "${TESTSRC}" = "" ] ; then
+ TESTSRC="."
+fi
+
+if [ "${TESTJAVA}" = "" ] ; then
+ JAVA_CMD=`which java`
+ TESTJAVA=`dirname $JAVA_CMD`/..
+fi
+
+# set platform-dependent variables
+OS=`uname -s`
+case "$OS" in
+ Windows_* )
+ FS="\\"
+ ;;
+ * )
+ FS="/"
+ ;;
+esac
+
+KSFILE=options.jks
+
+KT="${TESTJAVA}${FS}bin${FS}keytool ${TESTTOOLVMOPTS} -keystore options.jks -storepass changeit -keypass changeit -keyalg rsa"
+JAR="${TESTJAVA}${FS}bin${FS}jar ${TESTTOOLVMOPTS}"
+JS="${TESTJAVA}${FS}bin${FS}jarsigner ${TESTTOOLVMOPTS} -keystore options.jks -storepass changeit"
+
+rm options.*
+
+$KT -genkeypair -alias "hello world" -dname CN=Me
+
+touch options.txt
+$JAR cvf options.jar options.txt
+
+# Special alias name will be normalized
+$JS options.jar "hello world" || exit 1
+$JAR tvf options.jar | grep HELLO_WO.SF || exit 2
+
+# Special -sigfile option will be rejected
+$JS options.jar "hello world" -sigfile "a b c" && exit 11
+
+# Long -sigfile option allowed but truncated (jarsigner.html does not say it's illegal)
+$JS options.jar "hello world" -sigfile 123456789 || exit 21
+$JAR tvf options.jar | grep 12345678.SF || exit 22
+
+exit 0