#! /usr/local/bin/perl use LWP::UserAgent; use HTTP::Request; use Getopt::Long; # # parse options and configuration $tempfile="/tmp/getcert.$$.temp"; $openssl="openssl"; # just use default path $polname="ca-signing-policy.conf"; $opt_c="certs.cf"; @optdef=qw( v V d|certdir=s reloadcerts=s@ c|cf|config=s now h|help ); $0 =~ s/.*\///; $|=1; $Getopt::Long::ignorecase=0; &GetOptions(@optdef); $opt_V and $opt_v++; $opt_h and &help($0); $opt_d or $opt_d="."; -f $opt_c or die "Cannot find configuration $opt_c\n"; $config=`cat $opt_c`; $rc=eval $config; ( "$@" eq "" ) or die "Invalid configuration file $opt_c: $@"; # check target directory -d "$opt_d" or die "$0: $opt_d does not exist\n"; # implement the random wait $opt_now or do { $sleeptime=60*rand(10); sleep($sleeptime); }; # overview of procedure: # - put in arbitrary wait upto 10 minutes # - test validity on a per-ca basis and add the status field # 0) if this ca is in reloadcerts list, load CA cert and store # in this case be verybose # 1) is current cert equal to repository? If not, warn and # ignore a CRL from that repository # 2) reload the CRL from the repository if possible # if it is valid acc. to CA cert, replace current CRL # 3) check expiration of CRL. Warn and remove from list if expired # - then, loop over all ca's that have no "dis" set and are not # invalidated by previous loop. Write out the ca-signing-policy # $casp="# ca-signing-policy.conf\n#\n"; $casp.="# generated by get_certificates on ".`date`."#\n#\n"; foreach $caname ( keys %calist ) { %ca=%{$calist{$caname}}; $ca{crl} and $ca{loadcrl}=1; $opt_v and print "\nProcessing CA $caname\n"; $opt_V and print " CERT URL $ca{cert}\n"; $opt_V and print " CRL URL $ca{crl}\n"; $opt_V and do { foreach $dom ( @{$ca{auth}} ) { print " CA AUTH $dom\n" } }; $opt_V and print " CA STATU $ca{dis}\n"; $opt_V and print "\n"; isin_str($caname,@opt_reloadcerts) and do { if ( $ca{cert} ) { $cacert=&readurl($ca{cert},$ca{cert_filter}); $cacert and &writeca($caname,"cert",$cacert); } else { warn " CA $caname cannot be reloaded (no URL)\n"; } }; $ca{cert} and do { # check for cert validity $cert=&readpem($caname,"cert"); if ( $cert ) { $currfp=(&certinfo($cert))[2]; $webdata=&readurl($ca{cert}); $webfp=(&certinfo($webdata))[2]; $opt_V and print " CURR FP $currfp\n WEB FP $webfp\n"; $webfp eq $currfp or do { warn " FPCHECK $caname: inconsistent fingerprint\n". " URL $ca{cert}\n CURRFP $currfp\n WEB FP $webfp\n"; $ca{loadcrl}=0; }; } else { warn " FPCHECK $caname: cert not found\n"; $ca{dis}="CA cert not there"; } }; grep { /Certificate has expired/ and do { $ca{dis}="Certificate has expired"; $ca{loadcrl}=0; warn " CERT EXPIRED $caname\n"; }; } `$openssl verify $opt_d/cacert-$caname.pem 2>&1 `; $ca{loadcrl} and do { $opt_V and print " CRL LOAD=$ca{loadcrl} FROM $ca{crl}\n"; $cacrl=&readurl($ca{crl},$ca{crl_filter}); if ( $cacrl ) { ($nextupd=&crlvalid($caname,$cacrl)) and &writeca($caname,"crl",$cacrl); $opt_v and print " RELOAD CRL $ca{crl}\n NEXT UPD $nextupd\n"; } else { warn " FAILED $ca{crl}\n"; } }; if ( ! $ca{dis} ) { ($myhash,$mysub,$myfp,$myiss,$myser)=&certinfo(&readpem($caname,"cert")); $opt_V and print " CA HASH $myhash\n"; $opt_V and print " CA SUBJ $mysub\n"; $opt_V and print " CA MD5F $myfp\n"; $opt_V and print " CA ISSU $myiss\n"; $opt_V and print " CA SERL $myser\n"; $opt_v and print " ADDED to ca-signing-policy\n"; # also write out per-ca signing policy for GT2 if ( open PERCASP,">$opt_d/$myhash.signing_policy" ) { print PERCASP "# EACL for $myhash (symname: $caname)\n"; print PERCASP " access_id_CA X509 '$mysub'\n"; print PERCASP " pos_rights globus CA:sign\n"; print PERCASP " cond_subjects globus '\"$mysub\" "; foreach ( @{$ca{auth}} ) { print PERCASP "\"$_\" "; } print PERCASP "'\n\n"; close PERCASP; } else { warn "$0: writing $opt_d/$myhash.signing_policy:\n $!\n"; } # end of hack $casp.="# EACL for $myhash (symname: $caname)\n"; $casp.=" access_id_CA X509 '$mysub'\n"; $casp.=" pos_rights globus CA:sign\n"; $casp.=" cond_subjects globus '\"$mysub\" "; foreach ( @{$ca{auth}} ) { $casp.="\"$_\" "; } $casp.="'\n\n"; &hashlink($caname,$myhash); } else { $opt_v and print " DISTRST $ca{dis}\n"; } } if ( open CASP,">$opt_d/ca-signing-policy.conf" ) { print CASP $casp; close CASP; } else { warn "$0: writing $opt_d/ca-signing-policy.conf:\n $!\n"; } exit(0); sub readurl { my ($url,$filter) = @_; my $ua = LWP::UserAgent->new; my $req = HTTP::Request->new('GET', $url); my $resp = $ua->request($req); unless ($resp->is_success()) { $!="readurl($url): $!"; return undef; } $data=$resp->content; $filter and do { open DER,">$tempfile" or die "$0::readurl($url,$filter): cannot open temp\n $!\n"; print DER $resp->content; close DER; $filter=~s/^\|//; open OPENSSL,"$filter -in $tempfile |" or die "$0::readurl($url,$filter): cannot open $filter pipe:\n $!\n"; $data=""; while () { $data.=$_; } close OPENSSL; }; return $data; } sub readpem { my ($caname,$type) = @_; my ($fname)="$opt_d/ca$type-$caname.pem"; my ($txt); ( $type ne "cert" ) and ( $type ne "crl" ) and die "$0::writeca: invalid argument $type\n"; open PEM,$fname or do { warn "$0::readpem($fname): opening error:\n $!\n"; return undef }; while () { $txt.=$_; }; close PEM; return $txt; } sub writeca { my ($caname,$type,$data) = @_; my ($fname)="$opt_d/ca$type-$caname.pem"; ( $type ne "cert" ) and ( $type ne "crl" ) and die "$0::writeca: invalid argument $type\n"; $data or do { warn "$0::writeca($caname): $type contains no data\n"; return undef; }; &isa_pem($type,$data) or do { warn "$0::writeca($caname): data not of type $type\n"; $opt_V and print "-- DATA:\n$data\n-- END OF DATA\n"; return undef; }; -f "$fname" and do { unlink "$fname.old"; rename "$fname","$fname.old" or do { warn "$0::writeca($caname,$type): cannot make backup in $opt_d:\n $!\n"; return undef; }; }; open PEMDATA,">$fname" or do { warn "$0::cawrite($caname): writing $fname:\n $!\n"; return undef; }; print PEMDATA $data; close PEMDATA; $opt_V and print " Written $type for $caname\n"; return 1; } sub isin_str { my ($needle,@haystack) = @_; foreach $hay ( @haystack ) { $needle =~ /$hay/ and return 1; } return 0; } sub isa_pem { my ($type,$data) = @_; @lines=split(/\n/,$data); foreach $l ( @lines ) { $type eq "cert" and $l =~ /-----BEGIN CERTIFICATE-----/ and return 1; $type eq "crl" and $l =~ /-----BEGIN X509 CRL-----/ and return 1; } return undef; } sub certinfo { my ($cert) = @_; my ($key,$val); my ($myhash,$myfp,$mysubj,$myiss,$myser); open MKINFO, "|$openssl x509 -noout -hash -fingerprint -subject -issuer -serial >$tempfile" or do { warn "$0:certinfo(): cannot open $openssl or $tempfile:\n $!\n"; return undef; }; print MKINFO $cert; close MKINFO; open MKINFO,"$tempfile" or do { warn "$0:certinfo(): cannot open $tempfile:\n $!\n"; return undef; }; while () { chomp($_); $key=""; ($key,$val)=/^([^=]*)=\s*(.*)$/; $opt_V and print " CERT INFO read $_\n"; length==8 and $myhash=$_; ( $key eq "MD5 Fingerprint" ) and $myfp=$val; ( $key eq "subject" ) and $mysubj=$val; ( $key eq "issuer" ) and $myiss=$val; ( $key eq "serial" ) and $myser=$val; } close MKINFO; return ($myhash,$mysubj,$myfp,$myiss,$myser); } sub crlvalid { my ($caname,$crldata) = @_; my ($cafname)="$opt_d/cacert-$caname.pem"; my ($ok,$nextupd); open OPENSSL,"|$openssl crl -noout -CAfile $cafname -nextupdate > $tempfile 2>&1" or do { warn "$0::crlvalid($caname,...): cannot run $openssl:\n $!\n"; return 0; }; print OPENSSL $crldata; close OPENSSL; open OPENSSL,"$tempfile" or do { warn "$0::crlvalid($caname,...): cannot open output tempfile:\n $!\n"; return 0; }; $ok=0; while () { /verify OK/ and $ok=1; /nextUpdate/ and ($key,$nextupd)=split(/=/,$_,2); } close OPENSSL; chomp($nextupd); $ok and return $nextupd; return undef; } sub hashlink { my ($caname,$cahash)=@_; ( -f "$opt_d/$cahash.0" and ! -s "$opt_d/$cahash.0" ) and do { warn " HASH ref to CERT $caname is not a symlink\n"; return undef; }; ( -f "$opt_d/$cahash.r0" and ! -s "$opt_d/$cahash.r0" ) and do { warn " HASH ref to CRL $caname is not a symlink\n"; return undef; }; unlink "$opt_d/$cahash.0","$opt_d/$cahash.r0"; -f "$opt_d/cacert-$caname.pem" and do { symlink "cacert-$caname.pem","$opt_d/$cahash.0" or warn "$0::hashlink:symlink(cacert-$caname.pem,$opt_d/$cahash.0 failed:\n $!\n"; }; -f "$opt_d/cacrl-$caname.pem" and do { symlink "cacrl-$caname.pem","$opt_d/$cahash.r0" or warn "$0::hashlink:symlink(cacrl-$caname.pem,$opt_d/$cahash.0 failed:\n $!\n"; }; return 1; } sub help { my ($name) = @_; print < use as a list of CAs to process. See certs.cf(5) for details -d repository of CA certificates and location of ca-signing-policy.conf --reloadcerts list of ca symbolic names of which the CA certs is to be (re)loaded from the web repository EOD $opt_v and print < { cert => 'http://globus.home.cern.ch/globus/ca/c35c1972.0', crl => 'http://globus.home.cern.ch/globus/ca/cern.crl.pem', auth => ["/C=ch/O=CERN/*","/C=CH/O=CERN/*", "/O=Grid/O=CERN/*","/O=CERN/O=Grid/"] }, "nikhef-ms" => { cert => 'http://certificate.nikhef.nl/medium/cacert.pem', crl => 'http://certificate.nikhef.nl/medium/cacrl.pem', auth => ["/O=dutchgrid/O=users/*","/O=dutchgrid/O=hosts/*"] }, "nikhef-test-low" => { cert => 'http://certificate.nikhef.nl/test-low/cacert.pem', crl => 'http://certificate.nikhef.nl/test-low/cacrl.pem', auth => ["/C=nl/O=nikhef/*","/C=nl/O=UvA/OU=wins/*", "/C=nl/O=amolf/*","/O=dutchgrid/*"] }, "infn-2" => { crl => 'http://security.fi.infn.it/CA/crl.crl', crl_filter => "$openssl crl -inform der -text", auth => ["/C=IT/O=INFN/*"] }, "globus" => { dis => "distrusted due to local NIKHEF policy", auth => ["/C=us/O=Globus/*","/C=US/O=Globus/*","/O=Grid/O=Globus/*"] } ); EOD exit(0); }