#!/usr/bin/env perl

# TODO: when non-interactive, the default is used, e.g. c=AU, ST=Some-State,
# etc.

use 5.010001;
use strict;
use warnings;
use Log::Any::App '$log';

use Expect;
use File::chdir;
use File::Temp;
use Log::Any::For::Builtins qw(system);
use Perinci::CmdLine;
use SHARYANTO::Proc::ChildError qw(explain_child_error);
use String::ShellQuote;

BEGIN { no warnings; $main::Log_Level = "info" }

sub sq { shell_quote($_[0]) }

our %SPEC;

$SPEC{create_ssl_cert} = {
    v => 1.1,
    args => {
        hostname => {
            schema => ['str*' => match => qr/\A\w+(\.\w+)*\z/],
            req => 1,
            pos => 0,
        },
        ca => {
            summary => 'path to CA cert file',
            schema => ['str*'],
        },
        ca_key => {
            summary => 'path to CA key file',
            schema => ['str*'],
        },
        interactive => {
            schema => [bool => default => 0],
            cmdline_aliases => {
                i => {},
            },
        },
        wildcard => {
            schema => [bool => default => 0],
            summary => 'If set to 1 then Common Name is set to *.hostname',
            description => 'Only when non-interactive',
        },
    },
    deps => {
        exec => 'openssl',
    },
};
sub create_ssl_cert {
    my %args = @_;

    my $h = $args{hostname};

    system("openssl genrsa 2048 > ".sq("$h.key"));
    return [500, "Can't generate key: ".explain_child_error()] if $?;

    my $cmd = "openssl req -new -key ".sq("$h.key")." -out ".sq("$h.csr");
    if ($args{interactive}) {
        system $cmd;
        return [500, "Can't generate csr: ".explain_child_error()] if $?;
    } else {
        my $exp = Expect->spawn($cmd);
        return [500, "Can't spawn openssl req"] unless $exp;
        $exp->expect(
            30,
            [ qr!^.+\[[^\]]*\]:!m ,=> sub {
                  my $exp = shift;
                  my $prompt = $exp->exp_match;
                  if ($prompt =~ /common name/i) {
                      $exp->send("$h\n");
                  } else {
                      $exp->send("\n");
                  }
                  exp_continue;
              } ],
        );
        $exp->soft_close;
    }

    # we can provide options later, but for now let's
    system(join(
        "",
        "openssl x509 -req -days 3650 -in ", sq("$h.csr"),
        " -signkey ", sq("$h.key"),
        ($args{ca} ? " -CA ".sq($args{ca}) : ""),
        ($args{ca_key} ? " -CAkey ".sq($args{ca_key}) : ""),
        ($args{ca} ? " -CAcreateserial" : ""),
        " -out ", sq("$h.crt"),
    ));
    return [500, "Can't generate crt: ".explain_child_error()] if $?;

    system("openssl x509 -noout -fingerprint -text < ".sq("$h.crt").
               "> ".sq("$h.info"));
    return [500, "Can't generate info: ".explain_child_error()] if $?;

    system("cat ".sq("$h.crt")." ".sq("$h.key")." > ".sq("$h.pem"));
    return [500, "Can't generate pem: ".explain_child_error()] if $?;

    system("chmod 400 ".sq("$h.pem"));

    $log->info("Your certificate has been created at $h.pem");

    [200];
}

Perinci::CmdLine->new(url => '/main/create_ssl_cert')->run;

#ABSTRACT: Create self-signed SSL certificate
#PODNAME: create-self-signed-ssl-cert

__END__

=pod

=encoding UTF-8

=head1 NAME

create-self-signed-ssl-cert - Create self-signed SSL certificate

=head1 VERSION

version 0.01

=head1 SYNOPSIS

 # on success, will create example.com.pem
 % create-self-signed-ssl-cert example.com

 # Add --trace or TRACE=1 to see everything (including executed commands)
 % create-self-signed-ssl-cert example.com --trace
 % TRACE=1 create-self-signed-ssl-cert example.com

 # see more options
 % create-self-signed-ssl-cert --help

=head1 DESCRIPTION

This script simplifies creating self-signed SSL certificate. It executes the
L<openssl> command-line program and uses L<Expect> to supply defaults so the
script can be run non-interactively. You can just specify the hostname and it
will create I<hostname>.pem file for you.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-CreateSelfSignedSSL>.

=head1 SOURCE

Source repository is at L<https://github.com/sharyanto/perl-App-CreateSelfSignedSSL>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-CreateSelfSignedSSL>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

Steven Haryanto <stevenharyanto@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Steven Haryanto.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
