#!/usr/bin/env perl
#***************************************************************************
#                                  _   _ ____  _
#  Project                     ___| | | |  _ \| |
#                             / __| | | | |_) | |
#                            | (__| |_| |  _ <| |___
#                             \___|\___/|_| \_\_____|
#
# Copyright (C) EdelWeb for EdelKey and OpenEvidence
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################

use strict;
use warnings;

use File::Basename;
use File::Spec;
use IPC::Open3;
use Symbol 'gensym';

sub opensslfail {
    die "Missing or broken 'openssl' tool. openssl 1.0.2+ is required. ".
        "Without it, this script cannot generate the necessary certificates ".
        "the curl test suite needs for all its TLS related tests.";
}

my $OPENSSL = 'openssl';
if(-f '/usr/local/ssl/bin/openssl') {
    $OPENSSL = '/usr/local/ssl/bin/openssl';
}

my $SRCDIR = dirname(__FILE__);
my $fh;

my $KEYSIZE = 'prime256v1';
my $DURATION;
my $PREFIX;

sub redir {
    my $outfn = shift if($_[0] =~ /^>/);
    my $hideerr = shift if($_[0] =~ /^2>/);
    open(my $outfd, $outfn) or die if($outfn);
    my $pid = open3(my $in, my $out, my $err = gensym, @_);
    if(!$hideerr) { while(<$err>) { print STDERR $_; }; }
    if($outfn) { while(<$out>) { print $outfd $_; }; close($outfd); }
    else { while(<$out>) { print $_; }; }
    waitpid($pid, 0);
}

my $CAPREFIX = shift @ARGV;
if(!$CAPREFIX) {
    print "Usage: genserv.pl <caprefix> [<prefix> ...]\n";
    exit 1;
} elsif(! -f "$CAPREFIX-ca.cacert" ||
        ! -f "$CAPREFIX-ca.key") {

    if($OPENSSL eq basename($OPENSSL)) {  # has no directory component
        # find openssl in PATH
        my $found = 0;
        foreach(File::Spec->path()) {
            my $file = File::Spec->catfile($_, $OPENSSL);
            if(-f $file) {
                $OPENSSL = $file;
                $found = 1;
                last;
            }
        }
        if(!$found) {
            printf "PATH used: %s\n", join(', ', File::Spec->path());
            opensslfail();
        }
    }

    print "$OPENSSL\n";
    system($OPENSSL, ('version'));

    $PREFIX = $CAPREFIX;
    $DURATION = 6000;

    if(system($OPENSSL, ('genpkey', '-algorithm', 'EC', '-pkeyopt', "ec_paramgen_curve:$KEYSIZE", '-pkeyopt', 'ec_param_enc:named_curve',
        '-out', "$PREFIX-ca.key", '-pass', 'pass:secret')) != 0) {
        opensslfail();
    }
    redir('2>', $OPENSSL, ('req', '-config', "$SRCDIR/$PREFIX-ca.prm", '-new', '-key', "$PREFIX-ca.key", '-out', "$PREFIX-ca.csr", '-passin', 'pass:secret'));
    system($OPENSSL, ('x509', '-sha256', '-extfile', "$SRCDIR/$PREFIX-ca.prm", '-days', $DURATION,
        '-req', '-signkey', "$PREFIX-ca.key", '-in', "$PREFIX-ca.csr", '-out', "$PREFIX-ca.raw-cacert"));
    redir(">$PREFIX-ca.cacert", $OPENSSL, ('x509', '-in', "$PREFIX-ca.raw-cacert", '-text', '-nameopt', 'multiline'));
    system($OPENSSL, ('x509', '-in', "$PREFIX-ca.cacert", '-outform', 'der', '-out', "$PREFIX-ca.der"));
    redir(">$PREFIX-ca.crt", $OPENSSL, ('x509', '-in', "$PREFIX-ca.cacert", '-text', '-nameopt', 'multiline'));

    print "CA root generated: $PREFIX $DURATION days $KEYSIZE\n";
}

$DURATION = 300;

open($fh, '>>', "$CAPREFIX-ca.db") and close($fh);  # for revoke server cert

while(@ARGV) {
    $PREFIX = shift @ARGV;
    $PREFIX =~ s/\.prm$//;

    # pseudo-secrets
    system($OPENSSL, ('genpkey', '-algorithm', 'EC', '-pkeyopt', "ec_paramgen_curve:$KEYSIZE", '-pkeyopt', 'ec_param_enc:named_curve',
        '-out', "$PREFIX.keyenc", '-pass', 'pass:secret'));
    redir('2>', $OPENSSL, ('req', '-config', "$SRCDIR/$PREFIX.prm", '-new', '-key', "$PREFIX.keyenc", '-out', "$PREFIX.csr", '-passin', 'pass:secret'));
    system($OPENSSL, ('pkey', '-in', "$PREFIX.keyenc", '-out', "$PREFIX.key", '-passin', 'pass:secret'));

    system($OPENSSL, ('pkey', '-in', "$PREFIX.key", '-pubout', '-outform', 'DER', '-out', "$PREFIX.pub.der"));
    system($OPENSSL, ('pkey', '-in', "$PREFIX.key", '-pubout', '-outform', 'PEM', '-out', "$PREFIX.pub.pem"));
    redir(">$PREFIX.crt", '2>', $OPENSSL, ('x509', '-sha256', '-extfile', "$SRCDIR/$PREFIX.prm", '-days', $DURATION,
        '-req', '-CA', "$CAPREFIX-ca.cacert", '-CAkey', "$CAPREFIX-ca.key", '-CAcreateserial', '-in', "$PREFIX.csr"));

    # revoke server cert
    if(open($fh, '>', "$CAPREFIX-ca.cnt")) {
        print $fh '01';
        close($fh);
    }
    redir('2>', $OPENSSL, ('ca', '-config', "$SRCDIR/$CAPREFIX-ca.cnf", '-revoke', "$PREFIX.crt"));

    # issue CRL
    redir('2>', $OPENSSL, ('ca', '-config', "$SRCDIR/$CAPREFIX-ca.cnf", '-gencrl', '-out', "$PREFIX.crl"));
    system($OPENSSL, ('x509', '-in', "$PREFIX.crt", '-outform', 'der', '-out', "$PREFIX.der"));

    # concatenate all together now
    open($fh, '>', "$PREFIX.pem") and close($fh);
    chmod 0600, "$PREFIX.pem";
    if(open($fh, '>>', "$PREFIX.pem")) {
        my $fi;
        print $fh do { local $/; open $fi, '<', $_ and <$fi> } for("$SRCDIR/$PREFIX.prm", "$PREFIX.key", "$PREFIX.crt");
        close($fh);
    }

    print "Certificate generated: CA=$CAPREFIX ${DURATION}days $KEYSIZE $PREFIX\n";
}
