Netscape Client Certificate Management

A Netscape Navigator client certificate is created by:
  1. Using an HTML form to request a client certificate
  2. Processing the Request using a CGI script

HTML Form to Request Netscape Client Certificate

An HTML form is used to allow the user to fill in the fields for the distinguished name of the subject of the certificate. These fields include the commonName which should be unique. The form also includes an additional FORM tag, the <KEYGEN> tag, which is Netscape specific. This tag causes the browser to generate a key pair, and return the public key as a form value. The <KEYGEN> tag causes the form to display a choice of security grade. The choices available will depend on whether the version of Navigator supports export grade cryptography. An example of the form is:

Sample Netscape Navigator User
		    Certificate Form

The HTML source for this form is as follows:


<HTML><HEAD><TITLE>Create Client Certificate</TITLE></HEAD><BODY>
<CENTER><H1>Create Client Certificate</H1></CENTER>

<FORM NAME="GenerateForm" ACTION="http://example.opengroup.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="ssl_admin@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>

CGI script to process Netscape Client Certificate Request

A special CGI script is used to process the request generated by this form. The script operates as follows:
  1. It creates a request file from the CGI form data
  2. It calls the SSLeay "ca" command to create a certificate using the request file
  3. It loads the certificate into the browser

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.

The sample script source is as follows:

#!/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;

Cookbook