diff options
author | Guilhem Lavaux <guilhem@kaffe.org> | 2006-06-06 20:30:07 +0000 |
---|---|---|
committer | Guilhem Lavaux <guilhem@kaffe.org> | 2006-06-06 20:30:07 +0000 |
commit | 56c5b96a2d3754736d13eebb73270fb8f925301d (patch) | |
tree | 8f13d21d9dadf9810d11284977ed98a0a5c41868 /tools/gnu/classpath/tools/keytool/Command.java | |
parent | 68d82e8b935385c52f40123538e6e5b0dad7dbc9 (diff) | |
download | classpath-56c5b96a2d3754736d13eebb73270fb8f925301d.tar.gz |
2006-06-06 Guilhem Lavaux <guilhem@kaffe.org>
* Merged HEAD as of 2006-05-08.
Diffstat (limited to 'tools/gnu/classpath/tools/keytool/Command.java')
-rw-r--r-- | tools/gnu/classpath/tools/keytool/Command.java | 1147 |
1 files changed, 1147 insertions, 0 deletions
diff --git a/tools/gnu/classpath/tools/keytool/Command.java b/tools/gnu/classpath/tools/keytool/Command.java new file mode 100644 index 000000000..a59614644 --- /dev/null +++ b/tools/gnu/classpath/tools/keytool/Command.java @@ -0,0 +1,1147 @@ +/* Command.java -- Abstract implementation of a keytool command handler + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.classpath.tools.keytool; + +import gnu.classpath.SystemProperties; +import gnu.classpath.tools.common.CallbackUtil; +import gnu.classpath.tools.common.ProviderUtil; +import gnu.classpath.tools.common.SecurityProviderInfo; +import gnu.java.security.OID; +import gnu.java.security.Registry; +import gnu.java.security.der.BitString; +import gnu.java.security.der.DER; +import gnu.java.security.der.DERReader; +import gnu.java.security.der.DERValue; +import gnu.java.security.der.DERWriter; +import gnu.java.security.hash.IMessageDigest; +import gnu.java.security.hash.MD5; +import gnu.java.security.hash.Sha160; +import gnu.java.security.util.Util; +import gnu.java.security.x509.X500DistinguishedName; +import gnu.javax.security.auth.callback.ConsoleCallbackHandler; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.Key; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.DSAKey; +import java.security.interfaces.RSAKey; +import java.util.ArrayList; +import java.util.Date; +import java.util.logging.Logger; +import java.util.prefs.Preferences; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +/** + * A base class of the keytool command to facilitate implementation of concrete + * keytool Handlers. + */ +abstract class Command +{ + // Fields and constants ----------------------------------------------------- + + private static final Logger log = Logger.getLogger(Command.class.getName()); + /** Default value for the ALIAS argument. */ + private static final String DEFAULT_ALIAS = "mykey"; //$NON-NLS-1$ + /** Default algorithm for key-pair generation. */ + private static final String DEFAULT_KEY_ALGORITHM = "DSA"; //$NON-NLS-1$ + /** Default DSA digital signature algorithm to use with DSA keys. */ + private static final String DSA_SIGNATURE_ALGORITHM = "SHA1withDSA"; //$NON-NLS-1$ + /** Default RSA digital signature algorithm to use with RSA keys. */ + private static final String RSA_SIGNATURE_ALGORITHM = "MD5withRSA"; //$NON-NLS-1$ + /** Default validity (in days) of newly generated certificates. */ + private static final int DEFAULT_VALIDITY = 90; + /** OID of SHA1withDSA signature algorithm as stated in RFC-2459. */ + protected static final OID SHA1_WITH_DSA = new OID("1.2.840.10040.4.3"); //$NON-NLS-1$ + /** OID of MD2withRSA signature algorithm as stated in RFC-2459. */ + private static final OID MD2_WITH_RSA = new OID("1.2.840.113549.1.1.2"); //$NON-NLS-1$ + /** OID of MD5withRSA signature algorithm as stated in RFC-2459. */ + private static final OID MD5_WITH_RSA = new OID("1.2.840.113549.1.1.4"); //$NON-NLS-1$ + /** OID of SHA1withRSA signature algorithm as stated in RFC-2459. */ + private static final OID SHA1_WITH_RSA = new OID("1.2.840.113549.1.1.5"); //$NON-NLS-1$ + /** Number of milliseconds in one day. */ + private static final long MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000L; + + /** The Alias to use. */ + protected String alias; + /** The password characters protecting a Key Entry. */ + protected char[] keyPasswordChars; + /** A security provider to add. */ + protected Provider provider; + /** The key store type. */ + protected String storeType; + /** The password characters protecting the key store. */ + protected char[] storePasswordChars; + /** The key store URL. */ + protected URL storeURL; + /** The input stream from the key store URL. */ + protected InputStream storeStream; + /** The key store instance to use. */ + protected KeyStore store; + /** The output stream the concrete handler will use. */ + protected OutputStream outStream; + /** Whether we are printing to System.out. */ + protected boolean systemOut; + /** The key-pair generation algorithm instance to use. */ + protected KeyPairGenerator keyPairGenerator; + /** The digital signature algorithm instance to use. */ + protected Signature signatureAlgorithm; + /** Validity period, in number of days, to use when generating certificates. */ + protected int validityInDays; + /** The input stream the concrete handler will use. */ + protected InputStream inStream; + /** Whether verbose output is required or not. */ + protected boolean verbose; + + /** MD5 hash to use when generating certificate fingerprints. */ + private IMessageDigest md5 = new MD5(); + /** SHA1 hash to use when generating certificate fingerprints. */ + private IMessageDigest sha = new Sha160(); + /** The new position of a user-defined provider if it is not already installed. */ + private int providerNdx = -2; + /** The callback handler to use when needing to interact with user. */ + private CallbackHandler handler; + + // Constructor(s) ----------------------------------------------------------- + + // default 0-arguments constructor + + // Methods ------------------------------------------------------------------ + + /** + * A public method to allow using any keytool command handler programmatically + * by using a JavaBeans style of parameter(s) initialization. The user is + * assumed to have set individually the required options through their + * respective setters before invoking this method. + * <p> + * If an exception is encountered during the processing of the command, this + * implementation attempts to release any resources that may have been + * allocated at the time the exception occurs, before re-throwing that + * exception. + * + * @throws Exception if an exception occurs during the processing of this + * command. For a more comprehensive list of exceptions that may + * occur, see the documentation of the {@link #setup()} and + * {@link #start()} methods. + */ + public void doCommand() throws Exception + { + try + { + setup(); + start(); + } + finally + { + teardown(); + } + } + + /** + * @param flag whether to use, or not, more verbose output while processing + * the command. + */ + public void setVerbose(String flag) + { + this.verbose = Boolean.valueOf(flag).booleanValue(); + } + + // life-cycle methods ------------------------------------------------------- + + /** + * Given a potential sub-array of options for this concrete handler, starting + * at position <code>startIndex + 1</code>, potentially followed by other + * commands and their options, this method sets up this concrete command + * handler with its own options and returns the index of the first unprocessed + * argument in the array. + * <p> + * The general contract of this method is that it is invoked with the + * <code>startIndex</code> argument pointing to the keyword argument that + * uniquelly identifies the command itself; e.g. <code>-genkey</code> or + * <code>-list</code>, etc... + * + * @param args an array of options for this handler and possibly other + * commands and their options. + * @param startIndex the index of the first argument in <code>args</code> to + * process. + * @return the index of the first unprocessed argument in <code>args</code>. + */ + abstract int processArgs(String[] args, int startIndex); + + /** + * Initialize this concrete command handler for later invocation of the + * {@link #start()} or {@link #doCommand()} methods. + * <p> + * Handlers usually initialize their local variables and resources within the + * scope of this call. + * + * @throws IOException if an I/O related exception, such as opening an input + * stream, occurs during the execution of this method. + * @throws UnsupportedCallbackException if a requested callback handler + * implementation was not found, or was found but encountered an + * exception during its processing. + * @throws ClassNotFoundException if a designated security provider class was + * not found. + * @throws IllegalAccessException no 0-arguments constructor for the + * designated security provider class was found. + * @throws InstantiationException the designated security provider class is + * not instantiable. + * @throws KeyStoreException if an exception occurs during the instantiation + * of the KeyStore. + * @throws CertificateException if a certificate related exception, such as + * expiry, occurs during the loading of the KeyStore. + * @throws NoSuchAlgorithmException if no current security provider can + * provide a needed algorithm referenced by the KeyStore or one of + * its Key Entries or Certificates. + */ + abstract void setup() throws Exception; + + /** + * Do the real work this handler is supposed to do. + * <p> + * The code in this (abstract) class throws a <i>Not implemented yet</i> + * runtime exception. Concrete implementations MUST override this method. + * + * @throws CertificateException If no concrete implementation was found for a + * certificate Factory of a designated type. In this tool, the type + * is usually X.509 v1. + * @throws KeyStoreException if a keys-store related exception occurs; e.g. + * the key store has not been initialized. + * @throws IOException if an I/O related exception occurs during the process. + * @throws SignatureException if a digital signature related exception occurs. + * @throws InvalidKeyException if the genereated keys are invalid. + * @throws UnrecoverableKeyException if the password used to unlock a key in + * the key store was invalid. + * @throws NoSuchAlgorithmException if a concrete implementation of an + * algorithm used to store a Key Entry was not found at runtime. + * @throws UnsupportedCallbackException if a requested callback handler + * implementation was not found, or was found but encountered an + * exception during its processing. + */ + void start() throws Exception + { + throw new RuntimeException("Not implemented yet"); //$NON-NLS-1$ + } + + /** + * Tear down the handler, releasing any resources which may have been + * allocated at setup time. + */ + void teardown() + { + log.entering(this.getClass().getName(), "teardown"); //$NON-NLS-1$ + + if (storeStream != null) + try + { + storeStream.close(); + } + catch (IOException ignored) + { + log.fine("Exception while closing key store URL stream. Ignored: " //$NON-NLS-1$ + + ignored); + } + + if (outStream != null) + { + try + { + outStream.flush(); + } + catch (IOException ignored) + { + } + + if (! systemOut) + try + { + outStream.close(); + } + catch (IOException ignored) + { + } + } + + if (inStream != null) + try + { + inStream.close(); + } + catch (IOException ignored) + { + } + + if (providerNdx > 0) + ProviderUtil.removeProvider(provider.getName()); + + log.exiting(this.getClass().getName(), "teardown"); //$NON-NLS-1$ + } + + // parameter setup and validation methods ----------------------------------- + + /** + * Convenience method to setup the key store given its type, its password, its + * location and portentially a specialized security provider. + * + * @param className the potentially null fully qualified class name of a + * security provider to add at runtime, if no installed provider is + * able to provide a key store implementation of the desired type. + * @param type the potentially null type of the key store to request from the + * key store factory. + * @param password the potentially null password protecting the key store. + * @param url the URL of the key store. + */ + protected void setKeyStoreParams(String className, String type, + String password, String url) + throws IOException, UnsupportedCallbackException, KeyStoreException, + NoSuchAlgorithmException, CertificateException + { + setProviderClassNameParam(className); + setKeystoreTypeParam(type); + setKeystorePasswordParam(password); + setKeystoreURLParam(url); + } + + /** + * Set a security provider class name to (install and) use for key store + * related operations. + * + * @param className the possibly null, fully qualified class name of a + * security provider to add, if it is not already installed, to the + * set of available providers. + */ + protected void setProviderClassNameParam(String className) + { + log.finest("setProviderClassNameParam(" + className + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + if (className != null && className.trim().length() > 0) + { + className = className.trim(); + SecurityProviderInfo spi = ProviderUtil.addProvider(className); + provider = spi.getProvider(); + if (provider == null) + log.fine("Was unable to add provider from class " + className); + + providerNdx = spi.getPosition(); + } + } + + /** + * Set the type of key store to initialize, load and use. + * + * @param type the possibly null type of the key store. if this argument is + * <code>null</code>, or is an empty string, then this method sets + * the type of the key store to be the default value returned from + * the invocation of the {@link KeyStore#getDefaultType()} method. + * For GNU Classpath this is <i>gkr</i> which stands for the "Gnu + * KeyRing" specifications. + */ + protected void setKeystoreTypeParam(String type) + { + log.finest("setKeystoreTypeParam(" + type + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + if (type == null || type.trim().length() == 0) + storeType = KeyStore.getDefaultType(); + else + storeType = type.trim(); + } + + /** + * Set the key password given a command line option argument. If no value was + * present on the command line then prompt the user to provide one. + * + * @param password a possibly null key password gleaned from the command line. + * @throws IOException if an I/O related exception occurs. + * @throws UnsupportedCallbackException if no concrete implementation of a + * password callback was found at runtime. + */ + protected void setKeyPasswordParam(String password) throws IOException, + UnsupportedCallbackException + { + setKeyPasswordNoPrompt(password); + if (keyPasswordChars == null) + setKeyPasswordParam(); + } + + /** + * Set the Alias to use when associating Key Entries and Trusted Certificates + * in the current key store. + * + * @param name the possibly null alias to use. If this arfument is + * <code>null</code>, then a default value of <code>mykey</code> + * will be used instead. + */ + protected void setAliasParam(String name) + { + alias = name == null ? DEFAULT_ALIAS : name.trim(); + } + + /** + * Set the key password given a command line option argument. + * + * @param password a possibly null key password gleaned from the command line. + */ + protected void setKeyPasswordNoPrompt(String password) + { + if (password != null) + keyPasswordChars = password.toCharArray(); + } + + /** + * Prompt the user to provide a password to protect a Key Entry in the key + * store. + * + * @throws IOException if an I/O related exception occurs. + * @throws UnsupportedCallbackException if no concrete implementation of a + * password callback was found at runtime. + * @throws SecurityException if no password is available, even after prompting + * the user. + */ + protected void setKeyPasswordParam() throws IOException, + UnsupportedCallbackException + { + String prompt = Messages.getFormattedString("Command.21", alias); //$NON-NLS-1$ + PasswordCallback pcb = new PasswordCallback(prompt, false); + getCallbackHandler().handle(new Callback[] { pcb }); + keyPasswordChars = pcb.getPassword(); + pcb.clearPassword(); + if (keyPasswordChars == null) + throw new SecurityException(Messages.getString("Command.23")); //$NON-NLS-1$ + } + + protected void setKeystorePasswordParam(String password) throws IOException, + UnsupportedCallbackException + { + if (password != null) + storePasswordChars = password.toCharArray(); + else // ask the user to provide one + { + String prompt = Messages.getString("Command.24"); //$NON-NLS-1$ + PasswordCallback pcb = new PasswordCallback(prompt, false); + getCallbackHandler().handle(new Callback[] { pcb }); + storePasswordChars = pcb.getPassword(); + pcb.clearPassword(); + } + log.finest("storePasswordChars = [" + String.valueOf(storePasswordChars)+ "]"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Set the key store URL to use. + * + * @param url + * @throws IOException + * @throws KeyStoreException + * @throws UnsupportedCallbackException + * @throws NoSuchAlgorithmException + * @throws CertificateException + */ + protected void setKeystoreURLParam(String url) throws IOException, + KeyStoreException, UnsupportedCallbackException, NoSuchAlgorithmException, + CertificateException + { + log.finest("setKeystoreURLParam(" + url + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + if (url == null || url.trim().length() == 0) + { + String userHome = SystemProperties.getProperty("user.home"); //$NON-NLS-1$ + if (userHome == null || userHome.trim().length() == 0) + throw new InvalidParameterException(Messages.getString("Command.36")); //$NON-NLS-1$ + + url = userHome.trim() + "/.keystore"; //$NON-NLS-1$ + // if it does not exist create it + new File(url).createNewFile(); + url = "file:" + url; //$NON-NLS-1$ + } + else + { + url = url.trim(); + if (url.indexOf(":") == -1) // if it does not exist create it //$NON-NLS-1$ + new File(url).createNewFile(); + + url = "file:" + url; //$NON-NLS-1$ + } + + boolean newKeyStore = false; + storeURL = new URL(url); + storeStream = storeURL.openStream(); + if (storeStream.available() == 0) + { + log.fine("Store is empty. Will use <null> when loading, to create it"); //$NON-NLS-1$ + newKeyStore = true; + } + + try + { + store = KeyStore.getInstance(storeType); + } + catch (KeyStoreException x) + { + if (provider != null) + throw x; + + log.fine("Exception while getting key store with default provider(s)." //$NON-NLS-1$ + + " Will prompt user for another provider and continue"); //$NON-NLS-1$ + String prompt = Messages.getString("Command.40"); //$NON-NLS-1$ + NameCallback ncb = new NameCallback(prompt); + getCallbackHandler().handle(new Callback[] { ncb }); + String className = ncb.getName(); + setProviderClassNameParam(className); // we may have a Provider + if (provider == null) + { + x.fillInStackTrace(); + throw x; + } + // try again + store = KeyStore.getInstance(storeType, provider); + } + + // now we have a KeyStore instance. load it + // KeyStore public API claims: "...In order to create an empty keystore, + // you pass null as the InputStream argument to the load method. + if (newKeyStore) + store.load(null, storePasswordChars); + else + store.load(storeStream, storePasswordChars); + + // close the stream + try + { + storeStream.close(); + storeStream = null; + } + catch (IOException x) + { + log.fine("Exception while closing the key store input stream: " + x //$NON-NLS-1$ + + ". Ignore"); //$NON-NLS-1$ + } + } + + protected void setOutputStreamParam(String fileName) throws SecurityException, + IOException + { + if (fileName == null || fileName.trim().length() == 0) + { + outStream = System.out; + systemOut = true; + } + else + { + fileName = fileName.trim(); + File outFile = new File(fileName); + if (! outFile.exists()) + { + boolean ok = outFile.createNewFile(); + if (!ok) + throw new InvalidParameterException(Messages.getFormattedString("Command.19", //$NON-NLS-1$ + fileName)); + } + else + { + if (! outFile.isFile()) + throw new InvalidParameterException(Messages.getFormattedString("Command.42", //$NON-NLS-1$ + fileName)); + if (! outFile.canWrite()) + throw new InvalidParameterException(Messages.getFormattedString("Command.44", //$NON-NLS-1$ + fileName)); + } + outStream = new FileOutputStream(outFile); + } + } + + protected void setInputStreamParam(String fileName) + throws FileNotFoundException + { + if (fileName == null || fileName.trim().length() == 0) + inStream = System.in; + else + { + fileName = fileName.trim(); + File inFile = new File(fileName); + if (! (inFile.exists() && inFile.isFile() && inFile.canRead())) + throw new InvalidParameterException(Messages.getFormattedString("Command.46", //$NON-NLS-1$ + fileName)); + inStream = new FileInputStream(inFile); + } + } + + /** + * Set both the key-pair generation algorithm, and the digital signature + * algorithm instances to use when generating new entries. + * + * @param kpAlg the possibly null name of a key-pair generator algorithm. + * if this argument is <code>null</code> or is an empty string, the + * "DSS" algorithm will be used. + * @param sigAlg the possibly null name of a digital signature algorithm. + * If this argument is <code>null</code> or is an empty string, this + * method uses the "SHA1withDSA" (Digital Signature Standard, a.k.a. + * DSA, with the Secure Hash Algorithm function) as the default + * algorithm if, and only if, the key-pair generation algorithm ends + * up being "DSS"; otherwise, if the key-pair generation algorithm + * was "RSA", then the "MD5withRSA" signature algorithm will be used. + * If the key-pair generation algorithm is neither "DSS" (or its + * alias "DSA"), nor is it "RSA", then an exception is thrown. + * @throws NoSuchAlgorithmException if no concrete implementation of the + * designated algorithm is available. + */ + protected void setAlgorithmParams(String kpAlg, String sigAlg) + throws NoSuchAlgorithmException + { + if (kpAlg == null || kpAlg.trim().length() == 0) + kpAlg = DEFAULT_KEY_ALGORITHM; + else + kpAlg = kpAlg.trim().toLowerCase(); + + keyPairGenerator = KeyPairGenerator.getInstance(kpAlg); + + if (sigAlg == null || sigAlg.trim().length() == 0) + if (kpAlg.equalsIgnoreCase(Registry.DSS_KPG) + || kpAlg.equalsIgnoreCase(Registry.DSA_KPG)) + sigAlg = DSA_SIGNATURE_ALGORITHM; + else if (kpAlg.equalsIgnoreCase(Registry.RSA_KPG)) + sigAlg = RSA_SIGNATURE_ALGORITHM; + else + throw new IllegalArgumentException( + Messages.getFormattedString("Command.20", //$NON-NLS-1$ + new String[] { sigAlg, kpAlg })); + else + sigAlg = sigAlg.trim().toLowerCase(); + + signatureAlgorithm = Signature.getInstance(sigAlg); + } + + /** + * Set the signature algorithm to use when digitally signing private keys, + * certificates, etc... + * <p> + * If the designated algorithm name is <code>null</code> or is an empty + * string, this method checks the private key (the second argument) and based + * on its type decides which algorithm to use. The keytool public + * specification states that if the private key is a DSA key, then the + * signature algorithm will be <code>SHA1withDSA</code>, otherwise if it is + * an RSA private key, then the signature algorithm will be + * <code>MD5withRSA</code>. If the private key is neither a private DSA nor + * a private RSA key, then this method throws an + * {@link IllegalArgumentException}. + * + * @param algorithm the possibly null name of a digital signature algorithm. + * @param privateKey an instance of a private key to use as a fal-back option + * when <code>algorithm</code> is invalid. + * @throws NoSuchAlgorithmException if no concrete implementation of the + * designated, or default, signature algorithm is available. + */ + protected void setSignatureAlgorithmParam(String algorithm, Key privateKey) + throws NoSuchAlgorithmException + { + if (algorithm == null || algorithm.trim().length() == 0) + if (privateKey instanceof DSAKey) + algorithm = DSA_SIGNATURE_ALGORITHM; + else if (privateKey instanceof RSAKey) + algorithm = RSA_SIGNATURE_ALGORITHM; + else + throw new InvalidParameterException(Messages.getString("Command.48")); //$NON-NLS-1$ + else + algorithm = algorithm.trim(); + + signatureAlgorithm = Signature.getInstance(algorithm); + } + + /** + * Set the validity period, in number of days, to use when issuing new + * certificates. + * + * @param days the number of days, as a string, the generated certificate will + * be valid for, starting from today's date. if this argument is + * <code>null</code>, a default value of <code>90</code> days + * will be used. + * @throws NumberFormatException if the designated string is not a decimal + * integer. + * @throws InvalidParameterException if the integer value of the non-null + * string is not greater than zero. + */ + protected void setValidityParam(String days) + { + if (days == null || days.trim().length() == 0) + validityInDays = DEFAULT_VALIDITY; + else + { + days = days.trim(); + validityInDays = Integer.parseInt(days); + if (validityInDays < 1) + throw new InvalidParameterException(Messages.getString("Command.51")); //$NON-NLS-1$ + } + } + + /** + * RFC-2459 (http://rfc.net/rfc2459.html) fully describes the structure and + * semantics of X.509 certificates. The ASN.1 structures below are gleaned + * from that reference. + * + * <pre> + * Certificate ::= SEQUENCE { + * tbsCertificate TBSCertificate, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING + * } + * + * TBSCertificate ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * serialNumber CertificateSerialNumber, + * signature AlgorithmIdentifier, + * issuer Name, + * validity Validity, + * subject Name, + * subjectPublicKeyInfo SubjectPublicKeyInfo + * } + * + * Version ::= INTEGER { v1(0), v2(1), v3(2) } + * + * CertificateSerialNumber ::= INTEGER + * + * Validity ::= SEQUENCE { + * notBefore Time, + * notAfter Time + * } + * + * Time ::= CHOICE { + * utcTime UTCTime, + * generalTime GeneralizedTime + * } + * + * UniqueIdentifier ::= BIT STRING + * + * SubjectPublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING + * } + * </pre> + * + * @param distinguishedName the X.500 Distinguished Name to use as both the + * Issuer and Subject of the self-signed certificate to generate. + * @param publicKey the public key of the issuer/subject. + * @param privateKey the private key of the issuer/signer. + * @return the DER encoded form of a self-signed X.509 v1 certificate. + * @throws IOException If an I/O related exception occurs during the process. + * @throws SignatureException If a digital signature related exception occurs. + * @throws InvalidKeyException if the designated private key is invalid. + * @throws InvalidParameterException if the concrete signature algorithm does + * not know its name, no OID is known/supported for that name, or we + * were unable to match the name to a known string for which we can + * use a standard OID. + */ + protected byte[] getSelfSignedCertificate(X500DistinguishedName distinguishedName, + PublicKey publicKey, + PrivateKey privateKey) + throws IOException, SignatureException, InvalidKeyException + { + log.entering(this.getClass().getName(), "getSelfSignedCertificate", //$NON-NLS-1$ + new Object[] { distinguishedName, publicKey, privateKey }); + + byte[] versionBytes = new DERValue(DER.INTEGER, BigInteger.ZERO).getEncoded(); + DERValue derVersion = new DERValue(DER.CONSTRUCTED | DER.CONTEXT | 0, + versionBytes.length, versionBytes, null); + + // NOTE (rsn): the next 3 lines should be atomic but they're not. + Preferences prefs = Preferences.systemNodeForPackage(this.getClass()); + int lastSerialNumber = prefs.getInt(Main.LAST_SERIAL_NUMBER, 0) + 1; + prefs.putInt(Main.LAST_SERIAL_NUMBER, lastSerialNumber); + DERValue derSerialNumber = new DERValue(DER.INTEGER, + BigInteger.valueOf(lastSerialNumber)); + + OID signatureID = getSignatureAlgorithmOID(); + DERValue derSignatureID = new DERValue(DER.OBJECT_IDENTIFIER, signatureID); + ArrayList signature = new ArrayList(1); + signature.add(derSignatureID); + // rfc-2459 states the following: + // + // for the DSA signature: + // ...Where the id-dsa-with-sha1 algorithm identifier appears as the + // algorithm field in an AlgorithmIdentifier, the encoding shall omit + // the parameters field. That is, the AlgorithmIdentifier shall be a + // SEQUENCE of one component - the OBJECT IDENTIFIER id-dsa-with-sha1. + // + // for RSA signatures: + // ...When any of these three OIDs (i.e. xxxWithRSAEncryption) appears + // within the ASN.1 type AlgorithmIdentifier, the parameters component of + // that type shall be the ASN.1 type NULL. + if (! signatureID.equals(SHA1_WITH_DSA)) + signature.add(new DERValue(DER.NULL, null)); + + DERValue derSignature = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE, + signature); + + DERValue derIssuer = new DERReader(distinguishedName.getDer()).read(); + + long notBefore = System.currentTimeMillis(); + long notAfter = notBefore + validityInDays * MILLIS_IN_A_DAY; + + ArrayList validity = new ArrayList(2); + validity.add(new DERValue(DER.UTC_TIME, new Date(notBefore))); + validity.add(new DERValue(DER.UTC_TIME, new Date(notAfter))); + DERValue derValidity = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE, + validity); + + // for a self-signed certificate subject and issuer are identical + DERValue derSubject = derIssuer; + + DERValue derSubjectPublicKeyInfo = new DERReader(publicKey.getEncoded()).read(); + + ArrayList tbsCertificate = new ArrayList(7); + tbsCertificate.add(derVersion); + tbsCertificate.add(derSerialNumber); + tbsCertificate.add(derSignature); + tbsCertificate.add(derIssuer); + tbsCertificate.add(derValidity); + tbsCertificate.add(derSubject); + tbsCertificate.add(derSubjectPublicKeyInfo); + DERValue derTBSCertificate = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE, + tbsCertificate); + + // The 'signature' field MUST contain the same algorithm identifier as the + // 'signatureAlgorithm' field in the sequence Certificate. + DERValue derSignatureAlgorithm = derSignature; + + signatureAlgorithm.initSign(privateKey); + signatureAlgorithm.update(derTBSCertificate.getEncoded()); + byte[] sigBytes = signatureAlgorithm.sign(); + DERValue derSignatureValue = new DERValue(DER.BIT_STRING, + new BitString(sigBytes)); + + ArrayList certificate = new ArrayList(3); + certificate.add(derTBSCertificate); + certificate.add(derSignatureAlgorithm); + certificate.add(derSignatureValue); + DERValue derCertificate = new DERValue(DER.CONSTRUCTED | DER.SEQUENCE, + certificate); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DERWriter.write(baos, derCertificate); + byte[] result = baos.toByteArray(); + + log.exiting(this.getClass().getName(), "getSelfSignedCertificate"); //$NON-NLS-1$ + return result; + } + + /** + * This method attempts to find, and return, an OID representing the digital + * signature algorithm used to sign the certificate. The OIDs returned are + * those described in RFC-2459. They are listed here for the sake of + * completness. + * + * <pre> + * id-dsa-with-sha1 OBJECT IDENTIFIER ::= { + * iso(1) member-body(2) us(840) x9-57 (10040) x9cm(4) 3 + * } + * + * md2WithRSAEncryption OBJECT IDENTIFIER ::= { + * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 2 + * } + * + * md5WithRSAEncryption OBJECT IDENTIFIER ::= { + * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 4 + * } + * + * sha-1WithRSAEncryption OBJECT IDENTIFIER ::= { + * iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) 5 + * } + * </pre> + * + * <b>IMPORTANT</b>: This method checks the signature algorithm name against + * (a) The GNU algorithm implementation's name, and (b) publicly referenced + * names of the same algorithm. In other words this search is not + * comprehensive and may fail for uncommon names of the same algorithms. + * + * @return the OID of the signature algorithm in use. + * @throws InvalidParameterException if the concrete signature algorithm does + * not know its name, no OID is known/supported for that name, or we + * were unable to match the name to a known string for which we can + * return an OID. + */ + protected OID getSignatureAlgorithmOID() + { + String algorithm = signatureAlgorithm.getAlgorithm(); + // if we already have a non-null signature then the name was valid. the + // only case where algorithm is invalid would be if the implementation is + // flawed. check anyway + if (algorithm == null || algorithm.trim().length() == 0) + throw new InvalidParameterException(Messages.getString("Command.52")); //$NON-NLS-1$ + + algorithm = algorithm.trim(); + if (algorithm.equalsIgnoreCase(Registry.DSS_SIG) + || algorithm.equalsIgnoreCase("SHA1withDSA")) //$NON-NLS-1$ + return SHA1_WITH_DSA; + + if (algorithm.equalsIgnoreCase(Registry.RSA_PKCS1_V1_5_SIG + "-" //$NON-NLS-1$ + + Registry.MD2_HASH) + || algorithm.equalsIgnoreCase("MD2withRSA")) //$NON-NLS-1$ + return MD2_WITH_RSA; + + if (algorithm.equalsIgnoreCase(Registry.RSA_PKCS1_V1_5_SIG + "-" //$NON-NLS-1$ + + Registry.MD5_HASH) + || algorithm.equalsIgnoreCase("MD5withRSA") //$NON-NLS-1$ + || algorithm.equalsIgnoreCase("rsa")) //$NON-NLS-1$ + return MD5_WITH_RSA; + + if (algorithm.equalsIgnoreCase(Registry.RSA_PKCS1_V1_5_SIG + "-" //$NON-NLS-1$ + + Registry.SHA160_HASH) + || algorithm.equalsIgnoreCase("SHA1withRSA")) //$NON-NLS-1$ + return SHA1_WITH_RSA; + + throw new InvalidParameterException(Messages.getFormattedString("Command.60", //$NON-NLS-1$ + algorithm)); + } + + /** + * Saves the key store using the designated password. This operation is called + * by handlers if/when the key store password has changed, or amendements have + * been made to the contents of the store; e.g. addition of a new Key Entry or + * a Trusted Certificate. + * + * @param password the password protecting the key store. + * @throws IOException if an I/O related exception occurs during the process. + * @throws CertificateException if any of the certificates in the current key + * store could not be persisted. + * @throws NoSuchAlgorithmException if a required data integrity algorithm + * implementation was not found. + * @throws KeyStoreException if the key store has not been loaded previously. + */ + protected void saveKeyStore(char[] password) throws IOException, + KeyStoreException, NoSuchAlgorithmException, CertificateException + { + log.entering(this.getClass().getName(), "saveKeyStore", String.valueOf(password)); //$NON-NLS-1$ + + URLConnection con = storeURL.openConnection(); + con.setDoOutput(true); + con.setUseCaches(false); + OutputStream out = con.getOutputStream(); + if (verbose) + System.out.println(Messages.getFormattedString("Command.63", storeURL.getPath())); //$NON-NLS-1$ + + store.store(out, password); + out.flush(); + out.close(); + + log.exiting(this.getClass().getName(), "saveKeyStore"); //$NON-NLS-1$ + } + + /** + * Convenience method. Calls the method with the same name passing it the + * same password characters used to initially load the key-store. + * + * @throws IOException if an I/O related exception occurs during the process. + * @throws KeyStoreException if the key store has not been loaded previously. + * @throws NoSuchAlgorithmException if a required data integrity algorithm + * implementation was not found. + * @throws CertificateException if any of the certificates in the current key + * store could not be persisted. + */ + protected void saveKeyStore() throws IOException, KeyStoreException, + NoSuchAlgorithmException, CertificateException + { + saveKeyStore(storePasswordChars); + } + + /** + * Prints a human-readable form of the designated certificate to a designated + * {@link PrintWriter}. + * + * @param certificate the certificate to process. + * @param writer where to print it. + * @throws CertificateEncodingException if an exception occurs while obtaining + * the DER encoded form <code>certificate</code>. + */ + protected void printVerbose(Certificate certificate, PrintWriter writer) + throws CertificateEncodingException + { + X509Certificate x509 = (X509Certificate) certificate; + writer.println(Messages.getFormattedString("Command.66", x509.getSubjectDN())); //$NON-NLS-1$ + writer.println(Messages.getFormattedString("Command.67", x509.getIssuerDN())); //$NON-NLS-1$ + writer.println(Messages.getFormattedString("Command.68", x509.getSerialNumber())); //$NON-NLS-1$ + writer.println(Messages.getFormattedString("Command.69", x509.getNotBefore())); //$NON-NLS-1$ + writer.println(Messages.getFormattedString("Command.70", x509.getNotAfter())); //$NON-NLS-1$ + writer.println(Messages.getString("Command.71")); //$NON-NLS-1$ + byte[] derBytes = certificate.getEncoded(); + writer.println(Messages.getFormattedString("Command.72", digest(md5, derBytes))); //$NON-NLS-1$ + writer.println(Messages.getFormattedString("Command.73", digest(sha, derBytes))); //$NON-NLS-1$ + } + + /** + * Convenience method. Prints a human-readable form of the designated + * certificate to <code>System.out</code>. + * + * @param certificate the certificate to process. + * @throws CertificateEncodingException if an exception occurs while obtaining + * the DER encoded form <code>certificate</code>. + */ + protected void printVerbose(Certificate certificate) + throws CertificateEncodingException + { + printVerbose(certificate, new PrintWriter(System.out, true)); + } + + /** + * Digest the designated contents with MD5 and return a string representation + * suitable for use as a fingerprint; i.e. sequence of hexadecimal pairs of + * characters separated by a colon. + * + * @param contents the non-null contents to digest. + * @return a sequence of hexadecimal pairs of characters separated by colons. + */ + protected String digestWithMD5(byte[] contents) + { + return digest(md5, contents); + } + + private String digest(IMessageDigest hash, byte[] encoded) + { + hash.update(encoded); + byte[] b = hash.digest(); + StringBuilder sb = new StringBuilder().append(Util.toString(b, 0, 1)); + for (int i = 1; i < b.length; i++) + sb.append(":").append(Util.toString(b, i, 1)); //$NON-NLS-1$ + + String result = sb.toString(); + return result; + } + + /** + * Ensure that the currently set Alias is contained in the currently set key + * store; otherwise throw an exception. + * + * @throws KeyStoreException if the keystore has not been loaded. + * @throws IllegalArgumentException if the currently set alias is not known to + * the currently set key store. + */ + protected void ensureStoreContainsAlias() throws KeyStoreException + { + if (! store.containsAlias(alias)) + throw new IllegalArgumentException(Messages.getFormattedString("Command.75", //$NON-NLS-1$ + alias)); + } + + /** + * Ensure that the currently set Alias is associated with a Key Entry in the + * currently set key store; otherwise throw an exception. + * + * @throws KeyStoreException if the keystore has not been loaded. + * @throws SecurityException if the currently set alias is not a Key Entry in + * the currently set key store. + */ + protected void ensureAliasIsKeyEntry() throws KeyStoreException + { + if (! store.isKeyEntry(alias)) + throw new SecurityException(Messages.getFormattedString("Command.77", //$NON-NLS-1$ + alias)); + } + + protected Key getAliasPrivateKey() throws KeyStoreException, + NoSuchAlgorithmException, IOException, UnsupportedCallbackException, + UnrecoverableKeyException + { + ensureAliasIsKeyEntry(); + Key result; + if (keyPasswordChars == null) + try + { + result = store.getKey(alias, storePasswordChars); + // it worked. assign to keyPasswordChars for later use + keyPasswordChars = storePasswordChars; + } + catch (UnrecoverableKeyException x) + { + // prompt the user to provide one + setKeyPasswordParam(); + result = store.getKey(alias, keyPasswordChars); + } + else + result = store.getKey(alias, keyPasswordChars); + + return result; + } + + /** + * Return a CallbackHandler which uses the Console (System.in and System.out) + * for interacting with the user. + * <p> + * This method first finds all currently installed security providers capable + * of providing such service and then in turn attempts to instantiate the + * handler from those providers. As soon as one provider returns a non-null + * instance of the callback handler, the search stops and that instance is + * set to be used from now on. + * <p> + * If no installed providers were found, this method falls back on the GNU + * provider, by-passing the Security search mechanism. The default console + * callback handler implementation is {@link ConsoleCallbackHandler}. + * + * @return a console-based {@link CallbackHandler}. + */ + protected CallbackHandler getCallbackHandler() + { + if (handler == null) + handler = CallbackUtil.getConsoleHandler(); + + return handler; + } +} |