Security is important on the Web. Whether sharing financial, business, or personal information, people want to know with whom they are communicating (authentication), they want to ensure that what is sent is what is received (integrity), and they want to prevent others from intercepting what they are communicating (privacy). The Secure Sockets Layer protocol [13] provides one means for achieving these goals and is the subject of this article. We introduce SSL by reviewing cryptographic techniques and by discussing certificates. We then describe SSL and packages for implementing SSL. We conclude with examples of how to use SSLeay, the free implementation of SSL by Eric Young. We use the SSLeay toolkit to create a Certificate Authority, as well as server and client certificates.
There are two categories of cryptographic algorithms: conventional and public key.
A summary such as this is called a message digest, or one-way hash. Message digests are used to create short, fixed-length representations of longer, variable-length messages. Digest algorithms are designed to produce unique digests for different messages. Message digests are designed to make it too difficult to determine the message from the digest, and also impossible to find two different messages which create the same digest -- thus eliminating the possibility of substituting one message for another while maintaining the same digest.
Another challenge that Alice faces is finding a way to send the digest to the bank securely; when this is achieved, the integrity of the associated message is assured. One way to to this is to include the digest in a digital signature.
Digital signatures are created by encrypting a digest of the message, and other information (such as a sequence number) with the sender's private key. Though anyone may decrypt the signature using the public key, only the signer knows the private key. This means that only they may have signed it. Including the digest in the signature means the signature is only good for that message; it also ensures the integrity of the message since no one can change the digest and still sign it. To guard against interception and reuse of the signature by an intruder at a later date, the signature contains a unique sequence number. This protects the bank from a fraudulent claim from Alice that she did not send the message -- only she could have signed it (non-repudiation).
|
A distinguished name is used to provide an identity in a specific context -- for instance, an individual might have a personal certificate as well as one for their identity as an employee. Distinguished names are defined by the X.509 standard [2], which defines the fields, field names, and abbreviations used to refer to the fields (Table 2).
|
A Certificate Authority may define a policy specifying which distinguished field names are optional, and which are required. It may also place requirements upon the field contents, as may users of certificates. As an example, a Netscape browser requires that the Common Name for a certificate representing a server have a name which matches a regular expression for the domain name of that server, such as *.opengroup.org.
The binary format of a certificate is defined using the ASN.1 notation [ 1], [4]. This notation defines how to specify the contents, and encoding rules define how this information is translated into binary form. The binary encoding of the certificate is defined using Distinguished Encoding Rules (DER), which are based on the more general Basic Encoding Rules (BER). For those transmissions which cannot handle binary, the binary form may be translated into an ASCII form by using base64 encoding. This encoded version is called PEM encoded, when placed between the following lines:
-----BEGIN CERTIFICATE----- -----END CERTIFICATE-----
A number of companies, such as VeriSign [11] have established themselves as certificate authorities. These companies provide the following services:
It is also possible to create your own Certificate Authority. Although risky in the Internet environment, it may be useful within an Intranet where the organization can easily verify the identities of individuals and servers.
The protocol is designed to support a range of choices for specific algorithms used for cryptography, digests, and signatures. This allows algorithm selection for specific servers to be made based on legal, export or other concerns, and also enables the protocol to take advantage of new algorithms. Choices are negotiated between client and server at the start of establishing a protocol session.
There are a number of versions of the SSL protocol, as shown in Table 3.
Version | Source | Description | Browser Support |
---|---|---|---|
SSL 2.0 | published by Netscape [12] | Original protocol | Netscape 3.0, Internet Explorer 3.0 |
SSL 3.0 | Expired Internet Draft [13] | Revisions to prevent specific security attacks, add ciphers, and support certificate chains | Netscape 3.0, Internet Explorer 3.0 |
TLS 2.0 | IETF Draft [15] | Revision of SSL 3.0 | None |
As noted in Table 3, one of the benefits in SSL 3.0 is that it adds support of certificate chain loading. This feature allows a server to pass a server certificate along with issuer certificates to the browser. Chain loading also permits the browser to validate the server certificate, even if Certificate Authority certificates are not installed for the intermediate issuers, since they are included in the certificate chain. SSL 3.0 is the basis for the Transaction Layer Security [TLS] protocol standard, currently in development by the Internet Engineering Task Force (IETF).
The elements of the handshake sequence, as used by the client and server, are listed below:
The first step, Cipher Suite Negotiation, allows the client and server to choose a Cipher Suite supportable by both of them. The SSL3.0 protocol specification defines 31 Cipher Suites. A Cipher Suite is defined by the following components:
One variable in the choice of key exchange methods is digital signatures -- whether or not to use them, and if so, what kind of signatures to use. Signing with a private key provides assurance against a man-in-the-middle-attack during the information exchange used in generating the shared key [8, p516].
The message digest is used to create a Message Authentication Code (MAC) which is encrypted with the message to provide integrity and to prevent against replay attacks.
The encapsulation of SSL control protocols by the record protocol means that if an active session is renegotiated the control protocols will be transmitted securely. If there were no session before, then the NULL cipher suite is used, which means there is no encryption and messages have no integrity digests until the session has been established.
There are two prominent RSA public key packages available:
The RSA Reference implementation, an unsupported source code toolkit from RSA, may be used for freeware and noncommercial applications. Consensus Development Corp used to market a license for commercial use but will no longer do so [17].
Commercial implementation of RSARef[16].
Sample SSL 3.0 implementation from Netscape Communications Corporation [19].
Commercial source code toolkit available from Consensus Development Corp, an enhancement of SSLRef3.0. It requires BSAFE3.0 (from RSA) for use [20].
SSL3.0 compliant toolkit written in Java from Phaos Technology [18].
SSLeay-0.8.1 [21] is a free noncommercial implementation of SSL 2.0 and 3.0. It includes a public key implementation which may be used outside the United States. In the United States, RSARef or BSAFE3.0 must be used due to patent requirements. SSLeay offers an inexpensive way to get started with SSL, and is the subject of the next section.
Install the self-signed certificate in a browser so the browser will recognize server certificates signed by the Certificate Authority. Installing a CA certificate in a browser is somewhat dangerous, unless you trust that certificate and the security of the Certificate Authority. Once installed, the browser accepts any certificate signed by that authority.
To install the CA certificate, load it using HTTP Content-Type application/x-x509-ca-cert. To do this in a manner which does not depend on the server, use the cgi-script (Example 6 in the Appendix), or save the certificate in a file with a cacert suffix and define this suffix in the server configuration file to correspond to the application/x-x509-ca-cert MIME type. For the Apache server, for example, add the line AddType application/x-x509-ca-cert cacert to srm.conf. The certificate and key files must also remain available to SSLeay for the server to be able to use the public key, and the certificate authority to use the private key.
cp newcert.pem $certdir/sitecert.pem cp newkey.pem $certdir/sitekey.pem |
CD $certdir ln -s sitecert.pem `$SSLDIR/bin/x509 -noout -hash < sitecert.pem`.0 |
$SSLDIR/bin/x509 -in CAcert.pem -out CAcert.der -outform DER |
Once these steps have been completed, an SSL connection may be established if the server does not require client certificates.
Different clients such as Netscape Navigator 3.01 Gold and Microsoft Internet Explorer 3.02 support different mechanisms for creating client certificates. In this section, we demonstrate a technique for creating and installing a client certificate for each, using SSLeay certificate routines to sign certificate requests (Back up the Windows NT registry before creating client certificates with Internet Explorer).
The procedure for creating a client certificate involves HTML forms; these forms include client specific features such as special tags or JavaScript programs, and Perl CGI scripts that call SSLeay certificate handling applications. The procedures do not rely on special server features, other than the ability to run Perl CGI scripts. The examples completely automate the process, causing a client certificate to be installed once the request form is submitted. (In a production environment the Certificate Authority would need to perform validation instead of automatically issuing the certificate.)
The general steps for creating a client certificate are as follows:
The HTML form includes fields (containing defaults) for the different distinguished name attributes which are to be used in the client certificate, information allowing the browser to generate a key-pair, and a hidden field used to return this information to the CGI script. This hidden information is browser dependent.
In Netscape Navigator, the form contains an additional FORM tag, the <KEYGEN> tag. This tag creates a key pair, and causes the public key to be returned as a form value when the form is submitted (see Example 12 in the Appendix for source of a sample form). The <KEYGEN> tag causes the browser to display a choice of security grades, depending on the version of Navigator. All of the form information is used by the CGI script to create a certificate request, and this request is used to create a client certificate (See Figure 4).
The Microsoft Internet Explorer HTML form in Figure 5 is more complicated, because Internet Explorer requires a JavaScript (or Visual Basic) program in the page to use an ActiveX control to generate a key pair and create a certificate request. The JavaScript program is downloaded with the HTML page, and called when you press the Submit form button. The program calls the GenReqForm method of the certenr3 ActiveX control, passing it the distinguished name values from the form. The certificate request produced by the control is then loaded into a hidden field of the form, and returned with the form values to the server CGI script (see Example 15 in the Appendix for a sample HTML page).
Both Netscape Navigator and Microsoft Internet Explorer present a sequence of dialogs as part of the key-pair creation. If this sequence is successfully completed then the form will cause the server CGI script to be called with the data from the form. Examples 13 and 15 in the Appendix present two CGI scripts, one for each browser, since the processing is different.
The CGI script for Netscape Navigator (Example 14 in the Appendix) creates a file containing the distinguished name values returned by the form, and a special SPKAC value for the "Signed Public Key And Challenge" generated by Navigator The "ca" command is called with this file as an argument to generate a client certificate. If successful this is returned by the CGI script as an application/x-x509-user-cert HTTP Content-Type. Navigator recognizes this type, and prompts the user for the remainder of the steps required to install the user certificate in the browser. (See Example 13 for the sample CGI script for Netscape Navigator).
The CGI script for processing the Internet Explorer request (Example 16 in the Appendix) is more complicated. It takes the certificate request returned by the form and reformats it to conform to lines of length 72, and then uses the SSLeay ca command to create a signed user certificate based on this request. It then takes the client certificate and combines it with the SSLeay certificate revocation list (CRL) to create a PKCS#7 certificate using the SSLeay crl2pkcs7 utility. The CGI Perl script then dynamically creates an HTML page which includes a JavaScript program to call the AcceptCredentials method of the certenr3 ActiveX control to install the PKCS#7 certificate. This page is designed to take advantage of the JavaScript onLoad method to automatically call the JavaScript program when the page is loaded, so that the certificate is immediately installed without user interaction. (In a production system such automatic installation may not be desired.)
Once the HTML forms and CGI scripts have been written, and the ActiveX control installed, the process of creating client certificates using SSLeay is simple for users.
# Set SSLVerifyClient to: # 0 if no certificate is required # 1 if the client may present a valid certificate # 2 if the client must present a valid certificate # 3 if the client may present a valid certificate but it is not required to # have a valid CA SSLVerifyClient 0 |
The following examples assume you are working in the SSLeay-0.8.1 directory. Change to this directory and build SSLeay following the directions in the INSTALL file. The examples assume that the results of building SSLeay are installed in $SSLDIR (e.g. /opt/dev/ssl).
SSLeay uses a configuration file (ssleay.cnf) which supports named sections, so one configuration file may be used for several purposes. We have modified this file, and show some of these modifications in the examples that follow. Subsequent descriptions in this document show various relevant sections of the log file.
Note that the addition of SSL to the server requires rebuilding the server to include the SSL toolkit and public key sources, and modifying the server configuration to support SSL.
mkdir ${SSLDIR}/certs mkdir ${SSLDIR}/crl mkdir ${SSLDIR}/newcerts mkdir ${SSLDIR}/private echo "01" > ${SSLDIR}/serial touch ${SSLDIR}/index.txt |
#################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = /opt/dev/ssl # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/private/CAcert.pem # The CA certificate serial = $dir/serial # The current serial number crl = $dir/clr/crl.pem # The current CRL private_key = $dir/private/CAkey.pem # The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = x509v3_extensions # The extentions to add to the cert default_days = 365 # how long to certify for default_crl_days= 30 # how long before next CRL default_md = md5 # which md to use. preserve = no # keep passed DN ordering # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_match |
The "req" section of the configuration file is used when creating certificate requests, and supplies defaults and length limits for the various distinguished name fields. In our examples it has the configuration as shown in Example 3.
[ req ] default_bits = 512 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = US countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = MA localityName = Locality Name (eg, city) localityName_default = Cambridge organizationName = Organization Name (eg, company) organizationName_default = The Open Group organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = Research Institute commonName = Common Name (eg, YOUR name) commonName_default = example.osf.org commonName_max = 64 emailAddress = Email Address emailAddress_max = 40 [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20 |
The policy section of the configuration file is used to define different certificate request signing policies. The examples here include the most lenient policy (policy_anything) and a stricter policy (policy_match) which restricts the values of certificate fields. The purpose of a policy is to guide processing of a certificate request.
In Example 4, match means that the value of the field in the request must match the value in the CA certificate, or the request will not be signed. optional means the the field need not be present, while supplied means that it must be present in the certificate request.
# For the CA policy [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = match commonName = supplied emailAddress = optional # For the 'anything' policy # At this point in time, you must list all acceptable 'object' # types. [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional |
$SSLDIR/bin/ssleay req -new -x509 -keyout ${SSLDIR}/private/CAkey.pem \ -out ${SSLDIR}/private/CAcert.pem -config /opt/www/lib/ssleay.cnf Using configuration from /opt/www/lib/ssleay.cnf Generating a 512 bit private key writing new private key to '../private/CAkey.pem' Enter PEM pass phrase: Verifying password - Enter PEM pass phrase: ----- You are about to be asked to enter information that will be incorperated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [US]: State or Province Name (full name) [MA]: Locality Name (eg, city) [Cambridge]: Organization Name (eg, company) [The Open Group]: Organizational Unit Name (eg, section) [Research Institute]: Common Name (eg, YOUR name) [example.osf.org]:Example CA Email Address []:f.hirsch@opengroup.org |
The SSLeay configuration file specifies the location of the CA certificate and key files using the following directives in the ca section:
# The CA certificate certificate = $dir/private/CAcert.pem # The private key private_key = $dir/private/CAkey.pem |
The Apache-SSL server httpd.conf file specifies the CA certificate and key files as follows:
# Set the CA certificate verification path (must be PEM encoded). SSLCACertificatePath /opt/dev/ssl/private # Set the CA certificate verification file (must be PEM encoded). SSLCACertificateFile /opt/dev/ssl/private/CAcert.pem |
<HTML><HEAD><TITLE>Load CA Certificate</TITLE></HEAD><BODY> <H1>Load Certificate Authority Certificate</H1> <FORM ACTION="http://example.osf.org/cgi-bin/loadCAcert.pl" METHOD=post> <TABLE> <TR> <TD>Netscape Browser (PEM Format):</TD> <TD><INPUT TYPE="RADIO" NAME="FORMAT" VALUE="PEM" CHECKED></TD></TR> <TR><TD>Microsoft Browser (DER Format):</TD> <TD><INPUT TYPE="RADIO" NAME="FORMAT" VALUE="DER"></TD></TR> </TABLE> <INPUT TYPE="SUBMIT" VALUE="Load Certificate"> </FORM> </BODY></HTML> |
Example 7 shows the loadCAcert.pl script.
#!/usr/local/bin/perl -T require 5.003; use strict; use CGI; my $cert_dir = "/opt/www/lib/certs"; my $cert_file = "CAcert.pem"; my $query = new CGI; my $kind = $query->param('FORMAT'); if($kind eq 'DER') { $cert_file = "CAcert.der"; } my $cert_path = "$cert_dir/$cert_file"; open(CERT, "<$cert_path"); my $data = join '', |
Install the Certificate Authority certificate in the server certificate database as well as the browser, so that the server is able to locate the public key and validate certificate signatures signed by the certificate authority.
cd $SSLDIR/bin ./ssleay req -new -keyout newkey.pem -out newreq.pem -days 360\ -config /opt/www/lib/ssleay.cnf Using configuration from /opt/www/lib/ssleay.cnf Generating a 512 bit private key writing new private key to 'newkey.pem' Enter PEM pass phrase: Verifying password - Enter PEM pass phrase: ----- You are about to be asked to enter information that will be incorperated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [US]: State or Province Name (full name) [MA]: Locality Name (eg, city) [Cambridge]: Organization Name (eg, company) [The Open Group]: Organizational Unit Name (eg, section) [Research Institute]: Common Name (eg, YOUR name) [example.osf.org]: Email Address []:f.hirsch@opengroup.org Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: |
Example 9 shows the results in a certificate request being created in newreq.pem (in this paper certificates and keys are truncated).
-----BEGIN CERTIFICATE REQUEST----- MIIBXTCCAQcCAQAwgaMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNQTESMBAGA1UE ... Aty7AlcmN9XNwxUk1w0H3hk= -----END CERTIFICATE REQUEST----- |
Example 10 shows a private key for the certificate being created in newkey.pem.
-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,21F13B37A796482C XIY0c7gnv0BpVKkOqXIiqpyONx8xqW67wghzDlKyoOZt9NDcl9wF9jnddODwv9ZU ... QxS2zwfKG1u+YqS1c2v5ecBgqW78DQLvxMkpYU8+xge7vDeoYKE14w== -----END RSA PRIVATE KEY----- |
When this command is executed, it prompts for the certificate authority password, as shown in Example 11.
cat newreq.pem newkey.pem > new.pem ./ssleay ca -policy policy_anything -out newcert.pem \ -config /opt/www/lib/ssleay.cnf -infiles new.pem Using configuration from /opt/www/lib/ssleay.cnf Enter PEM pass phrase: Check that the request matches the signature Signature ok The Subjects Distinguished Name is as follows countryName :PRINTABLE:'US' stateOrProvinceName :PRINTABLE:'MA' localityName :PRINTABLE:'Cambridge' organizationName :PRINTABLE:'The Open Group' organizationalUnitName:PRINTABLE:'Research Institute' commonName :PRINTABLE:'example.osf.org' emailAddress :IA5STRING:'f.hirsch@opengroup.org' Certificate is to be certified until May 12 15:39:33 1998 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated |
The server certificate is created in the file newcert.pem as shown in Example 12 (line-breaks added for issuer and subject).
issuer :/C=US/SP=MA/L=Cambridge/O=The Open Group/OU=Research Institute/ CN=Example CA/Email=f.hirsch@opengroup.org subject:/C=US/SP=MA/L=Cambridge/O=The Open Group/OU=Research Institute/ CN=example.osf.org/Email=f.hirsch@opengroup.org serial :01 Certificate: Data: Version: 0 (0x0) Serial Number: 1 (0x1) Signature Algorithm: md5withRSAEncryption Issuer: C=US, SP=MA, L=Cambridge, O=The Open Group, OU=Research Institute, CN=Example CA/Email=f.hirsch@opengroup.org Validity Not Before: May 12 15:39:33 1997 GMT Not After : May 12 15:39:33 1998 GMT Subject: C=US, SP=MA, L=Cambridge, O=The Open Group, OU=Research Institute, CN=example.osf.org/Email=f.hirsch@opengroup.org Subject Public Key Info: Public Key Algorithm: rsaEncryption Modulus: 00:a1:41:0b:0c:15:53:a5:a5:c4:37:a8:48:f5:79: 39:9f:18:2d:f4:bf:43:34:36:21:23:03:48:a5:65: cb:e2:f8:97:af:9c:7d:df:1e:9b:54:e2:ad:21:e3: 41:3e:54:9a:ce:dc:66:4d:61:59:fb:83:11:36:BF: 9c:3b:47:20:fb Exponent: 65537 (0x10001) Signature Algorithm: md5withRSAEncryption 63:77:e7:f8:aa:0b:90:5e:13:9e:4b:57:f1:0f:22:f9:4c:e3: 7a:AA:ff:a7:8a:2e:3c:1c:a2:92:07:bc:9f:22:3f:2f:13:3f: 60:62:57:a7:74:12:35:28:82:b1:00:2a:36:54:de:67:CD:a2: 9e:24:3e:98:be:14:4e:35:b7:7f -----BEGIN CERTIFICATE----- MIICLTCCAdcCAQEwDQYJKoZIhvcNAQEEBQAwgZ4xCzAJBgNVBAYTAlVTMQswCQYD ... Ij8vEz9gYlendBI1KIKxACo2VN5nzaKeJD6YvhRONbd/ -----END CERTIFICATE----- |
The Apache-SSL httpd.conf file must be modified to specify the server certificate and key files as follows:
# Point SSLCertificateFile at a PEM encoded certificate. SSLCertificateFile /opt/www/lib/certs/sitecert.pem # If the key is not combined with the certificate, use this directive to # point at the key file. If this starts with a '/' it specifies an absolute # path, otherwise it is relative to the default certificate area. That is, # it means "/private/". SSLCertificateKeyFile /opt/www/lib/certs/sitekey.pem |
<HTML><HEAD><TITLE>Create Client Certificate</TITLE></HEAD><BODY> <CENTER><H1>Create Client Certificate</H1></CENTER> <FORM NAME="GenerateForm" ACTION="http://example.osf.org/cgi-bin/ns_key.pl"> <TABLE> <TR><TD>Common Name:</TD><TD> <INPUT TYPE="TEXT" NAME="commonName" VALUE="Client Certificate" SIZE=64> </TD></TR> <TR><TD>email:</TD><TD> <INPUT TYPE="TEXT" NAME="emailAddress" VALUE="f.hirsch@opengroup.org" SIZE=40> </TD></TR> <TR><TD>Organization:</TD><TD> <INPUT TYPE="TEXT" NAME="organizationName" VALUE="The Open Group"> </TD></TR> <TR><TD>Organizational Unit:</TD><TD> <INPUT TYPE="TEXT" NAME="organizationalUnitName" VALUE="Research Institute"> </TD></TR> <TR><TD>Locality (City):</TD><TD> <INPUT TYPE="TEXT" NAME="localityName" VALUE="Cambridge"> </TD></TR> <TR><TD>State:</TD><TD> <INPUT TYPE="TEXT" NAME="stateOrProvinceName" VALUE="MA"> </TD></TR> <TR><TD>Country:</TD><TD> <INPUT TYPE="TEXT" NAME="countryName" VALUE="US" SIZE="2"> </TD></TR> </TABLE> <!-- ' keygen is Netscape specific and will be ignored in ' internet explorer --> <KEYGEN NAME="SPKAC" CHALLENGE="challengePassword"> <INPUT TYPE="SUBMIT" NAME="SUBMIT"> </FORM> <P><HR></BODY></HTML> |
#!/usr/local/bin/perl require 5.003; use strict; use CGI; use File::CounterFile; # module to maintain certificate request counter my $doc_dir = $ENV{'DOCUMENT_ROOT'}; # apache specific location for storage unless($doc_dir) { print "<HTML><HEAD><TITLE>Failure</TITLE></HEAD>"; print "<BODY>DOCUMENT_ROOT not defined</BODY></HTML>"; exit(0); } my $base_dir = $doc_dir; $base_dir =~ s/\/htdocs//; my $SSLDIR = '/opt/dev/ssl'; # define where SSLeay files are located my $CA = "$SSLDIR/bin/ca"; my $CONFIG = "/opt/www/lib/ssleay.cnf"; my $CAPASS = "caKEY"; my $query = new CGI; # get a handle on the form data my $key = $query->param('SPKAC'); # this will fail if not Netscape browser unless($key) { fail("No Key provided $key. Netscape required"); } my $counter = new File::CounterFile("$base_dir/.counter", 1); unless($counter) { fail("Could not create counter: $!"); } my $count = $counter->inc(); my $certs_dir = "$base_dir/certs"; my $req_file = "$certs_dir/cert$count.req"; # certificate request filename my $result_file = "$certs_dir/cert$count.result"; # certificate filename # Explicitly list form fields we must have for certificate creation to work. my @req_names = ('commonName', 'emailAddress', 'organizationName', 'organizationalUnitName', 'localityName', 'stateOrProvinceName', 'countryName', 'SPKAC'); # build the request file open(REQ, ">$req_file") or fail("Could not create request $req_file: $!"); my $name; foreach $name (@req_names) { my $value = $query->param("$name"); $value =~ tr/\n//d; print REQ "$name = $value\n"; } close(REQ); # make sure we actually created a request file unless(-f $req_file) { fail("request missing: $req_file"); } unless(-e $CA) { fail("command missing"); } # ensure that ca command will run # command for processing certificate request, without password my $cmd = "$CA -config $CONFIG -spkac $req_file -out $result_file -days 360"; my $rc = system("$cmd -key $CAPASS 2>errs"); if($rc != 0) { fail("$cmd<P>rc = $rc", "errs"); } open(CERT, "<$result_file") or fail("Could not open $result_file<P>$!"); # send the client certificate to the browser print "Content-Type: application/x-x509-user-cert\n"; my $result = join '', <CERT>; close CERT; my $len = length($result); print "Content-Length: $Len\n\n"; print $result; exit(0); sub fail { my($msg, $errs) = @_; print $query->header; print $query->start_html(-title => "Certificate Request Failure"); print "<H2>Certificate request failed</H2>$MSG<P>"; if($errs) { if(open(ERR, "<errs")) { while(<ERR>) { print "$_<BR>"; } close ERR; } } print $query->dump(); print $query->end_html(); exit(0); } 1; |
The CGI script for Netscape Navigator creates a file containing the distinguished name values returned by the form, and a special SPKAC value for the "Signed Public Key And Challenge (SPKAC)" generated by Navigator. This file contains the information which would be in a certificate request, and is used to generate the certificate.
The SSLeay ca command is called with this file as an argument to generate a client certificate, as follows (Also see ns-ca.doc from the SSLeay documentation) :
$SSLDIR/bin/ca -spkac $req_file -out $result_file -days 360 -key $CAPASS \ -config /opt/www/lib/ssleay.cnf 2>errs |
This example shows the command after some of the Perl processing to create the command has been performed. The $req_file variable contains the name of a unique file in the certs directory used to contain the certificate request information obtained from the CGI form data. The $result_file variable contains the name of a unique file in the certs directory used to contain the certificate. The $CAPASS Perl variable contains the CA key.
If the ca command is successful, then the certificate is returned by the CGI script as an application/x-x509-user-cert HTTP Content-Type. Navigator recognizes this type, and prompts the user for the remainder of the steps required to install the user certificate in the browser.
<HTML><HEAD><TITLE>Client Certificate Request</TITLE></HEAD><BODY> <!-- Use the Microsoft ActiveX control to generate the certificate --> <OBJECT CLASSID="clsid:33BEC9E0-F78F-11cf-B782-00C04FD7BF43" CODEBASE=certenr3.dll ID=certHelper> </OBJECT> <!-- JavaScript or Visual Basic will work. --> <SCRIPT LANGUAGE="JavaScript"> <!--- // this is from JavaScript: The Definitive Guide, since // Microsoft implementation of Math.random() is broken // function random() { random.seed = (random.seed*random.a + random.c) % random.m; return random.seed/random.m; } random.m = 714025; random.a = 4096; random.c = 150889; random.seed = (new Date()).getTime()%random.m; function GenReq () { var sessionId = "a_unique_session_id"; var reqHardware = 0; var szName = ""; var szPurpose = "ClientAuth"; var doAcceptanceUINow = 0; var doAcceptanceUILater = 0; var doOnline = 1; var keySpec = 1; szName = ""; if (document.GenReqForm.commonName.value == "") { alert("No Common Name"); return false; } else szName = "CN=" + document.GenReqForm.commonName.value; if (document.GenReqForm.countryName.value == "") { alert("No Country"); return false; } else szName = szName + "; C=" + document.GenReqForm.countryName.value; if (document.GenReqForm.stateOrProvinceName.value == "") { alert("No State or Province"); return false; } else szName = szName + "; S=" + document.GenReqForm.stateOrProvinceName.value; if (document.GenReqForm.localityName.value == "") { alert("No City"); return false; } else szName = szName + "; L=" + document.GenReqForm.localityName.value; if (document.GenReqForm.organizationName.value == "") { alert("No Organization"); return false; } else szName = szName + "; O=" + document.GenReqForm.organizationName.value; if (document.GenReqForm.organizationalUnitName.value == "") { alert("No Organizational Unit"); return false; } else szName = szName + "; OU=" + document.GenReqForm.organizationalUnitName.value; /* make session id unique */ sessionId = "xx" + Math.round(random() * 1000); sz10 = certHelper.GenerateKeyPair(sessionId, reqHardware, szName, 0, szPurpose, doAcceptanceUINow, doOnline, keySpec, "", "", 1); /* * * The condition sz10 being empty occurs on any condition in which the * credential was not successfully generated. In particular, it occurs * when the operation was cancelled by the user, as well as additional * errors. A cancel is distinguished from other unsuccessful * generations by an empty sz10 and an error value of zero. * */ if (sz10 != "") { document.GenReqForm.reqEntry.value = sz10; document.GenReqForm.sessionId.value = sessionId; } else { alert("Key Pair Generation failed"); return false; } } //---> </SCRIPT> <CENTER><H3>Generate key pair and client certificate request</H3></CENTER> <FORM METHOD=POST ACTION="http://example.osf.org/cgi-bin/ms_key.pl" NAME="GenReqForm" onSubmit="GenReq()"> <TABLE> <TR><TD>Common Name:</TD><TD> <INPUT TYPE=TEXT NAME="commonName" VALUE="Client Certificate" SIZE=64> </TD></TR><TR><TD>Country:</TD><TD> <INPUT TYPE=TEXT NAME="countryName" VALUE="US" SIZE=2> </TD></TR><TR><TD>State or Province:</TD><TD> <INPUT TYPE=TEXT NAME="stateOrProvinceName" VALUE="MA"> </TD></TR><TR><TD>City:</TD><TD> <INPUT TYPE=TEXT NAME="localityName" VALUE="Cambridge"> </TD></TR><TR><TD>Organization:</TD><TD> <INPUT TYPE=TEXT NAME="organizationName" VALUE="The Open Group"> </TD></TR><TR><TD>Organizational Unit:</TD><TD> <INPUT TYPE=TEXT NAME="organizationalUnitName" VALUE="Research Institute"> </TD></TR></TABLE> <INPUT TYPE=HIDDEN NAME="sessionId"> <INPUT TYPE=HIDDEN NAME="reqEntry"> <INPUT TYPE="SUBMIT" name="SUBMIT"> </FORM> </BODY></HTML> |
#!/usr/local/bin/perl require 5.003; use strict; use CGI; use File::CounterFile; # module to maintain certificate request counter my $SSLDIR = '/opt/dev/ssl'; my $CA = "$SSLDIR/bin/ca"; my $CRL2PKCS7 = "$SSLDIR/bin/crl2pkcs7"; my $CONFIG = "/opt/www/lib/ssleay.cnf"; my $CRL = "$SSLDIR/crl/crl.pem"; my $CAPASS = "caKEY"; my $doc_dir = $ENV{'DOCUMENT_ROOT'}; # apache specific location for storage unless($doc_dir) { print "<HTML><HEAD><TITLE>Failure</TITLE></HEAD><BODY>DOCUMENT_ROOT not defined</BODY></HTML>"; exit(0); } my $base_dir = $doc_dir; $base_dir =~ s/\/htdocs//; my $query = new CGI; my $req = $query->param('reqEntry'); unless($req) { fail("No Certificate Request Provided"); } my $counter = new File::CounterFile("$base_dir/Counter", 1); unless($counter) { fail("Count not create counter: $!"); } my $count = $counter->inc(); my $certs_dir = "$base_dir/certs"; my $req_file = "$certs_dir/cert$count.req"; my $result_file = "$certs_dir/cert$count.result"; my $key_file = "$certs_dir/$count.key"; my $debug_file = "$certs_dir/$count.debug"; my $pkcs7_file = "$certs_dir/cert$count.pkcs"; #process request $req =~ tr/\r//d; $req =~ tr/\n//d; # save the certificate request to a file, as received open(REQ, ">$req_file") or fail("Could no save certificate request to file"); print REQ "-----BEGIN CERTIFICATE REQUEST-----\n"; my $result = 1; while($result) { $result = substr($req, 0, 72); if($result) { print REQ "$result\n"; $req = substr($req, 72); } } print REQ "-----END CERTIFICATE REQUEST-----\n"; close(REQ); unless(-e $CA) { fail("$CA command missing"); } my $cmd = "$CA -config $CONFIG -in $req_file -out $result_file -days 360 -policy policy_match"; my $rc = system("$cmd -key $CAPASS 2>errs <<END\ny\NY\nEND"); my $session = $query->param('sessionId'); my $cn = $query->param('commonName'); if($rc != 0) { fail("Certification Request Failed</h2>$cmd<P>rc = $rc<P>\ sessionID = $session<BR>req = $req<BR>", "errs"); } my $cmd = "$CRL2PKCS7 -certfile $result_file -in $CRL -out $pkcs7_file"; my $rc = system("$cmd 2>errs"); open(CERT, "<$pkcs7_file") or fail("Could not open $pkcs7_file<P>$!"); my $certificate = ""; my $started = 0; while(<CERT>) { if(/BEGIN PKCS7/) { $started = 1; next; } if(/END PKCS7/) { last; } if($started) { chomp; $certificate .= "$_"; } } close(CERT); open(MSG, ">MSG") or fail("Could not generate message"); print MSG <<_END_TEXT_; <HTML><HEAD><TITLE>Finish Client Certificate Installation</TITLE> <!-- Use the Microsoft ActiveX control to install the certificate --> <OBJECT CLASSID="clsid:33BEC9E0-F78F-11cf-B782-00C04FD7BF43" CODE=certenr3.dll ID=certHelper> </OBJECT> <SCRIPT LANGUAGE="JavaScript"> <!-- function InstallCert (subject, sessionId, cert) { if( sessionId == "") { alert("No Session id"); return; } if(cert == "") { alert("No Certificate"); return; } var doAcceptanceUILater = 0; result = certHelper.AcceptCredentials(sessionId, cert, 0, doAcceptanceUILater); if(result == "") { var MSG = "Attempt to install " + subject + " client certificate failed"; alert(MSG); return false; } else { var MSG = subject + " client certificate installed"; alert(MSG); } } --> </SCRIPT> </HEAD> <BODY onLoad="InstallCert('$cn', '$session', '$certificate');"> Installing client certificate for $cn<BR> session: $session<BR> </BODY> </HTML> _END_TEXT_ close(MSG); open(RD, "<MSG") or fail("Could not open MSG file"); my $MSG = join '', <RD>; close(RD); my $Len = length($MSG); print "Content-Type: text/html\n"; print "Content-Length: $Len\n\n"; print $MSG; exit(0); sub fail { my($MSG, $errs) = @_; print $query->header; print $query->start_html(-title => "Certificate Request Failure"); print "<H2>Certificate request failed</H2>$MSG<P>"; if($errs) { if(open(ERR, "<errs")) { while(<ERR>) { print "$_<BR>"; } close ERR; } } print $query->dump(); print $query->end_html(); exit(0); } 1; |
It is more complicated than the Netscape Navigator CGI script because it must create an HTML page containing JavaScript which calls an ActiveX control in order to install the client certificate. The CGI script does the following:
The script takes the certificate request generated by Internet Explorer from a hidden form field, and reformats it so that each line in the request is 72 characters long. It then passes this certificate request to the SSLeay ca command to generate a certificate, as follows:
$SSLDIR/bin/ca -in $req_file -out $result_file -days 360 -policy policy_match \ -config /opt/www/lib/ssleay.cnf -key $CAPASS 2>errs |
Once the certificate has been successfully generated, the SSLeay crl2pkcs7 utility is used to combine the certificate with the SSLeay certificate revocation list (CRL) to create a PKCS#7 certificate. This is done using the crl2pkcs7 command as follows:
$SSLDIR/bin/crl2pkcs7 -certfile $result_file -in $CRL -out $pkcs7_file 2>errsThis example shows the command after some of the Perl processing to create the command has been performed. The $result_file variable contains the name of a unique file in the certs directory used to contain the certificate. The $pkcs7_file Perl variable contains the name of a unique file in the certs directory used to contain the result PKCS#7 certificate. The $CRL Perl variable contains $SSLDIR/crl/crl.pem, the file containing the Certificate Authority certificate revocation list.
A certificate revocation list may be created using SSLeay as follows:
$SSLDIR/bin/ca -gencrl -config /opt/www/lib/ssleay.cnf -out $SSLDIR/crl/crl.pem
Once the PKCS#7 certificate has been successfully generated, an HTML page is dynamically generated. This page contains JavaScript code which calls an ActiveX control to install the certificate in the browser. The page in this example is designed to automatically load the certificate once the page has loaded into the browser, by using the JavaScript onLoad command. In a production system such automatic installation may not be desired.
Thanks to Rick Cormier, Ed Frankenberry, Scott Meeks, Tom Titchner, David Weisman and Mary Ellen Zurko for reviewing this article and providing many useful suggestions and insights. Thanks to Doug McEachern for reviewing the Perl code.
Frederick J. Hirsch
The Open Group Research Institute
11 Cambridge Center, Room 418
Cambridge, MA 02142
f.hirsch@opengroup.org
Frederick J. Hirsch is a Principal Research Engineer at The Open Group Research Institute.
His interests include human-computer interaction, artificial intelligence and marketing. He is currently working on software to add functionality and ease of use to the Web by extending browsers, filtering HTTP streams, and implementing server plugins.
Before joining the Open Group Research Institute he wrote software for security trading at the MacGregor group and for network analysis at Bolt, Beranek and Newman (BBN). Prior to that, he worked on the Datakit local network at AT&T Bell Laboratories. He has an MBA from Boston University, a Master's in Computer Engineering from Stanford University, and a Bachelor of Science in Computer Science and Electrical Engineering from MIT.