home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / TestSSLCA.pm < prev    next >
Encoding:
Perl POD Document  |  2003-08-12  |  10.9 KB  |  512 lines

  1. package Apache::TestSSLCA;
  2.  
  3. use strict;
  4. use warnings FATAL => 'all';
  5.  
  6. use Cwd ();
  7. use DirHandle ();
  8. use File::Path ();
  9. use File::Copy 'cp';
  10. use File::Basename;
  11. use Apache::TestConfig ();
  12. use Apache::TestTrace;
  13.  
  14. use constant SSLCA_DB => 'index.txt';
  15.  
  16. use vars qw(@EXPORT_OK &import);
  17.  
  18. use subs qw(symlink);
  19.  
  20. @EXPORT_OK = qw(dn dn_vars dn_oneline);
  21. *import = \&Exporter::import;
  22.  
  23. my $openssl = $ENV{APACHE_TEST_OPENSSL_CMD} || 'openssl';
  24. my $version = version();
  25.  
  26. my $CA = 'asf';
  27. my $Config; #global Apache::TestConfig object
  28.  
  29. my $days     = '-days 365';
  30. my $cakey    = 'keys/ca.pem';
  31. my $cacert   = 'certs/ca.crt';
  32. my $capolicy = '-policy policy_anything';
  33. my $cacrl    = 'crl/ca-bundle.crl';
  34.  
  35. #we use the same password for everything
  36. my $pass    = 'httpd';
  37. my $passin  = "-passin pass:$pass";
  38. my $passout = "-passout pass:$pass";
  39.  
  40. # in 0.9.7 s/Email/emailAddress/ in DN
  41. my $email_field = $version lt "0.9.7" ? "Email" : "emailAddress";
  42.  
  43. my $ca_dn = {
  44.     asf => {
  45.         C  => 'US',
  46.         ST => 'California',
  47.         L  => 'San Francisco',
  48.         O  => 'ASF',
  49.         OU => 'httpd-test',
  50.         CN => '',
  51.         $email_field => 'test-dev@httpd.apache.org',
  52.     },
  53. };
  54.  
  55. my $cert_dn = {
  56.     client_snakeoil => {
  57.         C  => 'AU',
  58.         ST => 'Queensland',
  59.         L  => 'Mackay',
  60.         O  => 'Snake Oil, Ltd.',
  61.         OU => 'Staff',
  62.     },
  63.     client_ok => {
  64.     },
  65.     client_revoked => {
  66.     },
  67.     server => {
  68.         CN => 'localhost',
  69.         OU => 'httpd-test/rsa-test',
  70.     },
  71.     server2 => {
  72.         CN => 'localhost',
  73.         OU => 'httpd-test/rsa-test-2',
  74.     },
  75.     server_des3 => {
  76.         CN => 'localhost',
  77.         OU => 'httpd-test/rsa-des3-test',
  78.     },
  79.     server2_des3 => {
  80.         CN => 'localhost',
  81.         OU => 'httpd-test/rsa-des3-test-2',
  82.     },
  83. };
  84.  
  85. #generate DSA versions of the server certs/keys
  86. while (my($key, $val) = each %$cert_dn) {
  87.     next unless $key =~ /^server/;
  88.     my $name = join '_', $key, 'dsa';
  89.     $cert_dn->{$name} = { %$val }; #copy
  90.     $cert_dn->{$name}->{OU} =~ s/rsa/dsa/;
  91. }
  92.  
  93. sub ca_dn {
  94.     $ca_dn = shift if @_;
  95.     $ca_dn;
  96. }
  97.  
  98. sub cert_dn {
  99.     $cert_dn = shift if @_;
  100.     $cert_dn;
  101. }
  102.  
  103. sub dn {
  104.     my $name = shift;
  105.  
  106.     my %dn = %{ $ca_dn->{$CA} }; #default values
  107.     $dn{CN} ||= $name; #try make sure each Common Name is different
  108.  
  109.     my $default_dn = $cert_dn->{$name};
  110.  
  111.     if ($default_dn) {
  112.         while (my($key, $value) = each %$default_dn) {
  113.             #override values
  114.             $dn{$key} = $value;
  115.         }
  116.     }
  117.  
  118.     return wantarray ? %dn : \%dn;
  119. }
  120.  
  121. sub dn_vars {
  122.     my($name, $type) = @_;
  123.  
  124.     my $dn = dn($name);
  125.     my $prefix = join '_', 'SSL', $type, 'DN';
  126.  
  127.     return { map { $prefix ."_$_", $dn->{$_} } keys %$dn };
  128. }
  129.  
  130. sub dn_oneline {
  131.     my($dn) = @_;
  132.  
  133.     unless (ref $dn) {
  134.         $dn = dn($dn);
  135.     }
  136.  
  137.     my $string = "";
  138.  
  139.     for my $k ((qw(C ST L O OU CN), $email_field)) {
  140.         next unless $dn->{$k};
  141.         $string .= "/$k=$dn->{$k}";
  142.     }
  143.  
  144.     $string;
  145. }
  146.  
  147. sub openssl {
  148.     return $openssl unless @_;
  149.  
  150.     my $cmd = "$openssl @_";
  151.  
  152.     info $cmd;
  153.  
  154.     unless (system($cmd) == 0) {
  155.         my $status = $? >> 8;
  156.         die "system @_ failed (exit status=$status)";
  157.     }
  158. }
  159.  
  160. my @dirs = qw(keys newcerts certs crl export csr conf proxy);
  161.  
  162. sub init {
  163.     for my $dir (@dirs) {
  164.         gendir($dir);
  165.     }
  166. }
  167.  
  168. sub config_file {
  169.     my $name = shift;
  170.  
  171.     my $file = "conf/$name.cnf";
  172.     return $file if -e $file;
  173.  
  174.     my $dn = dn($name);
  175.     my $db = sslca_db($name);
  176.  
  177.     writefile($db, '', 1);
  178.  
  179.     writefile($file, <<EOF);
  180. [ req ]
  181. distinguished_name     = req_distinguished_name
  182. attributes             = req_attributes
  183. prompt                 = no
  184. default_bits           = 1024
  185. output_password        = $pass
  186.  
  187. [ req_distinguished_name ]
  188. C                      = $dn->{C}
  189. ST                     = $dn->{ST}
  190. L                      = $dn->{L}
  191. O                      = $dn->{O}
  192. OU                     = $dn->{OU}
  193. CN                     = $dn->{CN}
  194. emailAddress           = $dn->{$email_field}
  195.  
  196. [ req_attributes ]
  197. challengePassword      = $pass
  198.  
  199. [ ca ]
  200. default_ca           = CA_default
  201.  
  202. [ CA_default ]
  203. certs            = certs        # Where the issued certs are kept
  204. new_certs_dir    = newcerts     # default place for new certs.
  205. crl_dir          = crl          # Where the issued crl are kept
  206. database         = $db          # database index file.
  207. serial           = serial       # The current serial number
  208.  
  209. certificate      = $cacert      # The CA certificate
  210. crl              = $cacrl       # The current CRL
  211. private_key      = $cakey       # The private key
  212.  
  213. default_days     = 365          # how long to certify for
  214. default_crl_days = 30           # how long before next CRL
  215. default_md       = md5          # which md to use.
  216. preserve         = no           # keep passed DN ordering
  217.  
  218. [ policy_anything ]
  219. countryName        = optional
  220. stateOrProvinceName    = optional
  221. localityName        = optional
  222. organizationName    = optional
  223. organizationalUnitName    = optional
  224. commonName        = supplied
  225. emailAddress        = optional
  226. EOF
  227.  
  228.     return $file;
  229. }
  230.  
  231. sub config {
  232.     my $name = shift;
  233.  
  234.     my $file = config_file($name);
  235.  
  236.     my $config = "-config $file";
  237.  
  238.     $config;
  239. }
  240.  
  241. use constant PASSWORD_CLEARTEXT =>
  242.     Apache::TestConfig::WIN32 || Apache::TestConfig::NETWARE;
  243.  
  244. #http://www.modssl.org/docs/2.8/ssl_reference.html#ToC21
  245. my $basic_auth_password =
  246.     PASSWORD_CLEARTEXT ? 'password': 'xxj31ZMTZzkVA';
  247. my $digest_auth_hash    = '$1$OXLyS...$Owx8s2/m9/gfkcRVXzgoE/';
  248.  
  249. sub new_ca {
  250.     writefile('serial', "01\n", 1);
  251.  
  252.     writefile('ssl.htpasswd',
  253.               join ':', dn_oneline('client_snakeoil'),
  254.               $basic_auth_password);
  255.  
  256.     openssl req => "-new -x509 -keyout $cakey -out $cacert $days",
  257.                    config('ca');
  258.  
  259.     export_cert('ca'); #useful for importing into IE
  260. }
  261.  
  262. sub new_key {
  263.     my $name = shift;
  264.  
  265.     my $encrypt = @_ ? "@_ $passout" : "";
  266.  
  267.     my $out = "-out keys/$name.pem $encrypt";
  268.  
  269.     if ($name =~ /dsa/) {
  270.         #this takes a long time so just do it once
  271.         #don't do this in real life
  272.         unless (-e 'dsa-param') {
  273.             openssl dsaparam => '-inform PEM -out dsa-param 1024';
  274.         }
  275.         openssl gendsa => "dsa-param $out";
  276.     }
  277.     else {
  278.         openssl genrsa => "$out 1024";
  279.     }
  280. }
  281.  
  282. sub new_cert {
  283.     my $name = shift;
  284.  
  285.     openssl req => "-new -key keys/$name.pem -out csr/$name.csr",
  286.                    $passin, $passout, config($name);
  287.  
  288.     sign_cert($name);
  289.  
  290.     export_cert($name);
  291. }
  292.  
  293. sub sign_cert {
  294.     my $name = shift;
  295.  
  296.     openssl ca => "$capolicy -in csr/$name.csr -out certs/$name.crt",
  297.                   $passin, config($name), '-batch';
  298. }
  299.  
  300. #handy for importing into a browser such as netscape
  301. sub export_cert {
  302.     my $name = shift;
  303.  
  304.     return if $name =~ /^server/; #no point in exporting server certs
  305.  
  306.     openssl pkcs12 => "-export -in certs/$name.crt -inkey keys/$name.pem",
  307.                       "-out export/$name.p12", $passin, $passout;
  308. }
  309.  
  310. sub sslca_db {
  311.     my $name = shift;
  312.     return "$name-" . SSLCA_DB;
  313. }
  314.  
  315. sub revoke_cert {
  316.     my $name = shift;
  317.  
  318.     my @args = (config('cacrl'), $passin);
  319.  
  320.     #revokes in the SSLCA_DB database
  321.     openssl ca => "-revoke certs/$name.crt", @args;
  322.  
  323.     my $db = sslca_db($name);
  324.     unless (-e $db) {
  325.         #hack required for win32
  326.         my $new = join '.', $db, 'new';
  327.         if (-e $new) {
  328.             cp $new, $db;
  329.         }
  330.     }
  331.  
  332.     #generates crl from the index.txt database
  333.     openssl ca => "-gencrl -out $cacrl", @args;
  334. }
  335.  
  336. sub symlink {
  337.     my($file, $symlink) = @_;
  338.  
  339.     my $what = 'linked';
  340.  
  341.     if (Apache::TestConfig::WINFU) {
  342.         cp $file, $symlink;
  343.         $what = 'copied';
  344.     }
  345.     else {
  346.         CORE::symlink($file, $symlink);
  347.     }
  348.  
  349.     info "$what $file to $symlink";
  350. }
  351.  
  352. sub hash_certs {
  353.     my($type, $dir) = @_;
  354.  
  355.     chdir $dir;
  356.  
  357.     my $dh = DirHandle->new('.') or die "opendir $dir: $!";
  358.     my $n = 0;
  359.  
  360.     for my $file ($dh->read) {
  361.         next unless $file =~ /\.cr[tl]$/;
  362.         chomp(my $hash = `openssl $type -noout -hash < $file`);
  363.         next unless $hash;
  364.         my $symlink = "$hash.r$n";
  365.         $n++;
  366.         symlink $file, $symlink;
  367.     }
  368.  
  369.     close $dh;
  370.  
  371.     chdir $CA;
  372. }
  373.  
  374. sub make_proxy_cert {
  375.     my $name = shift;
  376.  
  377.     my $from = "certs/$name.crt";
  378.     my $to = "proxy/$name.pem";
  379.  
  380.     info "generating proxy cert: $to";
  381.  
  382.     my $fh_to = Symbol::gensym();
  383.     my $fh_from = Symbol::gensym();
  384.  
  385.     open $fh_to, ">$to" or die "open $to: $!";
  386.     open $fh_from, $from or die "open $from: $!";
  387.  
  388.     cp $fh_from, $fh_to;
  389.  
  390.     $from = "keys/$name.pem";
  391.  
  392.     open $fh_from, $from or die "open $from: $!";
  393.  
  394.     cp $fh_from, $fh_to;
  395.  
  396.     close $fh_from;
  397.     close $fh_to;
  398. }
  399.  
  400. sub setup {
  401.     $CA = shift;
  402.  
  403.     unless ($ca_dn->{$CA}) {
  404.         die "unknown CA $CA";
  405.     }
  406.  
  407.     gendir($CA);
  408.  
  409.     chdir $CA;
  410.  
  411.     init();
  412.     new_ca();
  413.  
  414.     my @names = keys %$cert_dn;
  415.  
  416.     for my $name (@names) {
  417.         my @key_args = ();
  418.         if ($name =~ /_des3/) {
  419.             push @key_args, '-des3';
  420.         }
  421.  
  422.         new_key($name, @key_args);
  423.         new_cert($name);
  424.  
  425.         if ($name =~ /_revoked$/) {
  426.             revoke_cert($name);
  427.         }
  428.  
  429.         if ($name =~ /^client_/) {
  430.             make_proxy_cert($name);
  431.         }
  432.     }
  433.  
  434.     hash_certs(crl => 'crl');
  435. }
  436.  
  437. sub generate {
  438.     $Config = shift;
  439.  
  440.     $CA = shift || $Config->{vars}->{sslcaorg};
  441.  
  442.     my $root = $Config->{vars}->{sslca};
  443.  
  444.     return if -d $root;
  445.  
  446.     my $pwd  = Cwd::cwd();
  447.     my $base = dirname $root;
  448.     my $dir  = basename $root;
  449.  
  450.     chdir $base;
  451.  
  452.     #make a note that we created the tree
  453.     $Config->clean_add_path($root);
  454.  
  455.     gendir($dir);
  456.  
  457.     chdir $dir;
  458.  
  459.     warning "generating SSL CA for $CA";
  460.  
  461.     setup($CA);
  462.  
  463.     chdir $pwd;
  464. }
  465.  
  466. sub clean {
  467.     my $config = shift;
  468.  
  469.     #rel2abs adds same drive letter for win32 that clean_add_path added
  470.     my $dir = File::Spec->rel2abs($config->{vars}->{sslca});
  471.  
  472.     unless ($config->{clean}->{dirs}->{$dir}) {
  473.         return; #we did not generate this ca
  474.     }
  475.  
  476.     unless ($config->{clean_level} > 1) {
  477.         #skip t/TEST -conf
  478.         warning "skipping regeneration of SSL CA; run t/TEST -clean to force";
  479.         return;
  480.     }
  481.  
  482.     File::Path::rmtree([$dir], 1, 1);
  483. }
  484.  
  485. #not using Apache::TestConfig methods because the openssl commands
  486. #will generate heaps of files we cannot keep track of
  487.  
  488. sub writefile {
  489.     my($file, $content) = @_;
  490.  
  491.     my $fh = Symbol::gensym();
  492.     open $fh, ">$file" or die "open $file: $!";
  493.     print $fh $content;
  494.     close $fh;
  495. }
  496.  
  497. sub gendir {
  498.     my($dir) = @_;
  499.  
  500.     return if -d $dir;
  501.     mkdir $dir, 0755;
  502. }
  503.  
  504. sub version {
  505.     my $version = qx($openssl version);
  506.     return $1 if $version =~ /^OpenSSL (\S+) /;
  507.     return 0;
  508. }
  509.  
  510. 1;
  511. __END__
  512.