Marco Pfatschbacher wrote:
I attached a changed basic_csr. I have to modify the patch because we cannot split a dn simply if we find a comma because commas can be part of an attribute value if they are escaped.Hi,I found some bugs while setting up the 0.9.1 release. * PIN-Verification doesn't work. * IE-Request generates a wrong DN for the certificate. I merged this with basic_csr 1.10.2.2 from CVS.
Can somebody check the changed basic_csr? If it is ok then I will commit it to the head-branch and openca_0_9_1 too.
Thanks for the patch
Michael
--
-------------------------------------------------------------------
Michael Bell Email (private): [EMAIL PROTECTED]
Rechenzentrum - Datacenter Email: [EMAIL PROTECTED]
Humboldt-University of Berlin Tel.: +49 (0)30-2093 2482
Unter den Linden 6 Fax: +49 (0)30-2093 2959
10099 Berlin
Germany http://www.openca.org
## OpenCA - Public Web-Gateway Command
## (c) 1998-2001 by Massimiliano Pala and OpenCA Group
##
## File Name: basic_csr
## Brief: basic CSR
## Version: $Revision: 1.11 $
## Description: this script creates a CSR
## Parameters:
sub cmdBasic_csr {
my $minPinLength = getRequired('minpinlength');
my $OPERATION = $query->param ('operation');
our $type = $query->param ('CSR_TYPE');
## check the submitted data to be consistent
if ( not checkBasic_csr($OPERATION) ) {
my $formFile = getRequired('basic_csrstartform');
## build the data
## load the normal DN and build the html-elements
my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
my @user_dn = (); # array with the user fields
## scan every element of the DN
for (my $i=1; $i <= scalar @elements; $i++) {
my $html;
my $input_check = "";
if ( defined $config->getParam
("DN_TYPE_".$type."_ELEMENT_".$i."_SELECT") ) {
my @h = getRequiredList
("DN_TYPE_".$type."_ELEMENT_".$i."_SELECT");
$html = $query->newInput (
-regx => 'MIXED',
-intype => 'popup_menu',
-name => 'DN_VALUE_'.$i,
-value => \@h);
} elsif ( $elements [$i-1] =~ /EMAIL/i ) {
$html = $query->newInput (
-regx => 'EMAIL',
-intype => 'textfield',
-size => 30,
-name => 'DN_VALUE_'.$i,
-check => 'fill',
-minlen => 3,
-value => $query->param ('DN_VALUE_'.$i));
} else {
$html = $query->newInput (
-regx => 'LATIN1_LETTERS',
-intype => 'textfield',
-size => 30,
-name => 'DN_VALUE_'.$i,
-check => 'fill',
-minlen => 3,
-value => $query->param ('DN_VALUE_'.$i));
}
push ( @user_dn, [
getRequired ("DN_TYPE_".$type."_ELEMENT_".$i),
$html
]);
}
my $html_ra = $query->newInput (
-regx=>'LETTERS',
-intype=>'popup_menu',
-name=>'ra',
-values=>
[getRequiredList ('RegistrationAuthority')]);
my $html_role = $query->newInput (
-regx=>'LETTERS',
-intype=>'popup_menu',
-name=>'role',
-values=>[loadRoles()]);
my $html_passwd1 = $query->newInput (
-regx=>'*',
-intype=>'password_field',
-name=>'passwd1',
-check=>'all',
-size=>16,
-minlen=>$minPinLength,
-value=>$query->param('passwd1'));
my $html_passwd2 = $query->newInput (
-regx=>'*',
-intype=>'password_field',
-name=>'passwd2',
-check=>'fill',
-size=>16,
-minlen=>$minPinLength,
-value=>$query->param('passwd2'));
my $html_bits = $query->newInput (
-regx=>'NUMERIC',
-intype=>'popup_menu',
-name=>'bits',
-values=>
[getRequiredList('Basic_CSR_Keysizes')]);
if( not $form = $tools->getFile( $formFile )) {
configError(i18nGettext ("Cannot load file __FILE__!", "__FILE__",
$formFile));
}
my $header = $query->start_html(-title=>'Certificate Request',
-author=>'Massimiliano Pala <[EMAIL PROTECTED]>',
-BGCOLOR=>'white',
-TEXT=>'#264A8D',
-LINK=>'#A80C00',
-VLINK=>'#730000',
-ALINK=>'#000088');
$header .= $query->start_multipart_form(-method=>'POST',
-action=>$query->self_url());
##// Let's buid the page
my $page = $header . $form;
## add the configured RDNs
my $table;
foreach my $item (@user_dn) {
my @h = @{$item};
$table .= "<tr>\n".
"<th align=right>".$h[0].":</th>\n".
"<td align=left>".$h[1]."</td>\n".
"</tr>\n"
;
}
$page = $query->subVar($page,"\@TABLE\@",$table);
## add the default-fields
$page = $query->subVar($page,'@PASSWD1@', $html_passwd1);
$page = $query->subVar($page,'@PASSWD2@', $html_passwd2);
$page = $query->subVar($page,'@BITS@', $html_bits);
$page = $query->subVar($page,'@RA@', $html_ra);
$page = $query->subVar($page,'@ROLE@', $html_role);
$page = $query->subVar($page,'@CSR_TYPE@', $type);
$page = $query->subVar($page,'@MISTAKE@', $errstruct);
## for header-requests
$page = $query->subVar($page, '@_KEY@', $query->param ('key'));
print "$page";
return 1;
} elsif ( $OPERATION =~ /client-filled-form/i ) {
my $formFile2 = getRequired("DN_TYPE_".$type."_KEYGEN_SHEET");
## load the static data
my $PASSWD = $query->param('passwd1');
my $BITS = $query->param('bits');
my $RA = $query->param('ra');
my $ROLE = $query->param('role');
## load the dynamic data
## load the normal DN and build the html-elements
my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
my $dn = basic_csr_buildDN();
my $ms_dn = basic_csr_buildDN_MS();
my $form;
if( not $form = $tools->getFile( $formFile2 )) {
configError( i18nGettext ("Can't load __FILE__", "__FILE__",
$formFile2)) if ( not $form );
}
if (getRequired("DN_TYPE_".$type."_KEYGEN_MODE") =~ /IE/i)
{
$page = $form;
} else {
$header = $query->start_html (-title => 'Certificate Request',
-author => 'Massimiliano Pala
<[EMAIL PROTECTED]>',
-BGCOLOR => 'white',
-TEXT => '#264A8D',
-LINK => '#A80C00',
-VLINK => '#730000',
-ALINK => '#000088');
$header.= $query->start_form (-method=>'POST',
-action=>$query->referer() );
##// Let's buid the page
$page = $header . $form;
}
## Substitute the parameters with their true values
my $hidden = "";
my $table = "";
for (my $i=1; $i <= scalar @elements; $i++) {
$hidden .= "<INPUT TYPE=HIDDEN NAME=\"DN_VALUE_".$i."\" ".
"VALUE=\"" .$query->param("DN_VALUE_".$i)."\">\n";
$table .= "<tr bgcolor=#ffffff>\n".
"<th align=right>".getRequired
("DN_TYPE_".$type."_ELEMENT_".$i).":</th>\n".
"<td align=left>".$query->param("DN_VALUE_".$i)."</td>\n".
"</tr>\n"
;
}
$page = $query->subVar($page,'@PASSWD@', $PASSWD);
$page = $query->subVar($page,'@BITS@', $BITS);
$page = $query->subVar($page,'@CSR_TYPE@', $type);
$page = $query->subVar($page,'@TABLE@', $table);
$page = $query->subVar($page,'@HIDDEN@', $hidden);
$page = $query->subVar($page,'@RA@', $RA);
$page = $query->subVar($page,'@ROLE@', $ROLE);
$page = $query->subVar($page,'@DN@', $dn);
$page = $query->subVar($page,'@MS_DN@', $ms_dn);
## for header-requests
$page = $query->subVar($page, '@_KEY@', $query->param ('key'));
print $page;
return 1;
} elsif ( $OPERATION =~ /client-confirmed-form/i) {
my $successPage = getRequired('basic_csrsuccesspage');
my $dn = basic_csr_buildDN();
## load the static data
my $RA = $query->param('ra');
my $ROLE = $query->param('role');
## Status of FORM, possible values (in this order):
## 'client-filled-form' or 'client-confirmed-form'
my $PASSWD = $query->param('passwd1');
my $key = "";
my $req = undef;;
my $bits = $query->param('bits');
my $alg = $query->param('alg');
if (getRequired ("DN_TYPE_".$type."_BODY") =~ /(Y|YES)/i) {
if (getRequired("DN_TYPE_".$type."_KEYGEN_MODE") =~ /SPKAC/i )
{
my $spkac = "";
## load the normal DN and build the html-elements
my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
## scan every element of the DN
for (my $i=1; $i <= scalar @elements; $i++)
{
my $dn_element = $query->param ('DN_VALUE_'.$i);
$dn_element =~ s/\\/\\\\/g;
$dn_element =~ s/\//\\\//g;
$dn_element =~ s/,/\\,/g;
$dn_element =~ s/=/\\=/g;
$spkac .= $elements [$i-1]." = ".$dn_element."\n";
}
## load the base dn
my @base = getRequiredList ("DN_TYPE_".$type."_BASE");
for (my $i=1; $i <= scalar @base; $i++) {
if (getRequired ("DN_TYPE_".$type."_BASE_".$i) ne "")
{
$spkac .= $base [$i-1]." = ".getRequired
("DN_TYPE_".$type."_BASE_".$i)."\n";
}
}
## add the key
## Delete ^M
my $NEWKEY = $query->param('newkey');
$NEWKEY =~ s/\015|\n//g;
$spkac .= "SPKAC = $NEWKEY\n";
if( not $req = new OpenCA::REQ (SHELL => $cryptoShell,
DATA => $spkac,
INFORM => "SPKAC" )) {
generalError( gettext("Error while creating REQ object.").
" ".$OpenCA::REQ::errval, $OpenCA::REQ::errno );
}
} elsif (getRequired("DN_TYPE_".$type."_KEYGEN_MODE") =~ /IE/i ) {
my $ie = "";
$ie .= "-----BEGIN CERTIFICATE REQUEST-----\n";
$ie .= $query->param('request');
$ie .= "-----END CERTIFICATE REQUEST-----";
if( not $req = new OpenCA::REQ( SHELL => $cryptoShell,
DATA => $ie,
INFORM => "PEM" )) {
generalError( gettext ("Error while creating REQ object.").
" ".$OpenCA::REQ::errval, $OpenCA::REQ::errno );
};
} else { ## this is server mode
## generate keypair
my $TempDir = getRequired( 'tempdir' );
## Get the parameters
my $keyFile = "$TempDir/key_${$}.pem";
## create the key
if( not $cryptoShell->genKey(
BITS => $bits,
OUTFILE => $keyFile,
ALGORITHM => $alg,
PASSWD => $PASSWD ) ) {
generalError (gettext ("Cannot create keypair!").
"<br>".$OpenCA::OpenSSL::errno,
$OpenCA::OpenSSL::errno);
}
## change to pkcs#8
$key = $cryptoShell->dataConvert (
DATATYPE => "KEY",
INFORM => "PEM",
OUTFORM => "PKCS8",
INPASSWD => $PASSWD,
OUTPASSWD => $PASSWD,
INFILE => $keyFile );
if ( not $key ) {
generalError ( gettext ("Cannot convert key to PKCS#8!"));
}
## generate PKCS#10 request
$req = new OpenCA::REQ (
SHELL => $cryptoShell,
KEYFILE => $keyFile,
SUBJECT => $dn,
PASSWD => $PASSWD,
FORMAT => "PEM");
if (not $req) {
generalError ( i18nGettext ("Cannot create request!
<br>\n(__ERRVAL__)",
"__ERRVAL__",
$OpenCA::REQ::errval),
$OpenCA::REQ::errno);
}
unlink ($keyFile);
}
}
## compose request
my $tmp = "-----BEGIN HEADER-----\n";
if (getRequired ("DN_TYPE_".$type."_BODY") =~ /(Y|YES)/i) {
if (getRequired ("DN_TYPE_".$type."_KEYGEN_MODE") =~ /SPKAC/i)
{
$tmp .= "TYPE = SPKAC\n";
} elsif (getRequired ("DN_TYPE_".$type."_KEYGEN_MODE") =~ /IE/i) {
$tmp .= "TYPE = IE\n";
} else {
$tmp .= "TYPE = PKCS#10\n";
}
} else {
$tmp .= "TYPE = HEADER\n";
}
## build serial
if ( (getRequired ('CgiServerType') =~ /(RA|CA)/i) and $query->param ('key') )
{
$tmp .= "SERIAL = ".$query->param ('key')."\n";
} else {
my $req_elements = $db->elements (DATATYPE => "REQUEST");
if ((not defined $req_elements) or ($req_elements < 0)) {
generalError ( gettext ("Database fails during counting the already
existing requests!"), 669);
} else {
$req_elements++;
}
my $new_serial = ($req_elements << getRequired ("ModuleShift")) |
getRequired ("ModuleID");
$tmp .= "SERIAL = ".$new_serial."\n";
}
$tmp .= "NOTBEFORE = " . $tools->getDate() . "\n";
my $PASSWD = $query->param('passwd1');
if ($PASSWD) {
my $pin_digest = $cryptoShell->getDigest (
DATA => $PASSWD,
ALGORITHM => "sha1");
if (not $pin_digest) {
generalError ( gettext ("OpenSSL fails during the calculation of the
hash from the passphrase!"), 670);
}
$tmp .= "PIN = $pin_digest\n";
}
$tmp .= "RA = " . $query->param('ra') . "\n";
$tmp .= "ROLE = $ROLE\n";
if (getRequired ("DN_TYPE_".$type."_BODY") !~ /(Y|YES)/i) {
$tmp .= "SUBJECT = $dn\n";
$tmp .= "KEY_ALGORITHM = ".$alg."\n";
$tmp .= "KEY_BITS = ".$bits."\n";
}
$tmp .= "-----END HEADER-----\n";
if (getRequired ("DN_TYPE_".$type."_BODY") =~ /(Y|YES)/i) {
$tmp .= $req->getBody();
$tmp .= $key if ($key);
}
my $new_req;
if( not $new_req = new OpenCA::REQ( SHELL=>$cryptoShell, DATA=>$tmp) ) {
generalError( i18nGettext ("Internal Request Error (__ERRVAL__)",
"__ERRVAL__",
$OpenCA::REQ::errval),
$OpenCA::REQ::errno );
}
if( not $db->storeItem(
DATATYPE => PENDING_REQUEST,
OBJECT => $new_req,
INFORM => PEM,
MODE => "INSERT" )) {
generalError( gettext ("Error while storing REQ in database!").
" ".$db->errval(), $db->errno() );
};
## remove temporary files
$tools->deleteFiles (DIR => $TempDir, FILTER => "key_${$}.pem");
if( not $ret = $tools->getFile( $successPage )) {
configError( i18nGettext ("Can't load __FILE__", "__FILE__",
$successPage)) if ( not $ret );
}
$ret = $query->subVar($ret,"\@CSR_SERIAL\@",$new_req->getSerial());
print "$ret";
return 1;
}
die gettext ("ERROR: Status Unkown!");
}
## we support the following state:
##
## 1. ""
## 2. client-filled-form
## 3. client-confirmed-form
sub checkBasic_csr {
my $status = $_[0];
my $mistake = "";
my $minPinLength = getRequired('minpinlength');
## general check
## which type of request do we have to handle?
my @types = getRequiredList ('DN_TYPES');
if ( not grep (/^${type}$/i, @types) ) {
## starting browser detection
my $USER_AGENT = $query->user_agent();
## Get the Browser Name and the Version
if ( $USER_AGENT =~ /MSIE/i )
{
$type = "IE";
} elsif ( $USER_AGENT =~ /Mozilla/i ) {
$type = "SPKAC";
} else {
$type = "BASIC";
}
}
## second level check
if ($status)
{
## load the normal DN and build the html-elements
my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
## scan every element of the DN
for (my $i=1; $i <= scalar @elements; $i++)
{
if ( $elements [$i-1] =~ /EMAIL/i )
{
$mistake .= $query->newInputCheck({
-regx => 'EMAIL',
-intype => 'textfield',
-size => 30,
-name => 'DN_VALUE_'.$i,
-check => 'fill',
-minlen => 3});
} else {
$mistake .= $query->newInputCheck({
-regx => 'LATIN1_LETTERS',
-intype => 'textfield',
-size => 30,
-name => 'DN_VALUE_'.$i,
-check => 'fill',
-minlen => 3});
}
}
if ($query->param ('passwd1'))
{
$mistake .= $query->newInputCheck ({
-regx=>'*',
-intype=>'password_field',
-name=>'passwd1',
-check=>'all',
-size=>16,
-minlen=>$minPinLength});
}
#quick hack, to get a nicer output...
my $tmp = "<BR>|OK|[\ \(\)]";
$mistake =~ s/$tmp//g;
if (defined $query->param ('passwd1') and defined $query->param ('passwd2'))
{
if ($query->param ('passwd1') ne $query->param ('passwd2'))
{
$mistake .= "<BR>";
$mistake .= gettext ("Two different pin inserted. Please go
<B><I>back</I></B> and correct the error.");
}
}
## Checking for Boguous Systems ( POST method, REFERER doc, etc... )
my $METHOD = $query->request_method();
if ($METHOD !~ /POST/i) {
$mistake .= gettext ("This command can only be used with from which are
using POST as METHOD!");
};
} else {
return undef;
}
if ($mistake)
{
$errstruct = $mistake;
return undef;
} else {
return 1;
}
}
sub basic_csr_buildDN {
my $dn = "";
## load the normal DN and build the html-elements
my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
## scan every element of the DN
for (my $i=1; $i <= scalar @elements; $i++)
{
my $dn_element = $query->param ('DN_VALUE_'.$i);
$dn_element =~ s/\\/\\\\/g;
$dn_element =~ s/\//\\\//g;
$dn_element =~ s/,/\\,/g;
$dn_element =~ s/=/\\=/g;
$dn .= $elements [$i-1]."=".$dn_element.",";
}
## load the base dn
my @base = getRequiredList ("DN_TYPE_".$type."_BASE");
for (my $i=1; $i <= scalar @base; $i++) {
if (getRequired ("DN_TYPE_".$type."_BASE_".$i) ne "")
{
$dn .= $base [$i-1]."=".getRequired ("DN_TYPE_".$type."_BASE_".$i).",";
}
}
## remove trailing ","
$dn =~ s/,$//;
return $dn;
}
sub basic_csr_buildDN_MS {
my $dn = "";
## the DN must be build in the reverse order
## load the base dn
my @base = getRequiredList ("DN_TYPE_".$type."_BASE");
for (my $i=scalar @base; $i > 0; $i--) {
if (getRequired ("DN_TYPE_".$type."_BASE_".$i) ne "")
{
$dn .= $base [$i-1]."=".getRequired ("DN_TYPE_".$type."_BASE_".$i).",";
}
}
## load the normal DN and build the html-elements
my @elements = getRequiredList ("DN_TYPE_".$type."_ELEMENTS");
## scan every element of the DN
for (my $i=scalar @elements; $i > 0; $i--)
{
my $dn_element = $query->param ('DN_VALUE_'.$i);
$dn_element =~ s/\\/\\\\/g;
$dn_element =~ s/\//\\\//g;
$dn_element =~ s/,/\\,/g;
$dn_element =~ s/=/\\=/g;
$dn .= $elements [$i-1]."=".$dn_element.",";
}
## remove trailing ","
$dn =~ s/,$//;
## microsoft uses E for emailaddress
$ms_dn =~ s/,\s*emailAddress\s*=/,E=/i;
$ms_dn =~ s/^\s*emailAddress\s*=/E=/i;
return $dn;
}
1;
