Authen-NZRealMe

 view release on metacpan or  search on metacpan

lib/Authen/NZRealMe/ServiceProvider/CertFactory.pm  view on Meta::CPAN

package Authen::NZRealMe::ServiceProvider::CertFactory;
$Authen::NZRealMe::ServiceProvider::CertFactory::VERSION = '1.23';
use warnings;
use strict;

my $term = undef;

my @fields = (

    env => <<EOF,
Which environment do you wish to generate certficates for?  Please answer
either 'ITE' or 'PROD'.
EOF

    org => <<EOF,
Enter your organisation name (e.g.: "Department of Innovation" - without the
quotes).
EOF

    org_unit => <<EOF,
You may optionally include an organisational unit name (e.g.: "Innovation
Labs") - leave  this field blank if you don't need it.
EOF

    subject_suffix => <<EOF,
A certificate subject will be generated for you using the domain name,
organisation name and optional organisational unit name you supplied.  If
you want additional details in the subject, you can supply the here.  Prefix
each fieldname with a forward slash (e.g.: /L=Wellington/ST=Wellington/C=NZ ).
EOF

    domain => <<EOF,

Enter the domain name for your agency.  You might choose to include an
application prefix (e.g.: facetube.innovation.govt.nz) if your applications
will have separate RealMe integrations but just the domain name is usually
sufficient (e.g.: innovation.govt.nz).
EOF

);


sub generate_certs {
    my($class, $conf_dir, %args) = @_;

    %args = $class->_prompt_for_parameters(\%args) or exit 1;
    _check_args(\%args);
    $args{conf_dir} = $conf_dir;

    die "'$conf_dir' is not a directory\n" unless -d "$conf_dir/.";

    $args{domain} =~ s/^(www|secure)[.]//;

    my $key_file = "$conf_dir/sp-sign-key.pem";
    _generate_private_key($key_file);
    _generate_certificate('sig', $key_file, \%args);

    $key_file = "$conf_dir/sp-ssl-key.pem";
    _generate_private_key($key_file);
    _generate_certificate('ssl', $key_file, \%args);

    if(not $args{self_signed}) {
        print "\nSuccessfully generated two certificate signing requests.\n"
            . "You can dump the CSR contents to confirm with:\n\n"
            . "  openssl req -in sp-sign.csr -text\n"
            . "  openssl req -in sp-ssl.csr -text\n\n"
            . "Once you have the certificates signed, save them as\n"
            . "sp-sign-crt.pem and sp-ssl-crt.pem\n";
    }
    else {
        print "\nSuccessfully generated two self-signed certificates.\n";
    }
}


sub _prompt_for_parameters {
    my $class = shift;
    my $args  = shift // { };

    $term = Authen::NZRealMe->class_for('term_readline')->init_readline();
    if($term->Attribs and $term->Attribs->can('ornaments')) {
        $term->Attribs->ornaments(0);
    }
    else {
        warn "Consider installing Term::ReadLine::Gnu for better terminal handling.\n";
    }

    print <<EOF;
This tool will allow you to generate CSRs (Certificate Signing Requests)
for your ITE or Production integration.  You will be asked a short list
of questions.

EOF

    _prompt_yes_no('Do you wish to continue with this process? (y/n) ', 'y')
        or return;

    TRY: while(1) {
        for(my $i = 0; $i <= $#fields; $i += 2) {
            my $key    = $fields[$i];
            my $prompt = $fields[$i + 1];

            print "\n$prompt\n";
            my $field_ok = 0;
            my $value = $args->{$key};
            do {
                $value = $term->readline("$key> ", $value);
                my $method = "_validate_$key";
                $field_ok = $class->can($method) ? $class->$method($value) : 1;
            } until $field_ok;
            $args->{$key} = $value;
        }

        my $output = $args->{self_signed}
                   ? 'self-signed certificates'
                   : 'CSRs';
        print "\nReady to generate $output with the parameters:\n"
            . "  Environment:         $args->{env}\n"
            . "  Organisation:        $args->{org}\n"
            . "  Organisational Unit: $args->{org_unit}\n"
            . "  Subject Suffix:      $args->{subject_suffix}\n"
            . "  Domain:              $args->{domain}\n\n";

        last TRY if _prompt_yes_no("Do you wish to generate $output now? (y/n) ", '');
        redo TRY if _prompt_yes_no('Do you wish to try again? (y/n) ', '');
        exit 1;
    }

    return %$args;
}


sub _prompt_yes_no {
    my($prompt, $default) = @_;

    while(1) {
        my $resp = $term->readline($prompt, $default);

        next unless defined $resp;
        return 1 if $resp =~ /^(y|yes)$/i;
        return 0 if $resp =~ /^(n|no)$/i;
    }
}


sub _generate_private_key {
    my($key_path) = @_;

    system('openssl', 'genrsa', '-out', $key_path, '2048') == 0
        or exit 1;
}


sub _generate_certificate {
    my($type, $key_path, $args) = @_;

    my $conf_dir = $args->{conf_dir};
    my($name, $out_base);
    if($type eq 'sig') {
        $name     = "$args->{env}.sa.saml.sig.$args->{domain}";
        $out_base = "$conf_dir/sp-sign";
    }
    else {
        $name     = "$args->{env}.sa.mutual.ssl.$args->{domain}";
        $out_base = "$conf_dir/sp-ssl";
    }

    my $subject = "/CN=${name}/O=$args->{org}";
    if($args->{org_unit}  and  $args->{org_unit} =~ /\S/) {
        $subject .= "/OU=$args->{org_unit}";
    }
    if($args->{subject_suffix}  and  $args->{subject_suffix} =~ /\S/) {
        $subject .= $args->{subject_suffix};
    }
    my @command = (
        'openssl', 'req', '-new', '-key', $key_path,
        '-subj', $subject,
        '-days', '1095',
    );

    if(not $args->{self_signed}) {
        push @command, '-out', "${out_base}.csr";
    }
    else {
        push @command, '-out', "${out_base}-crt.pem",
            '-x509', '-set_serial', _gen_serial();
    }

    system(@command) == 0 or exit 1;
}


sub _gen_serial {
    my @h = qw(0 1 2 3 4 5 6 7 8 9 a b c d e f);

    return '0x' . join '', $h[rand(8)], map { $h[rand(16)] } (1..15);
}


sub _check_args {
    my($args) = @_;

    die "Need organisation name to generate certs\n" unless $args->{org};
    die "Need domain name to generate certs\n"       unless $args->{domain};

    die "Need environment (MTS/ITE/PROD) to generate certs\n"



( run in 1.218 second using v1.01-cache-2.11-cpan-13bb782fe5a )