Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion lib/Crypt/LE.pm
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,6 @@ sub _set_key {
my $pem = $key->get_private_key_string;
my ($n, $e) = $key->get_key_parameters;
return $self->_status(INVALID_DATA, "Key modulus is divisible by a small prime and will be rejected.") if $self->_is_divisible($n);
$key->use_pkcs1_padding;
$key->use_sha256_hash;
$self->{key_params} = { n => $n, e => $e };
$self->{key} = $key;
Expand Down Expand Up @@ -2106,6 +2105,27 @@ sub _convert {
return (!$content or $content=~/^\-+BEGIN/) ? $content : $self->der2pem($content, $type);
}

sub _save_state {
my $self = shift;
return {
domains => $self->{domains},
challenges => $self->{challenges},
active_challenges => $self->{active_challenges},
loaded_domains => $self->{loaded_domains},
fingerprint => $self->{fingerprint},
finalize => $self->{finalize},
};
}

sub _load_state {
my $self = shift;
my %attributes = %{(shift)};
foreach (keys %attributes) {
$self->{$_} = $attributes{$_};
}
return;
}

1;

=head1 AUTHOR
Expand Down
89 changes: 56 additions & 33 deletions script/le.pl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use MIME::Base64 'encode_base64url';
use Crypt::LE ':errors', ':keys';
use utf8;
use Storable;

my $VERSION = '0.40';

Expand Down Expand Up @@ -125,11 +126,11 @@ sub work {
} else {
$opt->{'logger'}->info("Generating a new CSR for domains $opt->{'domains'}");
if (-e $opt->{'csr-key'}) {
# Allow using pre-existing key when generating CSR
return $opt->{'error'}->("Could not load existing CSR key from $opt->{'csr-key'} - " . $le->error_details, 'CSR_KEY_LOAD') if $le->load_csr_key($opt->{'csr-key'});
$opt->{'logger'}->info("New CSR will be based on '$opt->{'csr-key'}' key");
# Allow using pre-existing key when generating CSR
return $opt->{'error'}->("Could not load existing CSR key from $opt->{'csr-key'} - " . $le->error_details, 'CSR_KEY_LOAD') if $le->load_csr_key($opt->{'csr-key'});
$opt->{'logger'}->info("New CSR will be based on '$opt->{'csr-key'}' key");
} else {
$opt->{'logger'}->info("New CSR will be based on a generated key");
$opt->{'logger'}->info("New CSR will be based on a generated key");
}
my ($type, $attr) = $opt->{'curve'} ? (KEY_ECC, $opt->{'curve'}) : (KEY_RSA, $opt->{'legacy'} ? 2048 : 4096);
$le->generate_csr($opt->{'domains'}, $type, $attr) == OK or return $opt->{'error'}->("Could not generate a CSR: " . $le->error_details, 'CSR_GENERATE');
Expand Down Expand Up @@ -159,12 +160,12 @@ sub work {
my %seen;
# Check wildcards last, try www for those unless already seen.
foreach my $e (sort { $b cmp $a } @{$le->domains}) {
my $domain = $e=~/^\*\.(.+)$/ ? "www.$1" : $e;
next if $seen{$domain}++;
$opt->{'logger'}->info("Checking $domain");
$opt->{'expires'} = $le->check_expiration("https://$domain/");
last if (defined $opt->{'expires'});
}
my $domain = $e=~/^\*\.(.+)$/ ? "www.$1" : $e;
next if $seen{$domain}++;
$opt->{'logger'}->info("Checking $domain");
$opt->{'expires'} = $le->check_expiration("https://$domain/");
last if (defined $opt->{'expires'});
}
}
}
return $opt->{'error'}->("Could not get the certificate expiration value, cannot renew.", 'EXPIRATION_GET') unless (defined $opt->{'expires'});
Expand All @@ -176,7 +177,6 @@ sub work {
}
$opt->{'logger'}->info("Expiration threshold set at $opt->{'renew'} days, the certificate " . ($opt->{'expires'} < 0 ? "has already expired" : "expires in $opt->{'expires'} days") . " - will be renewing.");
}

