#! /usr/bin/perl # Copyright (C) 2008 Nick Urbanik # 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; either version 2 # of the License, or (at your option) any later version. # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. use warnings; use strict; use Getopt::Long; use Readonly; use Carp; sub usage { ( my $prog = $0 ) =~ s{.*/}{}; print <', $fname or die "Cannot open '$fname': $!"; print $pf_fh "$passphrase\n" or die "Cannot write to '$fname': $!"; close $pf_fh or die "Cannot close '$fname': $!"; return $fname; } # Based on OIE::Utils::x509_cert and /etc/pki/tls/misc/CA.pl # with guidance from man ca, man openssl, /etc/pki/tls/openssl.cnf. sub gen_server_cert { my ( $certname, $hostname, $ca_cert, $ca_key, $valid_years, $passphrase_file, $alt_ref, $attr ) = @_; $attr ||= {}; $alt_ref ||= []; my $servercert = "$certname.crt"; my $serverkey = "$certname.key"; my $serverconfig = "./$certname.config"; my $certrequest = "$certname-req.pem"; # Always put the hostname in subjectAltName as well as commonName. # CN is deprecated in favour of subjectAltName: see page 134 # of "Network Security with OpenSSL", 2002. unshift @$alt_ref, $hostname unless grep { $_ eq $hostname } @$alt_ref; my $alt_name_conf = @$alt_ref ? "subjectAltName = " . join( q{,}, map { "DNS:$_" } @$alt_ref ) : q{}; defined $certname and defined $hostname and defined $ca_cert and defined $ca_key and defined $valid_years and ref $alt_ref eq 'ARRAY' and ref $attr eq 'HASH' or croak "Usage: gen_server_cert( \$certname, \$hostname, \$ca_cert, ", "\$ca_key, \$validity, \\\@alt_ref, \\\%attr )"; $attr->{C} ||= 'AU'; $attr->{ST} ||= 'New South Wales'; $attr->{L} ||= 'Sydney'; $attr->{O} ||= 'Nick Urbanik'; $attr->{OU} ||= 'nicku.org'; $attr->{CN} ||= $hostname; $attr->{validity} ||= $valid_years * 365; $attr->{emailAddress} ||= 'nicku@nicku.org'; open my $cfg_fh, '>', $serverconfig or die "Cannot open $serverconfig for writing: $!"; print $cfg_fh <{C} ST = $attr->{ST} L = $attr->{L} O = $attr->{O} OU = $attr->{OU} CN = $attr->{CN} #emailAddress = $attr->{emailAddress} [req_extensions] basicConstraints = CA:false subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always $alt_name_conf END_CONF close $cfg_fh or die "Cannot close $serverconfig: $!"; $ENV{"SSLEAY_CONFIG"} = $serverconfig; my @cmd_req = ( qw( /usr/bin/openssl req -newkey rsa:1024 -nodes ), -keyout => $serverkey, -out => $certrequest, -config => "$serverconfig", ); system( @cmd_req ) == 0 or die "system @cmd_req failed: $?\n"; print "Have created $serverkey and $certrequest\n"; my @cmd_cert = ( qw( /usr/bin/openssl x509 -req ), -extfile => $serverconfig, -in => $certrequest, -CA => $ca_cert, -CAkey => $ca_key, #'-CAcreateserial', -set_serial => time, -extensions => 'req_extensions', -passin => "file:$passphrase_file", -days => $attr->{validity}, -out => $servercert, ); system( @cmd_cert ) == 0 or die "system @cmd_cert failed: $?\n"; } my ( $hostname, $certname, $ca_cert, $ca_key, $valid_years, $passphrase, @altnames ); GetOptions( 'hostname=s' => \$hostname, 'certname=s' => \$certname, 'alt-name=s' => \@altnames, 'ca-cert=s' => \$ca_cert, 'ca-key=s' => \$ca_key, 'valid-years=i' => \$valid_years, 'passphrase=s' => \$passphrase, help => sub { usage }, ) or usage; usage unless $valid_years and $hostname and $ca_cert and $ca_key and $certname; warn "Cannot read CA certificate '$ca_cert'\n" and usage unless -r $ca_cert; warn "Cannot read CA key '$ca_key'\n" and usage unless -r $ca_key; @altnames = split /,/, join q{,}, @altnames; $passphrase = read_passphrase 'Pass phrase to decrypt CA key: ' unless $passphrase; warn "Need CA key passphrase\n" and usage unless $passphrase; $SIG{$_} = 'IGNORE' foreach qw( INT QUIT PIPE HUP ); my $passphrase_file = write_passphrase $passphrase; $SIG{__DIE__} = sub { unlink $passphrase_file; die @_ }; gen_server_cert $certname, $hostname, $ca_cert, $ca_key, $valid_years, $passphrase_file, \@altnames; unlink $passphrase_file or die "Cannot unlink '$passphrase_file': $!";