Skip to content

Commit ce3560e

Browse files
committed
Revocation reason, server-specified delay cap, sectigo CA.
1 parent 4d6216e commit ce3560e

File tree

5 files changed

+72
-25
lines changed

5 files changed

+72
-25
lines changed

Changes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
Revision history for Crypt-LE
22

3+
0.40 01 June 2024
4+
- Revocation reason can now be specified via 'revoke-reason'.
5+
- It is now possible to cap unreasonably long server-specified 'retry-after' via 'max-server-delay'.
6+
- Sectigo added to the CAs as 'sectigo.com'.
7+
38
0.39 11 March 2023
49
- EAB (External Account Binding) support used by some CAs.
510
- Asynchronous order finalization support.

lib/Crypt/LE.pm

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use 5.006;
44
use strict;
55
use warnings;
66

7-
our $VERSION = '0.39';
7+
our $VERSION = '0.40';
88

99
=head1 NAME
1010
1111
Crypt::LE - Let's Encrypt (and other ACME-based) API interfacing module and client.
1212
1313
=head1 VERSION
1414
15-
Version 0.39
15+
Version 0.40
1616
1717
=head1 SYNOPSIS
1818
@@ -166,6 +166,9 @@ our $cas = {
166166
'live' => 'https://dv.acme-v02.api.pki.goog/directory',
167167
'stage' => 'https://dv.acme-v02.test-api.pki.goog/directory',
168168
},
169+
'sectigo.com' => {
170+
'live' => 'https://acme.sectigo.com/v2/DV',
171+
},
169172
};
170173

