summaryrefslogtreecommitdiffstats
path: root/debian/perl-framework/Apache-Test/lib/Apache/TestSSLCA.pm
diff options
context:
space:
mode:
Diffstat (limited to 'debian/perl-framework/Apache-Test/lib/Apache/TestSSLCA.pm')
-rw-r--r--debian/perl-framework/Apache-Test/lib/Apache/TestSSLCA.pm595
1 files changed, 595 insertions, 0 deletions
diff --git a/debian/perl-framework/Apache-Test/lib/Apache/TestSSLCA.pm b/debian/perl-framework/Apache-Test/lib/Apache/TestSSLCA.pm
new file mode 100644
index 0000000..fc4c685
--- /dev/null
+++ b/debian/perl-framework/Apache-Test/lib/Apache/TestSSLCA.pm
@@ -0,0 +1,595 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+package Apache::TestSSLCA;
+
+use strict;
+use warnings FATAL => 'all';
+
+use Cwd ();
+use DirHandle ();
+use File::Path ();
+use File::Copy 'cp';
+use File::Basename;
+use File::Spec::Functions qw(devnull);
+use Apache::TestConfig ();
+use Apache::TestTrace;
+
+use constant SSLCA_DB => 'index.txt';
+
+use vars qw(@EXPORT_OK &import);
+
+use subs qw(symlink);
+
+@EXPORT_OK = qw(dn dn_vars dn_oneline);
+*import = \&Exporter::import;
+
+my $openssl = $ENV{APACHE_TEST_OPENSSL_CMD} || 'openssl';
+my $version = version();
+
+my $CA = 'asf';
+my $Config; #global Apache::TestConfig object
+
+my $days = '-days 365';
+my $cakey = 'keys/ca.pem';
+my $cacert = 'certs/ca.crt';
+my $capolicy = '-policy policy_anything';
+my $cacrl = 'crl/ca-bundle.crl';
+my $dgst = 'sha256';
+
+#we use the same password for everything
+my $pass = 'httpd';
+my $passin = "-passin pass:$pass";
+my $passout = "-passout pass:$pass";
+
+# (limited) subjectAltName otherName testing
+my $san_msupn = ', otherName:msUPN;UTF8:$mail';
+my $san_dnssrv = ', otherName:1.3.6.1.5.5.7.8.7;IA5:_https.$CN';
+
+# in 0.9.7 s/Email/emailAddress/ in DN
+my $email_field = Apache::Test::normalize_vstring($version) <
+ Apache::Test::normalize_vstring("0.9.7") ?
+ "Email" : "emailAddress";
+
+# downgrade to SHA-1 for OpenSSL before 0.9.8
+if (Apache::Test::normalize_vstring($version) <
+ Apache::Test::normalize_vstring("0.9.8")) {
+ $dgst = 'sha1';
+ # otherNames in x509v3_config are not supported either
+ $san_msupn = $san_dnssrv = "";
+}
+
+my $sslproto = "all";
+
+eval { require Net::SSLeay; };
+if (Apache::Test::normalize_vstring($version) >=
+ Apache::Test::normalize_vstring("1.1.1")
+ && !defined(&Net::SSLeay::CTX_set_post_handshake_auth)) {
+ # OpenSSL 1.1.1 disables PHA by default client-side in TLSv1.3 but
+ # most clients are not updated to enable it (at time of writing).
+ # Many mod_ssl tests require working PHA, so disable v1.3 unless
+ # using an updated Net::SSLeay. This is strictly insufficient
+ # since an updated IO::Socket::SSL is also needed; to be
+ # continued. Ref: https://github.com/openssl/openssl/issues/6933
+ $sslproto = "all -TLSv1.3";
+}
+
+my $ca_dn = {
+ asf => {
+ C => 'US',
+ ST => 'California',
+ L => 'San Francisco',
+ O => 'ASF',
+ OU => 'httpd-test',
+ CN => '',
+ $email_field => 'test-dev@httpd.apache.org',
+ },
+};
+
+my $cert_dn = {
+ client_snakeoil => {
+ C => 'AU',
+ ST => 'Queensland',
+ L => 'Mackay',
+ O => 'Snake Oil, Ltd.',
+ OU => 'Staff',
+ },
+ client_ok => {
+ },
+ client_colon => {
+ CN => "user:colon",
+ },
+ client_revoked => {
+ },
+ server => {
+ CN => 'localhost',
+ OU => 'httpd-test/rsa-test',
+ },
+ server2 => {
+ CN => 'localhost',
+ OU => 'httpd-test/rsa-test-2',
+ },
+ server_des3 => {
+ CN => 'localhost',
+ OU => 'httpd-test/rsa-des3-test',
+ },
+ server2_des3 => {
+ CN => 'localhost',
+ OU => 'httpd-test/rsa-des3-test-2',
+ },
+};
+
+#generate DSA versions of the server certs/keys
+for my $key (keys %$cert_dn) {
+ next unless $key =~ /^server/;
+ my $val = $$cert_dn{$key};
+ my $name = join '_', $key, 'dsa';
+ $cert_dn->{$name} = { %$val }; #copy
+ $cert_dn->{$name}->{OU} =~ s/rsa/dsa/;
+}
+
+sub ca_dn {
+ $ca_dn = shift if @_;
+ $ca_dn;
+}
+
+sub cert_dn {
+ $cert_dn = shift if @_;
+ $cert_dn;
+}
+
+sub dn {
+ my $name = shift;
+
+ my %dn = %{ $ca_dn->{$CA} }; #default values
+ $dn{CN} ||= $name; #try make sure each Common Name is different
+
+ my $default_dn = $cert_dn->{$name};
+
+ if ($default_dn) {
+ while (my($key, $value) = each %$default_dn) {
+ #override values
+ $dn{$key} = $value;
+ }
+ }
+
+ return wantarray ? %dn : \%dn;
+}
+
+sub dn_vars {
+ my($name, $type) = @_;
+
+ my $dn = dn($name);
+ my $prefix = join '_', 'SSL', $type, 'DN';
+
+ return { map { $prefix ."_$_", $dn->{$_} } keys %$dn };
+}
+
+sub dn_oneline {
+ my($dn, $rfc2253) = @_;
+
+ unless (ref $dn) {
+ $dn = dn($dn);
+ }
+
+ my $string = "";
+ my @parts = (qw(C ST L O OU CN), $email_field);
+ @parts = reverse @parts if $rfc2253;
+
+ for my $k (@parts) {
+ next unless $dn->{$k};
+ if ($rfc2253) {
+ my $tmp = $dn->{$k};
+ $tmp =~ s{([,+"\\<>;])}{\\$1}g;
+ $tmp =~ s{^([ #])}{\\$1};
+ $tmp =~ s{ $}{\\ };
+ $string .= "," if $string;
+ $string .= "$k=$tmp";
+ }
+ else {
+ $string .= "/$k=$dn->{$k}";
+ }
+ }
+
+ $string;
+}
+
+sub openssl {
+ return $openssl unless @_;
+
+ my $cmd = "$openssl @_";
+
+ info $cmd;
+
+ unless (system($cmd) == 0) {
+ my $status = $? >> 8;
+ die "system @_ failed (exit status=$status)";
+ }
+}
+
+my @dirs = qw(keys newcerts certs crl export csr conf proxy);
+
+sub init {
+ for my $dir (@dirs) {
+ gendir($dir);
+ }
+}
+
+sub config_file {
+ my $name = shift;
+
+ my $file = "conf/$name.cnf";
+ return $file if -e $file;
+
+ my $dn = dn($name);
+ my $db = SSLCA_DB;
+
+ writefile($db, '', 1) unless -e $db;
+
+ writefile($file, <<EOF);
+mail = $dn->{$email_field}
+CN = $dn->{CN}
+
+[ req ]
+distinguished_name = req_distinguished_name
+attributes = req_attributes
+prompt = no
+default_bits = 2048
+output_password = $pass
+
+[ req_distinguished_name ]
+C = $dn->{C}
+ST = $dn->{ST}
+L = $dn->{L}
+O = $dn->{O}
+OU = $dn->{OU}
+CN = \$CN
+$email_field = \$mail
+
+[ req_attributes ]
+challengePassword = $pass
+
+[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+certs = certs # Where the issued certs are kept
+new_certs_dir = newcerts # default place for new certs.
+crl_dir = crl # Where the issued crl are kept
+database = $db # database index file.
+serial = serial # The current serial number
+
+certificate = $cacert # The CA certificate
+crl = $cacrl # The current CRL
+private_key = $cakey # The private key
+
+default_days = 365 # how long to certify for
+default_crl_days = 365 # how long before next CRL
+default_md = $dgst # which md to use.
+preserve = no # keep passed DN ordering
+
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+$email_field = optional
+
+[ client_ok_ext ]
+nsComment = This Is A Comment
+1.3.6.1.4.1.18060.12.0 = DER:0c064c656d6f6e73
+subjectAltName = email:\$mail$san_msupn
+
+[ server_ext ]
+subjectAltName = DNS:\$CN$san_dnssrv
+EOF
+
+ return $file;
+}
+
+sub config {
+ my $name = shift;
+
+ my $file = config_file($name);
+
+ my $config = "-config $file";
+
+ $config;
+}
+
+use constant PASSWORD_CLEARTEXT =>
+ Apache::TestConfig::WIN32 || Apache::TestConfig::NETWARE;
+
+#http://www.modssl.org/docs/2.8/ssl_reference.html#ToC21
+my $basic_auth_password =
+ PASSWORD_CLEARTEXT ? 'password': 'xxj31ZMTZzkVA';
+my $digest_auth_hash = '$1$OXLyS...$Owx8s2/m9/gfkcRVXzgoE/';
+
+sub new_ca {
+ writefile('serial', "01\n", 1);
+
+ writefile('ssl.htpasswd',
+ join ':', dn_oneline('client_snakeoil'),
+ $basic_auth_password);
+
+ openssl req => "-new -x509 -keyout $cakey -out $cacert $days",
+ config('ca');
+
+ export_cert('ca'); #useful for importing into IE
+}
+
+sub new_key {
+ my $name = shift;
+
+ my $encrypt = @_ ? "@_ $passout" : "";
+
+ my $out = "-out keys/$name.pem $encrypt";
+
+ if ($name =~ /dsa/) {
+ #this takes a long time so just do it once
+ #don't do this in real life
+ unless (-e 'dsa-param') {
+ openssl dsaparam => '-inform PEM -out dsa-param 2048';
+ }
+ openssl gendsa => "$out dsa-param";
+ }
+ else {
+ openssl genrsa => "$out 2048";
+ }
+}
+
+sub new_cert {
+ my $name = shift;
+
+ openssl req => "-new -key keys/$name.pem -out csr/$name.csr",
+ $passin, $passout, config($name);
+
+ sign_cert($name);
+
+ export_cert($name);
+}
+
+sub sign_cert {
+ my $name = shift;
+ my $exts = '';
+
+ $exts = ' -extensions client_ok_ext' if $name =~ /client_ok/;
+
+ $exts = ' -extensions server_ext' if $name =~ /server/;
+
+ openssl ca => "$capolicy -in csr/$name.csr -out certs/$name.crt",
+ $passin, config($name), '-batch', $exts;
+}
+
+#handy for importing into a browser such as netscape
+sub export_cert {
+ my $name = shift;
+
+ return if $name =~ /^server/; #no point in exporting server certs
+
+ openssl pkcs12 => "-export -in certs/$name.crt -inkey keys/$name.pem",
+ "-out export/$name.p12", $passin, $passout;
+}
+
+sub revoke_cert {
+ my $name = shift;
+
+ my @args = (config('cacrl'), $passin);
+
+ #revokes in the SSLCA_DB database
+ openssl ca => "-revoke certs/$name.crt", @args;
+
+ #generates crl from the index.txt database
+ openssl ca => "-gencrl -out $cacrl", @args;
+}
+
+sub symlink {
+ my($file, $symlink) = @_;
+
+ my $what = 'linked';
+
+ if (Apache::TestConfig::WINFU) {
+ cp $file, $symlink;
+ $what = 'copied';
+ }
+ else {
+ CORE::symlink($file, $symlink);
+ }
+
+ info "$what $file to $symlink";
+}
+
+sub hash_certs {
+ my($type, $dir) = @_;
+
+ chdir $dir;
+
+ my $dh = DirHandle->new('.') or die "opendir $dir: $!";
+ my $n = 0;
+
+ for my $file ($dh->read) {
+ next unless $file =~ /\.cr[tl]$/;
+ chomp(my $hash = `openssl $type -noout -hash < $file`);
+ next unless $hash;
+ my $symlink = "$hash.r$n";
+ $n++;
+ symlink $file, $symlink;
+ }
+
+ close $dh;
+
+ chdir $CA;
+}
+
+sub make_proxy_cert {
+ my $name = shift;
+
+ my $from = "certs/$name.crt";
+ my $to = "proxy/$name.pem";
+
+ info "generating proxy cert: $to";
+
+ my $fh_to = Symbol::gensym();
+ my $fh_from = Symbol::gensym();
+
+ open $fh_to, ">$to" or die "open $to: $!";
+ open $fh_from, $from or die "open $from: $!";
+
+ cp $fh_from, $fh_to;
+
+ $from = "keys/$name.pem";
+
+ open $fh_from, $from or die "open $from: $!";
+
+ cp $fh_from, $fh_to;
+
+ close $fh_from;
+ close $fh_to;
+}
+
+sub setup {
+ $CA = shift;
+
+ unless ($ca_dn->{$CA}) {
+ die "unknown CA $CA";
+ }
+
+ gendir($CA);
+
+ chdir $CA;
+
+ init();
+ new_ca();
+
+ my @names = keys %$cert_dn;
+
+ for my $name (@names) {
+ my @key_args = ();
+ if ($name =~ /_des3/) {
+ push @key_args, '-des3';
+ }
+
+ new_key($name, @key_args);
+ new_cert($name);
+
+ if ($name =~ /_revoked$/) {
+ revoke_cert($name);
+ }
+
+ if ($name =~ /^client_/) {
+ make_proxy_cert($name);
+ }
+ }
+
+ hash_certs(crl => 'crl');
+}
+
+sub generate {
+ $Config = shift;
+
+ $CA = shift || $Config->{vars}->{sslcaorg};
+
+ my $root = $Config->{vars}->{sslca};
+
+ return if -d $root;
+
+ my $pwd = Cwd::cwd();
+ my $base = dirname $root;
+ my $dir = basename $root;
+
+ chdir $base;
+
+ # Ensure the CNs used in the server certs match up with the
+ # hostname being used for testing.
+ while (my($key, $val) = each %$cert_dn) {
+ next unless $key =~ /^server/;
+ $val->{CN} = $Config->{vars}->{servername};
+ }
+
+ #make a note that we created the tree
+ $Config->clean_add_path($root);
+
+ gendir($dir);
+
+ chdir $dir;
+
+ warning "generating SSL CA for $CA";
+
+ setup($CA);
+
+ chdir $pwd;
+}
+
+sub clean {
+ my $config = shift;
+
+ #rel2abs adds same drive letter for win32 that clean_add_path added
+ my $dir = File::Spec->rel2abs($config->{vars}->{sslca});
+
+ unless ($config->{clean}->{dirs}->{$dir}) {
+ return; #we did not generate this ca
+ }
+
+ unless ($config->{clean_level} > 1) {
+ #skip t/TEST -conf
+ warning "skipping regeneration of SSL CA; run t/TEST -clean to force";
+ return;
+ }
+
+ File::Path::rmtree([$dir], 1, 1);
+}
+
+#not using Apache::TestConfig methods because the openssl commands
+#will generate heaps of files we cannot keep track of
+
+sub writefile {
+ my($file, $content) = @_;
+
+ my $fh = Symbol::gensym();
+ open $fh, ">$file" or die "open $file: $!";
+ print $fh $content;
+ close $fh;
+}
+
+sub gendir {
+ my($dir) = @_;
+
+ return if -d $dir;
+ mkdir $dir, 0755;
+}
+
+sub version {
+ my $devnull = devnull();
+ my $version = qx($openssl version 2>$devnull);
+ return $1 if $version =~ /^\S+SSL (\S+)/;
+ die "FATAL: unable to determine openssl version via `$openssl version` from: $version";
+}
+
+sub dgst {
+ return $dgst;
+}
+
+sub email_field {
+ return $email_field;
+}
+
+sub sslproto {
+ return $sslproto;
+}
+
+1;
+__END__