home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / xampp / xampp-perl-addon-1.4.9-installer.exe / TestSSLCA.pm < prev    next >
Encoding:
Perl POD Document  |  2004-03-04  |  11.1 KB  |  528 lines

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