Commit d84aceda authored by Nigel Kukard's avatar Nigel Kukard

Added support for --force-verify and --use_dns

parent ce4f93e6
......@@ -26,7 +26,7 @@ package AWIT::CertMaster;
use strict;
use warnings;
use List::MoreUtils qw( any );
use List::MoreUtils qw( any none );
our $VERSION = '1.00';
......@@ -279,24 +279,15 @@ sub webserverCheckNginx
$vhost->{'cert_keyfile'} = sprintf('/etc/ssl/private/%s.key%s',$certBaseName,$self->{'file_suffix'});
# Check if we're only processing a certain vhost
if (defined(my $onlyVhosts = $self->{'only_vhosts'})) {
# Check if this is the host we're forcing
if (@{$onlyVhosts} > 0) {
# Check if its not in the list
if (! any {$_ eq $vhostName} @{$onlyVhosts}) {
$self->logger("INFO","WEBSERVER: - Skipping regeneration of certificate for '%s', not in --only list",$vhostName);
goto SKIP;
}
}
if (defined($self->{'only_vhosts'}) && none {$_ eq $vhostName} @{$self->{'only_vhosts'}}) {
$self->logger("INFO","WEBSERVER: - Skipping regeneration of certificate for '%s', not in --only list",$vhostName);
goto SKIP;
}
# Check if we need to force certificate generation
if (defined(my $forceVhosts = $self->{'force_vhosts'})) {
# Check if this is the host we're forcing
if (any {$_ eq $vhostName} @{$forceVhosts}) {
$self->logger("WARNING","WEBSERVER: - Forcing regeneration of certificate for '%s'",$vhostName);
next;
}
# Check if this is the host we're forcing
if (defined($self->{'force_vhosts'}) && any {$_ eq $vhostName} @{$self->{'force_vhosts'}}) {
$self->logger("WARNING","WEBSERVER: - Forcing regeneration of certificate for '%s'",$vhostName);
next;
}
# Check if the files exist
......@@ -499,8 +490,10 @@ sub _init
$self->{'key'} = undef;
# Keep track of what we're processing
$self->{'force_vhosts'} = $opts->{'force_vhosts'} // [ ];
$self->{'only_vhosts'} = $opts->{'only_vhosts'} // [ ];
$self->{'force_verify'} = $opts->{'force_verify'};
$self->{'force_vhosts'} = $opts->{'force_vhosts'};
$self->{'only_vhosts'} = $opts->{'only_vhosts'};
$self->{'use_dns'} = $opts->{'use_dns'};
return $self;
......@@ -696,18 +689,19 @@ use warnings;
use parent -norequire, 'AWIT::CertMaster';
# Check Crypt::OpenSSL::PKCS10
if (!eval {require Crypt::OpenSSL::PKCS10; 1;}) {
print STDERR "You're missing Crypt::OpenSSL::PKCS10, try 'apt-get install libcrypt-openssl-pkcs10-perl'\n";
exit 1;
}
use Digest::SHA qw( sha256 );
use File::Path qw( make_path );
use List::MoreUtils qw( any none );
# Check LWP::UserAgent
if (!eval {require LWP::UserAgent; 1;}) {
print STDERR "You're missing LWP::UserAgent, try 'apt-get install libwww-perl'\n";
exit 1;
}
# Check Crypt::OpenSSL::PKCS10
if (!eval {require Crypt::OpenSSL::PKCS10; 1;}) {
print STDERR "You're missing Crypt::OpenSSL::PKCS10, try 'apt-get install libcrypt-openssl-pkcs10-perl'\n";
exit 1;
}
use MIME::Base64 qw(
encode_base64url
);
......@@ -1223,7 +1217,9 @@ sub leAuthz
}
# Load challenges into ourself for this domain
$self->{'challenges'}->{$domain} = $jsonRef->{'challenges'};
foreach my $challenge (@{$jsonRef->{'challenges'}}) {
$self->{'challenges'}->{$domain}->{$challenge->{'type'}} = $challenge;
}
return $self;
}
......@@ -1241,6 +1237,24 @@ sub leHandleChallenge
exit 1;
}
# Check if any of the challenges were already validated
foreach my $type (keys %{$self->{'challenges'}->{$domain}}) {
my $challenge = $self->{'challenges'}->{$domain}->{$type};
# Check if the challenge was already validated
if ($challenge->{'status'} eq "valid") {
$self->logger("ERROR","LE: Challenge for domain '%s' already validated",$domain);
# Check if this is the host we're forcing
if (!defined($self->{'force_verify'}) || none {$_ eq $domain} @{$self->{'force_verify'}}) {
return $self;
}
$self->logger("NOTICE","LE: - Force verification");
}
}
$self->logger("INFO","LE: Creating challenge for domain '%s'",$domain);
# Create JWK
......@@ -1253,92 +1267,112 @@ sub leHandleChallenge
# Create fingerprint
my $fingerprint = encode_base64url(sha256($jwk));
# Find the HTTP challenge
my $httpChallenge;
foreach my $challenge (@{$self->{'challenges'}->{$domain}}) {
$self->logger("INFO","LE: - Challeng for '%s' received, URI is '%s'",$challenge->{'type'},
$challenge->{'uri'});
# Generate challenge key_authorization's
foreach my $type (keys %{$self->{'challenges'}->{$domain}}) {
my $challenge = $self->{'challenges'}->{$domain}->{$type};
$self->logger("INFO","LE: - Challenge for '%s' received",$challenge->{'type'});
# Create authorization key
my $keyAuthorization = sprintf('%s.%s',$challenge->{'token'},$fingerprint);
# Check if this is the challenge we're looking for
if ($challenge->{'type'} eq "http-01") {
$challenge->{'key_authorization'} = $keyAuthorization;
$httpChallenge = $challenge;
next;
}
$challenge->{'key_authorization'} = $keyAuthorization;
}
# If there was no http-01 challenge, then its an error
if (!defined($httpChallenge)) {
$self->logger("ERROR","LE: No 'http-01' challenge was received for domain '%s'",$domain);
exit 1;
}
# Check we have a 'token'
if (!exists($httpChallenge->{'token'})) {
$self->logger("ERROR","LE: The 'http-01' challenge for domain '%s' does not contain 'token'",$domain);
exit 1;
}
# Check if we're using DNS for this domain
my $challenge;
if (defined($self->{'use_dns'}) && any {$_ eq $domain} @{$self->{'use_dns'}}) {
$challenge = $self->{'challenges'}->{$domain}->{'dns-01'};
# Check we have a 'uri'
if (!exists($httpChallenge->{'uri'})) {
$self->logger("ERROR","LE: The 'http-01' challenge for domain '%s' does not contain 'uri'",$domain);
exit 1;
}
#
# BEGIN DNS-01
#
my $dnsKey = encode_base64url(sha256($challenge->{'key_authorization'}));
$self->logger("INFO","LE: - Please add DNS entry: _acme-challenge.%s IN TXT '%s'",$domain,$dnsKey);
#
# BEGIN HTTP-01
#
print STDERR "Press <ENTER> when its added...";
# Work out dir and file we need
my $dir = sprintf("%s/.well-known/acme-challenge",$self->{'www_root'});
my $challengeFile = sprintf("%s/%s",$dir,$httpChallenge->{'token'});
my $something = <STDIN>;
# Make sure the dir exists...
if (! -d $dir) {
my @created = make_path($dir, { 'verbose' => 1, 'mode' => 0755 });
}
# Check if the dir was created
if (! -d $dir) {
$self->logger("ERROR","LE: Failed to create directory '%s'",$dir);
exit 1;
}
# Create file...
umask(0022);
if (open(my $FH,'>',$challengeFile)) {
# Write out contents
print($FH $httpChallenge->{'key_authorization'});
close($FH);
# If the open failed, ERR out
#
# END DNS-01
#
} else {
$self->logger("ERROR","LE: Failed to create file '%s': %s",$dir,$!);
exit 1;
}
$challenge = $self->{'challenges'}->{$domain}->{'http-01'};
#
# BEGIN HTTP-01
#
# Add file to get cleaned up
push(@_created_files,$challengeFile);
# If there was no http-01 challenge, then its an error
if (!defined($challenge)) {
$self->logger("ERROR","LE: No 'http-01' challenge was received for domain '%s'",$domain);
exit 1;
}
#
# END HTTP-01
#
# Check we have a 'token'
if (!defined($challenge->{'token'})) {
$self->logger("ERROR","LE: The 'http-01' challenge for domain '%s' does not contain 'token'",$domain);
exit 1;
}
# Check we have a 'uri'
if (!defined($challenge->{'uri'})) {
$self->logger("ERROR","LE: The 'http-01' challenge for domain '%s' does not contain 'uri'",$domain);
exit 1;
}
# Work out dir and file we need
my $dir = sprintf("%s/.well-known/acme-challenge",$self->{'www_root'});
my $challengeFile = sprintf("%s/%s",$dir,$challenge->{'token'});
# Make sure the dir exists...
if (! -d $dir) {
my @created = make_path($dir, { 'verbose' => 1, 'mode' => 0755 });
}
# Check if the dir was created
if (! -d $dir) {
$self->logger("ERROR","LE: Failed to create directory '%s'",$dir);
exit 1;
}
# Create file...
umask(0022);
if (open(my $FH,'>',$challengeFile)) {
# Write out contents
print($FH $challenge->{'key_authorization'});
close($FH);
# If the open failed, ERR out
} else {
$self->logger("ERROR","LE: Failed to create file '%s': %s",$dir,$!);
exit 1;
}
# Add file to get cleaned up
push(@_created_files,$challengeFile);
#
# END HTTP-01
#
}
$self->logger("INFO","LE: - Verifying challenge");
# Build the verification we need to send back to state we've complied
my $json = $self->_encode_json({
'resource' => "challenge",
'keyAuthorization' => $httpChallenge->{'key_authorization'},
'keyAuthorization' => $challenge->{'key_authorization'},
});
my $jws = $self->_leCreateJWS($json);
my $resp = $self->_leRequestPost($httpChallenge->{'uri'}, $jws);
my $resp = $self->_leRequestPost($challenge->{'uri'}, $jws);
# Make sure if we got the wrong code we abort
if ($resp->code() != 202) {
......@@ -1519,7 +1553,7 @@ use Getopt::Long;
my $NAME = "AWIT-CertMaster";
our $VERSION = "1.0.3";
our $VERSION = "1.0.4";
......@@ -1535,8 +1569,10 @@ GetOptions(\%optctl,
"check-only",
"force=s@",
"force-verify=s@",
"live",
"only=s@",
"use-dns=s@",
) or exit 1;
# Check for help
......@@ -1555,9 +1591,11 @@ if (defined($optctl{'version'})) {
my $cm = AWIT::CertMaster::LetsEncrypt->new({
'check_only' => $optctl{'check-only'},
'force_verify' => $optctl{'force-verify'},
'force_vhosts' => $optctl{'force'},
'live' => $optctl{'live'},
'only_vhosts' => $optctl{'only'},
'use_dns' => $optctl{'use-dns'},
});
$cm->webserverCheckNginx();
......@@ -1590,8 +1628,10 @@ sub displayHelp
Certificate Functions:
--check-only Only check, don't do anything.
--force <VHOST> Force certificate generation.
--force-verify <DOMAIN> Force verification of domain.
--live Run live and manage production certs.
--only <VHOST> Only process this VHOST.
--use-dns <DOMAIN> Use DNS to verify this domain.
EOF
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment