Before creating any Microsoft Internet Explorer Client Certificates it is necessary to create a Certificate Revocation List. Create an empty Certificate Revocation List (CRL) as follows:
cd ${SSLDIR}/crl ${SSLDIR}/bin/ssleay ca -gencrl -out crl.pem -config /opt/www/lib/ssleay.cnf Using configuration from /opt/www/lib/ssleay.cnf Enter PEM pass phrase: [enter CA key here] |
A Microsoft Internet Explorer client certificate is created by:
Although client certificates may be installed in Microsoft Internet Explorer, we are still investigating how to establish an SSL session when they are required. It may be the case that Internet Explorer does not support them. A new release of SSLeay is expected shortly, which may solve this problem.
This form also includes some hidden code which is executed on the browser when the form is submitted. This code, used to call an ActiveX control with the values of the form fields, may be written using JavaScript or VisualBasic. The sample scripts use JavaScript.
The ActiveX control generates a key pair, installs the private key in the browser, and returns the public key as part of a certificate request which is sent to the server in a hidden form field. The program calls the GenReqForm method of the certenr3 ActiveX control, passing it the distinguished name values from the form.
The Internet Explorer form source is:
<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.opengroup.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> |
The CGI script does the following:
$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.
The CGI script is as follows:
#!/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 = "caKEY2"; 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/ //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; |