#!/usr/bin/perl -w # CommModule - CAcert Communication Module # Copyright (C) 2006-2009 CAcert Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # Production Client / CommModule use strict; use Device::SerialPort qw( :PARAM :STAT 0.07 ); use POSIX; use IO::Select; use Time::HiRes q(usleep); use File::CounterFile; use IPC::Open3; use File::Copy; use DBI; use Locale::gettext; use IO::Socket; use MIME::Base64; use Digest::SHA qw(sha1_hex); #Protocol version: my $ver=1; my $paranoid=1; my $debug=0; #my $serialport="/dev/ttyS0"; my $serialport="/dev/ttyUSB0"; my $gpgbin="/usr/bin/gpg"; my $opensslbin="/usr/bin/openssl"; my $mysqlphp="/home/cacert/www/includes/mysql.php"; my %revokefile=(2=>"../www/class3-revoke.crl",1=>"../www/revoke.crl"); my $newlayout=1; #End of configurations ######################################################## my %monarr = ("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12); my $password=""; if(open IN,"<$mysqlphp") { my $content=""; undef $/; $content=; $password=$1 if($content=~m/mysql_connect\s*\("[^"]+",\s*"\w+",\s*"(\w+)"/); close IN; $/="\n"; } else { die "Could not read file: $!\n"; } my $dbh = DBI->connect("DBI:mysql:cacert:localhost","cacert",$password, { RaiseError => 1, AutoCommit => 1 }) || die ("Error with the database connection.\n"); sub readfile($) { my $save=$/; undef $/; open READIN,"<$_[0]"; my $content=; close READIN; $/=$save; return $content; } #Logging functions: my $lastdate = ""; sub SysLog($) { return if(not defined($_[0])); my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime); my $currdate = substr($timestamp, 0, 10); if ($lastdate ne $currdate) { close LOG if ($lastdate ne ""); $lastdate = $currdate; open LOG,">>logfile$lastdate.txt"; } print LOG "$timestamp $_[0]"; flush LOG; } sub Error($) { SysLog($_[0]); if($paranoid) { die $_[0]; } } my $timestamp=strftime("%Y-%m-%d %H:%M:%S",localtime); #mkdir "revokehashes"; foreach (keys %revokefile) { next unless (-f $revokefile{$_}); my $revokehash=sha1_hex(readfile($revokefile{$_})); SysLog "Root $_: Hash $revokefile{$_} = $revokehash\n"; } sub mysql_query($) { $dbh->do($_[0]); } sub trim($) { my $new=$_[0]; $new=~s/^\s*//; $new=~s/\s*$//; return($new); } sub addslashes($) { my $new=$_[0]; $new=~s/['"\\]/\\$1/g; return($new); } sub recode { return $_[1]; } SysLog("Opening Serial interface:\n"); sub SerialSettings($) { my $PortObj=$_[0]; if(!defined($PortObj)) { Error "Could not open Serial Port!\n" ; } else { $PortObj->baudrate(115200); $PortObj->parity("none"); $PortObj->databits(8); $PortObj->stopbits(1); } } #We have to open the SerialPort and close it again, so that we can bind it to a Handle if(! -f "serial.conf") { my $PortObj = new Device::SerialPort($serialport); SerialSettings($PortObj); $PortObj->save("serial.conf"); undef $PortObj; } my $PortObj = tie (*SER, 'Device::SerialPort', "serial.conf") || Error "Can't tie using Configuration_File_Name: $!\n"; Error "Could not open Serial Interface!\n" if(not defined($PortObj)); SerialSettings($PortObj); #open SER,">$serialport"; SysLog("Serial interface opened: $PortObj\n"); my $sel = new IO::Select( \*SER ); #Hexdump function: Returns the hexdump representation of a string sub hexdump($) { return "" if(not defined($_[0])); my $content=""; $content.=sprintf("%02X ",unpack("C",substr($_[0],$_,1))) foreach (0 .. length($_[0])-1); return $content; } #pack3 packs together the length of the data in 3 bytes and the data itself, size limited to 16MB. In case the data is more than 16 MB, it is ignored, and a 0 Byte block is transferred sub pack3 { return "\x00\x00\x00" if(!defined($_[0])); my $data=(length($_[0]) >= 2**24)? "":$_[0]; my $len=pack("N",length($data)); SysLog "len: ".length($data)."\n" if($debug); return substr($len,1,3).$data; } #unpack3 unpacks packed data. sub unpack3($) { return undef if((not defined($_[0])) or length($_[0])<3); #SysLog "hexdump: ".hexdump("\x00".substr($_[0],0,3))."\n"; my $len=unpack("N","\x00".substr($_[0],0,3)); #SysLog "len3: $len length(): ".length($_[0])." length()-3: ".(length($_[0])-3)."\n"; return undef if(length($_[0])-3 != $len); return substr($_[0],3); } #unpack3array extracts a whole array of concatented pack3ed data. sub unpack3array($) { my @retarr=(); if((not defined($_[0])) or length($_[0])<3) { SysLog "Begin of structure corrupt\n"; return (); } my $dataleft=$_[0]; while(length($dataleft)>=3) { #SysLog "hexdump: ".hexdump("\x00".substr($dataleft,0,3))."\n"; my $len=unpack("N","\x00".substr($dataleft,0,3)); #SysLog "len3: $len length(): ".length($dataleft)." length()-3: ".(length($dataleft)-3)."\n"; if(length($dataleft)-3 < $len) { SysLog "Structure cut off\n"; return (); } push @retarr, substr($dataleft,3,$len); $dataleft=substr($dataleft,3+$len); } if(length($dataleft)!=0) { SysLog "End of structure cut off\n"; return (); } return @retarr; } #Raw send function over the Serial Interface (+debugging) sub SendIt($) { return unless defined($_[0]); SysLog "Sending ".length($_[0])."\n"; #hexdump($_[0])."\n" if($debug); my $data=$_[0]; my $runcount=0; my $total=0; my $mtu=30; while(length($data)) { my $iwrote=scalar($PortObj->write(substr($data,0,$mtu)))||0; #usleep(270*$iwrote+9000); # On Linux, we have to wait to make sure it is being sent, and we dont loose any data. $total+=$iwrote; $data=substr($data,$iwrote); if ($debug) { print "i wrote: $iwrote total: $total left: ".length($data)."\n" if(!($runcount++ %10)); } } SysLog "Sent message.\n" if($debug); # print "Sending ".length($_[0])."\n"; #hexdump($_[0])."\n"; # foreach(0 .. length($_[0])) # { # $PortObj->write(substr($_[0],$_,1)); # } } my $modus=0; my $cnt=0; #Send data over the Serial Interface with handshaking: sub SendHandshaked($) { SysLog "Shaking hands ...\n" if($debug); SendIt("\x02"); Error "Handshake uncompleted. Connection lost2! $!\n" if(!scalar($sel->can_read(20))); my $data=""; my $length=read SER,$data,1; if($length && $data eq "\x10") { #print "OK ...\n"; my $xor=0; foreach(0 .. length($_[0])-1) { #print "xor mit ".unpack("C",substr($_[0],$_,1))."\n"; $xor ^= unpack("C",substr($_[0],$_,1)); } #print "XOR: $xor\n"; my $tryagain=1; while($tryagain) { SendIt($_[0].pack("C",$xor)."rie4Ech7"); Error "Packet receipt was not confirmed in 5 seconds. Connection lost!\n" if(!scalar($sel->can_read(5))); $data=""; $length=read SER,$data,1; if($length && $data eq "\x10") { SysLog "Sent successfully!...\n"; $tryagain=0; } elsif($length && $data eq "\x11") { $tryagain=1; } else { Error "I cannot send! $length ".unpack("C",$data)."\n"; } } } else { print "!Cannot send! $length \n"; Error "!Stopped sending.\n"; } } sub Receive { my $data=""; my @ready = $sel->can_read(120); my $length=read SER,$data,1,0; #SysLog "Data: ".hexdump($data)."\n"; if($data eq "\x02") { $modus=1; SysLog "Start received, sending OK\n" if($debug); SendIt("\x10"); my $block=""; my $blockfinished=0; my $tries=100000; while(!$blockfinished) { Error("Tried reading too often\n") if(($tries--)<=0); # SysLog ("tries: $tries") if(!($tries%10)); $data=""; if(!scalar($sel->can_read(5))) { Error "Handshake uncompleted. Connection lost variant3! $!\n" ; return; } $length=read SER,$data,100,0; if($length) { $block.=$data; } #SysLog("Received: $length ".length($block)."\n"); $blockfinished=defined(unpack3(substr($block,0,-9)))?1:0; if(!$blockfinished and substr($block,-8,8) eq "rie4Ech7") { SysLog "BROKEN Block detected!\n"; SendIt("\x11"); $block=""; $blockfinished=0; $tries=100000; } } SysLog "Block done: ".hexdump($block)."\n" if($debug); SendIt("\x10"); return($block); } else { Error("Error: No Answer received, Timeout.\n") if(length($data)==0); Error("Error: Wrong Startbyte: ".hexdump($data)." !\n"); } SysLog "Waiting on next request ...\n"; } # @result(Version,Action,Errorcode,Response)=Request(Version=1,Action=1,System=1,Root=1,Configuration="...",Parameter="...",Request="..."); sub Request($$$$$$$$$$$) { SysLog "Version: $_[0] Action: $_[1] System: $_[2] Root: $_[3] Config: $_[4]\n"; $_[3]=0 if($_[3]<0); SendHandshaked(pack3(pack3(pack("C*",$_[0],$_[1],$_[2],$_[3],$_[4],$_[5],$_[6]>>8,$_[6]&255,$_[7])).pack3($_[8]).pack3($_[9]).pack3($_[10]))); my $data=Receive(); my @fields=unpack3array(substr($data,3,-9)); SysLog "Answer from Server: ".hexdump($data)."\n" if($debug); #if(open OUT,">result.dat") #{ # print OUT $data; # close OUT; #} #else #{ # SysLog "Could not write result: $!\n"; #} return $fields[1]; } sub calculateDays($) { if($_[0]) { my @sum = $dbh->selectrow_array("select sum(`points`) as `total` from `notary` where `to`='".$_[0]."' and `deleted`=0 group by `to`"); SysLog("Summe: $sum[0]\n") if($debug); return ($sum[0]>=50)?730:180; } return 180; } sub X509extractSAN($) { my @bits = split("/", $_[0]); my $SAN=""; my $newsubject=""; foreach my $val(@bits) { my @bit=split("=",$val); if($bit[0] eq "subjectAltName") { $SAN.="," if($SAN ne ""); $SAN.= trim($bit[1]); } else { $newsubject .= "/".$val; } } $newsubject=~s{^//}{/}; $newsubject=~s/[\n\r\t\x00"\\']//g; $SAN=~s/[ \n\r\t\x00"\\']//g; return($SAN,$newsubject); } sub X509extractExpiryDate($) { # TIMEZONE ?!? my $data=`$opensslbin x509 -in "$_[0]" -noout -enddate`; #notAfter=Aug 8 10:26:34 2007 GMT if($data=~m/notAfter=(\w{2,4}) *(\d{1,2}) *(\d{1,2}:\d{1,2}:\d{1,2}) (\d{4}) GMT/) { my $date="$4-".$monarr{$1}."-$2 $3"; SysLog "Expiry Date found: $date\n" if($debug); return $date; } else { SysLog "Expiry Date not found: $data\n"; } return ""; } sub CRLuptodate($) { return 0 unless(-f $_[0]); my $data=`$opensslbin crl -in "$_[0]" -noout -lastupdate -inform der`; SysLog "CRL: $data\n"; #lastUpdate=Aug 8 10:26:34 2007 GMT # Is the timezone handled properly? if($data=~m/lastUpdate=(\w{2,4}) *(\d{1,2}) *(\d{1,2}:\d{1,2}:\d{1,2}) (\d{4}) GMT/) { my $date=sprintf("%04d-%02d-%02d",$4,$monarr{$1},$2); SysLog "CRL Issueing Date found: $date\n" if($debug); my $compare = strftime("%Y-%m-%d", localtime); SysLog "Comparing $date with $compare\n" if($debug); return $date eq $compare; } else { SysLog "Expiry Date not found. Perhaps DER format is necessary? Hint: $data\n"; } return 0; } sub X509extractSerialNumber($) { # TIMEZONE ?!? my $data=`$opensslbin x509 -in "$_[0]" -noout -serial`; if($data=~m/serial=([0-9A-F]+)/) { return $1; } return ""; } sub OpenPGPextractExpiryDate ($) { my $r=""; my $cts; my @date; open(RGPG, $gpgbin.' -vv '.$_[0].' 2>&1 |') or Error('Can\'t start GnuPG($gpgbin): '.$!."\n"); open(OUT, '> infogpg.txt' ) or Error('Can\'t open output file: infogpg.txt: '.$!); $/="\n"; while () { print OUT $_; unless ($r) { if ( /^\s*version \d+, created (\d+), md5len 0, sigclass (?:0x[0-9a-fA-F]+|\d+)\s*$/ ) { SysLog "Detected CTS: $1\n"; $cts = int($1); } elsif ( /^\s*critical hashed subpkt \d+ len \d+ \(sig expires after ((\d+)y)?((\d+)d)?((\d+)h)?(\d+)m\)\s*$/ ) { SysLog "Detected FRAME $2 $4 $6 $8\n"; $cts += $2 * 31536000; # secs per year (60 * 60 * 24 * 365) $cts += $4 * 86400; # secs per day (60 * 60 * 24) $cts += $6 * 3600; # secs per hour (60 * 60) $cts += $8 * 60; # secs per min (60) $r = $cts; } elsif(/version/) { SysLog "Detected VERSION\n"; } } } close(OUT ); close(RGPG); SysLog "CTS: $cts R: $r\n"; if ( $r ) { @date = gmtime($r); $r = sprintf('%.4i-%.2i-%.2i %.2i:%.2i:%.2i', # date format $date[5] + 1900, $date[4] + 1, $date[3], # day $date[2], $date[1], $date[0], # time ); } SysLog "$r\n"; return $r; } #sub OpenPGPextractExpiryDate($) #{ # my $data=`$gpgbin -v $_[0]`; # open OUT,">infogpg.txt"; # print OUT $data; # close OUT; # if($data=~m/^sig\s+[0-9A-F]{8} (\d{4}-\d\d-\d\d) [^\[]/) # { # return "$1 00:00:00"; # } # return ""; #} # Sets the locale according to the users preferred language sub setUsersLanguage($) { my $lang="en_US"; print "Searching for the language of the user $_[0]\n"; my @a=$dbh->selectrow_array("select language from users where id='".int($_[0])."'"); $lang = $1 if($a[0]=~m/(\w+_[\w.@]+)/); SysLog "The users preferred language: $lang\n"; if($lang ne "") { $ENV{"LANG"}=$lang; setlocale(LC_ALL, $lang); } else { $ENV{"LANG"}="en_AU"; setlocale(LC_ALL, "en_AU"); } } sub getUserData($) { return() unless($_[0]=~m/^\d+$/); my $sth = $dbh->prepare("select * from users where id='$_[0]'"); $sth->execute(); #SysLog "USER DUMP:\n"; while ( my $rowdata = $sth->fetchrow_hashref() ) { my %tmp=%{$rowdata}; #foreach (sort keys %tmp) #{ #SysLog " $_ -> $tmp{$_}\n"; #} return %tmp; } return (); } sub _($) { return gettext($_[0]); } sub sendmail($$$$$$$) { my ($to, $subject, $message, $from, $replyto, $toname, $fromname)=@_; my $errorsto="returns\@cacert.org"; my $extra=""; # sendmail($user{email}, "[CAcert.org] Your GPG/PGP Key", $body, "support\@cacert.org", "", "", "CAcert Support"); my @lines=split("\n",$message); $message = ""; foreach my $line (@lines) { $line = trim($line); if($line eq ".") { $message .= " .\n"; } else { $message .= $line."\n"; } } $fromname = $from if($fromname eq ""); my @bits = split(",", $from); $from = addslashes($bits['0']); $fromname = addslashes($fromname); my $smtp = IO::Socket::INET->new(PeerAddr => 'localhost:25'); $/="\n"; SysLog "SMTP: ".<$smtp>; print $smtp "HELO hlin.cacert.org\r\n"; SysLog "SMTP: ".<$smtp>; print $smtp "MAIL FROM:\r\n"; SysLog "MAIL FROM: ".<$smtp>; @bits = split(",", $to); foreach my $user (@bits) { print $smtp "RCPT TO:<".trim($user).">\r\n"; SysLog "RCPT TO: ".<$smtp>; } print $smtp "DATA\r\n"; SysLog "DATA: ".<$smtp>; print $smtp "X-Mailer: CAcert.org Website\r\n"; print $smtp "X-OriginatingIP: ".$ENV{"REMOTE_ADDR"}."\r\n"; print $smtp "Sender: $errorsto\r\n"; print $smtp "Errors-To: $errorsto\r\n"; if($replyto ne "") { print $smtp "Reply-To: $replyto\r\n"; } else { print $smtp "Reply-To: $from\r\n"; } print $smtp "From: $from ($fromname)\r\n"; print $smtp "To: $to\r\n"; my $newsubj=encode_base64(recode("html..utf-8", trim($subject))); #SysLog("NewSubj: --".$newsubj."--\n") if($debug); $newsubj=~s/\n*$//; #SysLog("NewSubj: --".$newsubj."--\n") if($debug); print $smtp trim($subject)=~m/[^a-zA-Z0-9 ,.\[\]\/-]/?"Subject: =?utf-8?B?$newsubj?=\r\n":"Subject: $subject\r\n"; print $smtp "Mime-Version: 1.0\r\n"; if($extra eq "") { print $smtp "Content-Type: text/plain; charset=\"utf-8\"\r\n"; print $smtp "Content-Transfer-Encoding: 8bit\r\n"; } else { print $smtp "Content-Type: text/plain; charset=\"iso-8859-1\"\r\n"; print $smtp "Content-Transfer-Encoding: quoted-printable\r\n"; print $smtp "Content-Disposition: inline\r\n"; }; # print $smtp "Content-Transfer-Encoding: BASE64\r\n"; print $smtp "\r\n"; # print $smtp chunk_split(encode_base64(recode("html..utf-8", $message)))."\r\n.\r\n"; print $smtp recode("html..utf-8", $message)."\r\n.\r\n"; SysLog "ENDOFTEXT: ".<$smtp>; print $smtp "QUIT\n"; SysLog "QUIT: ".<$smtp>; close($smtp); } sub HandleCerts($$) { my $org=$_[0]?"org":""; my $server=$_[1]; my $table=$org.($server?"domaincerts":"emailcerts"); SysLog "HandleCerts $table\n"; my $sth = $dbh->prepare("select * from $table where crt_name='' and csr_name!='' and warning<3"); $sth->execute(); #$rowdata; while ( my $rowdata = $sth->fetchrow_hashref() ) { my %row=%{$rowdata}; my $prefix=$org.($server?"server":"client"); my $short=int($row{'id'}/1000); my $csrname = "../csr/$prefix-".$row{'id'}.".csr"; $csrname = "../csr/$prefix/$short/$prefix-".$row{'id'}.".csr" if($newlayout); SysLog("New Layout: "."../csr/$prefix/$short/$prefix-".$row{'id'}.".csr\n"); #my $crtname = "../crt/$prefix-".$row{'id'}.".crt"; my $crtname=$csrname; $crtname=~s/^\.\.\/csr/..\/crt/; $crtname=~s/\.csr$/.crt/; my $dirname=$crtname; $dirname=~s/\/[^\/]*\.crt//; mkdir $dirname,0777; SysLog("New Layout: $crtname\n"); if($server) { #Weird SQL structure ... my @sqlres=$dbh->selectrow_array("select memid from domains where id='".int($row{'domid'})."'"); $row{'memid'}=$sqlres[0]; SysLog("Fetched memid: $row{'memid'}\n") if($debug); } SysLog "Opening $csrname\n"; my $crt=""; my $profile=0; # "0"=>"client.cnf", # "1"=>"client-org.cnf", # "2"=>"client-codesign.cnf", # "3"=>"client-machine.cnf", # "4"=>"client-ads.cnf", # "5"=>"server.cnf", # "6"=>"server-org.cnf", # "7"=>"server-jabber.cnf", # "8"=>"server-ocsp.cnf", # "9"=>"server-timestamp.cnf", # "10"=>"proxy.cnf", # "11"=>"subca.cnf" if($row{"type"} =~ m/^(8|9)$/) { $profile=$row{"type"}; } elsif($org) { if($row{'codesign'}) { $profile=2; ## TODO! } elsif($server) { $profile=6; } else { $profile=1; } } else { if($row{'codesign'}) { $profile=2; } elsif($server) { $profile=5; } else { $profile=0; } } if(open(IN,"<$csrname")) { undef $/; my $content=; close IN; SysLog "Read $csrname.\n" if($debug); SysLog "Subject: --$row{'subject'}--\n" if($debug); my ($SAN,$subject)=X509extractSAN($row{'subject'}); SysLog "Subject: --$subject--\n" if($debug); SysLog "SAN: --$SAN--\n" if($debug); SysLog "memid: $row{'memid'}\n" if($debug); my $days=$org?($server?(365*2):365):calculateDays($row{"memid"}); my $md_id = 0; $md_id = 1 if( $row{'md'} eq "md5"); $md_id = 2 if( $row{'md'} eq "sha1"); $md_id = 3 if( $row{'md'} eq "rmd160"); $md_id = 8 if( $row{'md'} eq "sha256"); $md_id = 9 if( $row{'md'} eq "sha384"); $md_id =10 if( $row{'md'} eq "sha512"); $crt=Request($ver,1,1,$row{'rootcert'}-1,$profile,$md_id,$days,$row{'keytype'}eq"NS"?1:0,$content,$SAN,$subject); if(length($crt)) { if($crt=~m/^-----BEGIN CERTIFICATE-----/) { open OUT,">$crtname"; print OUT $crt; close OUT; } else { open OUT,">$crtname.der"; print OUT $crt; close OUT; system "$opensslbin x509 -in $crtname.der -inform der -out $crtname"; } } else { SysLog "ZERO Length certificate received.\n"; } } else { print "Error: $! Konnte $csrname nicht laden\n"; } if(-s $crtname) { SysLog "Opening $crtname\n"; my $date=X509extractExpiryDate($crtname); my $serial=X509extractSerialNumber($crtname); setUsersLanguage($row{memid}); my %user=getUserData($row{memid}); foreach (sort keys %user) { SysLog " $_ -> $user{$_}\n" if($debug); } SysLog("update `$table` set `crt_name`='$crtname', modified=now(), serial='$serial', `expire`='$date' where `id`='".$row{'id'}."'\n"); $dbh->do("update `$table` set `crt_name`='$crtname', modified=now(), serial='$serial', `expire`='$date' where `id`='".$row{'id'}."'"); my $body = _("Hi")." $user{fname},\n\n"; $body .= sprintf(_("You can collect your certificate for %s by going to the following location:")."\n\n", $row{'email'}.$row{'CN'}); $body .= "https://www.cacert.org/account.php?id=".($server?"15":"6")."&cert=$row{id}\n\n"; $body .= _("If you have not imported CAcert's root certificate, please go to:")."\n"; $body .= "https://www.cacert.org/index.php?id=3\n"; $body .= "Root cert fingerprint = A6:1B:37:5E:39:0D:9C:36:54:EE:BD:20:31:46:1F:6B\n"; $body .= "Root cert fingerprint = 135C EC36 F49C B8E9 3B1A B270 CD80 8846 76CE 8F33\n\n"; $body .= _("Best regards")."\n"._("CAcert.org Support!")."\n\n"; sendmail($user{email}, "[CAcert.org] "._("Your certificate"), $body, "support\@cacert.org", "", "", "CAcert Support"); } else { SysLog("Could not find the issued certificate. $crtname ".$row{"id"}."\n"); $dbh->do("update `$table` set warning=warning+1 where `id`='".$row{'id'}."'"); } } } sub DoCRL($$) { my $crl=$_[0]; my $crlname=$_[1]; if(length($crl)) { if($crl=~m/^-----BEGIN X509 CRL-----/) { open OUT,">$crlname.pem"; print OUT $crl; close OUT; system "$opensslbin crl -in $crlname.pem -outform der -out $crlname.tmp"; } else { open OUT,">$crlname.patch"; print OUT $crl; close OUT; my $res=system "xdelta patch $crlname.patch $crlname $crlname.tmp"; #print "xdelta res: $res\n"; if($res==512) { open OUT,">$crlname.tmp"; print OUT $crl; close OUT; } } my $res=`openssl crl -verify -in $crlname.tmp -inform der -noout 2>&1`; SysLog "verify: $res\n"; if($res=~m/verify OK/) { rename "$crlname.tmp","$crlname"; } else { SysLog "VERIFICATION OF NEW CRL DID NOT SUCCEED! PLEASE REPAIR!\n"; SysLog "Broken CRL is available as $crlname.tmp\n"; #Override for testing: rename "$crlname.tmp","$crlname"; } return 1; } else { SysLog("RECEIVED AN EMPTY CRL!\n"); } return 0; } sub RefreshCRLs() { foreach my $rootcert (keys %revokefile) { if(!CRLuptodate($revokefile{$rootcert})) { SysLog "Update of the CRL $rootcert is necessary!\n"; my $crlname = $revokefile{$rootcert}; my $revokehash=sha1_hex(readfile($crlname)); my $crl=Request($ver,2,1,$rootcert-1,0,0,365,0,"","",$revokehash); #print "Received ".length($crl)." ".hexdump($crl)."\n"; DoCRL($crl,$crlname); } } } sub RevokeCerts($$) { my $org=$_[0]?"org":""; my $server=$_[1]; my $table=$org.($server?"domaincerts":"emailcerts"); my $sth = $dbh->prepare("select * from $table where revoked='1970-01-01 10:00:01'"); # WHICH TIMEZONE? $sth->execute(); #$rowdata; while ( my $rowdata = $sth->fetchrow_hashref() ) { my %row=%{$rowdata}; my $prefix=$org.($server?"server":"client"); my $short=int($row{'id'}/1000); my $csrname = "../csr/$prefix-".$row{'id'}.".csr"; $csrname = "../csr/$prefix/$short/$prefix-".$row{'id'}.".csr" if($newlayout); SysLog("New Layout: "."../csr/$prefix/$short/$prefix-".$row{'id'}.".csr\n"); #my $crtname = "../crt/$prefix-".$row{'id'}.".crt"; my $crtname=$csrname; $crtname=~s/^\.\.\/csr/..\/crt/; $crtname=~s/\.csr$/.crt/; SysLog("New Layout: $crtname\n"); #my $csrname = "../csr/".$org.($server?"server-":"client-").$row{'id'}.".csr"; #my $crtname = "../crt/".$org.($server?"server-":"client-").$row{'id'}.".crt"; my $crlname = $revokefile{$row{'rootcert'}}; my $crt=""; if(open(IN,"<$crtname")) { undef $/; my $content=; close IN; my $revokehash=sha1_hex(readfile($crlname)); my $crl=Request($ver,2,1,$row{'rootcert'}-1,0,0,365,0,$content,"",$revokehash); my $result=DoCRL($crl,$crlname); if($result) { $dbh->do("update `$table` set `revoked`=now() where `id`='".$row{'id'}."'"); if($org eq "") { if($server) { my @a=$dbh->selectrow_array("select `memid` from `domains` where `id`='".int($row{domid})."'"); sendRevokeMail($a[0], $row{'CN'}, $row{'serial'}); } else { sendRevokeMail($row{memid}, $row{'CN'}, $row{'serial'}); } } else { my $orgsth = $dbh->prepare("select `memid` from `org` where `orgid`='".int($row{orgid})."'"); $orgsth->execute(); while ( my ($memid) = $orgsth->fetchrow_array() ) { sendRevokeMail($memid, $row{'CN'}, $row{'serial'}); } } } } else { SysLog("Error in RevokeCerts: $crtname $!\n") if($debug); } } } sub sendRevokeMail() { my $memid = $_[0]; my $certName = $_[1]; my $serial = $_[2]; setUsersLanguage($memid); my %user=getUserData($memid); my $body = _("Hi")." $user{fname},\n\n"; $body .= sprintf(_("Your certificate for '%s' with the serial number '%s' has been revoked, as per request.")."\n\n", $certName, $serial); $body .= _("Best regards")."\n"._("CAcert.org Support!")."\n\n"; SysLog("Sending email to ".$user{"email"}."\n") if($debug); sendmail($user{email}, "[CAcert.org] "._("Your certificate"), $body, "support\@cacert.org", "", "", "CAcert Support"); } sub HandleGPG() { my $sth = $dbh->prepare("select * from gpg where crt='' and csr!='' "); $sth->execute(); my $rowdata; while ( $rowdata = $sth->fetchrow_hashref() ) { my %row=%{$rowdata}; my $prefix="gpg"; my $short=int($row{'id'}/1000); my $csrname = "../csr/$prefix-".$row{'id'}.".csr"; $csrname = "../csr/$prefix/$short/$prefix-".$row{'id'}.".csr" if($newlayout); SysLog("New Layout: "."../csr/$prefix/$short/$prefix-".$row{'id'}.".csr\n"); #my $crtname = "../crt/$prefix-".$row{'id'}.".crt"; my $crtname=$csrname; $crtname=~s/^\.\.\/csr/..\/crt/; $crtname=~s/\.csr$/.crt/; SysLog("New Layout: $crtname\n"); #my $csrname = "../csr/gpg-".$row{'id'}.".csr"; #my $crtname = "../crt/gpg-".$row{'id'}.".crt"; SysLog "Opening $csrname\n"; my $crt=""; if(-s $csrname && open(IN,"<$csrname")) { undef $/; my $content=; close IN; SysLog "Read $csrname.\n"; $crt=Request($ver,1,2,0,0,2,366,0,$content,"",""); if(length($crt)) { open OUT,">$crtname"; print OUT $crt; close OUT; } } else { #Error("Error: $!\n"); next; } if(-s $crtname) { SysLog "Opening $crtname\n"; setUsersLanguage($row{memid}); my $date=OpenPGPextractExpiryDate($crtname); my %user=getUserData($row{memid}); $dbh->do("update `gpg` set `crt`='$crtname', issued=now(), `expire`='$date' where `id`='".$row{'id'}."'"); my $body = _("Hi")." $user{fname},\n\n"; $body .= sprintf(_("Your CAcert signed key for %s is available online at:")."\n\n", $row{'email'}); $body .= "https://www.cacert.org/gpg.php?id=3&cert=$row{id}\n\n"; $body .= _("To help improve the trust of CAcert in general, it's appreciated if you could also sign our key and upload it to a key server. Below is a copy of our primary key details:")."\n\n"; $body .= "pub 1024D/65D0FD58 2003-07-11 CA Cert Signing Authority (Root CA) \n"; $body .= "Key fingerprint = A31D 4F81 EF4E BD07 B456 FA04 D2BB 0D01 65D0 FD58\n\n"; $body .= _("Best regards")."\n"._("CAcert.org Support!")."\n\n"; sendmail($user{email}, "[CAcert.org] Your GPG/PGP Key", $body, "support\@cacert.org", "", "", "CAcert Support"); } else { SysLog("Could not find the issued gpg key. ".$row{"id"}."\n"); #$dbh->do("delete from `gpg` where `id`='".$row{'id'}."'"); } } } # Main program loop my $crlcheck=0; while ( -f "./client.pl-active" ) { SysLog("Handling GPG database ...\n"); HandleGPG(); SysLog("Issueing certs ...\n"); HandleCerts(0,0); #personal client certs HandleCerts(0,1); #personal server certs HandleCerts(1,0); #org client certs HandleCerts(1,1); #org server certs SysLog("Revoking certs ...\n"); RevokeCerts(0,0); #personal client certs RevokeCerts(0,1); #personal server certs RevokeCerts(1,0); #org client certs RevokeCerts(1,1); #org server certs $crlcheck++; RefreshCRLs() if(($crlcheck%100) == 1); #print "Sign Request X.509, Root0\n"; #my $reqcontent=""; #Request($ver,1,1,0,5,2,365,0,$reqcontent,"","/CN=supertest.cacert.at"); SysLog("NUL Request:\n"); my $timestamp=strftime("%m%d%H%M%Y.%S",gmtime); Request($ver,0,0,0,0,0,0,0,$timestamp,"",""); sleep(1); usleep(1700000); }