--- a/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java Thu Nov 19 12:57:59 2015 -0800
+++ b/jdk/src/java.base/share/classes/sun/security/x509/AlgorithmId.java Fri Nov 20 08:34:04 2015 +0800
@@ -977,4 +977,69 @@
}
return null;
}
+
+ /**
+ * Checks if a signature algorithm matches a key algorithm, i.e. a
+ * signature can be initialized with a key.
+ *
+ * @param kAlg must not be null
+ * @param sAlg must not be null
+ * @throws IllegalArgumentException if they do not match
+ */
+ public static void checkKeyAndSigAlgMatch(String kAlg, String sAlg) {
+ String sAlgUp = sAlg.toUpperCase(Locale.US);
+ if ((sAlgUp.endsWith("WITHRSA") && !kAlg.equalsIgnoreCase("RSA")) ||
+ (sAlgUp.endsWith("WITHECDSA") && !kAlg.equalsIgnoreCase("EC")) ||
+ (sAlgUp.endsWith("WITHDSA") && !kAlg.equalsIgnoreCase("DSA"))) {
+ throw new IllegalArgumentException(
+ "key algorithm not compatible with signature algorithm");
+ }
+ }
+
+ /**
+ * Returns the default signature algorithm for a private key. The digest
+ * part might evolve with time. Remember to update the spec of
+ * {@link jdk.security.jarsigner.JarSigner.Builder#getDefaultSignatureAlgorithm(PrivateKey)}
+ * if updated.
+ *
+ * @param k cannot be null
+ * @return the default alg, might be null if unsupported
+ */
+ public static String getDefaultSigAlgForKey(PrivateKey k) {
+ switch (k.getAlgorithm().toUpperCase()) {
+ case "EC":
+ return ecStrength(KeyUtil.getKeySize(k))
+ + "withECDSA";
+ case "DSA":
+ return ifcFfcStrength(KeyUtil.getKeySize(k))
+ + "withDSA";
+ case "RSA":
+ return ifcFfcStrength(KeyUtil.getKeySize(k))
+ + "withRSA";
+ default:
+ return null;
+ }
+ }
+
+ // Values from SP800-57 part 1 rev 3 tables 2 and three
+ private static String ecStrength (int bitLength) {
+ if (bitLength >= 512) { // 256 bits of strength
+ return "SHA512";
+ } else if (bitLength >= 384) { // 192 bits of strength
+ return "SHA384";
+ } else { // 128 bits of strength and less
+ return "SHA256";
+ }
+ }
+
+ // same values for RSA and DSA
+ private static String ifcFfcStrength (int bitLength) {
+ if (bitLength > 7680) { // 256 bits
+ return "SHA512";
+ } else if (bitLength > 3072) { // 192 bits
+ return "SHA384";
+ } else { // 128 bits and less
+ return "SHA256";
+ }
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,1286 @@
+/*
+ * 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.security.tools.PathList;
+import sun.security.tools.jarsigner.TimestampedSigner;
+import sun.security.util.ManifestDigester;
+import sun.security.util.SignatureFileVerifier;
+import sun.security.x509.AlgorithmId;
+
+import java.io.*;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.*;
+import java.security.cert.CertPath;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.function.BiConsumer;
+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.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * An immutable utility class to sign a jar file.
+ * <p>
+ * A caller creates a {@code JarSigner.Builder} object, (optionally) sets
+ * some parameters, and calls {@link JarSigner.Builder#build build} to create
+ * a {@code JarSigner} object. This {@code JarSigner} object can then
+ * be used to sign a jar file.
+ * <p>
+ * Unless otherwise stated, calling a method of {@code JarSigner} or
+ * {@code JarSigner.Builder} with a null argument will throw
+ * a {@link NullPointerException}.
+ * <p>
+ * Example:
+ * <pre>
+ * JarSigner signer = new JarSigner.Builder(key, certPath)
+ * .digestAlgorithm("SHA-1")
+ * .signatureAlgorithm("SHA1withDSA")
+ * .build();
+ * try (ZipFile in = new ZipFile(inputFile);
+ * FileOutputStream out = new FileOutputStream(outputFile)) {
+ * signer.sign(in, out);
+ * }
+ * </pre>
+ *
+ * @since 1.9
+ */
+@jdk.Exported
+public final class JarSigner {
+
+ /**
+ * A mutable builder class that can create an immutable {@code JarSigner}
+ * from various signing-related parameters.
+ *
+ * @since 1.9
+ */
+ @jdk.Exported
+ public static class Builder {
+
+ // Signer materials:
+ final PrivateKey privateKey;
+ final X509Certificate[] certChain;
+
+ // JarSigner options:
+ // Support multiple digestalg internally. Can be null, but not empty
+ String[] digestalg;
+ String sigalg;
+ // Precisely should be one provider for each digestalg, maybe later
+ Provider digestProvider;
+ Provider sigProvider;
+ URI tsaUrl;
+ String signerName;
+ BiConsumer<String,String> handler;
+
+ // Implementation-specific properties:
+ String tSAPolicyID;
+ String tSADigestAlg;
+ boolean signManifest = true;
+ boolean externalSF = true;
+ String altSignerPath;
+ String altSigner;
+
+ /**
+ * Creates a {@code JarSigner.Builder} object with
+ * a {@link KeyStore.PrivateKeyEntry} object.
+ *
+ * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.
+ */
+ public Builder(KeyStore.PrivateKeyEntry entry) {
+ this.privateKey = entry.getPrivateKey();
+ try {
+ // called internally, no need to clone
+ Certificate[] certs = entry.getCertificateChain();
+ this.certChain = Arrays.copyOf(certs, certs.length,
+ X509Certificate[].class);
+ } catch (ArrayStoreException ase) {
+ // Wrong type, not X509Certificate. Won't document.
+ throw new IllegalArgumentException(
+ "Entry does not contain X509Certificate");
+ }
+ }
+
+ /**
+ * Creates a {@code JarSigner.Builder} object with a private key and
+ * a certification path.
+ *
+ * @param privateKey the private key of the signer.
+ * @param certPath the certification 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 Builder(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 (the 1st in certPath)");
+ }
+ this.privateKey = privateKey;
+ try {
+ this.certChain = certs.toArray(new X509Certificate[certs.size()]);
+ } catch (ArrayStoreException ase) {
+ // Wrong type, not X509Certificate.
+ throw new IllegalArgumentException(
+ "Entry does not contain X509Certificate");
+ }
+ }
+
+ /**
+ * Sets the digest algorithm. If no digest algorithm is specified,
+ * the default algorithm returned by {@link #getDefaultDigestAlgorithm}
+ * will be used.
+ *
+ * @param algorithm the standard name of the algorithm. See
+ * the {@code MessageDigest} section in the <a href=
+ * "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
+ * Java Cryptography Architecture Standard Algorithm Name
+ * Documentation</a> for information about standard algorithm names.
+ * @return the {@code JarSigner.Builder} itself.
+ * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+ */
+ public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException {
+ MessageDigest.getInstance(Objects.requireNonNull(algorithm));
+ this.digestalg = new String[]{algorithm};
+ this.digestProvider = null;
+ return this;
+ }
+
+ /**
+ * Sets the digest algorithm from the specified provider.
+ * If no digest algorithm is specified, the default algorithm
+ * returned by {@link #getDefaultDigestAlgorithm} will be used.
+ *
+ * @param algorithm the standard name of the algorithm. See
+ * the {@code MessageDigest} section in the <a href=
+ * "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
+ * Java Cryptography Architecture Standard Algorithm Name
+ * Documentation</a> for information about standard algorithm names.
+ * @param provider the provider.
+ * @return the {@code JarSigner.Builder} itself.
+ * @throws NoSuchAlgorithmException if {@code algorithm} is not
+ * available in the specified provider.
+ */
+ public Builder digestAlgorithm(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
+ MessageDigest.getInstance(
+ Objects.requireNonNull(algorithm),
+ Objects.requireNonNull(provider));
+ this.digestalg = new String[]{algorithm};
+ this.digestProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the signature algorithm. If no signature algorithm
+ * is specified, the default signature algorithm returned by
+ * {@link #getDefaultSignatureAlgorithm} for the private key
+ * will be used.
+ *
+ * @param algorithm the standard name of the algorithm. See
+ * the {@code Signature} section in the <a href=
+ * "{@docRoot}/../technotes/guides/security/StandardNames.html#Signature">
+ * Java Cryptography Architecture Standard Algorithm Name
+ * Documentation</a> for information about standard algorithm names.
+ * @return the {@code JarSigner.Builder} itself.
+ * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
+ * @throws IllegalArgumentException if {@code algorithm} is not
+ * compatible with the algorithm of the signer's private key.
+ */
+ public Builder signatureAlgorithm(String algorithm)
+ throws NoSuchAlgorithmException {
+ // Check availability
+ Signature.getInstance(Objects.requireNonNull(algorithm));
+ AlgorithmId.checkKeyAndSigAlgMatch(
+ privateKey.getAlgorithm(), algorithm);
+ this.sigalg = algorithm;
+ this.sigProvider = null;
+ return this;
+ }
+
+ /**
+ * Sets the signature algorithm from the specified provider. If no
+ * signature algorithm is specified, the default signature algorithm
+ * returned by {@link #getDefaultSignatureAlgorithm} for the private
+ * key will be used.
+ *
+ * @param algorithm the standard name of the algorithm. See
+ * the {@code Signature} section in the <a href=
+ * "{@docRoot}/../technotes/guides/security/StandardNames.html#Signature">
+ * Java Cryptography Architecture Standard Algorithm Name
+ * Documentation</a> for information about standard algorithm names.
+ * @param provider the provider.
+ * @return the {@code JarSigner.Builder} itself.
+ * @throws NoSuchAlgorithmException if {@code algorithm} is not
+ * available in the specified provider.
+ * @throws IllegalArgumentException if {@code algorithm} is not
+ * compatible with the algorithm of the signer's private key.
+ */
+ public Builder signatureAlgorithm(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
+ // Check availability
+ Signature.getInstance(
+ Objects.requireNonNull(algorithm),
+ Objects.requireNonNull(provider));
+ AlgorithmId.checkKeyAndSigAlgMatch(
+ privateKey.getAlgorithm(), algorithm);
+ this.sigalg = algorithm;
+ this.sigProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the URI of the Time Stamping Authority (TSA).
+ *
+ * @param uri the URI.
+ * @return the {@code JarSigner.Builder} itself.
+ */
+ public Builder tsa(URI uri) {
+ this.tsaUrl = 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.Builder} 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 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;
+ }
+
+ /**
+ * Sets en event handler that will be triggered when a {@link JarEntry}
+ * is to be added, signed, or updated during the signing process.
+ * <p>
+ * The handler can be used to display signing progress. The first
+ * argument of the handler can be "adding", "signing", or "updating",
+ * and the second argument is the name of the {@link JarEntry}
+ * being processed.
+ *
+ * @param handler the event handler.
+ * @return the {@code JarSigner.Builder} itself.
+ */
+ public Builder eventHandler(BiConsumer<String,String> handler) {
+ this.handler = Objects.requireNonNull(handler);
+ return this;
+ }
+
+ /**
+ * Sets an additional implementation-specific property indicated by
+ * the specified key.
+ *
+ * @implNote This implementation supports the following properties:
+ * <ul>
+ * <li>"tsaDigestAlg": algorithm of digest data in the timestamping
+ * request. The default value is the same as the result of
+ * {@link #getDefaultDigestAlgorithm}.
+ * <li>"tsaPolicyId": TSAPolicyID for Timestamping Authority.
+ * No default value.
+ * <li>"internalsf": "true" if the .SF file is included inside the
+ * signature block, "false" otherwise. Default "false".
+ * <li>"sectionsonly": "true" if the .SF file only contains the hash
+ * value for each section of the manifest and not for the whole
+ * manifest, "false" otherwise. Default "false".
+ * </ul>
+ * All property names are case-insensitive.
+ *
+ * @param key the name of the property.
+ * @param value the value of the property.
+ * @return the {@code JarSigner.Builder} itself.
+ * @throws UnsupportedOperationException if the key is not supported
+ * by this implementation.
+ * @throws IllegalArgumentException if the value is not accepted as
+ * a legal value for this key.
+ */
+ public Builder setProperty(String key, String value) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(value);
+ switch (key.toLowerCase(Locale.US)) {
+ case "tsadigestalg":
+ try {
+ MessageDigest.getInstance(value);
+ } catch (NoSuchAlgorithmException nsae) {
+ throw new IllegalArgumentException(
+ "Invalid tsadigestalg", nsae);
+ }
+ this.tSADigestAlg = value;
+ break;
+ case "tsapolicyid":
+ this.tSAPolicyID = value;
+ break;
+ case "internalsf":
+ switch (value) {
+ case "true":
+ externalSF = false;
+ break;
+ case "false":
+ externalSF = true;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid internalsf value");
+ }
+ break;
+ case "sectionsonly":
+ switch (value) {
+ case "true":
+ signManifest = false;
+ break;
+ case "false":
+ signManifest = true;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid signManifest value");
+ }
+ break;
+ case "altsignerpath":
+ altSignerPath = value;
+ break;
+ case "altsigner":
+ altSigner = value;
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported key " + key);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the default digest algorithm.
+ *
+ * @implNote This implementation returns "SHA-256". The value may
+ * change in the future.
+ *
+ * @return the default digest algorithm.
+ */
+ public static String getDefaultDigestAlgorithm() {
+ return "SHA-256";
+ }
+
+ /**
+ * Gets the default signature algorithm for a private key.
+ * For example, SHA256withRSA for a 2048-bit RSA key, and
+ * SHA384withECDSA for a 384-bit EC key.
+ *
+ * @implNote This implementation makes use of comparable strengths
+ * as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.3.
+ * Specifically, if a DSA or RSA key with a key size greater than 7680
+ * bits, or an EC key with a key size greater than or equal to 512 bits,
+ * SHA-512 will be used as the hash function for the signature.
+ * If a DSA or RSA key has a key size greater than 3072 bits, or an
+ * EC key has a key size greater than or equal to 384 bits, SHA-384 will
+ * be used. Otherwise, SHA-256 will be used. The value may
+ * change in the future.
+ *
+ * @param key the private key.
+ * @return the default signature algorithm. Returns null if a default
+ * signature algorithm cannot be found. In this case,
+ * {@link #signatureAlgorithm} must be called to specify a
+ * signature algorithm. Otherwise, the {@link #build} method
+ * will throw an {@link IllegalArgumentException}.
+ */
+ public static String getDefaultSignatureAlgorithm(PrivateKey key) {
+ return AlgorithmId.getDefaultSigAlgForKey(Objects.requireNonNull(key));
+ }
+
+ /**
+ * Builds a {@code JarSigner} object from the parameters set by the
+ * setter methods.
+ * <p>
+ * This method does not modify internal state of this {@code Builder}
+ * object and can be called multiple times to generate multiple
+ * {@code JarSigner} objects. After this method is called, calling
+ * any method on this {@code Builder} will have no effect on
+ * the newly built {@code JarSigner} object.
+ *
+ * @return the {@code JarSigner} object.
+ * @throws IllegalArgumentException if a signature algorithm is not
+ * set and cannot be derived from the private key using the
+ * {@link #getDefaultSignatureAlgorithm} method.
+ */
+ public JarSigner build() {
+ return new JarSigner(this);
+ }
+ }
+
+ private static final String META_INF = "META-INF/";
+
+ // All fields in Builder are duplicated here as final. Those not
+ // provided but has a default value will be filled with default value.
+
+ // Precisely, a final array field can still be modified if only
+ // reference is copied, no clone is done because we are concerned about
+ // casual change instead of malicious attack.
+
+ // Signer materials:
+ private final PrivateKey privateKey;
+ private final X509Certificate[] certChain;
+
+ // JarSigner options:
+ private final String[] digestalg;
+ private final String sigalg;
+ private final Provider digestProvider;
+ private final Provider sigProvider;
+ private final URI tsaUrl;
+ private final String signerName;
+ private final BiConsumer<String,String> handler;
+
+ // Implementation-specific properties:
+ private final String tSAPolicyID;
+ private final String tSADigestAlg;
+ private final boolean signManifest; // "sign" the whole manifest
+ private final boolean externalSF; // leave the .SF out of the PKCS7 block
+ private final String altSignerPath;
+ private final String altSigner;
+
+ private JarSigner(JarSigner.Builder builder) {
+
+ this.privateKey = builder.privateKey;
+ this.certChain = builder.certChain;
+ if (builder.digestalg != null) {
+ // No need to clone because builder only accepts one alg now
+ this.digestalg = builder.digestalg;
+ } else {
+ this.digestalg = new String[] {
+ Builder.getDefaultDigestAlgorithm() };
+ }
+ this.digestProvider = builder.digestProvider;
+ if (builder.sigalg != null) {
+ this.sigalg = builder.sigalg;
+ } else {
+ this.sigalg = JarSigner.Builder
+ .getDefaultSignatureAlgorithm(privateKey);
+ if (this.sigalg == null) {
+ throw new IllegalArgumentException(
+ "No signature alg for " + privateKey.getAlgorithm());
+ }
+ }
+ this.sigProvider = builder.sigProvider;
+ this.tsaUrl = builder.tsaUrl;
+
+ if (builder.signerName == null) {
+ this.signerName = "SIGNER";
+ } else {
+ this.signerName = builder.signerName;
+ }
+ this.handler = builder.handler;
+
+ if (builder.tSADigestAlg != null) {
+ this.tSADigestAlg = builder.tSADigestAlg;
+ } else {
+ this.tSADigestAlg = Builder.getDefaultDigestAlgorithm();
+ }
+ this.tSAPolicyID = builder.tSAPolicyID;
+ this.signManifest = builder.signManifest;
+ this.externalSF = builder.externalSF;
+ this.altSigner = builder.altSigner;
+ this.altSignerPath = builder.altSignerPath;
+ }
+
+ /**
+ * 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) {
+ try {
+ sign0(Objects.requireNonNull(file),
+ Objects.requireNonNull(os));
+ } catch (SocketTimeoutException | CertificateException e) {
+ // CertificateException is thrown when the received cert from TSA
+ // has no id-kp-timeStamping in its Extended Key Usages extension.
+ throw new JarSignerException("Error applying timestamp", e);
+ } catch (IOException ioe) {
+ throw new JarSignerException("I/O error", ioe);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new JarSignerException("Error in signer materials", e);
+ } catch (SignatureException se) {
+ throw new JarSignerException("Error creating signature", se);
+ }
+ }
+
+ /**
+ * Returns the digest algorithm for this {@code JarSigner}.
+ * <p>
+ * The return value is never null.
+ *
+ * @return the digest algorithm.
+ */
+ public String getDigestAlgorithm() {
+ return digestalg[0];
+ }
+
+ /**
+ * Returns the signature algorithm for this {@code JarSigner}.
+ * <p>
+ * The return value is never null.
+ *
+ * @return the signature algorithm.
+ */
+ public String getSignatureAlgorithm() {
+ return sigalg;
+ }
+
+ /**
+ * Returns the URI of the Time Stamping Authority (TSA).
+ *
+ * @return the URI of the TSA.
+ */
+ public URI getTsa() {
+ return tsaUrl;
+ }
+
+ /**
+ * Returns the signer name of this {@code JarSigner}.
+ * <p>
+ * The return value is never null.
+ *
+ * @return the signer name.
+ */
+ public String getSignerName() {
+ return signerName;
+ }
+
+ /**
+ * Returns the value of an additional implementation-specific property
+ * indicated by the specified key. If a property is not set but has a
+ * default value, the default value will be returned.
+ *
+ * @implNote See {@link JarSigner.Builder#setProperty} for a list of
+ * properties this implementation supports. All property names are
+ * case-insensitive.
+ *
+ * @param key the name of the property.
+ * @return the value for the property.
+ * @throws UnsupportedOperationException if the key is not supported
+ * by this implementation.
+ */
+ public String getProperty(String key) {
+ Objects.requireNonNull(key);
+ switch (key.toLowerCase(Locale.US)) {
+ case "tsadigestalg":
+ return tSADigestAlg;
+ case "tsapolicyid":
+ return tSAPolicyID;
+ case "internalsf":
+ return Boolean.toString(!externalSF);
+ case "sectionsonly":
+ return Boolean.toString(!signManifest);
+ case "altsignerpath":
+ return altSignerPath;
+ case "altsigner":
+ return altSigner;
+ default:
+ throw new UnsupportedOperationException(
+ "Unsupported key " + key);
+ }
+ }
+
+ private void sign0(ZipFile zipFile, OutputStream os)
+ throws IOException, CertificateException, NoSuchAlgorithmException,
+ SignatureException, InvalidKeyException {
+ MessageDigest[] digests;
+ try {
+ 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 = zipFile.getInputStream(mfFile).readAllBytes();
+ 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)) {
+ 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 (handler != null) {
+ if (mfCreated) {
+ handler.accept("adding", mfFile.getName());
+ } else if (mfModified) {
+ handler.accept("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,
+ signerName, signManifest);
+
+ byte[] block;
+
+ Signature signer;
+ 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();
+
+ @SuppressWarnings("deprecation")
+ ContentSigner signingMechanism = null;
+ if (altSigner != null) {
+ signingMechanism = loadSigningMechanism(altSigner,
+ altSignerPath);
+ }
+
+ @SuppressWarnings("deprecation")
+ ContentSignerParameters params =
+ new JarSignerParameters(null, tsaUrl, 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 (handler != null) {
+ if (zipFile.getEntry(sfFilename) != null) {
+ handler.accept("updating", sfFilename);
+ } else {
+ handler.accept("adding", sfFilename);
+ }
+ }
+
+ // signature block file
+ zos.putNextEntry(bkFile);
+ zos.write(block);
+
+ if (handler != null) {
+ if (zipFile.getEntry(bkFilename) != null) {
+ handler.accept("updating", bkFilename);
+ } else {
+ handler.accept("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 (handler != null) {
+ if (manifest.getAttributes(ze.getName()) != null) {
+ handler.accept("signing", ze.getName());
+ } else if (!ze.isDirectory()) {
+ handler.accept("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 (handler != null) {
+ if (manifest.getAttributes(ze.getName()) != null) {
+ handler.accept("signing", ze.getName());
+ } else {
+ handler.accept("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 = 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;
+ }
+
+ /*
+ * Try to load the specified signing mechanism.
+ * The URL class loader is used.
+ */
+ @SuppressWarnings("deprecation")
+ private ContentSigner loadSigningMechanism(String signerClassName,
+ String signerClassPath) {
+
+ // construct class loader
+ String cpString; // make sure env.class.path defaults to dot
+
+ // do prepends to get correct ordering
+ cpString = PathList.appendPath(
+ System.getProperty("env.class.path"), null);
+ cpString = PathList.appendPath(
+ System.getProperty("java.class.path"), cpString);
+ cpString = PathList.appendPath(signerClassPath, cpString);
+ URL[] urls = PathList.pathToURLs(cpString);
+ ClassLoader appClassLoader = new URLClassLoader(urls);
+
+ try {
+ // attempt to find signer
+ Class<?> signerClass = appClassLoader.loadClass(signerClassName);
+ Object signer = signerClass.newInstance();
+ return (ContentSigner) signer;
+ } catch (ClassNotFoundException|InstantiationException|
+ IllegalAccessException|ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Invalid altSigner or altSignerPath", e);
+ }
+ }
+
+ 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 (MessageDigest digest: digests) {
+ mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest",
+ Base64.getEncoder().encodeToString(
+ md.manifestDigest(digest)));
+ }
+ }
+
+ // create digest of the manifest main attributes
+ ManifestDigester.Entry mde =
+ md.get(ManifestDigester.MF_MAIN_ATTRS, false);
+ if (mde != null) {
+ for (MessageDigest digest: digests) {
+ mattr.putValue(digest.getAlgorithm() +
+ "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
+ Base64.getEncoder().encodeToString(
+ mde.digest(digest)));
+ }
+ } 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();
+ for (String name: mf.getEntries().keySet()) {
+ mde = md.get(name, false);
+ if (mde != null) {
+ Attributes attr = new Attributes();
+ for (MessageDigest digest: digests) {
+ attr.putValue(digest.getAlgorithm() + "-Digest",
+ Base64.getEncoder().encodeToString(
+ mde.digest(digest)));
+ }
+ 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
+ @SuppressWarnings("deprecation")
+ 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);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ class JarSignerParameters implements ContentSignerParameters {
+
+ private String[] args;
+ private URI tsa;
+ 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,
+ String tSAPolicyID, String tSADigestAlg,
+ byte[] signature, String signatureAlgorithm,
+ X509Certificate[] signerCertificateChain,
+ byte[] content, ZipFile source) {
+
+ Objects.requireNonNull(signature);
+ Objects.requireNonNull(signatureAlgorithm);
+ Objects.requireNonNull(signerCertificateChain);
+
+ this.args = args;
+ this.tsa = tsa;
+ 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() {
+ // We don't use this param. Always provide tsaURI.
+ return null;
+ }
+
+ 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;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSignerException.java Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,56 @@
+/*
+ * 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 RuntimeException {
+
+ private static final long serialVersionUID = -4732217075689309530L;
+
+ /**
+ * Constructs a new {@code JarSignerException} with the specified detail
+ * message and cause.
+ * <p>
+ * Note that the detail message associated with
+ * {@code cause} is <i>not</i> automatically incorporated in
+ * this {@code JarSignerException}'s detail message.
+ *
+ * @param message the detail message (which is saved for later retrieval
+ * by the {@link #getMessage()} method).
+ * @param cause the cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). (A {@code null} value is permitted,
+ * and indicates that the cause is nonexistent or unknown.)
+ */
+ public JarSignerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
+
--- a/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java Thu Nov 19 12:57:59 2015 -0800
+++ b/jdk/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java Fri Nov 20 08:34:04 2015 +0800
@@ -29,22 +29,16 @@
import java.util.*;
import java.util.zip.*;
import java.util.jar.*;
-import java.math.BigInteger;
import java.net.URI;
-import java.net.URISyntaxException;
import java.text.Collator;
import java.text.MessageFormat;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.*;
-import java.lang.reflect.Constructor;
-import com.sun.jarsigner.ContentSigner;
-import com.sun.jarsigner.ContentSignerParameters;
import java.net.SocketTimeoutException;
import java.net.URL;
-import java.net.URLClassLoader;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertificateExpiredException;
@@ -53,11 +47,12 @@
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.util.Map.Entry;
+
+import jdk.security.jarsigner.JarSigner;
+import jdk.security.jarsigner.JarSignerException;
import sun.security.tools.KeyStoreUtil;
-import sun.security.tools.PathList;
import sun.security.x509.*;
import sun.security.util.*;
-import java.util.Base64;
/**
@@ -88,10 +83,6 @@
collator.setStrength(Collator.PRIMARY);
}
- private static final String META_INF = "META-INF/";
-
- private static final Class<?>[] PARAM_STRING = { String.class };
-
private static final String NONE = "NONE";
private static final String P11KEYSTORE = "PKCS11";
@@ -133,13 +124,13 @@
char[] keypass; // private key password
String sigfile; // name of .SF file
String sigalg; // name of signature algorithm
- String digestalg = "SHA-256"; // name of digest algorithm
+ String digestalg; // name of digest algorithm
String signedjar; // output filename
String tsaUrl; // location of the Timestamping Authority
String tsaAlias; // alias for the Timestamping Authority's certificate
String altCertChain; // file to read alternative cert chain from
String tSAPolicyID;
- String tSADigestAlg = "SHA-256";
+ String tSADigestAlg;
boolean verify = false; // verify the jar
String verbose = null; // verbose output when signing/verifying
boolean showcerts = false; // show certs when verifying
@@ -149,9 +140,6 @@
boolean strict = false; // treat warnings as error
// read zip entry raw bytes
- private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
- private byte[] buffer = new byte[8192];
- private ContentSigner signingMechanism = null;
private String altSignerClass = null;
private String altSignerClasspath = null;
private ZipFile zipFile = null;
@@ -216,6 +204,9 @@
if ((keystore != null) || (storepass != null)) {
System.out.println(rb.getString("jarsigner.error.") +
e.getMessage());
+ if (debug) {
+ e.printStackTrace();
+ }
System.exit(1);
}
}
@@ -229,12 +220,7 @@
loadKeyStore(keystore, true);
getAliasInfo(alias);
- // load the alternative signing mechanism
- if (altSignerClass != null) {
- signingMechanism = loadSigningMechanism(altSignerClass,
- altSignerClasspath);
- }
- signJar(jarfile, alias, args);
+ signJar(jarfile, alias);
}
} catch (Exception e) {
System.out.println(rb.getString("jarsigner.error.") + e);
@@ -626,8 +612,7 @@
InputStream is = null;
try {
is = jf.getInputStream(je);
- int n;
- while ((n = is.read(buffer, 0, buffer.length)) != -1) {
+ while (is.read(buffer, 0, buffer.length) != -1) {
// we just read. this will throw a SecurityException
// if a signature/digest check fails.
}
@@ -1035,7 +1020,6 @@
return cacheForInKS.get(signer);
}
- boolean found = false;
int result = 0;
List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
for (Certificate c : certs) {
@@ -1058,7 +1042,6 @@
}
if (alias != null) {
storeHash.put(c, "(" + alias + ")");
- found = true;
result |= IN_KEYSTORE;
}
}
@@ -1090,7 +1073,7 @@
return output;
}
- void signJar(String jarName, String alias, String[] args)
+ void signJar(String jarName, String alias)
throws Exception {
boolean aliasUsed = false;
X509Certificate tsaCert = null;
@@ -1110,17 +1093,17 @@
for (int j = 0; j < sigfile.length(); j++) {
char c = sigfile.charAt(j);
if (!
- ((c>= 'A' && c<= 'Z') ||
- (c>= '0' && c<= '9') ||
- (c == '-') ||
- (c == '_'))) {
+ ((c>= 'A' && c<= 'Z') ||
+ (c>= '0' && c<= '9') ||
+ (c == '-') ||
+ (c == '_'))) {
if (aliasUsed) {
// convert illegal characters from the alias to be _'s
c = '_';
} else {
- throw new
- RuntimeException(rb.getString
- ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
+ throw new
+ RuntimeException(rb.getString
+ ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
}
}
tmpSigFile.append(c);
@@ -1149,275 +1132,88 @@
error(rb.getString("unable.to.create.")+tmpJarName, ioe);
}
- PrintStream ps = new PrintStream(fos);
- ZipOutputStream zos = new ZipOutputStream(ps);
-
- /* First guess at what they might be - we don't xclude RSA ones. */
- String sfFilename = (META_INF + sigfile + ".SF").toUpperCase(Locale.ENGLISH);
- String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase(Locale.ENGLISH);
-
- 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;
-
- try {
- MessageDigest digests[] = { MessageDigest.getInstance(digestalg) };
+ CertPath cp = CertificateFactory.getInstance("X.509")
+ .generateCertPath(Arrays.asList(certChain));
+ JarSigner.Builder builder = new JarSigner.Builder(privateKey, cp);
- // Check if manifest exists
- ZipEntry mfFile;
- if ((mfFile = getManifestFile(zipFile)) != null) {
- // Manifest exists. Read its raw bytes.
- mfRawBytes = getBytes(zipFile, mfFile);
- 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;
- }
+ if (verbose != null) {
+ builder.eventHandler((action, file) -> {
+ System.out.println(rb.getString("." + action + ".") + file);
+ });
+ }
+
+ if (digestalg != null) {
+ builder.digestAlgorithm(digestalg);
+ }
+ if (sigalg != null) {
+ builder.signatureAlgorithm(sigalg);
+ }
- /*
- * 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<>();
+ URI tsaURI = null;
- 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 (tsaUrl != null) {
+ tsaURI = new URI(tsaUrl);
+ } else if (tsaAlias != null) {
+ tsaCert = getTsaCert(tsaAlias);
+ tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
+ }
- if (SignatureFileVerifier.isBlockOrSF(
- ze.getName().toUpperCase(Locale.ENGLISH))) {
- wasSigned = true;
- }
-
- if (signatureRelated(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;
+ if (tsaURI != null) {
+ if (verbose != null) {
+ System.out.println(
+ rb.getString("requesting.a.signature.timestamp"));
+ if (tsaUrl != null) {
+ System.out.println(rb.getString("TSA.location.") + tsaUrl);
+ } else if (tsaCert != null) {
+ System.out.println(rb.getString("TSA.certificate.") +
+ printCert("", tsaCert, false, null, false));
}
}
-
- // 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();
- }
+ builder.tsa(tsaURI);
+ if (tSADigestAlg != null) {
+ builder.setProperty("tsaDigestAlg", tSADigestAlg);
}
- // Write out the manifest
- if (mfModified) {
- // manifest file has new length
- mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
+ if (tSAPolicyID != null) {
+ builder.setProperty("tsaPolicyId", tSAPolicyID);
}
+ } else {
+ noTimestamp = true;
+ }
+
+ if (altSignerClass != null) {
+ builder.setProperty("altSigner", altSignerClass);
if (verbose != null) {
- if (mfCreated) {
- System.out.println(rb.getString(".adding.") +
- mfFile.getName());
- } else if (mfModified) {
- System.out.println(rb.getString(".updating.") +
- mfFile.getName());
- }
+ System.out.println(
+ rb.getString("using.an.alternative.signing.mechanism"));
}
- zos.putNextEntry(mfFile);
- zos.write(mfRawBytes);
+ }
- // Calculate SignatureFile (".SF") and SignatureBlockFile
- ManifestDigester manDig = new ManifestDigester(mfRawBytes);
- SignatureFile sf = new SignatureFile(digests, manifest, manDig,
- sigfile, signManifest);
+ if (altSignerClasspath != null) {
+ builder.setProperty("altSignerPath", altSignerClasspath);
+ }
- if (tsaAlias != null) {
- tsaCert = getTsaCert(tsaAlias);
- }
+ builder.signerName(sigfile);
- if (tsaUrl == null && tsaCert == null) {
- noTimestamp = true;
- }
-
- SignatureFile.Block block = null;
+ builder.setProperty("sectionsOnly", Boolean.toString(!signManifest));
+ builder.setProperty("internalSF", Boolean.toString(!externalSF));
- try {
- block =
- sf.generateBlock(privateKey, sigalg, certChain,
- externalSF, tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg,
- signingMechanism, args, zipFile);
- } catch (SocketTimeoutException e) {
+ try {
+ builder.build().sign(zipFile, fos);
+ } catch (JarSignerException e) {
+ Throwable cause = e.getCause();
+ if (cause != null && cause instanceof SocketTimeoutException) {
// Provide a helpful message when TSA is beyond a firewall
error(rb.getString("unable.to.sign.jar.") +
- rb.getString("no.response.from.the.Timestamping.Authority.") +
- "\n -J-Dhttp.proxyHost=<hostname>" +
- "\n -J-Dhttp.proxyPort=<portnumber>\n" +
- rb.getString("or") +
- "\n -J-Dhttps.proxyHost=<hostname> " +
- "\n -J-Dhttps.proxyPort=<portnumber> ", e);
- }
-
- sfFilename = sf.getMetaName();
- bkFilename = block.getMetaName();
-
- 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 (verbose != null) {
- if (zipFile.getEntry(sfFilename) != null) {
- System.out.println(rb.getString(".updating.") +
- sfFilename);
- } else {
- System.out.println(rb.getString(".adding.") +
- sfFilename);
- }
+ rb.getString("no.response.from.the.Timestamping.Authority.") +
+ "\n -J-Dhttp.proxyHost=<hostname>" +
+ "\n -J-Dhttp.proxyPort=<portnumber>\n" +
+ rb.getString("or") +
+ "\n -J-Dhttps.proxyHost=<hostname> " +
+ "\n -J-Dhttps.proxyPort=<portnumber> ", e);
+ } else {
+ error(rb.getString("unable.to.sign.jar.")+e.getCause(), e.getCause());
}
-
- if (verbose != null) {
- if (tsaUrl != null || tsaCert != null) {
- System.out.println(
- rb.getString("requesting.a.signature.timestamp"));
- }
- if (tsaUrl != null) {
- System.out.println(rb.getString("TSA.location.") + tsaUrl);
- }
- if (tsaCert != null) {
- URI tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
- if (tsaURI != null) {
- System.out.println(rb.getString("TSA.location.") +
- tsaURI);
- }
- System.out.println(rb.getString("TSA.certificate.") +
- printCert("", tsaCert, false, null, false));
- }
- if (signingMechanism != null) {
- System.out.println(
- rb.getString("using.an.alternative.signing.mechanism"));
- }
- }
-
- // signature block file
- zos.putNextEntry(bkFile);
- block.write(zos);
- if (verbose != null) {
- if (zipFile.getEntry(bkFilename) != null) {
- System.out.println(rb.getString(".updating.") +
- bkFilename);
- } else {
- System.out.println(rb.getString(".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)) {
- 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 (verbose != null) {
- if (manifest.getAttributes(ze.getName()) != null)
- System.out.println(rb.getString(".signing.") +
- ze.getName());
- else
- System.out.println(rb.getString(".adding.") +
- ze.getName());
- }
- writeEntry(zipFile, zos, ze);
- }
- }
- } catch(IOException ioe) {
- error(rb.getString("unable.to.sign.jar.")+ioe, ioe);
} finally {
// close the resouces
if (zipFile != null) {
@@ -1425,8 +1221,8 @@
zipFile = null;
}
- if (zos != null) {
- zos.close();
+ if (fos != null) {
+ fos.close();
}
}
@@ -1527,35 +1323,6 @@
}
/**
- * Find the length of header inside bs. The header is a multiple (>=0)
- * lines of attributes plus an empty line. The empty line is included
- * in the header.
- */
- @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;
- }
-
- /**
* signature-related files include:
* . META-INF/MANIFEST.MF
* . META-INF/SIG-*
@@ -1619,45 +1386,6 @@
return result;
}
- 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);
- }
-
- /**
- * Writes all the bytes for a given entry to the specified output stream.
- */
- private synchronized void writeBytes
- (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
- int n;
-
- InputStream is = null;
- try {
- is = zf.getInputStream(ze);
- long left = ze.getSize();
-
- while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
- os.write(buffer, 0, n);
- left -= n;
- }
- } finally {
- if (is != null) {
- is.close();
- }
- }
- }
-
void loadKeyStore(String keyStoreName, boolean prompt) {
if (!nullStream && keyStoreName == null) {
@@ -1958,15 +1686,13 @@
}
}
- void error(String message)
- {
+ void error(String message) {
System.out.println(rb.getString("jarsigner.")+message);
System.exit(1);
}
- void error(String message, Exception e)
- {
+ void error(String message, Throwable e) {
System.out.println(rb.getString("jarsigner.")+message);
if (debug) {
e.printStackTrace();
@@ -1990,8 +1716,7 @@
}
}
- char[] getPass(String prompt)
- {
+ char[] getPass(String prompt) {
System.err.print(prompt);
System.err.flush();
try {
@@ -2008,569 +1733,4 @@
// this shouldn't happen
return null;
}
-
- /*
- * Reads all the bytes for a given zip entry.
- */
- private synchronized byte[] getBytes(ZipFile zf,
- ZipEntry ze) throws IOException {
- int n;
-
- InputStream is = null;
- try {
- is = zf.getInputStream(ze);
- baos.reset();
- long left = ze.getSize();
-
- while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
- baos.write(buffer, 0, n);
- left -= n;
- }
- } finally {
- if (is != null) {
- is.close();
- }
- }
-
- return baos.toByteArray();
- }
-
- /*
- * 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;
- }
-
- /*
- * Computes the digests of a zip entry, and returns them as an array
- * of base64-encoded strings.
- */
- private synchronized String[] getDigests(ZipEntry ze, ZipFile zf,
- MessageDigest[] digests)
- throws IOException {
-
- int n, i;
- InputStream is = null;
- try {
- is = zf.getInputStream(ze);
- long left = ze.getSize();
- 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;
- }
- } finally {
- if (is != null) {
- is.close();
- }
- }
-
- // 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;
- }
-
- /*
- * Computes the digests of a zip entry, and returns them as a list of
- * attributes
- */
- 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;
- }
-
- /*
- * Updates the digest attributes of a manifest entry, by adding or
- * replacing digest values.
- * A digest value is added if the manifest entry does not contain a digest
- * for that particular algorithm.
- * A digest value is replaced if it is obsolete.
- *
- * Returns true if the manifest entry has been changed, and false
- * otherwise.
- */
- 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
- 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;
- }
-
- /*
- * Try to load the specified signing mechanism.
- * The URL class loader is used.
- */
- private ContentSigner loadSigningMechanism(String signerClassName,
- String signerClassPath) throws Exception {
-
- // construct class loader
- String cpString = null; // make sure env.class.path defaults to dot
-
- // do prepends to get correct ordering
- cpString = PathList.appendPath(System.getProperty("env.class.path"), cpString);
- cpString = PathList.appendPath(System.getProperty("java.class.path"), cpString);
- cpString = PathList.appendPath(signerClassPath, cpString);
- URL[] urls = PathList.pathToURLs(cpString);
- ClassLoader appClassLoader = new URLClassLoader(urls);
-
- // attempt to find signer
- Class<?> signerClass = appClassLoader.loadClass(signerClassName);
-
- // Check that it implements ContentSigner
- Object signer = signerClass.newInstance();
- if (!(signer instanceof ContentSigner)) {
- MessageFormat form = new MessageFormat(
- rb.getString("signerClass.is.not.a.signing.mechanism"));
- Object[] source = {signerClass.getName()};
- throw new IllegalArgumentException(form.format(source));
- }
- return (ContentSigner)signer;
- }
}
-
-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) {
- // sign the whole manifest
- 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);
- }
- }
- }
-
- /**
- * Writes the SignatureFile to the specified OutputStream.
- *
- * @param out the output stream
- * @exception IOException if an I/O error has occurred
- */
-
- public void write(OutputStream out) throws IOException
- {
- sf.write(out);
- }
-
- /**
- * get .SF file name
- */
- public String getMetaName()
- {
- return "META-INF/"+ baseName + ".SF";
- }
-
- /**
- * get base file name
- */
- public String getBaseName()
- {
- return baseName;
- }
-
- /*
- * Generate a signed data block.
- * If a URL or a certificate (containing a URL) for a Timestamping
- * Authority is supplied then a signature timestamp is generated and
- * inserted into the signed data block.
- *
- * @param sigalg signature algorithm to use, or null to use default
- * @param tsaUrl The location of the Timestamping Authority. If null
- * then no timestamp is requested.
- * @param tsaCert The certificate for the Timestamping Authority. If null
- * then no timestamp is requested.
- * @param signingMechanism The signing mechanism to use.
- * @param args The command-line arguments to jarsigner.
- * @param zipFile The original source Zip file.
- */
- @SuppressWarnings("deprecation")
- public Block generateBlock(PrivateKey privateKey,
- String sigalg,
- X509Certificate[] certChain,
- boolean externalSF, String tsaUrl,
- X509Certificate tsaCert,
- String tSAPolicyID,
- String tSADigestAlg,
- ContentSigner signingMechanism,
- String[] args, ZipFile zipFile)
- throws NoSuchAlgorithmException, InvalidKeyException, IOException,
- SignatureException, CertificateException
- {
- return new Block(this, privateKey, sigalg, certChain, externalSF,
- tsaUrl, tsaCert, tSAPolicyID, tSADigestAlg, signingMechanism, args, zipFile);
- }
-
-
- public static class Block {
-
- private byte[] block;
- private String blockFileName;
-
- /*
- * Construct a new signature block.
- */
- @SuppressWarnings("deprecation")
- Block(SignatureFile sfg, PrivateKey privateKey, String sigalg,
- X509Certificate[] certChain, boolean externalSF, String tsaUrl,
- X509Certificate tsaCert, String tSAPolicyID, String tSADigestAlg,
- ContentSigner signingMechanism, String[] args, ZipFile zipFile)
- throws NoSuchAlgorithmException, InvalidKeyException, IOException,
- SignatureException, CertificateException {
-
- Principal issuerName = certChain[0].getIssuerDN();
- if (!(issuerName instanceof X500Name)) {
- // must extract the original encoded form of DN for subsequent
- // name comparison checks (converting to a String and back to
- // an encoded DN could cause the types of String attribute
- // values to be changed)
- X509CertInfo tbsCert = new
- X509CertInfo(certChain[0].getTBSCertificate());
- issuerName = (Principal)
- tbsCert.get(X509CertInfo.ISSUER + "." +
- X509CertInfo.DN_NAME);
- }
- BigInteger serial = certChain[0].getSerialNumber();
-
- String signatureAlgorithm;
- String keyAlgorithm = privateKey.getAlgorithm();
- /*
- * If no signature algorithm was specified, we choose a
- * default that is compatible with the private key algorithm.
- */
- if (sigalg == null) {
-
- if (keyAlgorithm.equalsIgnoreCase("DSA"))
- signatureAlgorithm = "SHA256withDSA";
- else if (keyAlgorithm.equalsIgnoreCase("RSA"))
- signatureAlgorithm = "SHA256withRSA";
- else if (keyAlgorithm.equalsIgnoreCase("EC"))
- signatureAlgorithm = "SHA256withECDSA";
- else
- throw new RuntimeException("private key is not a DSA or "
- + "RSA key");
- } else {
- signatureAlgorithm = sigalg;
- }
-
- // check common invalid key/signature algorithm combinations
- String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.ENGLISH);
- if ((sigAlgUpperCase.endsWith("WITHRSA") &&
- !keyAlgorithm.equalsIgnoreCase("RSA")) ||
- (sigAlgUpperCase.endsWith("WITHECDSA") &&
- !keyAlgorithm.equalsIgnoreCase("EC")) ||
- (sigAlgUpperCase.endsWith("WITHDSA") &&
- !keyAlgorithm.equalsIgnoreCase("DSA"))) {
- throw new SignatureException
- ("private key algorithm is not compatible with signature algorithm");
- }
-
- blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm;
-
- AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm);
- AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm);
-
- Signature sig = Signature.getInstance(signatureAlgorithm);
- sig.initSign(privateKey);
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- sfg.write(baos);
-
- byte[] content = baos.toByteArray();
-
- sig.update(content);
- byte[] signature = sig.sign();
-
- // Timestamp the signature and generate the signature block file
- if (signingMechanism == null) {
- signingMechanism = new TimestampedSigner();
- }
- URI tsaUri = null;
- try {
- if (tsaUrl != null) {
- tsaUri = new URI(tsaUrl);
- }
- } catch (URISyntaxException e) {
- throw new IOException(e);
- }
-
- // Assemble parameters for the signing mechanism
- ContentSignerParameters params =
- new JarSignerParameters(args, tsaUri, tsaCert, tSAPolicyID,
- tSADigestAlg, signature,
- signatureAlgorithm, certChain, content, zipFile);
-
- // Generate the signature block
- block = signingMechanism.generateSignedData(
- params, externalSF, (tsaUrl != null || tsaCert != null));
- }
-
- /*
- * get block file name.
- */
- public String getMetaName()
- {
- return blockFileName;
- }
-
- /**
- * Writes the block file to the specified OutputStream.
- *
- * @param out the output stream
- * @exception IOException if an I/O error has occurred
- */
-
- public void write(OutputStream out) throws IOException
- {
- out.write(block);
- }
- }
-}
-
-
-/*
- * This object encapsulates the parameters used to perform content signing.
- */
-@SuppressWarnings("deprecation")
-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;
-
- /**
- * Create a new object.
- */
- JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
- String tSAPolicyID, String tSADigestAlg,
- byte[] signature, String signatureAlgorithm,
- X509Certificate[] signerCertificateChain, byte[] content,
- ZipFile source) {
-
- if (signature == null || signatureAlgorithm == null ||
- signerCertificateChain == null || tSADigestAlg == null) {
- throw new NullPointerException();
- }
- 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;
- }
-
- /**
- * Retrieves the command-line arguments.
- *
- * @return The command-line arguments. May be null.
- */
- public String[] getCommandLine() {
- return args;
- }
-
- /**
- * Retrieves the identifier for a Timestamping Authority (TSA).
- *
- * @return The TSA identifier. May be null.
- */
- public URI getTimestampingAuthority() {
- return tsa;
- }
-
- /**
- * Retrieves the certificate for a Timestamping Authority (TSA).
- *
- * @return The TSA certificate. May be null.
- */
- public X509Certificate getTimestampingAuthorityCertificate() {
- return tsaCertificate;
- }
-
- public String getTSAPolicyID() {
- return tSAPolicyID;
- }
-
- public String getTSADigestAlg() {
- return tSADigestAlg;
- }
-
- /**
- * Retrieves the signature.
- *
- * @return The non-null signature bytes.
- */
- public byte[] getSignature() {
- return signature;
- }
-
- /**
- * Retrieves the name of the signature algorithm.
- *
- * @return The non-null string name of the signature algorithm.
- */
- public String getSignatureAlgorithm() {
- return signatureAlgorithm;
- }
-
- /**
- * Retrieves the signer's X.509 certificate chain.
- *
- * @return The non-null array of X.509 public-key certificates.
- */
- public X509Certificate[] getSignerCertificateChain() {
- return signerCertificateChain;
- }
-
- /**
- * Retrieves the content that was signed.
- *
- * @return The content bytes. May be null.
- */
- public byte[] getContent() {
- return content;
- }
-
- /**
- * Retrieves the original source ZIP file before it was signed.
- *
- * @return The original ZIP file. May be null.
- */
- public ZipFile getSource() {
- return source;
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/security/jarsigner/Function.java Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,182 @@
+/*
+ * 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 test the functions of JarSigner API
+ * @modules java.base/sun.security.tools.keytool
+ * jdk.jartool
+ */
+
+import jdk.security.jarsigner.JarSigner;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+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.concurrent.atomic.AtomicInteger;
+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;
+
+public class Function {
+ 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();
+ }
+
+ sun.security.tools.keytool.Main.main(
+ ("-storetype jks -keystore ks -storepass changeit" +
+ " -keypass changeit -dname" +
+ " CN=RSA -alias r -genkeypair -keyalg rsa").split(" "));
+
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(new FileInputStream("ks"), "changeit".toCharArray());
+ PrivateKey key = (PrivateKey)ks.getKey("r", "changeit".toCharArray());
+ Certificate cert = ks.getCertificate("r");
+ JarSigner.Builder jsb = new JarSigner.Builder(key,
+ CertificateFactory.getInstance("X.509").generateCertPath(
+ Collections.singletonList(cert)));
+
+ jsb.digestAlgorithm("SHA1");
+ jsb.signatureAlgorithm("SHA1withRSA");
+
+ AtomicInteger counter = new AtomicInteger(0);
+ StringBuilder sb = new StringBuilder();
+ jsb.eventHandler(
+ (a, f)->{
+ counter.incrementAndGet();
+ sb.append(a).append(' ').append(f).append('\n');
+ });
+
+ OutputStream blackHole = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException { }
+ };
+
+ try (ZipFile src = new ZipFile("src.zip")) {
+ jsb.build().sign(src, blackHole);
+ }
+
+ if (counter.get() != 4) {
+ throw new Exception("Event number is " + counter.get()
+ + ":\n" + sb.toString());
+ }
+
+ // Provider test.
+ Provider p = new MyProvider();
+ jsb.digestAlgorithm("Five", p);
+ jsb.signatureAlgorithm("SHA1WithRSA", p);
+ try (ZipFile src = new ZipFile("src.zip");
+ FileOutputStream out = new FileOutputStream("out.jar")) {
+ jsb.build().sign(src, out);
+ }
+
+ try (JarFile signed = new JarFile("out.jar")) {
+ Manifest man = signed.getManifest();
+ assertTrue(man.getAttributes("x").getValue("Five-Digest").equals("FAKE"));
+
+ Manifest sf = new Manifest(signed.getInputStream(
+ signed.getJarEntry("META-INF/SIGNER.SF")));
+ assertTrue(sf.getMainAttributes().getValue("Five-Digest-Manifest")
+ .equals("FAKE"));
+ assertTrue(sf.getAttributes("x").getValue("Five-Digest").equals("FAKE"));
+
+ try (InputStream sig = signed.getInputStream(
+ signed.getJarEntry("META-INF/SIGNER.RSA"))) {
+ byte[] data = sig.readAllBytes();
+ assertTrue(Arrays.equals(
+ Arrays.copyOfRange(data, data.length-8, data.length),
+ "FAKEFAKE".getBytes()));
+ }
+ }
+ }
+
+ private static void assertTrue(boolean v) {
+ if (!v) {
+ throw new AssertionError();
+ }
+ }
+
+ public static class MyProvider extends Provider {
+ MyProvider() {
+ super("MY", 1.0d, null);
+ put("MessageDigest.Five", Five.class.getName());
+ put("Signature.SHA1WithRSA", SHA1WithRSA.class.getName());
+ }
+ }
+
+ // "Five" is a MessageDigest always returns the same value
+ 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() { }
+ }
+
+ // This fake "SHA1withRSA" is a Signature always returns the same value.
+ // An existing name must be used otherwise PKCS7 does not which OID to use.
+ 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; }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/security/jarsigner/Spec.java Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,251 @@
+/*
+ * 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 Make sure JarSigner impl conforms to spec
+ * @library /lib/testlibrary
+ * @modules java.base/sun.security.tools.keytool
+ * java.base/sun.security.provider.certpath
+ * jdk.jartool
+ */
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import jdk.security.jarsigner.JarSigner;
+import jdk.testlibrary.JarUtils;
+import sun.security.provider.certpath.X509CertPath;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+
+public class Spec {
+
+ public static void main(String[] args) throws Exception {
+
+ // Prepares raw file
+ Files.write(Paths.get("a"), "a".getBytes());
+
+ // Pack
+ JarUtils.createJar("a.jar", "a");
+
+ // Prepare a keystore
+ sun.security.tools.keytool.Main.main(
+ ("-keystore ks -storepass changeit -keypass changeit -dname" +
+ " CN=RSA -alias r -genkeypair -keyalg rsa").split(" "));
+ sun.security.tools.keytool.Main.main(
+ ("-keystore ks -storepass changeit -keypass changeit -dname" +
+ " CN=DSA -alias d -genkeypair -keyalg dsa").split(" "));
+
+ char[] pass = "changeit".toCharArray();
+
+ KeyStore ks = KeyStore.getInstance(
+ new File("ks"), pass);
+ PrivateKey pkr = (PrivateKey)ks.getKey("r", pass);
+ PrivateKey pkd = (PrivateKey)ks.getKey("d", pass);
+ CertPath cp = CertificateFactory.getInstance("X.509")
+ .generateCertPath(Arrays.asList(ks.getCertificateChain("r")));
+
+ Provider sun = Security.getProvider("SUN");
+
+ // throws
+ npe(()->new JarSigner.Builder(null));
+ npe(()->new JarSigner.Builder(null, cp));
+ iae(()->new JarSigner.Builder(
+ pkr, new X509CertPath(Collections.emptyList())));
+ iae(()->new JarSigner.Builder(pkd, cp)); // unmatched certs alg
+
+ JarSigner.Builder b1 = new JarSigner.Builder(pkr, cp);
+
+ npe(()->b1.digestAlgorithm(null));
+ nsae(()->b1.digestAlgorithm("HAHA"));
+ b1.digestAlgorithm("SHA-256");
+
+ npe(()->b1.digestAlgorithm("SHA-256", null));
+ npe(()->b1.digestAlgorithm(null, sun));
+ nsae(()->b1.digestAlgorithm("HAHA", sun));
+ b1.digestAlgorithm("SHA-256", sun);
+
+ npe(()->b1.signatureAlgorithm(null));
+ nsae(()->b1.signatureAlgorithm("HAHAwithHEHE"));
+ iae(()->b1.signatureAlgorithm("SHA256withECDSA"));
+
+ npe(()->b1.signatureAlgorithm(null, sun));
+ npe(()->b1.signatureAlgorithm("SHA256withRSA", null));
+ nsae(()->b1.signatureAlgorithm("HAHAwithHEHE", sun));
+ iae(()->b1.signatureAlgorithm("SHA256withDSA", sun));
+
+ npe(()->b1.tsa(null));
+
+ npe(()->b1.signerName(null));
+ iae(()->b1.signerName(""));
+ iae(()->b1.signerName("123456789"));
+ iae(()->b1.signerName("a+b"));
+
+ npe(()->b1.setProperty(null, ""));
+ uoe(()->b1.setProperty("what", ""));
+ npe(()->b1.setProperty("tsadigestalg", null));
+ iae(()->b1.setProperty("tsadigestalg", "HAHA"));
+ npe(()->b1.setProperty("tsapolicyid", null));
+ npe(()->b1.setProperty("internalsf", null));
+ iae(()->b1.setProperty("internalsf", "Hello"));
+ npe(()->b1.setProperty("sectionsonly", null));
+ iae(()->b1.setProperty("sectionsonly", "OK"));
+ npe(()->b1.setProperty("altsigner", null));
+ npe(()->b1.eventHandler(null));
+
+ // default values
+ JarSigner.Builder b2 = new JarSigner.Builder(pkr, cp);
+ JarSigner js2 = b2.build();
+
+ assertTrue(js2.getDigestAlgorithm().equals(
+ JarSigner.Builder.getDefaultDigestAlgorithm()));
+ assertTrue(js2.getSignatureAlgorithm().equals(
+ JarSigner.Builder.getDefaultSignatureAlgorithm(pkr)));
+ assertTrue(js2.getTsa() == null);
+ assertTrue(js2.getSignerName().equals("SIGNER"));
+ assertTrue(js2.getProperty("tsadigestalg").equals(
+ JarSigner.Builder.getDefaultDigestAlgorithm()));
+ assertTrue(js2.getProperty("tsapolicyid") == null);
+ assertTrue(js2.getProperty("internalsf").equals("false"));
+ assertTrue(js2.getProperty("sectionsonly").equals("false"));
+ assertTrue(js2.getProperty("altsigner") == null);
+ uoe(()->js2.getProperty("invalid"));
+
+ // default values
+ BiConsumer<String,String> myeh = (a,s)->{};
+ URI tsa = new URI("https://tsa.com");
+
+ JarSigner.Builder b3 = new JarSigner.Builder(pkr, cp)
+ .digestAlgorithm("SHA-1")
+ .signatureAlgorithm("SHA1withRSA")
+ .signerName("Duke")
+ .tsa(tsa)
+ .setProperty("tsadigestalg", "SHA-512")
+ .setProperty("tsapolicyid", "1.2.3.4")
+ .setProperty("internalsf", "true")
+ .setProperty("sectionsonly", "true")
+ .setProperty("altsigner", "MyContentSigner")
+ .eventHandler(myeh);
+ JarSigner js3 = b3.build();
+
+ assertTrue(js3.getDigestAlgorithm().equals("SHA-1"));
+ assertTrue(js3.getSignatureAlgorithm().equals("SHA1withRSA"));
+ assertTrue(js3.getTsa().equals(tsa));
+ assertTrue(js3.getSignerName().equals("DUKE"));
+ assertTrue(js3.getProperty("tsadigestalg").equals("SHA-512"));
+ assertTrue(js3.getProperty("tsapolicyid").equals("1.2.3.4"));
+ assertTrue(js3.getProperty("internalsf").equals("true"));
+ assertTrue(js3.getProperty("sectionsonly").equals("true"));
+ assertTrue(js3.getProperty("altsigner").equals("MyContentSigner"));
+ assertTrue(js3.getProperty("altsignerpath") == null);
+
+ assertTrue(JarSigner.Builder.getDefaultDigestAlgorithm().equals("SHA-256"));
+
+ // Calculating large DSA and RSA keys are too slow.
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(1024);
+ assertTrue(JarSigner.Builder
+ .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+ .equals("SHA256withRSA"));
+
+ kpg = KeyPairGenerator.getInstance("DSA");
+ kpg.initialize(1024);
+ assertTrue(JarSigner.Builder
+ .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+ .equals("SHA256withDSA"));
+
+ kpg = KeyPairGenerator.getInstance("EC");
+ kpg.initialize(192);
+ assertTrue(JarSigner.Builder
+ .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+ .equals("SHA256withECDSA"));
+ kpg.initialize(384);
+ assertTrue(JarSigner.Builder
+ .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+ .equals("SHA384withECDSA"));
+ kpg.initialize(571);
+ assertTrue(JarSigner.Builder
+ .getDefaultSignatureAlgorithm(kpg.generateKeyPair().getPrivate())
+ .equals("SHA512withECDSA"));
+ }
+
+ interface RunnableWithException {
+ void run() throws Exception;
+ }
+
+ static void uoe(RunnableWithException r) throws Exception {
+ checkException(r, UnsupportedOperationException.class);
+ }
+
+ static void nsae(RunnableWithException r) throws Exception {
+ checkException(r, NoSuchAlgorithmException.class);
+ }
+
+ static void npe(RunnableWithException r) throws Exception {
+ checkException(r, NullPointerException.class);
+ }
+
+ static void iae(RunnableWithException r) throws Exception {
+ checkException(r, IllegalArgumentException.class);
+ }
+
+ static void checkException(RunnableWithException r, Class ex)
+ throws Exception {
+ try {
+ r.run();
+ } catch (Exception e) {
+ if (ex.isAssignableFrom(e.getClass())) {
+ return;
+ }
+ throw e;
+ }
+ throw new Exception("No exception thrown");
+ }
+
+ static void assertTrue(boolean x) throws Exception {
+ if (!x) throw new Exception("Not true");
+ }
+
+ static class MyContentSigner extends ContentSigner {
+ @Override
+ public byte[] generateSignedData(
+ ContentSignerParameters parameters,
+ boolean omitContent,
+ boolean applyTimestamp) throws NoSuchAlgorithmException,
+ CertificateException, IOException {
+ return new byte[0];
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/jarsigner/Options.java Fri Nov 20 08:34:04 2015 +0800
@@ -0,0 +1,137 @@
+/*
+ * 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 Make sure the jarsigner tool still works after it's modified to
+ * be based on JarSigner API
+ * @library /lib/testlibrary
+ * @modules java.base/sun.security.tools.keytool
+ * jdk.jartool/sun.security.tools.jarsigner
+ * java.base/sun.security.pkcs
+ * java.base/sun.security.x509
+ */
+
+import com.sun.jarsigner.ContentSigner;
+import com.sun.jarsigner.ContentSignerParameters;
+import jdk.testlibrary.JarUtils;
+import sun.security.pkcs.PKCS7;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+public class Options {
+
+ public static void main(String[] args) throws Exception {
+
+ // Prepares raw file
+ Files.write(Paths.get("a"), "a".getBytes());
+
+ // Pack
+ JarUtils.createJar("a.jar", "a");
+
+ // Prepare a keystore
+ sun.security.tools.keytool.Main.main(
+ ("-keystore jks -storepass changeit -keypass changeit -dname" +
+ " CN=A -alias a -genkeypair -keyalg rsa").split(" "));
+
+ // -altsign
+ sun.security.tools.jarsigner.Main.main(
+ ("-debug -signedjar altsign.jar -keystore jks -storepass changeit" +
+ " -altsigner Options$X a.jar a").split(" "));
+
+ try (JarFile jf = new JarFile("altsign.jar")) {
+ JarEntry je = jf.getJarEntry("META-INF/A.RSA");
+ try (InputStream is = jf.getInputStream(je)) {
+ if (!Arrays.equals(is.readAllBytes(), "1234".getBytes())) {
+ throw new Exception("altsign go wrong");
+ }
+ }
+ }
+
+ // -sigfile, -digestalg, -sigalg, -internalsf, -sectionsonly
+ sun.security.tools.jarsigner.Main.main(
+ ("-debug -signedjar new.jar -keystore jks -storepass changeit" +
+ " -sigfile olala -digestalg SHA1 -sigalg SHA224withRSA" +
+ " -internalsf -sectionsonly a.jar a").split(" "));
+
+ try (JarFile jf = new JarFile("new.jar")) {
+ JarEntry je = jf.getJarEntry("META-INF/OLALA.SF");
+ Objects.requireNonNull(je); // check -sigfile
+ byte[] sf = null; // content of .SF
+ try (InputStream is = jf.getInputStream(je)) {
+ sf = is.readAllBytes(); // save for later comparison
+ Attributes attrs = new Manifest(new ByteArrayInputStream(sf))
+ .getMainAttributes();
+ // check -digestalg
+ if (!attrs.containsKey(new Attributes.Name(
+ "SHA1-Digest-Manifest-Main-Attributes"))) {
+ throw new Exception("digestalg incorrect");
+ }
+ // check -sectionsonly
+ if (attrs.containsKey(new Attributes.Name(
+ "SHA1-Digest-Manifest"))) {
+ throw new Exception("SF should not have file digest");
+ }
+ }
+
+ je = jf.getJarEntry("META-INF/OLALA.RSA");
+ try (InputStream is = jf.getInputStream(je)) {
+ PKCS7 p7 = new PKCS7(is.readAllBytes());
+ String alg = p7.getSignerInfos()[0]
+ .getDigestAlgorithmId().getName();
+ if (!alg.equals("SHA-224")) { // check -sigalg
+ throw new Exception("PKCS7 signing is using " + alg);
+ }
+ // check -internalsf
+ if (!Arrays.equals(sf, p7.getContentInfo().getData())) {
+ throw new Exception("SF not in RSA");
+ }
+ }
+
+ }
+
+ // TSA-related ones are checked in ts.sh
+ }
+
+ public static class X extends ContentSigner {
+ @Override
+ public byte[] generateSignedData(ContentSignerParameters parameters,
+ boolean omitContent, boolean applyTimestamp)
+ throws NoSuchAlgorithmException, CertificateException,
+ IOException {
+ return "1234".getBytes();
+ }
+ }
+}