Home | History | Annotate | Line # | Download | only in apps
      1 #!{- $config{HASHBANGPERL} -}
      2 # Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
      3 #
      4 # Licensed under the Apache License 2.0 (the "License").  You may not use
      5 # this file except in compliance with the License.  You can obtain a copy
      6 # in the file LICENSE in the source distribution or at
      7 # https://www.openssl.org/source/license.html
      8 
      9 #
     10 # Wrapper around the ca to make it easier to use
     11 #
     12 # {- join("\n# ", @autowarntext) -}
     13 
     14 use strict;
     15 use warnings;
     16 
     17 my $verbose = 1;
     18 my @OPENSSL_CMDS = ("req", "ca", "pkcs12", "x509", "verify");
     19 
     20 my $openssl = $ENV{'OPENSSL'} // "openssl";
     21 $ENV{'OPENSSL'} = $openssl;
     22 my $OPENSSL_CONFIG = $ENV{"OPENSSL_CONFIG"} // "";
     23 
     24 # Command invocations.
     25 my $REQ = "$openssl req $OPENSSL_CONFIG";
     26 my $CA = "$openssl ca $OPENSSL_CONFIG";
     27 my $VERIFY = "$openssl verify";
     28 my $X509 = "$openssl x509";
     29 my $PKCS12 = "$openssl pkcs12";
     30 
     31 # Default values for various configuration settings.
     32 my $CATOP = "./demoCA";
     33 my $CAKEY = "cakey.pem";
     34 my $CAREQ = "careq.pem";
     35 my $CACERT = "cacert.pem";
     36 my $CACRL = "crl.pem";
     37 my $DAYS = "-days 365";
     38 my $CADAYS = "-days 1095";	# 3 years
     39 my $NEWKEY = "newkey.pem";
     40 my $NEWREQ = "newreq.pem";
     41 my $NEWCERT = "newcert.pem";
     42 my $NEWP12 = "newcert.p12";
     43 
     44 # Commandline parsing
     45 my %EXTRA;
     46 my $WHAT = shift @ARGV || "";
     47 @ARGV = parse_extra(@ARGV);
     48 my $RET = 0;
     49 
     50 # Split out "-extra-CMD value", and return new |@ARGV|. Fill in
     51 # |EXTRA{CMD}| with list of values.
     52 sub parse_extra
     53 {
     54     foreach ( @OPENSSL_CMDS ) {
     55         $EXTRA{$_} = '';
     56     }
     57 
     58     my @result;
     59     while ( scalar(@_) > 0 ) {
     60         my $arg = shift;
     61         if ( $arg !~ m/-extra-([a-z0-9]+)/ ) {
     62             push @result, $arg;
     63             next;
     64         }
     65         $arg =~ s/-extra-//;
     66         die("Unknown \"-${arg}-extra\" option, exiting")
     67             unless scalar grep { $arg eq $_ } @OPENSSL_CMDS;
     68         $EXTRA{$arg} .= " " . shift;
     69     }
     70     return @result;
     71 }
     72 
     73 
     74 # See if reason for a CRL entry is valid; exit if not.
     75 sub crl_reason_ok
     76 {
     77     my $r = shift;
     78 
     79     if ($r eq 'unspecified' || $r eq 'keyCompromise'
     80         || $r eq 'CACompromise' || $r eq 'affiliationChanged'
     81         || $r eq 'superseded' || $r eq 'cessationOfOperation'
     82         || $r eq 'certificateHold' || $r eq 'removeFromCRL') {
     83         return 1;
     84     }
     85     print STDERR "Invalid CRL reason; must be one of:\n";
     86     print STDERR "    unspecified, keyCompromise, CACompromise,\n";
     87     print STDERR "    affiliationChanged, superseded, cessationOfOperation\n";
     88     print STDERR "    certificateHold, removeFromCRL";
     89     exit 1;
     90 }
     91 
     92 # Copy a PEM-format file; return like exit status (zero means ok)
     93 sub copy_pemfile
     94 {
     95     my ($infile, $outfile, $bound) = @_;
     96     my $found = 0;
     97 
     98     open IN, $infile || die "Cannot open $infile, $!";
     99     open OUT, ">$outfile" || die "Cannot write to $outfile, $!";
    100     while (<IN>) {
    101         $found = 1 if /^-----BEGIN.*$bound/;
    102         print OUT $_ if $found;
    103         $found = 2, last if /^-----END.*$bound/;
    104     }
    105     close IN;
    106     close OUT;
    107     return $found == 2 ? 0 : 1;
    108 }
    109 
    110 # Wrapper around system; useful for debugging.  Returns just the exit status
    111 sub run
    112 {
    113     my $cmd = shift;
    114     print "====\n$cmd\n" if $verbose;
    115     my $status = system($cmd);
    116     print "==> $status\n====\n" if $verbose;
    117     return $status >> 8;
    118 }
    119 
    120 
    121 if ( $WHAT =~ /^(-\?|-h|-help)$/ ) {
    122     print STDERR <<EOF;
    123 Usage:
    124     CA.pl -newcert | -newreq | -newreq-nodes | -xsign | -sign | -signCA | -signcert | -crl | -newca [-extra-cmd parameter]
    125     CA.pl -pkcs12 [certname]
    126     CA.pl -verify certfile ...
    127     CA.pl -revoke certfile [reason]
    128 EOF
    129     exit 0;
    130 }
    131 
    132 if ($WHAT eq '-newcert' ) {
    133     # create a certificate
    134     $RET = run("$REQ -new -x509 -keyout $NEWKEY -out $NEWCERT $DAYS"
    135             . " $EXTRA{req}");
    136     print "Cert is in $NEWCERT, private key is in $NEWKEY\n" if $RET == 0;
    137 } elsif ($WHAT eq '-precert' ) {
    138     # create a pre-certificate
    139     $RET = run("$REQ -x509 -precert -keyout $NEWKEY -out $NEWCERT $DAYS"
    140             . " $EXTRA{req}");
    141     print "Pre-cert is in $NEWCERT, private key is in $NEWKEY\n" if $RET == 0;
    142 } elsif ($WHAT =~ /^\-newreq(\-nodes)?$/ ) {
    143     # create a certificate request
    144     $RET = run("$REQ -new $1 -keyout $NEWKEY -out $NEWREQ $DAYS $EXTRA{req}");
    145     print "Request is in $NEWREQ, private key is in $NEWKEY\n" if $RET == 0;
    146 } elsif ($WHAT eq '-newca' ) {
    147     # create the directory hierarchy
    148     my @dirs = ( "${CATOP}", "${CATOP}/certs", "${CATOP}/crl",
    149                 "${CATOP}/newcerts", "${CATOP}/private" );
    150     die "${CATOP}/index.txt exists.\nRemove old sub-tree to proceed,"
    151         if -f "${CATOP}/index.txt";
    152     die "${CATOP}/serial exists.\nRemove old sub-tree to proceed,"
    153         if -f "${CATOP}/serial";
    154     foreach my $d ( @dirs ) {
    155         if ( -d $d ) {
    156             warn "Directory $d exists" if -d $d;
    157         } else {
    158             mkdir $d or die "Can't mkdir $d, $!";
    159         }
    160     }
    161 
    162     open OUT, ">${CATOP}/index.txt";
    163     close OUT;
    164     open OUT, ">${CATOP}/crlnumber";
    165     print OUT "01\n";
    166     close OUT;
    167     # ask user for existing CA certificate
    168     print "CA certificate filename (or enter to create)\n";
    169     my $FILE;
    170     $FILE = "" unless defined($FILE = <STDIN>);
    171     $FILE =~ s{\R$}{};
    172     if ($FILE ne "") {
    173         copy_pemfile($FILE,"${CATOP}/private/$CAKEY", "PRIVATE");
    174         copy_pemfile($FILE,"${CATOP}/$CACERT", "CERTIFICATE");
    175     } else {
    176         print "Making CA certificate ...\n";
    177         $RET = run("$REQ -new -keyout ${CATOP}/private/$CAKEY"
    178                 . " -out ${CATOP}/$CAREQ $EXTRA{req}");
    179         $RET = run("$CA -create_serial"
    180                 . " -out ${CATOP}/$CACERT $CADAYS -batch"
    181                 . " -keyfile ${CATOP}/private/$CAKEY -selfsign"
    182                 . " -extensions v3_ca"
    183                 . " -infiles ${CATOP}/$CAREQ $EXTRA{ca}") if $RET == 0;
    184         print "CA certificate is in ${CATOP}/$CACERT\n" if $RET == 0;
    185     }
    186 } elsif ($WHAT eq '-pkcs12' ) {
    187     my $cname = $ARGV[0];
    188     $cname = "My Certificate" unless defined $cname;
    189     $RET = run("$PKCS12 -in $NEWCERT -inkey $NEWKEY"
    190             . " -certfile ${CATOP}/$CACERT -out $NEWP12"
    191             . " -export -name \"$cname\" $EXTRA{pkcs12}");
    192     print "PKCS #12 file is in $NEWP12\n" if $RET == 0;
    193 } elsif ($WHAT eq '-xsign' ) {
    194     $RET = run("$CA -policy policy_anything -infiles $NEWREQ $EXTRA{ca}");
    195 } elsif ($WHAT eq '-sign' ) {
    196     $RET = run("$CA -policy policy_anything -out $NEWCERT"
    197             . " -infiles $NEWREQ $EXTRA{ca}");
    198     print "Signed certificate is in $NEWCERT\n" if $RET == 0;
    199 } elsif ($WHAT eq '-signCA' ) {
    200     $RET = run("$CA -policy policy_anything -out $NEWCERT"
    201             . " -extensions v3_ca -infiles $NEWREQ $EXTRA{ca}");
    202     print "Signed CA certificate is in $NEWCERT\n" if $RET == 0;
    203 } elsif ($WHAT eq '-signcert' ) {
    204     $RET = run("$X509 -x509toreq -in $NEWREQ -signkey $NEWREQ"
    205             . " -out tmp.pem $EXTRA{x509}");
    206     $RET = run("$CA -policy policy_anything -out $NEWCERT"
    207             .  "-infiles tmp.pem $EXTRA{ca}") if $RET == 0;
    208     print "Signed certificate is in $NEWCERT\n" if $RET == 0;
    209 } elsif ($WHAT eq '-verify' ) {
    210     my @files = @ARGV ? @ARGV : ( $NEWCERT );
    211     foreach my $file (@files) {
    212         # -CAfile quoted for VMS, since the C RTL downcases all unquoted
    213         # arguments to C programs
    214         my $status = run("$VERIFY \"-CAfile\" ${CATOP}/$CACERT $file $EXTRA{verify}");
    215         $RET = $status if $status != 0;
    216     }
    217 } elsif ($WHAT eq '-crl' ) {
    218     $RET = run("$CA -gencrl -out ${CATOP}/crl/$CACRL $EXTRA{ca}");
    219     print "Generated CRL is in ${CATOP}/crl/$CACRL\n" if $RET == 0;
    220 } elsif ($WHAT eq '-revoke' ) {
    221     my $cname = $ARGV[0];
    222     if (!defined $cname) {
    223         print "Certificate filename is required; reason optional.\n";
    224         exit 1;
    225     }
    226     my $reason = $ARGV[1];
    227     $reason = " -crl_reason $reason"
    228         if defined $reason && crl_reason_ok($reason);
    229     $RET = run("$CA -revoke \"$cname\"" . $reason . $EXTRA{ca});
    230 } else {
    231     print STDERR "Unknown arg \"$WHAT\"\n";
    232     print STDERR "Use -help for help.\n";
    233     exit 1;
    234 }
    235 
    236 exit $RET;
    237