Torsten Förtsch
IT System Development & Security
Kaum macht man's richtig, schon geht's, ;-)

>> Home >> ModPerl >> Apache::Test & SSL


Content

How to have Apache::Test generate SSL certificates?

Sometimes one needs to write module tests for modperl or CGI applications that involve connections over SSL. Apache::Test can help with generating the certificates. But how to do that isn't documented anywhere.

After a few hours searching and reading the code I discovered that it is really simple and comes down to a single

mkdir t/conf/ssl

The Details

When using Apache::Test a typical directory layout looks like:

Makefile.PL (or Build.PL)
t/
t/TEST.PL
t/conf/
t/conf/extra.conf.in

The typical Makefile.PL reads

use strict;

use 5.008008;

BEGIN {
  eval {
    require ModPerl::MM;
    require Apache::TestMM;
    1;
  } or exit 0;
  Apache::TestMM->import( qw(test clean) );
}

# accept the configs from command line
Apache::TestMM::filter_args();
Apache::TestMM::generate_script('t/TEST');

ModPerl::MM::WriteMakefile(
    NAME            => 'My::Module',
    VERSION_FROM    => 'lib/My/Module.pm',
    ABSTRACT_FROM   => 'lib/My/Module.pm',
    AUTHOR          => 'Just Me <me@mymail.com>',
    PREREQ_PM       => {
        'Apache2::Const'    => 0,
        'ModPerl::MM'       => 0,
        'Apache::TestMM'    => 0,
        'Test::More'        => 0,
    },
    dist => {
        COMPRESS => 'gzip -9f',
	PREOP    => './mk_README.sh',
    },
    clean        => {
	FILES=>"t/TEST",
    },
);

The important thing here is Apache::TestMM::generate_script('t/TEST');. This line converts t/TEST.PL into t/TEST.

Now, lets have a look at t/TEST.PL.

#!perl

use strict;
use warnings FATAL => 'all';

use lib qw(lib);

use Apache::TestRunPerl ();

Apache::TestRunPerl->new->run(@ARGV);

Nothing new so far.

Now, create a directory called t/conf/ssl and run Makefile.PL and then t/TEST -configure (or simply make test) and Apache::Test will create a bunch of certificates for you (provided OpenSSL is available):

$ mkdir t/conf/ssl
$ perl Makefile.PL
[   info] generating script t/TEST
...
$ t/TEST -configure
[warning] setting ulimit to allow core files
ulimit -c unlimited; /usr/bin/perl /home/r2/xx/t/TEST -configure
[warning] cleaning out current configuration                    
[  error] port 8529 is in use, cannot determine server pid to shutdown
[warning] generating SSL CA for asf                                   
[   info] openssl req -new -x509 -keyout keys/ca.pem -out certs/ca.crt -days 365 -config conf/ca.cnf
...

There are certainly ways to influence the parameters of the certificates created. But for the simple case of testing some code over SSL the certificate/key pair in t/conf/ssl/ca/asf/certs/server.crt and t/conf/ssl/ca/asf/keys/server.pem is sufficient.

Using the certificates

Apache configuration

To use the certificates one probably wants to create a VirtualHost to handle SSL requests. This can be done in any configuration file in t/conf. Usually, there is already such a file called t/conf/extra.conf.in or t/conf/extra.last.conf.in. Put the following snippet there:

<IfModule ssl_module>
    SSLRandomSeed startup builtin
    SSLRandomSeed connect builtin

    <VirtualHost ssl>
	SSLEngine on
	SSLCertificateFile "@SSLCA@/asf/certs/server.crt"
	SSLCertificateKeyFile "@SSLCA@/asf/keys/server.pem"
    </VirtualHost>
</IfModule>

Note how the certificate is referred to.

Accessing the server

So, now that the server is up and running we have to find a way to figure out what port it is listening on. In the configuration file we have written <VirtualHost ssl> instead of the normal <VirtualHost _default_:443>. Further, the Listen directive has been completely omitted. The point is, ssl in the code snippet above is simply a name. Test scripts can use that name to fetch the actual host and port. Here a one-liner, without a parameter it returns the hostport to access the default server. The parameter ssl which references the virtual host name produces the hostport of the SSL server.

$ perl -MApache::TestRequest -le 'Apache::TestRequest::module shift; print Apache::TestRequest::hostport'
localhost:8529
$ perl -MApache::TestRequest -le 'Apache::TestRequest::module shift; print Apache::TestRequest::hostport' ssl
localhost:8533

So, lets try it using curl as browser:

$ curl https://$(perl -MApache::TestRequest -le 'Apache::TestRequest::module shift; print Apache::TestRequest::hostport' ssl)/
curl: (60) SSL certificate problem, verify that the CA cert is OK. Details:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

Oops! Of course, that cannot work. Our browser does not know how to trust the certificate displayed by the server. One can use the -k option as suggested. But that would make the whole SSL thing useless. Though, for a test it may be permissible.

However, Apache::Test has certainly generated a CA certificate. Lets find it! First thing, the SSLCA location:

$ perl -MApache::Test -le 'print Apache::Test::vars shift' sslca
/path/to/My-Module/t/conf/ssl/ca

The CA certificate is named asf/certs/ca.crt within this directory.

Now, we have all the tools together to issue a HTTPS request to our test server:

curl https://"$(perl -MApache::TestRequest -le 'Apache::TestRequest::module shift; print Apache::TestRequest::hostport' ssl)"/ \
     --cacert "$(perl -MApache::Test -le 'print Apache::Test::vars shift' sslca)"/asf/certs/ca.crt
<!-- WARNING: this file is generated, do not edit
generated on Sat Mar 13 12:17:28 2010
...

And how about test scripts?

I use this approach:

# switch to ssl VHost
Apache::TestRequest::module 'ssl';

my $sslhostport=Apache::TestRequest::hostport;
t_debug "Using $sslhostport for HTTPS";

# Define a helper function. Note the prototype.
# It allows for 'GET S $url, header=>...' w/o parentheses
sub S ($) {'https://'.$sslhostport.$_[0]}

# switch back
Apache::TestRequest::module 'default';

# fetch it unencrypted
$resp=GET '/shop/something', Cookie=>...;

# or encrypted
$resp=GET S '/shop/something, Cookie=>...;

Now, sometimes you need to generate links other resources in the application. Of course, for testing purposes only one can generate HTTPS links as https://localhost/... but I like it if I can start the test server via t/TEST -start-httpd and then browse the application. But then one needs to generate links that contain the correct SSL port. For this I use something like the following <Perl> section in the extra.last.conf.in. Then the code can check $ENV{SSL_PORT} when it generates links.

<Perl>
  use Apache::Test;
  $ENV{SSL_PORT}=Apache::Test::vars('ssl_port');
</Perl>

Letzte Aktualisierung: 14.03.2010