171174
use constant {
@@ -250,6 +253,15 @@ my $compat = {
250253
revokeCert => 'revoke-cert',
251254
};
252255

256+
# Subset of https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1 as supported by Boulder.
257+
my $revocation_reasons = {
258+
unspecified => 0,
259+
keycompromise => 1,
260+
affiliationchanged => 3,
261+
superseded => 4,
262+
cessationofoperation => 5,
263+
};
264+
253265
=head1 METHODS (API Setup)
254266
255267
The following methods are provided for the API setup. Please note that account key setup by default requests the resource directory from Let's Encrypt servers.
@@ -297,8 +309,13 @@ Enables automatic retrieval of the resource directory (required for normal API p
297309
298310
=item C<delay>
299311
300-
Specifies the time in seconds to wait before Let's Encrypt servers are checked for the challenge verification results again. By default set to 2 seconds.
301-
Non-integer values are supported (so for example you can set it to 1.5 if you like).
312+
Specifies the time in seconds to wait before the challenge verification results are checked again. By default set to 2 seconds.
313+
Non-integer values are supported (so for example you can set it to 1.5 if you like). Please note that the server-specified delay overrides this value,
314+
but it can be adjusted by using max_server_delay (see below).
315+
316+
=item C<max_server_delay>
317+
318+
Overrides server-specified delay in seconds to wait before the challenge verification results are checked again.
302319
303320
=item C<version>
304321
@@ -323,20 +340,23 @@ sub new {
323340
my $class = shift;
324341
my %params = @_;
325342
my $self = {
326-
ua => '',
327-
server => '',
328-
ca => '',
329-
dir => '',
330-
live => 0,
331-
debug => 0,
332-
autodir => 1,
333-
delay => 2,
334-
version => 0,
335-
try => 300,
343+
ua => '',
344+
server => '',
345+
ca => '',
346+
dir => '',
347+
live => 0,
348+
debug => 0,
349+
autodir => 1,
350+
delay => 0,
351+
max_server_delay => 0,
352+
version => 0,
353+
try => 300,
336354
};
337355
foreach my $key (keys %{$self}) {
338356
$self->{$key} = $params{$key} if (exists $params{$key} and !ref $params{$key});
339357
}
358+
# Some defaults.
359+
$self->{delay} ||= 2;
340360
# Init UA
341361
$self->{ua} = HTTP::Tiny->new( agent => $self->{ua} || __PACKAGE__ . " v$VERSION", verify_SSL => 1 );
342362
# Init server
@@ -1371,7 +1391,7 @@ sub request_issuer_certificate {
13711391
return $self->_status(ERROR, $content);
13721392
}
13731393

1374-
=head2 revoke_certificate($certificate_file|$scalar_ref)
1394+
=head2 revoke_certificate($certificate_file|$scalar_ref, [ $revoke_reason ])
13751395
13761396
Revokes a certificate.
13771397
@@ -1380,13 +1400,22 @@ Returns: OK | READ_ERROR | ALREADY_DONE | ERROR.
13801400
=cut
13811401

13821402
sub revoke_certificate {
1383-
my $self = shift;
1384-
my $file = shift;
1403+
my ($self, $file, $reason) = @_;
13851404
my $crt = $self->_file($file);
13861405
return $self->_status(READ_ERROR, "Certificate reading error.") unless $crt;
1387-
my ($status, $content) = $self->_request($self->{directory}->{'revoke-cert'},
1388-
{ resource => 'revoke-cert', certificate => encode_base64url($self->pem2der($crt)) },
1389-
{ jwk => 0 });
1406+
1407+
my $reason_code = 0;
1408+
my $payload = { resource => 'revoke-cert', certificate => encode_base64url($self->pem2der($crt)) };
1409+
if ($reason) {
1410+
$reason_code = $revocation_reasons->{lc $reason};
1411+
return $self->_status(ERROR, "Unsupported revocation reason specified.") unless defined $reason_code;
1412+
# Only add the reason field if it is different from Unspecified/0,
1413+
# since custom CAs might not support the reason (as per rfc8555#section-7.6)
1414+
$payload->{reason} = $reason_code if $reason_code;
1415+
}
1416+
1417+
my ($status, $content) = $self->_request($self->{directory}->{'revoke-cert'}, $payload, { jwk => 0 });
1418+
13901419
if ($status == SUCCESS) {
13911420
return $self->_status(OK, "Certificate has been revoked.");
13921421
} elsif ($status == ALREADY_DONE) {
@@ -1863,6 +1892,11 @@ sub _request {
18631892
$self->{location} = $resp->{headers}->{location} ? $resp->{headers}->{location} : undef;
18641893
if ($resp->{headers}->{'retry-after'} and $resp->{headers}->{'retry-after'}=~/^(\d+)$/) {
18651894
$self->{retry} = $1; # Set retry based on the last request where it was present, do not reset.
1895+
# Some servers might be sending unreasonably long retry-after (such as 86400 seconds - the whole day),
1896+
# effectively pausing the process - this behaviour can be overriden by 'max_server_delay' option.
1897+
if ($self->{max_server_delay} and $self->{max_server_delay} < $self->{retry}) {
1898+
$self->{retry} = $self->{max_server_delay};
1899+
}
18661900
}
18671901

18681902
return wantarray ? ($status, $rv) : $rv;
@@ -1875,6 +1909,7 @@ sub _await {
18751909
$opts ||= {};
18761910
my $expected_status = $opts->{status} || SUCCESS;
18771911
($status, $content) = $self->_request($url, $payload, $opts);
1912+
$self->_debug("Retry is set to " . ($self->{retry} ? $self->{retry} : '-') . ", and delay is $self->{delay}");
18781913
while ($status == $expected_status and $content and $content->{status} and $content->{status}=~/^(?:pending|processing)$/) {
18791914
select(undef, undef, undef, $self->{retry} || $self->{delay});
18801915
($status, $content) = $self->_request($url, $payload, $opts);

lib/Crypt/LE/Challenge/Simple.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use warnings;
44
use Digest::SHA 'sha256';
55
use MIME::Base64 'encode_base64url';
66

7-
our $VERSION = '0.39';
7+
our $VERSION = '0.40';
88

99
=head1 NAME
1010

lib/Crypt/LE/Complete/Simple.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use Data::Dumper;
33
use strict;
44
use warnings;
55

6-
our $VERSION = '0.39';
6+
our $VERSION = '0.40';
77

88
=head1 NAME
99

script/le.pl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use Crypt::LE ':errors', ':keys';
1414
use utf8;
1515

16-
my $VERSION = '0.39';
16+
my $VERSION = '0.40';
1717

1818
exit main();
1919

@@ -43,6 +43,8 @@ sub work {
4343
version => $opt->{'api'}||0,
4444
debug => $opt->{'debug'},
4545
logger => $opt->{'logger'},
46+
delay => $opt->{'delay'},
47+
max_server_delay => $opt->{'max-server-delay'},
4648
);
4749

4850
# Check if CA is supported if it was specified explicitly.
@@ -93,7 +95,7 @@ sub work {
9395
# Register.
9496
my $reg = _register($le, $opt);
9597
return $reg if $reg;
96-
my $rv = $le->revoke_certificate(\$crt);
98+
my $rv = $le->revoke_certificate(\$crt, $opt->{'revoke-reason'});
9799
if ($rv == OK) {
98100
$opt->{'logger'}->info("Certificate has been revoked.");
99101
} elsif ($rv == ALREADY_DONE) {
@@ -292,7 +294,7 @@ sub parse_options {
292294

293295
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',
294296
'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',
295-
'eab-kid=s', 'eab-hmac-key=s', 'ca=s', 'alternative=i', 'generate-missing', 'generate-only', 'revoke', 'legacy', 'unlink', 'delayed', 'live', 'quiet', 'debug+', 'help') ||
297+
'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') ||
296298
return $opt->{'error'}->("Use --help to see the usage examples.", 'PARAMETERS_PARSE');
297299

298300
if ($opt->{'config'}) {
@@ -783,6 +785,8 @@ sub usage_and_exit {
783785
784786
le.pl --key account.key --crt domain.crt --revoke
785787
788+
le.pl --key account.key --crt domain.crt --revoke --revoke-reason "Superseded"
789+
786790
i) To update your contact details:
787791
788792
le.pl --key account.key --update-contacts "one@example.com, two@example.com" --live
@@ -889,6 +893,9 @@ sub usage_and_exit {
889893
-generate-only : Exit after generating the missing files.
890894
-unlink : Remove challenge files automatically.
891895
-revoke : Revoke a certificate.
896+
-revoke-reason <reason> : Revocation reason.
897+
-delay <seconds> : Delay between attempts to check the challenge results.
898+
-max-server-delay <seconds> : Cap server-specified delay (which could be unreasonably long).
892899
-legacy : Legacy mode (shorter keys, separate CA file).
893900
-delayed : Exit after requesting the challenge.
894901
-live : Use the live server instead of the test one.

0 commit comments

Comments
 (0)