Mojo-ACME

 view release on metacpan or  search on metacpan

lib/Mojolicious/Plugin/ACME/Command/acme/cert/generate.pm  view on Meta::CPAN


use Mojo::Collection 'c';
use Mojo::File;

use Getopt::Long qw(GetOptionsFromArray :config no_ignore_case); # no_auto_abbrev

has description => 'Generate a certificate signed by the ACME service';
has usage => sub {
  my $c = shift;
  $c->extract_usage . $c->common_usage;
};

sub run {
  my ($c, @args) = @_;
  my $acme = $c->build_acme(\@args);
  $acme->server_url($c->app->config('acme')->{challenge_url});

  my @domains;
  GetOptionsFromArray(\@args,
    'name|n=s' => \my $name,
    'domain|d=s' => \@domains,
    'intermediate|i=s' => \my $int_url,
    'full!' => \(my $full = 1),
    'wildcard|w' => \my $wildcard,
  );
  $name ||= $c->app->moniker;

  push @domains, @args;
  die 'a domain name is required' unless @domains;

  #Note: wildcard domains are at the discrecion of the ACME service and
  #are not supported by letsencrypt, even if they are allowed they are
  #never to be challenged and thus @new is not @domains

  my @new = grep { $_ !~ /^\*/ } @domains;
  die "ACME does not explicitly allow wildcard certs, use --wildcard to override\n"
    unless (@new == @domains || $wildcard);

  my $intermediate;
  if ($full) {
    my $msg = "No certificate generation was attempted. Use --no-full to proceed without it.\n";
    $int_url ||= eval { $acme->ca->intermediate };
    die "Intermediate certificate not specified. $msg"
      unless $int_url;
    my $tx = $acme->ua->get($int_url);
    die "Failed to fetch intermediate cert. $msg"
      unless $tx->success;
    die "Intermediate cert was empty. $msg"
      unless $intermediate = $tx->res->body;
  }

  $acme->new_authz($_) for @new;

  my $cert;
  Mojo::IOLoop->delay(
    sub { $acme->check_all_challenges(shift->begin) },
    sub {
      my ($delay, $err) = @_;
      die Mojo::Util::dumper($err) if $err;
      my $bad = c(values %{ $acme->challenges })->grep(sub { $_->{status} ne 'valid' });
      die 'The following challenges were not validated ' . Mojo::Util::dumper($bad->to_array) if $bad->size;
      #TODO poll for cert when delayed
      $cert = $acme->get_cert(@domains);
    },
  )->catch(sub{ warn "$_[-1]\n" })->wait;

  die "No cert was generated\n" unless $cert;

  if ($acme->cert_key->generated) {
    my $key_path = "$name.key";
    say "Writing $key_path";
    Mojo::File->new($key_path)->spurt($acme->cert_key->string);
  }

  if ($intermediate) {
    $cert = $cert . $intermediate;
  }

  my $cert_path = "$name.crt";
  say "Writing $cert_path";
  Mojo::File->new($cert_path)->spurt($cert);
}

1;

=head1 NAME

Mojolicious::Plugin::ACME::Command::acme::cert::generate - ACME signed certificate generation

=head1 SYNOPSIS

  Usage: APPLICATION acme cert generate [OPTIONS]
    myapp acme cert generate mydomain.com
    myapp acme cert generate -t -a myaccount.key mydomain.com

  Options:

    -n, --name          the name of the file(s) to be generated, defaults to the app's moniker
    -d, --domain        the domain (or domains is passed multiple times) to be issued (on a single cert)
                          note that bare arguments are also used as domains
    -i, --intermediate  the url of the intermediate cert to be chained if "full" is passed
    --full, --no-full   automatically chain the resulting certificate with the intermediate
                          defaults to true, use --no-full to disable
    -w, --wildcard      allow wildcard requests, letsencrypt does not issue wildcard certs (yet?), though others might
=cut



( run in 2.015 seconds using v1.01-cache-2.11-cpan-0b5f733616e )