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;

Reply via email to