home *** CD-ROM | disk | FTP | other *** search
/ Java Developer's Companion / Java Developer's Companion.iso / documentation / jsdk / CA.jav < prev    next >
Encoding:
Text File  |  1997-08-17  |  21.1 KB  |  630 lines

  1. /*
  2.  * @(#)CA.java    1.18 97/05/22
  3.  * 
  4.  * Copyright (c) 1996-1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  * CopyrightVersion 1.0
  20.  */
  21.  
  22.  
  23. import java.io.*;
  24. import java.security.*;
  25. import java.util.Date;
  26.  
  27. import javax.servlet.*;
  28. import javax.servlet.http.*;
  29.  
  30. import sun.misc.BASE64Decoder;
  31. import sun.security.x509.*;
  32. import sun.security.util.*;
  33.  
  34. import sun.security.AuthContext;
  35. import sun.server.https.KeyStore;
  36.  
  37.  
  38. /**
  39.  * <em>This is a simple <b>Certificate Authority (CA) Servlet</b>, which
  40.  * supports generation of personal X.509 certificates for use in SSL client
  41.  * authentication (and other purposes, as allowed by the system which stores
  42.  * that certificate and private key).  It is intended to illustrate use of
  43.  * the servlet API, as well as to provide private sources of low assurance
  44.  * SSL client certificates.  (In some environments, high assurance key
  45.  * management is not needed due to the specific risks being managed.)
  46.  *
  47.  * <P>In its current form, this is not appropriate for use in running any 
  48.  * trustworthy authentication system.  The enrollment process is simpler
  49.  * than most organizations should contemplate, there are no lifecycle
  50.  * services (e.g. revocation, renewal, online status checks), integrity
  51.  * checks aren't made, and certs aren't even stored for reference.
  52.  * </em>
  53.  *
  54.  * <P><hr> Using a key pair assigned to this Certificate Authority role,
  55.  * this allows a Java Web Server to create X.509 certificates for uses
  56.  * in application systems such as: <UL>
  57.  *
  58.  *    <LI> Secure Web Page Services (e.g. HTTPS)
  59.  *    <LI> User Authentication (e.g. SSL clients)
  60.  *    <LI> Signing Applets and Servlets
  61.  *    </UL>
  62.  *
  63.  * <P>This can be used either programmatically (e.g. by an application
  64.  * or an applet), or through conventional HTML forms.  So for example,
  65.  * you may be using this to generate personal certificates using forms
  66.  * through Navigator 3.0, to generate site certificates online, or to
  67.  * consult an organizational CA to get a personal code signing cert.
  68.  *
  69.  * <P>Sites could subclass this servlet to support their own operational
  70.  * (or administrative) policies, within the confines supported by this
  71.  * base framework.  For example, perhaps a site needs to store CA data
  72.  * its own database rather than the simple one used by the generic
  73.  * version of this servlet, to add different data into certificates,
  74.  * or to implement different client validation procedures.
  75.  *
  76.  * <P>The primary restriction on servlets being used to provide critical
  77.  * services is that if operations are linked (perhaps as part of a
  78.  * transaction), this must be done by a layer above servlet invocations.
  79.  * Well implemented servlet operations will always be atomic.
  80.  *
  81.  * <P><hr><em> <b>NOTE:</b>  This servlet relies on "login" to have been
  82.  * performed at initializiation time, to get access to the private key
  83.  * and certificates for the CA itself. </em>
  84.  *
  85.  * @version 1.18
  86.  * @author David Brownell
  87.  */
  88. public class CA extends HttpServlet
  89. {
  90.     /**
  91.      * Constructs an uninitialized instance of this servlet.
  92.      * Servlets are initialized using the <em>init</em> method
  93.      */
  94.     public CA () {}
  95.  
  96.     /**
  97.      * Returns information about this servlet.
  98.      */
  99.     public String getServletInfo ()
  100.     {
  101.     return "CA Sample Servlet, version " + version;
  102.     }
  103.  
  104.  
  105.     /**
  106.      * Initializes the servlet using its statically configured parameters.
  107.      * Otherwise, default values may be used:<UL>
  108.      *
  109.      * <LI><em>ca_alias</em> ... alias within the keystore, used to
  110.      *    identify which private key and certificate chain are used for
  111.      *    generating certificates.  The default is that if the web server
  112.      *    is initialized for serving SSL it can create certificates.
  113.      *
  114.      * <LI><em>validity_days</em> ... how many days (starting immediately)
  115.      * the certificate should remain valid.  The default value is 90 days.
  116.      *
  117.      * <LI>... more values may be provided over time, particularly for
  118.      * database configuration information, certification policy statement,
  119.      * kind of cert being cut (e.g. SSL client, code signer), and values
  120.      * that must exist in the X509 certificates being created such as
  121.      * the specific organization name or X509v3 extended attributes.
  122.      *
  123.      * </UL>
  124.      *
  125.      * <P><em> We want to administer this servlet within the standard
  126.      * servlet admin tool framework ... including capabilities like
  127.      * providing task-specific GUIs to customize init parameters, with
  128.      * task-specific help, and management of all stored certs.
  129.      * </em>
  130.      *
  131.      * @exception UnavailableException on initialization error
  132.      */
  133.     public void init (ServletConfig config)
  134.     throws ServletException
  135.     {
  136.     super.init (config);
  137.  
  138.     String        temp;
  139.  
  140.     //
  141.     // Get CA's private key and cert chain from the server's keystore.
  142.     // We won't modify that keystore!!
  143.     //
  144.     // Note also that any fatal init time diagnostics need to tell
  145.     // the server administrator how to fix the problem we found.
  146.     //
  147.     try {
  148.         InputStream    in;
  149.         KeyStore    ks;
  150.  
  151.         //
  152.         // Load the default keystore.  If we weren't a trusted
  153.         // servlet, we'd not be able to get do any of this!!
  154.         //
  155.         temp = System.getProperty ("user.keystore");
  156.         in = new FileInputStream (temp);
  157.         ks = new KeyStore (AuthContext.getDefault (),
  158.             AuthContext.getPassphraseIndex ());
  159.         ks.load (in);
  160.         in.close ();
  161.  
  162.         //
  163.         // It's one of the keys and cert chains stored there.
  164.         // (defaulted to use the SSL site's RSA cert).
  165.         //
  166.         temp = getInitParameter ("ca_alias");
  167.         if (temp == null)
  168.         temp = "ssl-RSA-default";
  169.  
  170.         caPrivateKey = ks.getPrivateKey (temp);
  171.         caCertChain = ks.getCertificateChain (temp);
  172.  
  173.         if (caPrivateKey == null || caCertChain == null)
  174.         throw new UnavailableException (this,
  175.             "RSA certificate and private key are needed for CA.");
  176.  
  177.     } catch (NoSuchAlgorithmException e) {
  178.         throw new UnavailableException (this,
  179.             "Need a cryptographic algorithm:" + e.toString ());
  180.  
  181.     } catch (IOException e) {
  182.         throw new UnavailableException (this,
  183.         "Need a private key and certificate to run CA servlet.");
  184.     }
  185.  
  186.     //
  187.     // Get validity period for certs, in days... translate
  188.     // to milliseconds.  (Defaulted to 90 days.)
  189.     //
  190.     try {
  191.         temp = getInitParameter ("validity_days");
  192.         validityPeriod = Long.parseLong (temp);
  193.     } catch (NumberFormatException e) {
  194.         validityPeriod = 90;
  195.     }
  196.     validityPeriod *= (24 * 60 * 60 * 1000); 
  197.  
  198.     //
  199.     // Database info:  last used serial number, directory for storing
  200.     // certificates and their status (revoked, suspended, etc).
  201.     //
  202.     // XXX this is all faked out for now; a real CA would need to
  203.     // at least track certs it creates, as well as guarantee unique
  204.     // serial numbers.
  205.     //
  206.     serial = (int)(new Date().getTime () / 1000);
  207.  
  208.     //
  209.     // XXX policy options ... e.g. force organization, org unit,
  210.     // locality, state, and country to prespecified values.
  211.     //
  212.     }
  213.  
  214.  
  215.     /**
  216.      * This servlet handles requests going to a particular node in
  217.      * a given web server's URI namespace, through which a Certificate
  218.      * Authority service is provided.  Those requests (will) enable the
  219.      * following kinds of remote CA operations: <UL>
  220.      *
  221.      *  <LI><em>GET</em> methods, for which access is often unrestricted
  222.      *    since they never change state and won't usually expose sensitive
  223.      *    information (but beware priorities of CPU cycles):<UL>
  224.      *        <LI> Look up a cert;
  225.      *        <LI> Validity check;
  226.      *        <LI> List certs;
  227.      *        <LI> Search cert database;
  228.      *        </UL>
  229.      *
  230.      *    <LI><em>POST</em> methods, usually access-restricted because they
  231.      *    modify the authentication system's state: <UL>
  232.      *        <LI> Creation (currently supports preauthorization);
  233.      *        <LI> Renewal;
  234.      *        <LI> Revocation;
  235.      *        </UL>
  236.      *  </UL>
  237.      *
  238.      * <P><em><b>NOTE:</b>
  239.      * Most of these operations are intended to serve three kinds of user
  240.      * interfaces.  The simplest kind uses Netscape extensions to support
  241.      * verification protocols.  Another simple kind uses a set of HTML pages
  242.      * with forms configured to drive the servlet.  The third kind is
  243.      * through a remote invocation API, perhaps used by a set of applets.
  244.      * The operations supported by the servlet have no fundamental need
  245.      * to differ based on what user interface is being used. </em>
  246.      *
  247.      * <P>Areas for future extension include support of optional policy
  248.      * configuration options, and combining the CA module support with other
  249.      * servlets to enable flexible configuration of system characteristics
  250.      * such as database location, required certificate and name attributes,
  251.      * and user interface structure.
  252.      *
  253.      * @param req general servlet request parameter
  254.      * @param resp general servlet response parameter
  255.      * @exception ServletException yes
  256.      * @exception IOException yes
  257.      */
  258.     public void
  259.     doPost (HttpServletRequest req, HttpServletResponse resp)
  260.     throws ServletException, IOException
  261.     {
  262.     String    method = req.getMethod ();
  263.     String    opname = req.getParameter ("opname");
  264.  
  265.     if (opname == null || method == null)
  266.         throw new ServletException ("null opname or method");
  267.     
  268.     // XXX check if the user has "POST" permission
  269.  
  270.     if (opname.equals ("genCert")) {
  271.         doGenCert (req, resp);
  272.  
  273.     } else if (opname.equals ("renew")) {
  274.         // renew the ID'd key, return it
  275.         throw new ServletException ("unimplemented: " + opname);
  276.  
  277.     } else if (opname.equals ("revoke")) {
  278.         // revoke the ID'd cert
  279.         throw new ServletException ("unimplemented: " + opname);
  280.  
  281.     } else
  282.         throw new ServletException ("invalid operation: " + opname);
  283.     }
  284.  
  285.  
  286.     /*
  287.      * Cert creation ... lots is hardwired, but can be overridden by
  288.      * subclasses which provide alternate policies:
  289.      *
  290.      *    - The value of the challenge;
  291.      *    - Name of the KEYGEN response field;
  292.      *    - X.500 names are created from specific attributes;
  293.      *    - Certificate Attributes are not (yet) supported;
  294.      *    - The certificate is effectively pre-authorized.
  295.      *
  296.      * Note that this doesn't care what kind of key is given so long
  297.      * as it's supported in the runtime environment; and that the key
  298.      * is given in the format returned by Navigator 3.0's KEYGEN tag.
  299.      * Navigator only does RSA keys; DSS/DSA keys are also useful.
  300.      */
  301.     private void
  302.     doGenCert (HttpServletRequest req, HttpServletResponse resp)
  303.     throws ServletException, IOException
  304.     {
  305.     try {
  306.         //
  307.         // Get the subject's X.500 name, to be stored in the
  308.         // certificate we're creating.
  309.         //
  310.         // NOTE that this is currently "the" policy hook for
  311.         // modifying this information to support the policies
  312.         // used by this site, or rejecting invalid data.
  313.         //
  314.         X500Name    subjectName = getSubjectName (req);
  315.  
  316.         //
  317.         // Get the user's public key, verifying that they actually know
  318.         // the corresponding private key (using the signed challenge).
  319.         //
  320.         // Then see if the key meets policy criteria.
  321.         //
  322.         // XXX the challenge should have been associated with this
  323.         // SSL session by servlet code which returned the form to the
  324.         // client, and provided a random challenge string with the
  325.         // KEYGEN tag.
  326.         //
  327.         String    challenge = "fixed-for-now";
  328.         String    response = req.getParameter ("keygen");
  329.         X509Key    subjectKey = getVerifiedKey (challenge, response);
  330.  
  331.         checkKeyAppropriate (subjectName, subjectKey);
  332.  
  333.         //
  334.         // Check for preauthorization token in the form data;
  335.         // and in fact do any other work needed before the
  336.         // cert actually gets cut.
  337.         //
  338.         checkPreAuthorization (req);
  339.  
  340.         //
  341.         // OK, cut the cert.
  342.         //
  343.         AlgorithmId    sigAlg;
  344.         X500Signer    signer;
  345.         X509Cert    userCert;
  346.         Date    now;
  347.         Date    lastValidDate;
  348.         byte    userCertBytes [];
  349.  
  350.         sigAlg = caCertChain [0].getIssuerAlgorithmId ();
  351.         signer = caCertChain [0].getSigner (sigAlg, caPrivateKey);
  352.  
  353.         now = new Date ();
  354.         lastValidDate = new Date (now.getTime () + validityPeriod);
  355.  
  356.         userCert = new X509Cert (subjectName, subjectKey,
  357.             now, lastValidDate);
  358.         addCertExtensions (userCert);
  359.         userCertBytes = userCert.encodeAndSign ( new BigInt (serial),
  360.             signer);
  361.  
  362.         log ("Created X509v1 Cert for <" + subjectName + "> from "
  363.         + req.getRemoteHost ());
  364.         
  365.         //
  366.         // XXX store the (unrevoked) cert and persistently increment
  367.         // the serial number we'll use ...
  368.         //
  369.         serial++;
  370.  
  371.         //
  372.         // Write the (single) cert as the response.
  373.         //
  374.         // XXX Prefer to write the whole cert chain; there are two
  375.         // main formats, either using the "certificates" field in a
  376.         // PKCS #7 "SignedData" object, or Netscape's simpler format
  377.         // of a PKCS #7 ContentInfo object holding an instance of the
  378.         // "CertificateSequence" type.
  379.         //
  380.         resp.setContentType ("application/x-x509-user-cert");
  381.         resp.getOutputStream ().write (userCertBytes);
  382.  
  383.     } catch (NoSuchAlgorithmException e) {
  384.         throw new ServletException (
  385.             "Algorithm unavailable: " + e.getMessage ());
  386.  
  387.     } catch (SignatureException e) {
  388.         throw new ServletException (
  389.             "Bad Signature: " + e.getMessage ());
  390.  
  391.     } catch (InvalidKeyException e) {
  392.         throw new ServletException (
  393.             "Invalid key: " + e.getMessage ());
  394.  
  395.     }
  396.     }
  397.  
  398.  
  399.     /**
  400.      * Returns the X.500 name of the subject, as reported in the
  401.      * HTTP form parameters in the argument.  This method may be
  402.      * overridden in a policy-enhancing subclass.
  403.      *
  404.      * <P>The default policy takes a <em>Common Name</em> attribute
  405.      * from the "name" form field, an <em>Organization</em> attribute
  406.      * from the "org" form field, an <em>Organizational Unit</em>
  407.      * attribute from the "unit" form field, a <em>Locality</em>
  408.      * (city) attribute from the "locality" form field, a <em>State</em>
  409.      * attribute from the "state" form field, and a <em>Country</em>
  410.      * attribute from the "country" form field.  These are combined
  411.      * into an X.500 name, with each attribute in a separate level
  412.      * of the X.500 distinguished name.
  413.      *
  414.      * @param req holds form parameters
  415.      * @return the X.500 name of the subject
  416.      * @exception ServletException if any parameter is missing
  417.      */
  418.     protected X500Name getSubjectName (HttpServletRequest req)
  419.     throws ServletException, IOException
  420.     {
  421.     String    commonName = req.getParameter ("name");
  422.     String    orgName = req.getParameter ("org");
  423.     String    orgUnit = req.getParameter ("unit");
  424.     String    locality = req.getParameter ("locality");
  425.     String    state = req.getParameter ("state");
  426.     String    country = req.getParameter ("country");
  427.  
  428.     // XXX optionally override most name fields from static params;
  429.     // for example, to ensure that organizational data is correct.
  430.  
  431.     if (commonName == null || orgName == null
  432.         || orgUnit == null || country == null
  433.         || locality == null || state == null
  434.         )
  435.         throw new ServletException ("missing subject name param");
  436.  
  437.     return new X500Name (commonName, orgUnit, orgName,
  438.         locality, state, country);
  439.     }
  440.  
  441.  
  442.     /**
  443.      * Validate the key according to a security policy.  The default
  444.      * policy allows all keys.  Subclasses might have policies about
  445.      * what kinds of keys are allowed (e.g. RSA vs DSS/DSA), key sizes
  446.      * (maybe 512 bit keys are insufficient), algorithm parameters, or
  447.      * preventing reuse of keys from revoked or expired certificates.
  448.      *
  449.      * @param subjectName the name of the subject using the key
  450.      * @param subjectKey the key being checked
  451.      * @exception ServletException if the key is inappropriate
  452.      */
  453.     protected void
  454.     checkKeyAppropriate (X500Name subjectName, X509Key subjectKey)
  455.     throws ServletException
  456.     {
  457.     }
  458.  
  459.  
  460.     /**
  461.      * Checks whether the request was appropriately pre-authorized,
  462.      * and throws an exception if not.  This is normally overridden
  463.      * by a policy-providing subclass.  The default implementation
  464.      * authorizes all certificate creation requests.
  465.      *
  466.      * <P>The pre-authorization model is (currently) as follows:  a
  467.      * user account may be pre-authorized by the administrator, who
  468.      * may assign an authorization token.  That token is communicated
  469.      * out of band to the user whose credentials are being generated.
  470.      * (For example, face to face, or over the phone.)  When they fill
  471.      * out the "new account" form, this token must be presented.  This
  472.      * function validates that preauthorization, or throws an exception.
  473.      * The authorization token should be invalidated after being used.
  474.      *
  475.      * <P>This is probably a pretty general "before" invocation hook;
  476.      * probably worth renaming it and adding an "after".
  477.      *
  478.      * @param req request which includes preauthorization parameters
  479.      * @exception ServletException yes
  480.      */
  481.     protected void checkPreAuthorization (HttpServletRequest req)
  482.     throws ServletException
  483.     {
  484.     }
  485.  
  486.  
  487.     /**
  488.      * Add X.509 certificate extensions to the application.  By default,
  489.      * options are added to support interoperation with this servlet.
  490.      *
  491.      * @param X509Cert the certificate being modified (not yet signed)
  492.      * @exception IOException yes
  493.      */
  494.     protected void addCertExtensions (X509Cert subjectCert) throws IOException
  495.     {
  496.     //
  497.     // XXX Add Navigator V3 client extensions; can compress
  498.     // Netscape URLS with netscape-base-url.  The idea is to
  499.     // drive at least renewal and revocation through this
  500.     // CA servlet.
  501.     //
  502.     // Ones directly supported by the servlet above:
  503.     //    netscape-cert-renewal-url ... for cert renewal
  504.     //    netscape-revocation-url ... for revocation check
  505.     //
  506.     // Ones supporting user interaction per Navigator policies:
  507.     //    netscape-ca-policy-url ... for CA's CPS
  508.     //    netscape-cert-type ... e.g. ssl client
  509.     //    netscape-comment ... user-meaningful info
  510.     //
  511.     // Verisign is another definer of extension fields.
  512.     //
  513.     }
  514.  
  515.  
  516.     // XXX database ops needed:
  517.     //    - get a cert by ID (serial number)
  518.     //    - tell if an ID corresponds to a valid (and nonrevoked) cert
  519.     //    - get an enumeration of certs with their statuses
  520.     //
  521.     //    - store a cert (key is ID; also, revocation flag)
  522.     //    - revoke a cert by ID
  523.     //    - renew a cert by ID (?how verify?)
  524.     //
  525.     // Build on sun.server.realm.Realm class.
  526.  
  527.  
  528.     /**
  529.      * This synchronizes CA data to persistent storage, such as a
  530.      * local disk or remote backup media.
  531.      */
  532.     public void sync ()
  533.     {
  534.     // we're not persistent yet !!
  535.     }
  536.  
  537.  
  538.     /*
  539.      * Parse and verify the SignedPublicKeyAndChallenge data as
  540.      * produced by KEYGEN ... return the key.
  541.      */
  542.     private X509Key getVerifiedKey (String challenge, String response)
  543.     throws SignatureException, IOException,
  544.     InvalidKeyException, NoSuchAlgorithmException
  545.     {
  546.     //
  547.     // Navigator 3.0 returns the data in BASE64 encoding
  548.     //
  549.     BASE64Decoder    decoder = new BASE64Decoder ();
  550.     byte        data [] = decoder.decodeBuffer (response);
  551.  
  552.     //
  553.     // SignedPublicKeyAndChallenge ::= SEQUENCE {
  554.     //    publicKeyAndChallenge    PublicKeyAndChallenge,
  555.     //    signatureAlgorithm    AlgorithmIdentifier,
  556.     //    signature        BIT STRING
  557.     // }
  558.     //
  559.     DerInputStream    in = new DerInputStream (data);
  560.     DerValue    spkac [] = in.getSequence (3);
  561.     AlgorithmId    sigAlg;
  562.     byte        sigBits [];
  563.  
  564.     if (spkac.length != 3)
  565.         throw new SignatureException ("invalid SPKAC");
  566.  
  567.     sigAlg = AlgorithmId.parse (spkac [1]);
  568.     sigBits = spkac [2].getBitString ();
  569.  
  570.  
  571.     //
  572.     // PublicKeyAndChallenge ::= SEQUENCE {
  573.     //    spki            SubjectPublicKeyInfo,
  574.     //    challenge        IA5STRING
  575.     // }
  576.     //
  577.     DerValue    pkac [];
  578.     X509Key        retval;
  579.  
  580.     // in = spkac [0].toDerInputStream ();
  581.     in = new DerInputStream (spkac [0].toByteArray ());
  582.     pkac = in.getSequence (2);
  583.  
  584.     if (pkac.length != 2)
  585.         throw new SignatureException ("invalid PKAC");
  586.     if (!challenge.equals (pkac [1].getIA5String ()))
  587.         throw new SignatureException ("bad challenge in PKAC");
  588.  
  589.     retval = X509Key.parse (pkac [0]);
  590.  
  591.     //
  592.     // Verify the signature .. shows the response was generated
  593.     // by someone who knew the associated private key
  594.     //
  595.     Signature    sig = Signature.getInstance (sigAlg.getName ());
  596.  
  597.     sig.initVerify (retval);
  598.     sig.update (spkac [0].toByteArray ());
  599.     if (sig.verify (sigBits))
  600.         return retval;
  601.     else
  602.         throw new SignatureException ("bad SPKAC Signature");
  603.     }
  604.  
  605.  
  606.     /*
  607.      * May need two databases, or at least a guarantee that CA can
  608.      * always access the read-only one (and only the CA can corrupt
  609.      * the one it updates during cert lifecycle operations)
  610.      *
  611.      * the database needs auditing capability.
  612.      *
  613.      * search merits query by part of DN etc, as well as full dump
  614.      */
  615.  
  616.     /*
  617.      * Information used to run the CA.
  618.      */
  619.     private X509Cert    caCertChain [];
  620.     private PrivateKey    caPrivateKey;
  621.     private int        serial;
  622.     private long    validityPeriod;        // millisecs
  623.  
  624.  
  625.     /*
  626.      * Various private constants.
  627.      */
  628.     private static final String version = "1.18";
  629. }
  630.