Unreleased changes from upstream author CVS http://devel.it.su.se/cgi-bin/local/cvsweb.cgi/CSP/ Index: CSP.pm --- CSP.pm.orig 2002-09-24 20:33:20 +0200 +++ CSP.pm 2005-05-06 21:42:11 +0200 @@ -8,6 +8,7 @@ use IO::File; use Term::Prompt; use POSIX qw(strftime); +use Date::Calc qw(Day_of_Week Gmtime Add_Delta_Days Add_Delta_DHMS); @ISA = qw(Exporter AutoLoader); # Items to export into callers namespace by default. Note: do not export @@ -15,7 +16,7 @@ # Do not simply export all your public functions/methods/constants. @EXPORT = qw(); @EXPORT_OK = qw($_openssl); -$VERSION = '0.26'; +$VERSION = '0.29'; # Preloaded methods go here. @@ -49,7 +50,7 @@ my $me = bless { dir=>$dir,name=>$name },$class; open ALIASES,"$dir/etc/aliases.txt"; - while () + while () { chomp; next unless /\s*([^:]+)\s*:\s*([^:]+)\s*/; @@ -58,6 +59,8 @@ } close ALIASES; + $me->{openssl} = CSP::OpenSSL->new($me); + $me; } @@ -158,7 +161,7 @@ $self->mppFile($cf,$vars,$1),last SWITCH if /^%include\s+(.+)/; - $cf->print(&_rewrite($vars,$_)); + print $cf &_rewrite($vars,$_); } } close IN; @@ -168,11 +171,11 @@ { my $self = shift; my $cmd = shift; - my $vars = shift; + my $args = shift; my $cadir = "$self->{dir}/csp/$self->{name}"; - $ENV{TMPDIR} = '/tmp' unless exists $ENV{TMPDIR}; + $ENV{TMPDIR} ||= ("$self->{dir}/tmp" || '/tmp'); my $cff = $self->tempFile("csp","conf"); my $cf = IO::File->new(); eval @@ -187,15 +190,30 @@ \#\# by hand. Please see the CSP-documentation for details. \#\# $date +openssl_conf = openssl_init + +[openssl_init] +engines = engine_section +oid_section = oids + +[engine_section] +EOW + + $ENV{CSP_OPENSC} && $cf->print(<print("\noid_section = oids\n\n[ oids ]\n"); + $cf->print("[ oids ]\n"); $self->addFile($cf,"$self->{dir}/etc/oids.conf"); $cf->print("\n[ csp ]\n\n"); my ($k,$v); - while (($k,$v) = each %{$vars}) + while (($k,$v) = each %{$args}) { $cf->print("$k\t= $v\n") if ($k ne 'keypass' && $k ne 'capass'); } @@ -209,9 +227,9 @@ [ ca ] default_ca = $self->{name} - + [ $self->{name} ] - + dir = \${csp::home}/csp/\${csp::ca} certs = \$dir/certs database = \$dir/index.txt @@ -228,7 +246,7 @@ policy = policy [ req ] - + default_bits = \${csp::keysize} default_keyfile = privkey.pem distinguished_name = req_dn @@ -241,15 +259,15 @@ ## Extension based on command - my $type = $vars->{type}; + my $type = $args->{type}; my $name = $self->{name}; - if ($cmd eq 'x509' || $cmd eq 'req' || $cmd eq 'ca') + if ($cmd eq 'x509' || $cmd eq 'req' || $cmd eq 'ca') { - $self->die("missing \"type\" parameter") + $self->die("missing \"type\" parameter") unless $type; $cf->print("[ policy ]\n\n"); - foreach my $attr (keys %{$vars->{name_attributes}}) + foreach my $attr (keys %{$args->{name_attributes}}) { next unless $attr; if ($self->{aname}->{lc($attr)}) @@ -261,43 +279,43 @@ $cf->print("$attr = optional\n"); } } - + ## Define a few CPP/MPP-style variables and run the prototype file - $vars->{uc("type_$type")}++; - foreach my $x (qw(email url ip dns)) + $args->{uc("type_$type")}++; + foreach my $x (qw(email url ip dns)) { - $vars->{uc($x)} = $vars->{$x}; + $args->{uc($x)} = $args->{$x}; } $cf->print("\n\n"); - if ($name) + if ($name) { my $econf = "$self->{dir}/csp/$name/extensions.conf"; $econf = "$self->{dir}/etc/extensions.conf" unless -f $econf; - $self->mppFile($cf,$vars,$econf); + $self->mppFile($cf,$args,$econf); } $cf->print("\n\n"); - if ($name) + if ($name) { my $econf = "$self->{dir}/csp/$name/crl_extensions.conf"; $econf = "$self->{dir}/etc/crl_extensions.conf" unless -f $econf; - $self->mppFile($cf,$vars,$econf); + $self->mppFile($cf,$args,$econf); } $cf->print("\n"); } - my $dn = $vars->{dn}; - if ($dn) + my $dn = $args->{dn}; + if ($dn) { my %acount; - + $cf->print("[ req_dn ]\n\n"); foreach my $rdn (split /\s*,\s*/,$dn) { next unless $rdn =~ /^([^=]+)\s*=\s*([^=]+)$/; my $n = exists $self->{aname}->{lc($1)} ? $self->{aname}->{lc($1)} : $1; - $vars->{_nrdn}++; ## At the end of the run _nrdn contains the + $args->{_nrdn}++; ## At the end of the run _nrdn contains the ## number of newlines to send to openssl. my $pos = $acount{$n}++; $cf->print($pos.".${n}\t\t= $2\n"); @@ -307,13 +325,13 @@ $cf->close; }; - if ($@) + if ($@) { $cf->close; #unlink $cff; ## uncomment when debugging $self->die($@); } - + return $cff; } @@ -321,7 +339,7 @@ { my $self = shift; my $dir = $self->caDir(); - + mkdir $dir,00755; mkdir "$dir/certs",00755; open SERIAL,">$dir/serial"; @@ -329,7 +347,7 @@ close SERIAL; mkdir "$dir/tmp",00700; system('touch',"$dir/index.txt"); - + mkdir "$dir/private",00700; mkdir "$dir/private/keys",00700; system('cp','-p',"$self->{dir}/etc/extensions.conf","$dir/extensions.conf"); @@ -337,12 +355,12 @@ system('cp','-rp',"$self->{dir}/etc/public_html","$dir/"); } -sub caDir +sub caDir { my $self = shift; my $name = $self->{name}; $self->die("CA Name not set") unless $name; - + "$self->{dir}/csp/$self->{name}"; } @@ -360,9 +378,9 @@ my $self = shift; my $comment = shift; my $reenter = shift; - + my ($pw,$pwr); - + system("stty -echo") && $self->die("Unable to configure tty for password entry"); @@ -370,22 +388,22 @@ chop($pw = ); print STDERR "\n"; - if ($reenter) + if ($reenter) { $pwr = $self->getPassword("Re-enter $comment"); } - else + else { $pwr = $pw; } - + system("stty echo") && $self->die("Unable to configure tty for password entry"); - + $self->die("Passwords do not match") unless $pw eq $pwr; - $self->die("Zero-length password") + return undef if length($pw) == 0; $pw; @@ -398,17 +416,14 @@ $self->die("Required parameter keyfile missing") unless $args->{keyfile}; - + $args->{keysize} = 1024 unless $args->{keysize} > 0; - $args->{keypass} = $self->getPassword("Private key password",1); - - my $process = - CSP::OpenSSL->new($self, - 'genrsa', - "-des3 -passout stdin -out $args->{keyfile} $args->{keysize}",$args); - - $process->print($args->{keypass}); - $process->closeok(); + $args->{keypass} = $self->getPassword("Private key password",1) + unless $args->{keypass}; + + my $cmd = "-out $args->{keyfile} $args->{keysize}"; + $cmd = "-des3 -passout pass:$args->{keypass} ".$cmd if defined($args->{keypass}); + $self->{openssl}->cmd('genrsa',$cmd,$args); } sub create @@ -417,35 +432,35 @@ my $args = shift; $self->createFiles(); - $self->warn("Successfully created CA $self->{name}") - unless $args->{verobse} > 1; + $self->warn("Successfully created CA $self->{name}") + if $args->{verbose}; } sub delete { my $self = shift; my $args = shift; - + my $dir = $self->caDir(); system('rm','-rf',$dir); - $self->warn("Successfully deleted CA $self->{name}") - unless $args->{verbose} > 1; + $self->warn("Successfully deleted CA $self->{name}") + if $args->{verbose}; } sub init { my $self = shift; my $args = shift; - + my $dir = $self->caDir(); - $self->create($args) unless -d $dir; - + $self->die("You must create the CA before it can be initialized") unless -d $dir; + if ($args->{crtfile}) { system('cp',$args->{crtfile},"$dir/ca.crt"); - $self->warn("Successfully initialized CA $self->{name}") - unless $args->{verbose} > 1; + $self->warn("Successfully initialized CA $self->{name}") + if $args->{verbose}; } else { @@ -458,38 +473,38 @@ my $cakey = "$dir/private/ca.key"; my $cacert = "$dir/ca.crt"; - ## Generate the CA key - $self->warn("Generating CA key") - unless $args->{verbose} > 1; - $args->{keyfile} = $cakey; - $self->genkey($args) or - $self->die("Unable to generate CA key in $cakey"); + unless (-f $args->{keyfile}) + { + ## Generate the CA key + $self->warn("Generating CA key") + if $args->{verbose}; + $args->{keyfile} = $cakey; + defined $self->genkey($args) or + $self->die("Unable to generate CA key in $cakey"); + $self->die("CA key must have a password") + unless defined($args->{keypass}); + } $args->{capass} = $args->{keypass}; ## Generate and optionally self-sign the request my $process; my $what; - my $common_args = "-$args->{digest} -days $args->{days} -key $cakey -passin stdin"; + my $common_args = "-$args->{digest} -days $args->{days} ". + " -key $cakey -passin pass:$args->{keypass}"; if ($args->{csrfile}) { - $process = - $self->req("-new $common_args -out $args->{csrfile}",$args); - $process->print("$args->{keypass}\n"); - $process->closeok(); + $self->{openssl}->cmd('req',"-new $common_args -out $args->{csrfile}",$args); $what = "generated CA request for"; } - else + else { - $process = - $self->req("-x509 $common_args -new -out $cacert",$args); - $process->print("$args->{keypass}\n"); - $process->closeok(); + $self->{openssl}->cmd('req',"-x509 $common_args -new -out $cacert",$args); $what = "initialized self-signed"; } - $self->warn("Successfully $what CA $self->{name}") - unless $args->{verbose} > 1; + $self->warn("Successfully $what CA $self->{name}") + if $args->{verbose}; } } @@ -497,13 +512,13 @@ { my $self = shift; my $dir = $self->caDir(); - + $self->die("Uninitialized CA: missing or unreadable ca certificate") unless -r "$dir/ca.crt"; $self->die("Uninitialized CA: missing or unreadable ca private key") unless -r "$dir/private/ca.key"; - + $dir; } @@ -518,7 +533,7 @@ my $file = "$dir/tmp/$base-$$.$ext"; $self->{_tmpfiles}->{$file}++; - + $file; } @@ -526,7 +541,7 @@ { my $self = shift; my $file = shift; - + delete $self->{_tmpfiles}->{$file}; } @@ -534,7 +549,7 @@ { my $self = shift; my $dir = $self->caDir(); - + my $serial = shift; "$dir/private/keys/$serial.key"; } @@ -543,7 +558,7 @@ { my $self = shift; my $dir = $self->caDir(); - + my $serial = shift; "$dir/certs/$serial.pem"; } @@ -552,30 +567,29 @@ { my $self = shift; my $args = shift; - - $self->die("Required parameter dn missing") + + $self->die("Required parameter dn missing") unless $args->{'dn'}; - + my $dir = $self->checkCA(); - + $args->{type} = 'user' unless $args->{type}; $args->{csrfile} = $self->tempFile("request","csr") unless $args->{csrfile}; $args->{keyfile} = $self->tempFile("request","key") unless $args->{keyfile}; - ## Generate a key unless one already exists - unless (-r $args->{keyfile}) + if (! -r $args->{keyfile}) { - $self->warn("Generating new key") unless $args->{verbose} > 1; + $self->warn("Generating new key") if $args->{verbose}; $self->genkey($args) or $self->die("Unable to generate key in $args->{keyfile}"); } ## Generate a certificate request - $self->warn("Create certificate request for $args->{dn}") - unless $args->{verbose} > 1; - my $process = $self->req("-new -$args->{digest} -key $args->{keyfile} -out $args->{csrfile} -passin stdin",$args); - $process->print("$args->{keypass}\n"); - $process->closedie(); + $self->warn("Create certificate request for $args->{dn}") + if $args->{verbose}; + my $cmd = "-new -$args->{digest} -key $args->{keyfile} -out $args->{csrfile}"; + $cmd .= " -passin pass:$args->{keypass}" if defined($args->{keypass}); + $self->{openssl}->cmd('req',$cmd,$args); } sub gencrl @@ -587,6 +601,8 @@ $args->{capass} = $self->getPassword("CA Private key password") unless $args->{capass}; + $self->die("CA key must have a password") + unless defined($args->{capass}); my $days = $args->{crldays} || 30; my $hours = $args->{crlhours}; @@ -600,26 +616,19 @@ { $time = "-crldays $days"; } - my $common = "-batch -passin stdin -gencrl $time"; + my $common = "-batch -passin pass:$args->{capass} -gencrl $time"; ## Generate both version 1 and version 2 (with extensions) CRLs ## and convert from PEM to DER format - my $p1 = - $self->ca("$common -out $dir/crl-v1.pem",$args); - $p1->print("$args->{capass}\n"); - $p1->closedie(); - - CSP::OpenSSL->new()-> - sopen($self,'dummy',"crl -outform DER -out $dir/crl-v1.crl -in $dir/crl-v1.pem")->closeok(); - - my $p2 = - $self->ca("$common -crlexts crl_extensions -out $dir/crl-v2.pem",$args); - $p2->print("$args->{capass}\n"); - $p2->closedie(); + $self->{openssl}->cmd('ca',"$common -out $dir/crl-v1.pem",$args); + + $self->{openssl}->cmd('crl',"-outform DER -out $dir/crl-v1.crl -in $dir/crl-v1.pem"); + + $self->{openssl}->cmd('ca',"$common -crlexts crl_extensions -out $dir/crl-v2.pem",$args); - CSP::OpenSSL->new()-> - sopen($self,'dummy',"crl -outform DER -out $dir/crl-v2.crl -in $dir/crl-v2.pem")->closedie(); + $self->{openssl}-> + cmd('crl',"-outform DER -out $dir/crl-v2.crl -in $dir/crl-v2.pem"); } sub list @@ -639,8 +648,7 @@ my @row = split /\t/; next if ($row[0] ne 'V' && !$args->{all}); - - next if ($args->{serial} && $row[3] ne $args->{serial}); + next if ($args->{serial} && $row[3] != $args->{serial}); my $entity = $eclass->new($self,\@row,$args->{xinfo},$args->{contents}); push(@out,$entity) if ref $entity; @@ -653,7 +661,7 @@ { my $date = shift; my @parts = split /\s+/,$date; - warn "$parts[0] $parts[1] $parts[3]"; + #warn "$parts[0] $parts[1] $parts[3]"; my ($y,$m,$d) = Date::Calc::Parse_Date("$parts[0] $parts[1] $parts[3]"); my $t = $parts[2]; $t =~ s/://og; @@ -674,9 +682,8 @@ $self->die("Not a directory: $args->{export}") unless -d $args->{export}; - + my $odir = $args->{export}; - mkdir $odir,00755; mkdir "$odir/certs",00755; my $expired_count = 0; @@ -721,7 +728,7 @@ $e->{info}->{fingerprint_md5} $e->{info}->{notbefore} - $e->{info}->{notbefore} + $e->{info}->{notafter} EOXML @@ -733,8 +740,8 @@ } my $file = $self->certFile($serial); - CSP::OpenSSL->new()-> - ropen($self,'dummy',"x509 -in $file -outform DER -out $odir/certs/$serial.crt")->closeok(); + $self->{openssl}-> + cmd('x509',"-in $file -outform DER -out $odir/certs/$serial.crt",{noconfig=>1}); system('cp',$file,"$odir/certs/$serial.pem"); @@ -764,8 +771,8 @@ $revoked_html .= "\n"; $expired_html .= "\n"; - my $pp = CSP::OpenSSL->new()-> - sopen($self,'dummy',"x509 -inform PEM -in $dir/ca.crt -outform PEM -out $odir/ca.crt")->closeok(); + my $pp = $self->{openssl}-> + cmd('x509',"-inform PEM -in $dir/ca.crt -outform PEM -out $odir/ca.crt",{noconfig=>1}); #system('cp',"$dir/ca.crt","$odir/ca.crt"); system('cp',"$dir/crl-v1.crl","$odir/crl-v1.crl"); system('cp',"$dir/crl-v2.crl","$odir/crl-v2.crl"); @@ -844,7 +851,7 @@ { my $self = shift; my $fn = shift; - + my $html = IO::File->new(); my ($base) = $fn =~ /(.+)\.html\.mpp$/; $self->die("Filename missing .html.mpp extension: $fn") @@ -858,16 +865,10 @@ { my $self = shift; my $args = shift; - + my $dir = $self->checkCA(); - - my $process = CSP::OpenSSL->new()-> - ropen($self,'dummy',"x509 -noout -text -in $dir/ca.crt"); - my $fh = $process->handle(); - while (<$fh>) - { - print $_; - } + + print $self->{openssl}->cmd('x509',"-noout -text -in $dir/ca.crt",{noconfig=>1}); } sub caBundle @@ -894,10 +895,10 @@ print BUNDLE $_; } close CERT; - my $process = CSP::OpenSSL->new()-> - ropen($self,'dummy',"x509 -noout -text -in $certfile"); + my $process = $self->{openssl}-> + cmd('x509',"-noout -text -in $certfile",{noconfig=>1}); my $fh = $process->handle(); - while (<$fh>) + while (<$fh>) { print BUNDLE $_; } @@ -910,27 +911,39 @@ { my $self = shift; my $args = shift; - + my $dir = $self->checkCA(); my $serial = $args->{serial}; my $file = $self->certFile($serial); - + $self->die("Serial $serial not issued by this CA") unless -f $file; - + if ($args->{confirm}) { $self->dumpcert($file); $self->confirm("Really revoke this?","Bye..."); } - + $args->{capass} = $self->getPassword("CA Private key password") unless $args->{capass}; + $self->die("CA key must have a password") + unless defined($args->{capass}); - my $process = - $self->ca("-passin stdin -batch -revoke $file",$args); - $process->print("$args->{capass}\n"); - $process->closeok(); + $self->{openssl}->cmd('ca',"-passin pass:$args->{capass} -batch -revoke $file",$args); + } + +sub _time + { + my ($self,$Dd,$Dh,$Dm,$Ds) = @_; + + my ($year,$month,$day,$hour,$min,$sec,$doy,$dow,$dst) = Gmtime(); + my ($nyear,$nmonth,$nday,$nhour,$nmin,$nsec) = + Add_Delta_DHMS($year,$month,$day,$hour,$min,$sec,$Dd,$Dh,$Dm,$Ds); + + my $tmp = sprintf("%02d%02d%02d%02d%02d%02dZ",$nyear,$nmonth,$nday,$nhour,$nmin,$nsec); + $tmp =~ s/^[0-9][0-9]//; + $tmp; } sub issue @@ -939,26 +952,26 @@ my $args = shift; my $dir = $self->checkCA(); - + $args->{type} = 'user' unless $args->{type}; - unless ($args->{csrfile}) + unless ($args->{csrfile}) { $args->{csrfile} = $self->tempFile("request","csr"); - eval + eval { $self->request($args); $args->{p12pass} = $args->{keypass}; }; - if ($@) + if ($@) { $self->die("Unable to generate request: ".$self->exm($@)); } } - - $self->die("No csr file $args->{csrfile}") - unless -f $args->{csrfile}; - + +# $self->die("No csr file $args->{csrfile}") +# unless -r $args->{csrfile}; + eval { if ($args->{confirm}) @@ -967,7 +980,7 @@ $self->confirm("Really sign this?","Bye..."); } - $self->warn("Signing request") unless $args->{verbose} > 1; + $self->warn("Signing request") if $args->{verbose}; my $serial; open SERIAL,"$dir/serial"; @@ -976,18 +989,25 @@ $args->{capass} = $self->getPassword("CA Private key password") unless $args->{capass}; - - $args->{days} = 365 unless $args->{days}; - - my $process = - $self->ca("-batch -md $args->{digest} -days $args->{days} -passin stdin -preserveDN -outdir $dir/certs -in $args->{csrfile}",$args); - $process->print("$args->{capass}\n"); - $process->closeok(); + $self->die("CA key must have a password") + unless defined($args->{capass}); + + $args->{startdate} = $self->_time() + unless $args->{startdate}; + $args->{enddate} = + $self->_time($args->{days} or 365,$args->{hours},$args->{mins},$args->{secs}) + unless $args->{enddate}; + + $self->{openssl}->cmd('ca', + "-batch -md $args->{digest} -startdate $args->{startdate} ". + "-enddate $args->{enddate} ". + "-passin pass:$args->{capass} -preserveDN -outdir $dir/certs ". + "-in $args->{csrfile}",$args); rename $args->{keyfile},"$dir/private/keys/$serial.key"; $self->unTempFile($args->{keyfile}); $args->{serial} = $serial; }; - if ($@) + if ($@) { $self->die("Unable to sign request: ".$self->exm($@)); } @@ -999,26 +1019,26 @@ my $args = shift; my $dir = $self->checkCA(); - + my $serial = $args->{serial}; $self->die("Missing serial number") unless $serial; - + $args->{keypass} = $self->getPassword("Private key password") - unless $args->{keypass}; - + unless defined($args->{keypass}); + $args->{p12pass} = $self->getPassword("PKCS12 export password") - unless $args->{p12pass}; + unless defined($args->{p12pass}); my $othercerts; - if (-f "$dir/certpath.crt") + if (-f "$dir/certpath.crt") { $othercerts = "-certfile $dir/certpath.crt"; } - else + else { $othercerts = "-certfile $dir/ca.crt"; } - + my $certFile = $self->certFile($serial); my $keyFile = $self->keyFile($serial); $self->die("The private key of $serial is not on-line") @@ -1026,15 +1046,14 @@ $self->die("The certificate of $serial is not on-line") unless -f $certFile; - eval + eval { mkdir "$dir/p12",00755 unless -d "$dir/p12"; my $p12File = "$dir/p12/$serial.p12"; - my $process = - $self->pkcs12("-export -des3 $othercerts -inkey $keyFile -in $certFile -out $p12File -passout stdin -passin stdin",$args); - $process->print("$args->{keypass}\n"); - $process->print("$args->{p12pass}\n"); - $process->closeok(); + my $cmd = "-export -des3 $othercerts -inkey $keyFile -in $certFile -out $p12File"; + $cmd .= " -passout pass:$args->{p12pass}" if defined($args->{p12pass}); + $cmd .= " -passin pass:$args->{keypass}" if defined($args->{keypass}); + $self->{openssl}->cmd('pkcs12',$cmd,$args); }; if ($@) { @@ -1046,10 +1065,10 @@ { my $self = shift; my $dn = shift; - + my @rdns = split /\//,$dn; shift @rdns; - foreach my $aname (keys %{$self->{alias}}) + foreach my $aname (keys %{$self->{alias}}) { map { s/$aname/$self->{alias}->{$aname}/ig; } @rdns; } @@ -1063,21 +1082,21 @@ my $args = shift; my $dn; - - SWITCH: + + SWITCH: { - $dn = $x,last SWITCH + $dn = $x,last SWITCH if $x =~ /=/; ## probably a distinguished name - - $dn = $self->email2DN($1,$2,$args),last SWITCH + + $dn = $self->email2DN($1,$2,$args),last SWITCH if $x =~ /([^@]+)\@([^@]+)/; ## probably an email address - - $dn = $self->domainName2DN($x,$args),last SWITCH + + $dn = $self->domainName2DN($x,$args),last SWITCH if $x =~/\./; ## probably a DNS domain name $self->die("Unknown name form: $x"); } - + foreach my $av (split /\s*[,\/]\s*/,$dn) { $self->die("Bad X.501 name $dn") unless $av =~ /([a-zA-Z]+)\s*=\s*([^=]+)/; @@ -1095,14 +1114,14 @@ { my $self = shift; my ($lp,$dp,$args) = @_; - + #my $attr = 'uid'; #$attr = 'CN' if $lp =~ /[-\.\_]/; my $attr = 'CN'; - + $args->{email} = "$lp\@$dp"; - return "$attr=$lp,".$self->domainName2DN($dp); + return $self->domainName2DN($dp).",$attr=$lp"; } sub domainName2DN @@ -1115,59 +1134,29 @@ $args->{ip} = $dns; my @dn = split /\./,$dns; @dn = map { "dc=$_" } @dn; - join(',',@dn); - } - -sub req - { - my $self = shift; - - CSP::OpenSSL->new($self,'req',@_); - } - -sub ca - { - my $self = shift; - - CSP::OpenSSL->new($self,'ca',@_); - } - -sub pkcs12 - { - my $self = shift; - - CSP::OpenSSL->new($self,'pkcs12',@_); - } - -sub x509 - { - my $self = shift; - - CSP::OpenSSL->new($self,'x509',@_); + join(',',reverse @dn); } sub dumpcert { my $self = shift; my $certfile = shift; - - CSP::OpenSSL->new()-> - sopen($self, - 'x509', - "-text -in $certfile -noout -nameopt RFC2253", - {noconfig=>1,verbose=>1})->closeok(); + + print $self->{openssl}-> + cmd('x509', + "-text -in $certfile -noout -nameopt RFC2253", + {noconfig=>1,verbose=>1}); } sub dumpreq { my $self = shift; my $reqfile = shift; - - CSP::OpenSSL->new()-> - sopen($self, - 'req', - "-text -in $reqfile -noout", - {noconfig=>1,verbose=>1})->closeok(); + + print $self->{openssl}-> + cmd('req', + "-text -in $reqfile -noout", + {noconfig=>1,verbose=>1}); } sub exm @@ -1183,74 +1172,57 @@ { my $self = shift; my $certfile = shift; - + my (%info,$fh,$process); - - $process = CSP::OpenSSL->new()->ropen($self,'dummy',"x509 -noout -hash -in $certfile"); - $fh = $process->handle(); - $info{hash} = <$fh>; - chomp $info{hash}; - $process->closedie; - - $process = - CSP::OpenSSL->new()->ropen($self,'dummy',"x509 -noout -serial -dates -subject -issuer -in $certfile"); - $fh = $process->handle(); - while (<$fh>) + + $info{hash} = $self->{openssl}->cmd('x509',"-noout -hash -in $certfile",{noconfig=>1}); + + local $_ = $self->{openssl}->cmd('x509',"-noout -serial -dates -subject -issuer -in $certfile",{noconfig=>1}); + while ($_) { - chomp; - if (/^subject=\s*(.+)/) + s/^\s*\n//o; + if (s/^subject=\s*(.+)//o) { $info{subject}=$1; $info{subject} =~ s/\//,/og; $info{subject} =~ s/^,//og; - next; } - elsif (/^issuer=\s*(.+)/) + elsif (s/^issuer=\s*(.+)//o) { $info{issuer}=$1; $info{issuer} =~ s/\//,/og; $info{issuer} =~ s/^,//og; - next; } - elsif (/^notBefore=\s*(.+)/) + elsif (s/^notBefore=\s*(.+)//o) { $info{notbefore}=$1; - next; } - elsif (/^notAfter=\s*(.+)/) + elsif (s/^notAfter=\s*(.+)//o) { $info{notafter}=$1; - next; } - elsif (/^serial=\s*(.+)/) + elsif (s/^serial=\s*(.+)//o) { $info{serial}=$1; - next; } } - - $process->closedie; - - $process = - CSP::OpenSSL->new()->ropen($self,'dummy',"x509 -noout -md5 -fingerprint -in $certfile"); - $fh = $process->handle(); - while (<$fh>) + + $_ = $self->{openssl}->cmd('x509',"-noout -md5 -fingerprint -in $certfile",{noconfig=>1}); + while ($_) { - chomp; - $info{fingerprint_md5}=$1,next if /MD5 Fingerprint=(.+)/; + chomp; + s/^\s*\n//o; + $info{fingerprint_md5}=$1,last if /MD5 Fingerprint=(.+)/o; } - $process->closedie; - $process = - CSP::OpenSSL->new()->ropen($self,'dummy',"x509 -noout -sha1 -fingerprint -in $certfile"); - $fh = $process->handle(); - while (<$fh>) + $_ = $self->{openssl}->cmd('x509',"-noout -sha1 -fingerprint -in $certfile",{noconfig=>1}); + while ($_) { - chomp; - $info{fingerprint_sha1}=$1,next if /SHA1 Fingerprint=(.+)/; + chomp; + s/^\s*\n//o; + $info{fingerprint_sha1}=$1,last if /SHA1 Fingerprint=(.+)/; } - $process->closedie; - + \%info; } @@ -1260,7 +1232,7 @@ { my $self = shift; my $class = ref $self || $self; - + my @stack = @_; bless \@stack,$class; } @@ -1284,15 +1256,15 @@ package CSP::Entity; # Just a db object @CSP::Entity::ISA = qw(CSP); -use Date::Calc qw(Day_of_Week); +use Date::Calc qw(Day_of_Week Gmtime Add_Delta_Days Add_Delta_DHMS); use POSIX qw(strftime); sub parse_date { my $str = shift; - + my ($y,$mon,$mday,$h,$m,$s) = $str =~ /([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})Z$/; - + $y += 100; my $wday = Day_of_Week($y+1999,$mon,$mday); my @date = ($s,$m,$h,$mday,$mon,$y,$wday,0); @@ -1303,12 +1275,12 @@ { my $self = shift; my $class = ref $self || $self; - + my $csp = shift; my $row = shift; my $getinfo = shift; my $getcontents = shift; - + my $serial = $row->[3]; my $file = ($row->[4] && $row->[4] ne 'unknown' ? $row->[4] : $csp->certFile($serial)); @@ -1335,7 +1307,7 @@ sub dump { my $self = shift; - + printf "%-8s: %s\n",'Serial',$self->{serial}; my $status = $self->{status}; printf "%-8s: %s\n",'Status',exists $_status{$status} ? $_status{$status} : "Unknown"; @@ -1344,7 +1316,7 @@ printf "%-8s: %s\n",'Revoked',strftime("%a %b %e %H:%M:%S %Y",@{$self->{revoked}}) if $self->{revoked}; printf "%-8s: %s\n",'SHA1',$self->info('fingerprint_sha1') if $self->info('fingerprint_sha1'); printf "%-8s: %s\n",'MD5',$self->info('fingerprint_md5') if $self->info('fingerprint_md5'); - if ($self->{getcontents}) + if ($self->{getcontents}) { $self->dumpcert($self->{file}); } @@ -1362,53 +1334,86 @@ *** EOTXT +use IPC::Run qw( start pump finish timeout new_appender new_chunker); + sub new { my $self = shift; my $class = ref $self || $self; + my $csp = shift; - local *FH; - my $me = bless - { - fh=>*FH, - openssl=>$ENV{OPENSSL} || die $_unset - },$class; - @_ > 0 ? $me->wopen(@_) : $me; - } + my %me; + my @openssl = ($ENV{OPENSSL},@_); -sub DESTROY - { - $_[0]->closeok(); - } + $me{csp} = $csp; + $me{_handle} = start(\@openssl, + '<',new_appender("\n"),\${$me{_in}}, + '>',\${$me{_out}},'2>&1',debug=>0) + or die "Cannot start $ENV{OPENSSL}: $!\n"; -sub ropen - { - my $self = shift; - $self->rws_open('r',@_); + bless \%me,$class; } -sub wopen +sub cmd { my $self = shift; - $self->rws_open('w',@_); - } + my $cmd = shift; + my $cmdline = shift; + my $args = shift; -sub sopen - { - my $self = shift; - $self->rws_open('s',@_); + my $conf; + my $cfgcmd; + if ( (grep $_ eq $cmd,qw(req ca)) && !$args->{noconfig}) + { + $conf = $self->{csp}->writeConfig($cmd,$args); + $self->{csp}->die("Unable to write configuration file") unless -f $conf; + $cfgcmd = " -config $conf "; + } + elsif ($cmd eq 'x509' && !$args->{noconfig}) + { + $conf = $self->{csp}->writeConfig($cmd,$args); + $self->{csp}->die("Unable to write configuration file") unless -f $conf; + $cfgcmd = " -extfile $conf -extensions extensions "; + } + $cmd = '' if $cmd eq 'dummy'; + + ${$self->{_in}} = "$cmd $cfgcmd $cmdline"; + $self->warn("# openssl $cmd $cfgcmd $cmdline\n") if $ENV{CSPDEBUG}; + $self->{_handle}->pump while length ${$self->{_in}}; + $self->{_handle}->finish; + + my @out = split /\n/,${$self->{_out}}; + my @err; + my @nout; + foreach $_ (@out) + { + chomp; + s/\s*OpenSSL>\s*//og; + next unless $_; + if (/:error:/) { + push (@err,$_); + } else { + push (@nout,$_); + } + } + + $self->{csp}->die(sprintf "OpenSSL Error\n%s",join("\n",@err)) + if @err; + + join("\n",@nout)."\n"; } -sub handle +sub DESTROY { - $_[0]->{fh}; + $_[0]->{_handle}->close(); + finish $_[0]->{_handle}; } sub rws_open { my $self = shift; my $rw = shift; - my $scp = shift; + my $csp = shift; my $cmd = shift; my $cmdline = shift; my $args = shift; @@ -1428,31 +1433,33 @@ my $cfgcmd; if ( (grep $_ eq $cmd,qw(req ca)) && !$args->{noconfig}) { - $self->{conf} = $scp->writeConfig($cmd,$args); - $scp->die("Unable to write configuration file") unless -f $self->{conf}; + $self->{conf} = $self->{csp}->writeConfig($cmd,$args); + $self->{csp}->die("Unable to write configuration file") unless -f $self->{conf}; $cfgcmd = " -config $self->{conf} "; } - elsif ($cmd eq 'x509' && !$args->{noconfig}) + elsif ($cmd eq 'x509' && !$args->{noconfig}) { - $self->{conf} = $scp->writeConfig($cmd,$args); - $scp->die("Unable to write configuration file") unless -f $self->{conf}; + $self->{conf} = $self->{csp}->writeConfig($cmd,$args); + $self->{csp}->die("Unable to write configuration file") unless -f $self->{conf}; $cfgcmd = " -extfile $self->{conf} -extensions extensions "; } - $self->{csp} = $scp; + $self->{csp} = $csp; $cmd = '' if $cmd eq 'dummy'; + my $engine = "-engine opensc" if $ENV{CSP_OPENSC}; + my $redirect = ($args->{verbose} == 0 && $rw ne 'r' ? ">/dev/null 2>&1" : ""); warn "${lp}$self->{openssl} $cmd $cfgcmd $cmdline ${redirect}${rp}" if $ENV{CSPDEBUG}; if ($rw eq 's') { - $self->{rc} = system("$self->{openssl} $cmd $cfgcmd $cmdline ${redirect}"); + $self->{rc} = system("$self->{openssl} $cmd $engine $cfgcmd $cmdline ${redirect}"); } else { - open $self->{fh},"${lp}$self->{openssl} $cmd $cfgcmd $cmdline ${redirect}${rp}" or - $scp->die("Unable to execute: $!"); + open $self->{fh},"${lp}$self->{openssl} $cmd $engine $cfgcmd $cmdline ${redirect}${rp}" or + $self->{csp}->die("Unable to execute: $!"); } $self; @@ -1463,7 +1470,7 @@ my $self = shift; close $self->{fh} if defined $self->{fh}; - unless ($ENV{CSPDEBUG}) + unless ($ENV{CSPDEBUG}) { unlink $self->{conf} if $self->{conf}; } @@ -1523,12 +1530,12 @@ CSP is designed to easily handle multiple distinct Certificate Authorities. Hence the name which stands for Certificate Service Provider. -= item o +=item o CSP can be used to produce a web site (certificate repository, CRLs etc etc) without the need for cgi-scripts. -= item o +=item o CSP tries to be as PKIX-compliant as OpenSSL allows. @@ -1551,22 +1558,22 @@ writer or some other means for making backups of the certificate directory. Day to day operations include the following tasks. -= over 4 +=over 4 -= item 1 +=item 1 Issuing certificates based on pkcs10 or out-of-band (non pkcs10) requests. -= item 2 +=item 2 Backing up the csp main directory (see below) to read-only medium. -= item 3 +=item 3 Producing the public web site and exporting it (typically using floppy or zip-drive) to your web server. -= back +=back =head1 CONFIGURATION Index: csp --- csp.orig 2005-05-06 21:41:40 +0200 +++ csp 2005-05-06 21:44:23 +0200 @@ -15,101 +15,157 @@ closedir CSPD; grep /^[^.]/,@dirs; } - -$usage=< create +my %usage; + +$usage{create}=< create +EOU + +$usage{delete}=< delete +EOU + +$usage{init}=< init + [--crtfile=] + +$0 init + [--keysize=] + [--keypass=] + [--keyfile=] + [--csrfile=] + [--days=] + [--email=] + [--url=] + [--crldays=] + [--crlhours=] + [--digest=] + [--verbose]+ + +EOU + +$usage{request}=< request + [--keysize=] + [--keypass=] + [--keyfile=] + [--type=<*user|server|objsign|ca>] + [--csrfile=] + [--noconfirm] + [--verbose]+ + [--digest=] + {||} +EOU + +$usage{issue}=< issue + [--keysize= ] + [--keypass=] + [--keyfile=] + [--noconfirm] + [--verbose]+ + [--type=<*user|server|objsign|ca>] + + - delta - + [--days=] + [--hours=] + [--mins=] + [--secs=] + - absolute - + [--startdate=] + [--enddate=] + + [--capass=] + [--email=] + [--url=] + [--ip=] + [--dns=] + [--digest=] + {||} +EOU + +$usage{sign}=< sign + [--type=<*user|server|objsign|ca>] + - delta - + [--days=] + [--hours=] + [--mins=] + [--secs=] + - absolute - + [--startdate=] + [--enddate=] + + [--capass=] + [--csrfile=] + [--email=] + [--url=] + [--ip=] + [--dns=] + [--digest=] + [--verbose]+ +EOU + +$usage{p12}=< p12 + [--p12pass=] + [--keypass=] + [--verbose]+ + +EOU + +$usage{revoke}=< revoke + [--noconfirm] [--quiet[=]] +EOU + +$usage{gencrl}=< gencrl + [--crldays=] + [--crlhours=] + [--digest=] + [--verbose]+ +EOU + +$usage{genpublic}=< genpublic + [--export=] + [--verbose]+ +EOU + +$usage{list}=< list + [--serial=] + [--all] + [--xinfo] + [--contents] + [--verbose]+ +EOU + +$usage{dump}=< dump +EOU + +my $cmds = join(' ',sort keys %usage); + +$usage{_nocmd_}=< delete +$0 --bundle - $0 init - [--crtfile=] - - $0 init - [--keysize=] - [--keypass=] - [--keyfile=] - [--csrfile=] - [--days=] - [--email=] - [--url=] - [--crldays=] - [--crlhours=] - [--digest=] - [--verbose]+ - - - $0 request - [--keysize=] - [--keypass=] - [--keyfile=] - [--type=<*user|server|objsign|ca>] - [--csrfile=] - [--noconfirm] - [--verbose]+ - [--digest=] - {||} - - $0 issue - [--keysize= ] - [--keypass=] - [--keyfile=] - [--noconfirm] - [--verbose]+ - [--type=<*user|server|objsign|ca>] - [--days=] - [--capass=] - [--email=] - [--url=] - [--ip=] - [--dns=] - [--digest=] - {||} - - $0 sign - [--type=<*user|server|objsign|ca>] - [--capass=] - [--csrfile=] - [--email=] - [--url=] - [--ip=] - [--dns=] - [--digest=] - [--verbose]+ - - $0 p12 - [--p12pass=] - [--keypass=] - [--verbose]+ - - - $0 revoke - [--noconfirm] [--quiet[=]] - - $0 gencrl - [--crldays=] - [--crlhours=] - [--digest=] - [--verbose]+ - - $0 genpublic - [--export=] - [--verbose]+ - - $0 list - [--serial=] - [--all] - [--xinfo] - [--contents] - [--verbose]+ +$0 --help [] - $0 --list +$0 [--help] * - $0 --bundle +Where is one of + +$cmds. EOU -die $usage unless @ARGV > 0; +die $usage{_nocmd_} unless @ARGV > 0; my $name = shift @ARGV; @@ -129,7 +185,7 @@ mkdir "$home/csp",00755 unless -d "$home/csp"; $name eq '--list' and - do + do { map { print "$_\n"; } &list_csp($home); },exit; @@ -141,7 +197,9 @@ CSP->caBundle({bundle=>"$home/ca-bundle.crt"},@certs); },exit; -die $usage unless @ARGV > 0; + +$name eq '--help' && @ARGV == 1 and die $usage{$ARGV[0]}; +$name eq '--help' || @ARGV == 0 and die $usage{_nocmd_}; my $cmd = shift @ARGV; @@ -157,6 +215,7 @@ xinfo => 0, contents=> 0, digest => 'sha1', + help => 0, all => 0); my @args = ("type=s", @@ -165,10 +224,17 @@ "confirm!", "keysize=i", "days=i", + "hours=i", + "mins=i", + "secs=i", + "startdate=s", + "enddate=s", "xinfo!", "contents!", "serial=i", "keypass=s", + "capass=s", + "p12pass:s", "keyfile=s", "csrfile=s", "crtfile=s", @@ -177,59 +243,55 @@ "dns=s", "export:s", "digest=s", + "help!", "url=s"); -SWITCH: +GetOptions(\%args,@args) or die $usage{$cmd}; +die $usage{$cmd} if $args{help}; + +SWITCH: { ## ## Dump (text form) the CA certificate - ## + ## $cmd eq 'dump' and do { - GetOptions(\%args,@args) or die $usage; - $csp->dump(\%args); },last SWITCH; - ## + ## ## Drop the CA ## - $cmd eq 'delete' and - do + $cmd eq 'delete' and + do { - GetOptions(\%args,@args) or die $usage; - $csp->delete(\%args); },last SWITCH; - - ## + + ## ## Initialize a ca using a self-signed certificate. ## - $cmd eq 'create' and + $cmd eq 'create' and do { - GetOptions(\%args,@args) or die $usage; + $args{keysize} = 2048 unless $args{keysize}; + $args{type} = 'root'; $csp->create(\%args); },last SWITCH; - - ## + + ## ## Initialize a ca using a self-signed certificate. ## - $cmd eq 'init' and - do + $cmd eq 'init' and + do { - $args{keysize} = 2048; - $args{type} = 'root'; - - GetOptions(\%args,@args) or die $usage; - - die $usage unless @ARGV == 1 or $args{crtfile}; + die $usage{init} unless @ARGV == 1 or $args{crtfile}; $args{dn} = $csp->getDN(shift @ARGV,\%args) if @ARGV; $csp->init(\%args); @@ -238,13 +300,11 @@ ## ## Request a certificate of a specific type ## - + $cmd eq 'request' and do { - GetOptions(\%args,@args) or die $usage; - - die $usage unless @ARGV == 1; + die $usage{request} unless @ARGV == 1; $args{dn} = $csp->getDN(shift @ARGV,\%args); @@ -258,10 +318,8 @@ $cmd eq 'p12' and do { - GetOptions(\%args,@args) or die $usage; - - die $usage unless @ARGV == 1; - $args{serial} = $ARGV[0]; + die $usage{p12} unless @ARGV == 1; + $args{serial} = $ARGV[0] unless $args{serial}; $csp->export_pkcs12(\%args); },last SWITCH; @@ -273,15 +331,13 @@ $cmd eq 'issue' and do { - GetOptions(\%args,@args) or die $usage; - - die $usage unless @ARGV == 1; + die $usage{issue} unless 1 == @ARGV; $args{dn} = $csp->getDN(shift @ARGV,\%args); $csp->issue(\%args); },last SWITCH; - + ## ## Sign a certificate request (PKCS10 file) ## @@ -289,11 +345,9 @@ $cmd eq 'sign' and do { - GetOptions(\%args,@args) or die $usage; - $csp->issue(\%args); },last SWITCH; - + ## ## Revoke a certificate given by serial ## @@ -301,9 +355,7 @@ $cmd eq 'revoke' and do { - GetOptions(\%args,@args) or die $usage; - - die $usage unless @ARGV == 1 || $args{serial}; + die $usage{revoke} unless 1 == @ARGV || $args{serial}; $args{serial} = shift unless $args{serial}; @@ -317,20 +369,16 @@ $cmd eq 'gencrl' and do { - GetOptions(\%args,@args) or die $usage; - $csp->gencrl(\%args); },last SWITCH; - + ## ## Generate public sites (www & ldap) - ## + ## ($cmd eq 'genpublic') and do { - GetOptions(\%args,@args) or die $usage; - $csp->genPublic(\%args); },last SWITCH; @@ -341,9 +389,8 @@ ($cmd eq 'list' or $cmd eq 'show') and do { - GetOptions(\%args,@args) or die $usage; map { $_->dump(); } $csp->list(\%args,'CSP::Entity'); },last SWITCH; - - die $usage; + + die $usage{_nocmd_}; }