if ($opt->{'email'}) {
return $opt->{'error'}->($le->error_details, 'EMAIL_SET') if $le->set_account_email($opt->{'email'});
}
Expand All @@ -185,33 +185,51 @@ sub work {
my $reg = _register($le, $opt);
return $reg if $reg;

# Build a copy of the parameters from the command line and added during the runtime, reduced to plain vars and hashrefs.
my %callback_data = map { $_ => $opt->{$_} } grep { ! ref $opt->{$_} or ref $opt->{$_} eq 'HASH' } keys %{$opt};

# We might not need to re-verify, verification holds for a while. NB: Only do that for the standard LE servers.
my $new_crt_status = ($opt->{'server'} or $opt->{'directory'}) ? AUTH_ERROR : $le->request_certificate();
unless ($new_crt_status) {
$opt->{'logger'}->info("Received domain certificate, no validation required at this time.");
} else {
# If it's not an auth problem, but blacklisted domains for example - stop.
return $opt->{'error'}->("Error requesting certificate: " . $le->error_details, 'CERTIFICATE_GET') if $new_crt_status != AUTH_ERROR;
# Handle DNS internally along with HTTP
my ($challenge_handler, $verification_handler) = ($opt->{'handler'}, $opt->{'handler'});
if (!$opt->{'handler'}) {
if ($opt->{'handle-as'}) {
return $opt->{'error'}->("Only 'http' and 'dns' can be handled internally, use external modules for other verification types.", 'VERIFICATION_METHOD') unless $opt->{'handle-as'}=~/^(http|dns)$/i;
if (lc($1) eq 'dns') {
($challenge_handler, $verification_handler) = (\&process_challenge_dns, \&process_verification_dns);
}

my %callback_data;

# Handle DNS internally along with HTTP
my ($challenge_handler, $verification_handler) = ($opt->{'handler'}, $opt->{'handler'});
if (!$opt->{'handler'}) {
if ($opt->{'handle-as'}) {
return $opt->{'error'}->("Only 'http' and 'dns' can be handled internally, use external modules for other verification types.", 'VERIFICATION_METHOD') unless $opt->{'handle-as'}=~/^(http|dns)$/i;
if (lc($1) eq 'dns') {
($challenge_handler, $verification_handler) = (\&process_challenge_dns, \&process_verification_dns);
}
}
}

return $opt->{'error'}->($le->error_details, 'CHALLENGE_REQUEST') if $le->request_challenge();
return $opt->{'error'}->($le->error_details, 'CHALLENGE_ACCEPT') if $le->accept_challenge($challenge_handler || \&process_challenge, \%callback_data, $opt->{'handle-as'});
unless ($opt->{'resume'}) {
# Build a copy of the parameters from the command line and added during the runtime, reduced to plain vars and hashrefs.
%callback_data = map { $_ => $opt->{$_} } grep { ! ref $opt->{$_} or ref $opt->{$_} eq 'HASH' } keys %{$opt};

# If delayed mode is requested, exit early with the same code as for the issuance.
return { code => $opt->{'issue-code'}||0 } if $opt->{'delayed'};
unless ($new_crt_status) {
$opt->{'logger'}->info("Received domain certificate, no validation required at this time.");
} else {
# If it's not an auth problem, but blacklisted domains for example - stop.
return $opt->{'error'}->("Error requesting certificate: " . $le->error_details, 'CERTIFICATE_GET') if $new_crt_status != AUTH_ERROR;
mkdir('./challenges');
return $opt->{'error'}->($le->error_details, 'CHALLENGE_REQUEST') if $le->request_challenge();
return $opt->{'error'}->($le->error_details, 'CHALLENGE_ACCEPT') if $le->accept_challenge($challenge_handler || \&process_challenge, \%callback_data, $opt->{'handle-as'});

# If delayed mode is requested, exit early with the same code as for the issuance.
if ($opt->{'delayed'}) {
store(\%callback_data, 'store_callback_data');
store($le->_save_state(), 'store_le');
return { code => $opt->{'issue-code'} || 0 };
}
}
} else {
%callback_data = %{retrieve('store_callback_data')};
my $state = retrieve('store_le');
$le->_load_state($state);
unlink 'store_callback_data';
unlink 'store_le';
}

if ($new_crt_status || $opt->{'resume'}) {
# Refresh nonce in case of a long delay between the challenge and the verification step.
return $opt->{'error'}->($le->error_details, 'NONCE_REFRESH') unless $le->new_nonce();
return $opt->{'error'}->($le->error_details, 'CHALLENGE_VERIFY') if $le->verify_challenge($verification_handler || \&process_verification, \%callback_data, $opt->{'handle-as'});
Expand Down Expand Up @@ -294,7 +312,7 @@ sub parse_options {

GetOptions ($opt, 'key=s', 'csr=s', 'csr-key=s', 'domains=s', 'path=s', 'crt=s', 'email=s', 'curve=s', 'server=s', 'directory=s', 'api=i', 'config=s', 'renew=i', 'renew-check=s','issue-code=i',
'handle-with=s', 'handle-as=s', 'handle-params=s', 'complete-with=s', 'complete-params=s', 'log-config=s', 'update-contacts=s', 'export-pfx=s', 'tag-pfx=s',
'eab-kid=s', 'eab-hmac-key=s', 'ca=s', 'alternative=i', 'generate-missing', 'generate-only', 'delay=i', 'max-server-delay=i', 'revoke', 'revoke-reason=s', 'legacy', 'unlink', 'delayed', 'live', 'quiet', 'debug+', 'help') ||
'eab-kid=s', 'eab-hmac-key=s', 'ca=s', 'alternative=i', 'generate-missing', 'generate-only', 'delay=i', 'max-server-delay=i', 'revoke', 'revoke-reason=s', 'legacy', 'unlink', 'delayed', 'resume', 'live', 'quiet', 'debug+', 'help') ||
return $opt->{'error'}->("Use --help to see the usage examples.", 'PARAMETERS_PARSE');

if ($opt->{'config'}) {
Expand Down Expand Up @@ -678,6 +696,10 @@ sub process_challenge_dns {
unless ($params->{'delayed'}) {
print "Wait for DNS to update by checking it with the command: nslookup -q=TXT _acme-challenge.$host\nWhen you see a text record returned, press <Enter>\n";
<STDIN>;
} else {
my $filename = "$challenge->{domain}.".time;
$filename =~ s/\*/wildcard/;
_write("./challenges/$filename", "_acme-challenge.$host\n$value");
}
return 1;
}
Expand Down Expand Up @@ -886,7 +908,7 @@ sub usage_and_exit {
-update-contacts <emails> : Update contact details.
-export-pfx <password> : Export PFX (Windows binaries only).
-tag-pfx <tag> : Tag PFX with a specific name.
-alternative <num> : Save an alternative ceritifcate (if available).
-alternative <num> : Save an alternative certificate (if available).
-config <file> : Configuration file for the client.
-log-config <file> : Configuration file for logging.
-generate-missing : Generate missing files (key, csr and csr-key).
Expand All @@ -898,6 +920,7 @@ sub usage_and_exit {
-max-server-delay <seconds> : Cap server-specified delay (which could be unreasonably long).
-legacy : Legacy mode (shorter keys, separate CA file).
-delayed : Exit after requesting the challenge.
-resume : Pick-up after running delayed and completing challenge(s).
-live : Use the live server instead of the test one.
-debug : Print out debug messages.
-quiet : Suppress all messages but errors.
Expand Down