home *** CD-ROM | disk | FTP | other *** search
- /*
- * @(#)CA.java 1.18 97/05/22
- *
- * Copyright (c) 1996-1997 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the confidential and proprietary information of Sun
- * Microsystems, Inc. ("Confidential Information"). You shall not
- * disclose such Confidential Information and shall use it only in
- * accordance with the terms of the license agreement you entered into
- * with Sun.
- *
- * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
- * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
- * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
- * THIS SOFTWARE OR ITS DERIVATIVES.
- *
- * CopyrightVersion 1.0
- */
-
-
- import java.io.*;
- import java.security.*;
- import java.util.Date;
-
- import javax.servlet.*;
- import javax.servlet.http.*;
-
- import sun.misc.BASE64Decoder;
- import sun.security.x509.*;
- import sun.security.util.*;
-
- import sun.security.AuthContext;
- import sun.server.https.KeyStore;
-
-
- /**
- * <em>This is a simple <b>Certificate Authority (CA) Servlet</b>, which
- * supports generation of personal X.509 certificates for use in SSL client
- * authentication (and other purposes, as allowed by the system which stores
- * that certificate and private key). It is intended to illustrate use of
- * the servlet API, as well as to provide private sources of low assurance
- * SSL client certificates. (In some environments, high assurance key
- * management is not needed due to the specific risks being managed.)
- *
- * <P>In its current form, this is not appropriate for use in running any
- * trustworthy authentication system. The enrollment process is simpler
- * than most organizations should contemplate, there are no lifecycle
- * services (e.g. revocation, renewal, online status checks), integrity
- * checks aren't made, and certs aren't even stored for reference.
- * </em>
- *
- * <P><hr> Using a key pair assigned to this Certificate Authority role,
- * this allows a Java Web Server to create X.509 certificates for uses
- * in application systems such as: <UL>
- *
- * <LI> Secure Web Page Services (e.g. HTTPS)
- * <LI> User Authentication (e.g. SSL clients)
- * <LI> Signing Applets and Servlets
- * </UL>
- *
- * <P>This can be used either programmatically (e.g. by an application
- * or an applet), or through conventional HTML forms. So for example,
- * you may be using this to generate personal certificates using forms
- * through Navigator 3.0, to generate site certificates online, or to
- * consult an organizational CA to get a personal code signing cert.
- *
- * <P>Sites could subclass this servlet to support their own operational
- * (or administrative) policies, within the confines supported by this
- * base framework. For example, perhaps a site needs to store CA data
- * its own database rather than the simple one used by the generic
- * version of this servlet, to add different data into certificates,
- * or to implement different client validation procedures.
- *
- * <P>The primary restriction on servlets being used to provide critical
- * services is that if operations are linked (perhaps as part of a
- * transaction), this must be done by a layer above servlet invocations.
- * Well implemented servlet operations will always be atomic.
- *
- * <P><hr><em> <b>NOTE:</b> This servlet relies on "login" to have been
- * performed at initializiation time, to get access to the private key
- * and certificates for the CA itself. </em>
- *
- * @version 1.18
- * @author David Brownell
- */
- public class CA extends HttpServlet
- {
- /**
- * Constructs an uninitialized instance of this servlet.
- * Servlets are initialized using the <em>init</em> method
- */
- public CA () {}
-
- /**
- * Returns information about this servlet.
- */
- public String getServletInfo ()
- {
- return "CA Sample Servlet, version " + version;
- }
-
-
- /**
- * Initializes the servlet using its statically configured parameters.
- * Otherwise, default values may be used:<UL>
- *
- * <LI><em>ca_alias</em> ... alias within the keystore, used to
- * identify which private key and certificate chain are used for
- * generating certificates. The default is that if the web server
- * is initialized for serving SSL it can create certificates.
- *
- * <LI><em>validity_days</em> ... how many days (starting immediately)
- * the certificate should remain valid. The default value is 90 days.
- *
- * <LI>... more values may be provided over time, particularly for
- * database configuration information, certification policy statement,
- * kind of cert being cut (e.g. SSL client, code signer), and values
- * that must exist in the X509 certificates being created such as
- * the specific organization name or X509v3 extended attributes.
- *
- * </UL>
- *
- * <P><em> We want to administer this servlet within the standard
- * servlet admin tool framework ... including capabilities like
- * providing task-specific GUIs to customize init parameters, with
- * task-specific help, and management of all stored certs.
- * </em>
- *
- * @exception UnavailableException on initialization error
- */
- public void init (ServletConfig config)
- throws ServletException
- {
- super.init (config);
-
- String temp;
-
- //
- // Get CA's private key and cert chain from the server's keystore.
- // We won't modify that keystore!!
- //
- // Note also that any fatal init time diagnostics need to tell
- // the server administrator how to fix the problem we found.
- //
- try {
- InputStream in;
- KeyStore ks;
-
- //
- // Load the default keystore. If we weren't a trusted
- // servlet, we'd not be able to get do any of this!!
- //
- temp = System.getProperty ("user.keystore");
- in = new FileInputStream (temp);
- ks = new KeyStore (AuthContext.getDefault (),
- AuthContext.getPassphraseIndex ());
- ks.load (in);
- in.close ();
-
- //
- // It's one of the keys and cert chains stored there.
- // (defaulted to use the SSL site's RSA cert).
- //
- temp = getInitParameter ("ca_alias");
- if (temp == null)
- temp = "ssl-RSA-default";
-
- caPrivateKey = ks.getPrivateKey (temp);
- caCertChain = ks.getCertificateChain (temp);
-
- if (caPrivateKey == null || caCertChain == null)
- throw new UnavailableException (this,
- "RSA certificate and private key are needed for CA.");
-
- } catch (NoSuchAlgorithmException e) {
- throw new UnavailableException (this,
- "Need a cryptographic algorithm:" + e.toString ());
-
- } catch (IOException e) {
- throw new UnavailableException (this,
- "Need a private key and certificate to run CA servlet.");
- }
-
- //
- // Get validity period for certs, in days... translate
- // to milliseconds. (Defaulted to 90 days.)
- //
- try {
- temp = getInitParameter ("validity_days");
- validityPeriod = Long.parseLong (temp);
- } catch (NumberFormatException e) {
- validityPeriod = 90;
- }
- validityPeriod *= (24 * 60 * 60 * 1000);
-
- //
- // Database info: last used serial number, directory for storing
- // certificates and their status (revoked, suspended, etc).
- //
- // XXX this is all faked out for now; a real CA would need to
- // at least track certs it creates, as well as guarantee unique
- // serial numbers.
- //
- serial = (int)(new Date().getTime () / 1000);
-
- //
- // XXX policy options ... e.g. force organization, org unit,
- // locality, state, and country to prespecified values.
- //
- }
-
-
- /**
- * This servlet handles requests going to a particular node in
- * a given web server's URI namespace, through which a Certificate
- * Authority service is provided. Those requests (will) enable the
- * following kinds of remote CA operations: <UL>
- *
- * <LI><em>GET</em> methods, for which access is often unrestricted
- * since they never change state and won't usually expose sensitive
- * information (but beware priorities of CPU cycles):<UL>
- * <LI> Look up a cert;
- * <LI> Validity check;
- * <LI> List certs;
- * <LI> Search cert database;
- * </UL>
- *
- * <LI><em>POST</em> methods, usually access-restricted because they
- * modify the authentication system's state: <UL>
- * <LI> Creation (currently supports preauthorization);
- * <LI> Renewal;
- * <LI> Revocation;
- * </UL>
- * </UL>
- *
- * <P><em><b>NOTE:</b>
- * Most of these operations are intended to serve three kinds of user
- * interfaces. The simplest kind uses Netscape extensions to support
- * verification protocols. Another simple kind uses a set of HTML pages
- * with forms configured to drive the servlet. The third kind is
- * through a remote invocation API, perhaps used by a set of applets.
- * The operations supported by the servlet have no fundamental need
- * to differ based on what user interface is being used. </em>
- *
- * <P>Areas for future extension include support of optional policy
- * configuration options, and combining the CA module support with other
- * servlets to enable flexible configuration of system characteristics
- * such as database location, required certificate and name attributes,
- * and user interface structure.
- *
- * @param req general servlet request parameter
- * @param resp general servlet response parameter
- * @exception ServletException yes
- * @exception IOException yes
- */
- public void
- doPost (HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- String method = req.getMethod ();
- String opname = req.getParameter ("opname");
-
- if (opname == null || method == null)
- throw new ServletException ("null opname or method");
-
- // XXX check if the user has "POST" permission
-
- if (opname.equals ("genCert")) {
- doGenCert (req, resp);
-
- } else if (opname.equals ("renew")) {
- // renew the ID'd key, return it
- throw new ServletException ("unimplemented: " + opname);
-
- } else if (opname.equals ("revoke")) {
- // revoke the ID'd cert
- throw new ServletException ("unimplemented: " + opname);
-
- } else
- throw new ServletException ("invalid operation: " + opname);
- }
-
-
- /*
- * Cert creation ... lots is hardwired, but can be overridden by
- * subclasses which provide alternate policies:
- *
- * - The value of the challenge;
- * - Name of the KEYGEN response field;
- * - X.500 names are created from specific attributes;
- * - Certificate Attributes are not (yet) supported;
- * - The certificate is effectively pre-authorized.
- *
- * Note that this doesn't care what kind of key is given so long
- * as it's supported in the runtime environment; and that the key
- * is given in the format returned by Navigator 3.0's KEYGEN tag.
- * Navigator only does RSA keys; DSS/DSA keys are also useful.
- */
- private void
- doGenCert (HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException
- {
- try {
- //
- // Get the subject's X.500 name, to be stored in the
- // certificate we're creating.
- //
- // NOTE that this is currently "the" policy hook for
- // modifying this information to support the policies
- // used by this site, or rejecting invalid data.
- //
- X500Name subjectName = getSubjectName (req);
-
- //
- // Get the user's public key, verifying that they actually know
- // the corresponding private key (using the signed challenge).
- //
- // Then see if the key meets policy criteria.
- //
- // XXX the challenge should have been associated with this
- // SSL session by servlet code which returned the form to the
- // client, and provided a random challenge string with the
- // KEYGEN tag.
- //
- String challenge = "fixed-for-now";
- String response = req.getParameter ("keygen");
- X509Key subjectKey = getVerifiedKey (challenge, response);
-
- checkKeyAppropriate (subjectName, subjectKey);
-
- //
- // Check for preauthorization token in the form data;
- // and in fact do any other work needed before the
- // cert actually gets cut.
- //
- checkPreAuthorization (req);
-
- //
- // OK, cut the cert.
- //
- AlgorithmId sigAlg;
- X500Signer signer;
- X509Cert userCert;
- Date now;
- Date lastValidDate;
- byte userCertBytes [];
-
- sigAlg = caCertChain [0].getIssuerAlgorithmId ();
- signer = caCertChain [0].getSigner (sigAlg, caPrivateKey);
-
- now = new Date ();
- lastValidDate = new Date (now.getTime () + validityPeriod);
-
- userCert = new X509Cert (subjectName, subjectKey,
- now, lastValidDate);
- addCertExtensions (userCert);
- userCertBytes = userCert.encodeAndSign ( new BigInt (serial),
- signer);
-
- log ("Created X509v1 Cert for <" + subjectName + "> from "
- + req.getRemoteHost ());
-
- //
- // XXX store the (unrevoked) cert and persistently increment
- // the serial number we'll use ...
- //
- serial++;
-
- //
- // Write the (single) cert as the response.
- //
- // XXX Prefer to write the whole cert chain; there are two
- // main formats, either using the "certificates" field in a
- // PKCS #7 "SignedData" object, or Netscape's simpler format
- // of a PKCS #7 ContentInfo object holding an instance of the
- // "CertificateSequence" type.
- //
- resp.setContentType ("application/x-x509-user-cert");
- resp.getOutputStream ().write (userCertBytes);
-
- } catch (NoSuchAlgorithmException e) {
- throw new ServletException (
- "Algorithm unavailable: " + e.getMessage ());
-
- } catch (SignatureException e) {
- throw new ServletException (
- "Bad Signature: " + e.getMessage ());
-
- } catch (InvalidKeyException e) {
- throw new ServletException (
- "Invalid key: " + e.getMessage ());
-
- }
- }
-
-
- /**
- * Returns the X.500 name of the subject, as reported in the
- * HTTP form parameters in the argument. This method may be
- * overridden in a policy-enhancing subclass.
- *
- * <P>The default policy takes a <em>Common Name</em> attribute
- * from the "name" form field, an <em>Organization</em> attribute
- * from the "org" form field, an <em>Organizational Unit</em>
- * attribute from the "unit" form field, a <em>Locality</em>
- * (city) attribute from the "locality" form field, a <em>State</em>
- * attribute from the "state" form field, and a <em>Country</em>
- * attribute from the "country" form field. These are combined
- * into an X.500 name, with each attribute in a separate level
- * of the X.500 distinguished name.
- *
- * @param req holds form parameters
- * @return the X.500 name of the subject
- * @exception ServletException if any parameter is missing
- */
- protected X500Name getSubjectName (HttpServletRequest req)
- throws ServletException, IOException
- {
- String commonName = req.getParameter ("name");
- String orgName = req.getParameter ("org");
- String orgUnit = req.getParameter ("unit");
- String locality = req.getParameter ("locality");
- String state = req.getParameter ("state");
- String country = req.getParameter ("country");
-
- // XXX optionally override most name fields from static params;
- // for example, to ensure that organizational data is correct.
-
- if (commonName == null || orgName == null
- || orgUnit == null || country == null
- || locality == null || state == null
- )
- throw new ServletException ("missing subject name param");
-
- return new X500Name (commonName, orgUnit, orgName,
- locality, state, country);
- }
-
-
- /**
- * Validate the key according to a security policy. The default
- * policy allows all keys. Subclasses might have policies about
- * what kinds of keys are allowed (e.g. RSA vs DSS/DSA), key sizes
- * (maybe 512 bit keys are insufficient), algorithm parameters, or
- * preventing reuse of keys from revoked or expired certificates.
- *
- * @param subjectName the name of the subject using the key
- * @param subjectKey the key being checked
- * @exception ServletException if the key is inappropriate
- */
- protected void
- checkKeyAppropriate (X500Name subjectName, X509Key subjectKey)
- throws ServletException
- {
- }
-
-
- /**
- * Checks whether the request was appropriately pre-authorized,
- * and throws an exception if not. This is normally overridden
- * by a policy-providing subclass. The default implementation
- * authorizes all certificate creation requests.
- *
- * <P>The pre-authorization model is (currently) as follows: a
- * user account may be pre-authorized by the administrator, who
- * may assign an authorization token. That token is communicated
- * out of band to the user whose credentials are being generated.
- * (For example, face to face, or over the phone.) When they fill
- * out the "new account" form, this token must be presented. This
- * function validates that preauthorization, or throws an exception.
- * The authorization token should be invalidated after being used.
- *
- * <P>This is probably a pretty general "before" invocation hook;
- * probably worth renaming it and adding an "after".
- *
- * @param req request which includes preauthorization parameters
- * @exception ServletException yes
- */
- protected void checkPreAuthorization (HttpServletRequest req)
- throws ServletException
- {
- }
-
-
- /**
- * Add X.509 certificate extensions to the application. By default,
- * options are added to support interoperation with this servlet.
- *
- * @param X509Cert the certificate being modified (not yet signed)
- * @exception IOException yes
- */
- protected void addCertExtensions (X509Cert subjectCert) throws IOException
- {
- //
- // XXX Add Navigator V3 client extensions; can compress
- // Netscape URLS with netscape-base-url. The idea is to
- // drive at least renewal and revocation through this
- // CA servlet.
- //
- // Ones directly supported by the servlet above:
- // netscape-cert-renewal-url ... for cert renewal
- // netscape-revocation-url ... for revocation check
- //
- // Ones supporting user interaction per Navigator policies:
- // netscape-ca-policy-url ... for CA's CPS
- // netscape-cert-type ... e.g. ssl client
- // netscape-comment ... user-meaningful info
- //
- // Verisign is another definer of extension fields.
- //
- }
-
-
- // XXX database ops needed:
- // - get a cert by ID (serial number)
- // - tell if an ID corresponds to a valid (and nonrevoked) cert
- // - get an enumeration of certs with their statuses
- //
- // - store a cert (key is ID; also, revocation flag)
- // - revoke a cert by ID
- // - renew a cert by ID (?how verify?)
- //
- // Build on sun.server.realm.Realm class.
-
-
- /**
- * This synchronizes CA data to persistent storage, such as a
- * local disk or remote backup media.
- */
- public void sync ()
- {
- // we're not persistent yet !!
- }
-
-
- /*
- * Parse and verify the SignedPublicKeyAndChallenge data as
- * produced by KEYGEN ... return the key.
- */
- private X509Key getVerifiedKey (String challenge, String response)
- throws SignatureException, IOException,
- InvalidKeyException, NoSuchAlgorithmException
- {
- //
- // Navigator 3.0 returns the data in BASE64 encoding
- //
- BASE64Decoder decoder = new BASE64Decoder ();
- byte data [] = decoder.decodeBuffer (response);
-
- //
- // SignedPublicKeyAndChallenge ::= SEQUENCE {
- // publicKeyAndChallenge PublicKeyAndChallenge,
- // signatureAlgorithm AlgorithmIdentifier,
- // signature BIT STRING
- // }
- //
- DerInputStream in = new DerInputStream (data);
- DerValue spkac [] = in.getSequence (3);
- AlgorithmId sigAlg;
- byte sigBits [];
-
- if (spkac.length != 3)
- throw new SignatureException ("invalid SPKAC");
-
- sigAlg = AlgorithmId.parse (spkac [1]);
- sigBits = spkac [2].getBitString ();
-
-
- //
- // PublicKeyAndChallenge ::= SEQUENCE {
- // spki SubjectPublicKeyInfo,
- // challenge IA5STRING
- // }
- //
- DerValue pkac [];
- X509Key retval;
-
- // in = spkac [0].toDerInputStream ();
- in = new DerInputStream (spkac [0].toByteArray ());
- pkac = in.getSequence (2);
-
- if (pkac.length != 2)
- throw new SignatureException ("invalid PKAC");
- if (!challenge.equals (pkac [1].getIA5String ()))
- throw new SignatureException ("bad challenge in PKAC");
-
- retval = X509Key.parse (pkac [0]);
-
- //
- // Verify the signature .. shows the response was generated
- // by someone who knew the associated private key
- //
- Signature sig = Signature.getInstance (sigAlg.getName ());
-
- sig.initVerify (retval);
- sig.update (spkac [0].toByteArray ());
- if (sig.verify (sigBits))
- return retval;
- else
- throw new SignatureException ("bad SPKAC Signature");
- }
-
-
- /*
- * May need two databases, or at least a guarantee that CA can
- * always access the read-only one (and only the CA can corrupt
- * the one it updates during cert lifecycle operations)
- *
- * the database needs auditing capability.
- *
- * search merits query by part of DN etc, as well as full dump
- */
-
- /*
- * Information used to run the CA.
- */
- private X509Cert caCertChain [];
- private PrivateKey caPrivateKey;
- private int serial;
- private long validityPeriod; // millisecs
-
-
- /*
- * Various private constants.
- */
- private static final String version = "1.18";
- }
-