Hello together,
attached you find a modified version of Martins Scep script.
Modifications where done in "sub scepStoreRequest" around line 440 for serving Cisco SCEP requests. The Script parses the DN from the request, reorders the elements and exrtacts IP and FQDN and puts it in the Subject Alternative Name....
Discussion: I had an idea to make this more general:
The main work does the regexp that picks some data from the DN and assembles this to some new data.
I think we can easily move the DN and the assemble-line to the config, so this must not be hardcoded in the Script.
To allow more than one transformation alternative, we can make up a kind of Array of rules...
Any comments on this ?
Oliver
-- Diese Nachricht wurde digital unterschrieben oliwel's public key: http://www.oliwel.de/oliwel.crt Basiszertifikat: http://www.ldv.ei.tum.de/page72
## OpenCA - Public Web-Gateway Command ## (c) 2002-2003 by Massimiliano Pala and OpenCA Group ## (c) Copyright 2004 The OpenCA Project ## ## File Name: scepPKIOperation ## Version: $Revision : 0.2 $ ## Brief: manage SCEP certificates/crl operations ## Description: supports handling of SCEP commands and PKI Operations ## for enrollment. ## Parameters: operation, message # # 2005-01-25 Martin Bartosch <[EMAIL PROTECTED]> # - new features: configurable policy allows to prevent SCEP operation # from new enrollment and/or renewal # - configurable default RA and certificate role for new enrollments # - re-inserts requests with same RA and Role of old certificate if the # same DN (configurable match) already exists # - fixed problems with error handling, added logging # - only valid certificates are now returned to the client # - introduced additional scepCheckRequest method that enforces # the configured policy #
#use strict; use Data::Dumper; our ($plain_csr, $ReqRole, $ReqRA); sub cmdScepPKIOperation { my ( $operation, $message, $cert ); my ( $h, $i, $p7_file, $csr_file, $cert_file, $reccert_file, $response ); my ( $scep_cmd, $scep_pwd, $scep_cert, $scep_key, $scep_crl, $scep_failinfo ); my ($deep_debug, $ScepAllowEnrollment, $ScepAllowRenewal, $ScepDefaultRole, $ScepRenewalRDNMatch, $ScepDefaultRA); my ( $scep_tid ); # SCEP command debug $deep_debug = 1; ##// Let's get parameters $operation = $query->param('operation'); $message = $query->param('message'); ## setup local vars $scep_cmd = getRequired ("scepPath"); $scep_pwd = getRequired ("scepRAPasswd"); $scep_cert = getRequired ("scepRACert"); $scep_key = getRequired ("scepRAKey"); $scep_crl = getRequired ("CRLDir") . "/cacrl.pem"; foreach (qw(ScepAllowEnrollment ScepAllowRenewal ScepDefaultRole ScepDefaultRA ScepRenewalRDNMatch)) { my $val = getRequired($_); eval "\$$_ = \$val"; } $p7_file = getRequired ( 'tempdir' ) . "/scep_pkiOp_$$.p7"; $csr_file = getRequired ( 'tempdir' ) . "/scep_pkiOp_$$.csr"; $cert_file = getRequired ( 'tempdir' ) . "/scep_pkiOp_$$.crt"; $reccert_file = getRequired ( 'tempdir' ) . "/scep_client_$$.crt"; ## insert newlines every 64 characters ## to avoid crashs because of too long lines my $h_message = $message; $message = ""; while ($h_message) { $message .= substr ($h_message, 0, 64)."\n"; $h_message = substr ($h_message, 64, length ($h_message)); } $message =~ s/[\n\r][\n\r]*/\n/g; $message =~ s/\n$//; $tools->saveFile( FILENAME=>$p7_file, DATA=>$message ); $errno = 0; $errval = ""; $scep_failinfo = ""; ## We have to: ## 1. parse the request ## 2. check the requirements ## 3. build the response ## 4. send back the reply ## Send the response to the SCEP client print "Content-type: application/x-pki-message\n\n"; ## ## common actions per scep-msg ## ## get transid # FIXME: error handling scepGetTID() or goto BAILOUT; ## get recipient cert # FIXME: error handling scepGetReqClientCert() or goto BAILOUT; ## first we need the message type to know what to do debug_cmds("cmdScepPKIOperation: execute1: $scep_cmd -in $p7_file -noout -print_msgtype"); open OUT, "-|", "$scep_cmd -in $p7_file -noout -print_msgtype 2>&1" or debug_cmds("Open error"); my $msgtype = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); debug_cmds("msgtype: $msgtype") if ($deep_debug); ## FIXME: is this correct perl!? $_ = $msgtype; SWITCH: { if (/PKCSReq/i) { ## get request last SWITCH if not scepGetRequest(); ## check request if (not scepCheckRequest()) { $scep_failinfo = "badRequest"; scepAnswerFailure(); last SWITCH; } ## store request last SWITCH if not scepStoreRequest(); ## send pending answer last SWITCH if not scepAnswerPending(); } if (/GetCertInitial/i) { ## search the request for issued my @reqs = $db->searchItems (DATATYPE => "ARCHIVED_REQUEST", SCEP_TID => $scep_tid); my $response; if (scalar @reqs) { ## SUCCESS ## search the highest CSR my $csr = undef; foreach my $req (@reqs) { $csr = $req if (not $csr or $csr->getSerial() < $req->getSerial()) } ## load certificate via CSR_SERIAL my @certs = $db->searchItems (DATATYPE => "VALID_CERTIFICATE", CSR_SERIAL => $csr->getSerial()); my $cert = $certs[0]; $tools->saveFile (FILENAME => $cert_file,DATA=>$cert->getPEM()); ## FIXME: check if Cert has been revoked, than send fail-msg instead ## no openca-error!! just scep-failure-msg for client ## build response $ENV{pwd} = $scep_pwd; debug_cmds("cmdScepPKIOperation: execute2: $scep_cmd -new -signcert $scep_cert -msgtype CertRep -status SUCCESS -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -issuedcert $cert_file -outform DER"); open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status SUCCESS -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -issuedcert $cert_file -outform DER 2>&1" or debug_cmds("Open error"); $response = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); delete $ENV{pwd}; debug_cmds("response: $response") if ($deep_debug); print $response; last SWITCH; } ## search the request for deleted my @reqs = $db->searchItems (DATATYPE => "DELETED_REQUEST", SCEP_TID => $scep_tid); if (scalar @reqs) { # we found a deleted request - so send failur msg $scep_failinfo = "badRequest"; scepAnswerFailure(); last SWITCH; } else { scepAnswerPending(); last SWITCH; } } if (/GetCert/i) { ## FIXME: to be implemented - so long we send an failure msg $scep_failinfo = "badRequest"; scepAnswerFailure(); last SWITCH; ## only success or failure possible, no pending ## same reply like sending out cert } if (/GetCRL/i) { ## send crl message ## build response $ENV{pwd} = $scep_pwd; debug_cmds("cmdScepPKIOperation: execute3: $scep_cmd -new -signcert $scep_cert -msgtype CertRep -status SUCCESS -crlfile $scep_crl -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER"); open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status SUCCESS -crlfile $scep_crl -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER 2>&1" or debug_cmds("Open error"); $response = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); delete $ENV{pwd}; print $response; debug_cmds("response: $response") if ($deep_debug); last SWITCH; } } BAILOUT: if ($scep_failinfo) { ## build error response $ENV{pwd} = $scep_pwd; debug_cmds("cmdScepPKIOperation: execute4: $scep_cmd -new -signcert $scep_cert -msgtype CertRep -status FAILURE -failinfo $scep_failinfo -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER"); open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status FAILURE -failinfo $scep_failinfo -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER 2>&1" or debug_cmds("Open error"); $response = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); delete $ENV{pwd}; print $response; debug_cmds("response: $response") if ($deep_debug); } ## output error if necessary if ($errno) { ## do openca error stuff generalError ($errval, $errno); } ## cleanup tmp-files and passphrases delete $ENV{pwd}; unlink $p7_file if -e $p7_file; unlink $csr_file if -e $csr_file; unlink $cert_file if -e $cert_file; unlink $reccert_file if -e $reccert_file; ## return success for storage commit return 1; ## ## Functions for MSG-Evaluation ## sub scepGetTID { debug_cmds("cmdScepPKIOperation: execute5: $scep_cmd -in $p7_file -noout -print_transid"); open OUT, "-|", "$scep_cmd -in $p7_file -noout -print_transid 2>&1" or debug_cmds("Open error"); $scep_tid = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); debug_cmds("tid: $scep_tid") if ($deep_debug); $scep_tid =~ s/.*=//; $scep_tid =~ s/[\n\r]//g; if (not $scep_tid) { $errno = 723705; $errval = gettext ("Cannot extract the transaction ID from the SCEP message!"); ## FIXME: appropriate Error Reason? $scep_failinfo = "badMessageCheck"; return undef; } return 1; } ## end of scepGetTID sub scepGetReqClientCert { $ENV{pwd} = $scep_pwd; debug_cmds("cmdScepPKIOperation: execute_bt: $scep_cmd -in $p7_file -keyfile $scep_key -passin env:pwd -noout -print_scert > $reccert_file"); `$scep_cmd -in $p7_file -keyfile $scep_key -passin env:pwd -noout -print_scert > $reccert_file`; debug_cmds("Backtick expansion returned error code $?"); delete $ENV{pwd}; if ($? != 0) { $errno = 723709; $errval = gettext ("There is a problem with the scep program."); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badMessageCheck"; return undef; } return 1; } ## end of scepGetReqClientCert sub scepGetRequest { $ENV{pwd} = $scep_pwd; debug_cmds("cmdScepPKIOperation: execute6: $scep_cmd -in $p7_file -keyfile $scep_key -passin env:pwd -noout -print_req"); open OUT, "-|", "$scep_cmd -in $p7_file -keyfile $scep_key -passin env:pwd -noout -print_req 2>&1" or debug_cmds("Open error"); $plain_csr = join '', <OUT>; close OUT; my $rc = $?; debug_cmds("Pipe returned error code $rc"); delete $ENV{pwd}; debug_cmds("csr: $plain_csr") if ($deep_debug); if ((not $plain_csr) or ($rc != 0)) { $errno = 723701; $errval = gettext ("Cannot extract the request from the SCEP message!"); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badRequest"; return undef; } return 1; } ## end of scepGetRequest # validate incoming request # global variable $plain_csr is expected to contain the client request sub scepCheckRequest { my $req; if(not $req = new OpenCA::REQ(SHELL => $cryptoShell, GETTEXT => \&i18nGettext, DATA => $plain_csr)) { $errno = 723717; $errval = gettext ("Internal Request Error"); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badRequest"; return undef; } my $DN = $req->getParsed()->{"DN"}; debug_cmds("scepCheckRequest() requester DN: $DN"); # split DN into individual RDNs. This regex splits at the ',' # character if it is not escaped with a \ (negative look-behind) my @reqrdn = split(/(?<!\\),\s*/, $DN); my $index = 0; my @querydn; my $querydn; if ($ScepRenewalRDNMatch ne "") { my @rdntomatch = split(/,\s*/, $ScepRenewalRDNMatch); # iterate through all RDNs that must match from our DN foreach (@rdntomatch) { if ($reqrdn[$index] =~ /^$_=/) { push(@querydn, $reqrdn[$index]); $index++; } else { push(@querydn, "%"); } } $querydn = join(",", @querydn); } else { $querydn = join(",", @reqrdn); } $querydn .= "%" unless ($querydn eq ""); debug_cmds("scepCheckRequest() DB search expression DN: $querydn"); my @list = $db->searchItems(DATATYPE => "VALID_CERTIFICATE", DN => $querydn); debug_cmds("DB DN search returned:") if ($deep_debug); debug_cmds(join("\n", Dumper @list)) if ($deep_debug); if (not @list or $#list == -1) { # matching certificate was not found (initial enrollment) if ($ScepAllowEnrollment =~ /yes/i) { debug_cmds("scepCheckRequest: initial enrollment allowed"); $ReqRole = $ScepDefaultRole; $ReqRA = $ScepDefaultRA; return 1; } else { # reject it if initial enrollment is not allowed debug_cmds("scepCheckRequest: initial enrollment is not allowed"); return undef; } } else { if ($ScepAllowRenewal =~ /yes/i) { # at least one matching valid certificate already exists # identify it and extract the original cert date to be # inserted into the new request debug_cmds("scepCheckRequest: renewal allowed"); $ReqRole = $ScepDefaultRole; $ReqRA = $ScepDefaultRA; my $originalCert = $list[0]; # determine the matching certificate from the certificate # list if ($#list > 0) { # more than one single entry was found by the db search debug_cmds("scepCheckRequest: multiple certificates matched this request, not yet implemented"); return undef; #foreach my $entry (@list) { # my $cn = $entry->getParsed()->{DN_HASH}->{CN}[0]; # debug_cmds("scepCheckRequest: found certificate CN $cn"); #} } if (defined $originalCert and $originalCert) { $ReqRole = $originalCert->getParsed()->{HEADER}->{ROLE}; my $originalCSRSerial = $originalCert->getParsed()->{HEADER}->{CSR_SERIAL}; debug_cmds("scepCheckRequest: using original role $ReqRole"); debug_cmds("scepCheckRequest: original CSR: $originalCSRSerial"); my $req = $db->getItem(DATATYPE=>"REQUEST", KEY => $originalCSRSerial); if (defined $req and $req) { $ReqRA = $req->getParsed()->{HEADER}->{RA}; debug_cmds("scepCheckRequest: found original request, using original RA $ReqRA"); } else { # use default if CSR cannot be found $ReqRA = $ScepDefaultRA; debug_cmds("scepCheckRequest: original request was not found, using default RA $ReqRA"); } } } else { # reject it if renewal is not allowed debug_cmds("scepCheckRequest: renewal is not allowed"); return undef; } } return 1; } ## ## Functions for SCEP-Processing ## sub scepStoreRequest { ## never store a request twice my @list = $db->searchItems (DATATYPE => "REQUEST", SCEP_TID => $scep_tid); if (not @list or not scalar @list) { ## create the cisco stuff my $dnreq; if(not $dnreq = new OpenCA::REQ(SHELL => $cryptoShell, GETTEXT => \&i18nGettext, DATA => $plain_csr)) { $errno = 723717; $errval = gettext ("Internal Request Error"); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badRequest"; return undef; } my $DN = $dnreq->getParsed()->{"DN"}; # find the fancy unstructured Fields... my $cisco_header; if ($DN =~ /unstructuredName=(.+)\+unstructuredAddress=([0-9\.]*),(.*)$/) { my $ip = $2; my $fqdn = $1; my $topdn = $3; $cisco_header = "SUBJECT = unstructuredAddress=$ip, unstructuredName=$fqdn, $topdn\n"; $cisco_header .= "SUBJECT_ALT_NAME = DNS:$fqdn, IP:$ip\n"; } ## build OpenCA request my $tmp; $tmp = "-----BEGIN HEADER-----\n"; $tmp .= "TYPE = PKCS#10\n"; my $last_req = libDBGetLastItem ("REQUEST"); my $req_elements = 0; $req_elements = $last_req->getSerial("REQUEST") if ($last_req); $req_elements >>= getRequired ("ModuleShift"); if ((not defined $req_elements) or ($req_elements < 0)) { $errno = 723713; $errval = gettext ("The database fails during counting the already existing requests!"); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badRequest"; return undef; } else { $req_elements++; } my $new_serial = ($req_elements << getRequired ("ModuleShift")) | getRequired ("ModuleID"); $tmp .= "SERIAL = $new_serial\n"; $tmp .= "NOTBEFORE = " . $tools->getDate() . "\n"; $tmp .= "LOA = 10\n" if ( getRequired('USE_LOAS') =~ m/yes/i); $tmp .= "ROLE = $ReqRole\n"; $tmp .= "RA = $ReqRA\n"; $tmp .= $cisco_header; $tmp .= "SCEP_TID = ".$scep_tid."\n"; $tmp .= "-----END HEADER-----\n"; $tmp .= $plain_csr; ## create new object my $req; if( not $req = new OpenCA::REQ( SHELL => $cryptoShell, GETTEXT => \&i18nGettext, DATA => $tmp) ) { $errno = 723717; $errval = gettext ("Internal Request Error"); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badRequest"; return undef; } ## store request in database if( not $db->storeItem( DATATYPE=>"NEW_REQUEST", OBJECT=>$req, INFORM=>"PEM", MODE=>"INSERT" )) { $errno = 723721; $errval = gettext ("Error while storing REQ in database!"); print STDERR $errno.": ".$errval."\n"; $scep_failinfo = "badRequest"; return undef; } } ## end of creating new cert-req return 1; } ## end of scepStoreRequest ## ## Functions for generating SCEP-Answers ## sub scepAnswerPending { $ENV{pwd} = $scep_pwd; debug_cmds("cmdScepPKIOperation: execute7: $scep_cmd -new -signcert $scep_cert -msgtype CertRep -status PENDING -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER"); open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status PENDING -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER 2>&1" or debug_cmds("Open error"); $response = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); delete $ENV{pwd}; debug_cmds("response: $response") if ($deep_debug); print $response; ## FIXME: do errorchecking, message created without errors? ## create openca-error return 1; } ## end of scepAnswerPending sub scepAnswerFailure { $ENV{pwd} = $scep_pwd; ## set standard error if no is specified so far $scep_failinfo = "badRequest" if not $scep_failinfo; debug_cmds("cmdScepPKIOperation: execute8: $scep_cmd -new -signcert $scep_cert -msgtype CertRep -status FAILURE -failinfo $scep_failinfo -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER"); open OUT, "-|", "$scep_cmd -new -signcert $scep_cert -msgtype CertRep -status FAILURE -failinfo $scep_failinfo -keyfile $scep_key -passin env:pwd -in $p7_file -reccert $reccert_file -outform DER 2>&1" or debug_cmds("Open error"); $response = join '', <OUT>; close OUT; debug_cmds("Pipe returned error code $?"); delete $ENV{pwd}; print $response; debug_cmds("response: $response") if ($deep_debug); ## FIXME: do errorchecking, message created without errors? ## create openca-error return 1; } ## end of scepAnswerFailure } 1;
