File Coverage

blib/lib/Crypt/LE.pm
Criterion Covered Total %
statement 411 867 47.4
branch 167 558 29.9
condition 57 371 15.3
subroutine 63 97 64.9
pod 49 49 100.0
total 747 1942 38.4


line stmt bran cond sub pod time code
1             package Crypt::LE;
2              
3 4     4   348309 use 5.006;
  4         36  
4 4     4   18 use strict;
  4         5  
  4         86  
5 4     4   16 use warnings;
  4         6  
  4         246  
6              
7             our $VERSION = '0.39';
8              
9             =head1 NAME
10              
11             Crypt::LE - Let's Encrypt (and other ACME-based) API interfacing module and client.
12              
13             =head1 VERSION
14              
15             Version 0.39
16              
17             =head1 SYNOPSIS
18              
19             use Crypt::LE;
20            
21             my $le = Crypt::LE->new();
22             $le->load_account_key('account.pem');
23             $le->load_csr('domain.csr');
24             $le->register();
25             $le->accept_tos();
26             $le->request_challenge();
27             $le->accept_challenge(\&process_challenge);
28             $le->verify_challenge();
29             $le->request_certificate();
30             my $cert = $le->certificate();
31             ...
32             sub process_challenge {
33             my $challenge = shift;
34             print "Challenge for $challenge->{domain} requires:\n";
35             print "A file '/.well-known/acme-challenge/$challenge->{token}' with the text: $challenge->{token}.$challenge->{fingerprint}\n";
36             print "When done, press <Enter>";
37             <STDIN>;
38             return 1;
39             };
40              
41             =head1 DESCRIPTION
42              
43             Crypt::LE provides the functionality necessary to use Let's Encrypt API and generate free SSL certificates for your domains. It can also
44             be used to generate RSA keys and Certificate Signing Requests or to revoke previously issued certificates. Crypt::LE is shipped with a
45             self-sufficient client for obtaining SSL certificates - le.pl.
46              
47             B<Provided client supports 'http' and 'dns' domain verification out of the box.>
48              
49             Crypt::LE can be easily extended with custom plugins to handle Let's Encrypt challenges. See L<Crypt::LE::Challenge::Simple> module
50             for an example of a challenge-handling plugin.
51              
52             Basic usage:
53              
54             B<le.pl --key account.key --csr domain.csr --csr-key domain.key --crt domain.crt --domains "www.domain.ext,domain.ext" --generate-missing>
55              
56             That will generate an account key and a CSR (plus key) if they are missing. If any of those files exist, they will just be loaded, so it is safe to re-run
57             the client. Run le.pl without any parameters or with C<--help> to see more details and usage examples.
58              
59             In addition to challenge-handling plugins, the client also supports completion-handling plugins, such as L<Crypt::LE::Complete::Simple>. You can easily
60             handle challenges and trigger specific actions when your certificate gets issued by using those modules as templates, without modifying the client code.
61             You can also pass custom parameters to your modules from le.pl command line:
62              
63             B<le.pl ... --handle-with Crypt::LE::Challenge::Simple --handle-params '{"key1": 1, "key2": "one"}'>
64            
65             B<le.pl ... --complete-with Crypt::LE::Complete::Simple --complete-params '{"key1": 1, "key2": "one"}'>
66            
67             The parameters don't have to be put directly in the command line, you could also give a name of a file containing valid JSON to read them from.
68              
69             B<le.pl ... --complete-params complete.json>
70            
71             Crypt::LE::Challenge:: and Crypt::LE::Complete:: namespaces are suggested for new plugins.
72              
73             =head1 EXPORT
74              
75             Crypt::LE does not export anything by default, but allows you to import the following constants:
76              
77             =over
78              
79             =item *
80             OK
81              
82             =item *
83             READ_ERROR
84              
85             =item *
86             LOAD_ERROR
87              
88             =item *
89             INVALID_DATA
90              
91             =item *
92             DATA_MISMATCH
93              
94             =item *
95             UNSUPPORTED
96              
97             =item *
98             ALREADY_DONE
99              
100             =item *
101             BAD_REQUEST
102              
103             =item *
104             AUTH_ERROR
105              
106             =item *
107             ERROR
108              
109             =back
110              
111             To import all of those, use C<':errors'> tag:
112              
113             use Crypt::LE ':errors';
114             ...
115             $le->load_account_key('account.pem') == OK or die "Could not load the account key: " . $le->error_details;
116            
117             If you don't want to use error codes while checking whether the last called method has failed or not, you can use the
118             rule of thumb that on success it will return zero. You can also call error() or error_details() methods, which
119             will be set with some values on error.
120              
121             =cut
122              
123 4     4   1582 use Crypt::OpenSSL::RSA;
  4         24061  
  4         124  
124 4     4   1539 use JSON::MaybeXS;
  4         23890  
  4         191  
125 4     4   2386 use HTTP::Tiny;
  4         133563  
  4         146  
126 4     4   1519 use IO::File;
  4         5456  
  4         372  
127 4     4   1656 use Digest::SHA qw<sha256 hmac_sha256>;
  4         9868  
  4         302  
128 4     4   1170 use MIME::Base64 qw<encode_base64url decode_base64url decode_base64 encode_base64>;
  4         1535  
  4         243  
129 4     4   1978 use Net::SSLeay qw<XN_FLAG_RFC2253 ASN1_STRFLGS_ESC_MSB MBSTRING_UTF8>;
  4         16622  
  4         1453  
130 4     4   36 use Scalar::Util 'blessed';
  4         7  
  4         168  
131 4     4   2725 use Encode 'encode_utf8';
  4         48259  
  4         263  
132 4     4   27 use Storable 'dclone';
  4         6  
  4         158  
133 4     4   1505 use Convert::ASN1;
  4         72460  
  4         157  
134 4     4   1512 use Module::Load;
  4         3680  
  4         21  
135 4     4   1853 use Time::Piece;
  4         31211  
  4         20  
136 4     4   244 use Time::Seconds;
  4         7  
  4         218  
137 4     4   2028 use Data::Dumper;
  4         20051  
  4         196  
138 4     4   24 use base 'Exporter';
  4         8  
  4         682  
139              
140             Net::SSLeay::randomize();
141             Net::SSLeay::load_error_strings();
142             Net::SSLeay::ERR_load_crypto_strings();
143             Net::SSLeay::OpenSSL_add_ssl_algorithms();
144             Net::SSLeay::OpenSSL_add_all_digests();
145             our $keysize = 4096;
146             our $keycurve = 'prime256v1';
147             our $headers = { 'Content-type' => 'application/jose+json' };
148             our $default_ca = 'letsencrypt.org';
149              
150             our $cas = {
151             'letsencrypt.org' => {
152             'live' => 'https://acme-v02.api.letsencrypt.org/directory',
153             'stage' => 'https://acme-staging-v02.api.letsencrypt.org/directory',
154             },
155             'buypass.com' => {
156             'live' => 'https://api.buypass.com/acme/directory',
157             'stage' => 'https://api.test4.buypass.no/acme/directory',
158             },
159             'ssl.com' => {
160             'live' => 'https://acme.ssl.com/sslcom-dv-rsa',
161             },
162             'zerossl.com' => {
163             'live' => 'https://acme.zerossl.com/v2/DV90/directory',
164             },
165             'google.com' => {
166             'live' => 'https://dv.acme-v02.api.pki.goog/directory',
167             'stage' => 'https://dv.acme-v02.test-api.pki.goog/directory',
168             },
169             };
170              
171             use constant {
172 4         42101 OK => 0,
173             READ_ERROR => 1,
174             LOAD_ERROR => 2,
175             INVALID_DATA => 3,
176             DATA_MISMATCH => 4,
177             UNSUPPORTED => 5,
178             ERROR => 500,
179              
180             SUCCESS => 200,
181             CREATED => 201,
182             ACCEPTED => 202,
183             BAD_REQUEST => 400,
184             AUTH_ERROR => 403,
185             ALREADY_DONE => 409,
186              
187             KEY_RSA => 0,
188             KEY_ECC => 1,
189              
190             PEER_CRT => 4,
191             CRT_DEPTH => 5,
192              
193             SAN => '2.5.29.17',
194 4     4   23 };
  4         7  
195              
196             our @EXPORT_OK = (qw<OK READ_ERROR LOAD_ERROR INVALID_DATA DATA_MISMATCH UNSUPPORTED ERROR BAD_REQUEST AUTH_ERROR ALREADY_DONE KEY_RSA KEY_ECC>);
197             our %EXPORT_TAGS = ( 'errors' => [ @EXPORT_OK[0..9] ], 'keys' => [ @EXPORT_OK[10..11] ] );
198              
199             my $pkcs12_available = 0;
200             my $j = JSON->new->canonical()->allow_nonref();
201             my $url_safe = qr/^[-_A-Za-z0-9]+$/; # RFC 4648 section 5.
202             my $flag_rfc22536_utf8 = (XN_FLAG_RFC2253) & (~ ASN1_STRFLGS_ESC_MSB);
203             if ($^O eq 'MSWin32') {
204             eval { autoload 'Crypt::OpenSSL::PKCS12'; };
205             $pkcs12_available = 1 unless $@;
206             }
207              
208             # https://github.com/letsencrypt/boulder/blob/master/core/good_key.go
209             my @primes = map { Crypt::OpenSSL::Bignum->new_from_decimal($_) } (
210             2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
211             53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107,
212             109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167,
213             173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
214             233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
215             293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
216             367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431,
217             433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491,
218             499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571,
219             577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641,
220             643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709,
221             719, 727, 733, 739, 743, 751
222             );
223              
224             my $asn = Convert::ASN1->new();
225             $asn->prepare(q<
226             Extensions ::= SEQUENCE OF Extension
227             Extension ::= SEQUENCE {
228             extnID OBJECT IDENTIFIER,
229             critical BOOLEAN OPTIONAL,
230             extnValue OCTET STRING
231             }
232             SubjectAltName ::= GeneralNames
233             GeneralNames ::= SEQUENCE OF GeneralName
234             GeneralName ::= CHOICE {
235             otherName [0] ANY,
236             rfc822Name [1] IA5String,
237             dNSName [2] IA5String,
238             x400Address [3] ANY,
239             directoryName [4] ANY,
240             ediPartyName [5] ANY,
241             uniformResourceIdentifier [6] IA5String,
242             iPAddress [7] OCTET STRING,
243             registeredID [8] OBJECT IDENTIFIER
244             }
245             >);
246              
247             my $compat = {
248             newAccount => 'new-reg',
249             newOrder => 'new-cert',
250             revokeCert => 'revoke-cert',
251             };
252              
253             =head1 METHODS (API Setup)
254              
255             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.
256             This can be changed by resetting the 'autodir' parameter of the constructor.
257              
258             =head2 new()
259              
260             Create a new instance of the class. Initialize the object with passed parameters. Normally you don't need to use any, but the following are supported:
261              
262             =over 12
263              
264             =item C<ua>
265              
266             User-agent name to use while sending requests to Let's Encrypt servers. By default set to module name and version.
267              
268             =item C<server>
269              
270             Server URL to connect to. Only needed if the default live or staging server URLs have changed and this module has not yet been updated with the new
271             information or if you are using a custom server supporting ACME protocol. Note: the value is supposed to point to the root of the API (for example:
272             https://some.server/acme/) rather than the directory handler. This parameter might be deprecated in the future in favour of the 'dir' one below.
273              
274             =item C<live>
275              
276             Set to true to connect to a live Let's Encrypt server. By default it is not set, so staging server is used, where you can test the whole process of getting
277             SSL certificates.
278              
279             =item C<debug>
280              
281             Activates printing debug messages to the standard output when set. If set to 1, only standard messages are printed. If set to any greater value, then structures and
282             server responses are printed as well.
283              
284             =item C<dir>
285              
286             Full URL of a 'directory' handler on the server (the actual name of the handler can be different in certain configurations, where multiple handlers
287             are mapped). Only needed if you are using a custom server supporting ACME protocol. This parameter replaces the 'server' one.
288              
289             =item C<ca>
290              
291             The name of CA (Certificate Authority) to use. If the name is found in the list of supported ones, the URLs to use will be automatically set.
292             Please note that this parameter will be ignored if the 'directory' or 'server' are explicitly set.
293              
294             =item C<autodir>
295              
296             Enables automatic retrieval of the resource directory (required for normal API processing) from the servers. Enabled by default.
297              
298             =item C<delay>
299              
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).
302              
303             =item C<version>
304              
305             Enforces the API version to be used. If the response is not found to be compatible, an error will be returned. If not set, system will try to make an educated guess.
306              
307             =item C<try>
308              
309             Specifies the amount of retries to attempt while in 'pending' state and waiting for verification results response. By default set to 300, which combined
310             with the delay of 2 seconds gives you 10 minutes of waiting.
311              
312             =item C<logger>
313              
314             Logger instance to use for debug messages. If not given, the messages will be printed to STDOUT.
315              
316             =back
317              
318             Returns: L<Crypt::LE> object.
319              
320             =cut
321              
322             sub new {
323 3     3 1 2875 my $class = shift;
324 3         10 my %params = @_;
325 3         24 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,
336             };
337 3         6 foreach my $key (keys %{$self}) {
  3         11  
338 30 100 66     66 $self->{$key} = $params{$key} if (exists $params{$key} and !ref $params{$key});
339             }
340             # Init UA
341 3   33     35 $self->{ua} = HTTP::Tiny->new( agent => $self->{ua} || __PACKAGE__ . " v$VERSION", verify_SSL => 1 );
342             # Init server
343 3         230 my $opts;
344 3 50       15 if ($self->{server}) {
    50          
    50          
345             # Custom server - drop the protocol if given (defaults to https later). If that leaves nothing, the check below
346             # will set the servers to LE standard ones.
347 0         0 $self->{server}=~s~^\w+://~~;
348             } elsif ($self->{dir}) {
349 0 0       0 $self->{dir} = "https://$self->{dir}" unless $self->{dir}=~m~^https?://~i;
350             } elsif ($self->{ca}) {
351 0   0     0 $opts = $cas->{lc($self->{ca})} || $cas->{$default_ca};
352             } else {
353 3         10 $opts = $cas->{$default_ca};
354             }
355              
356 3 50       8 if ($opts) {
357             # Only check for live option if the 'stage' is supported by CA. Otherwise use live URL.
358 3 50       9 if ($opts->{'stage'}) {
359 3 50       10 $self->{dir} = $self->{live} ? $opts->{live} : $opts->{stage};
360             } else {
361 0         0 $self->{dir} = $opts->{live};
362             }
363             }
364              
365             # Init logger
366 3 50 33     15 $self->{logger} = $params{logger} if ($params{logger} and blessed $params{logger});
367 3         9 bless $self, $class;
368             }
369              
370             #====================================================================================================
371             # API Setup functions
372             #====================================================================================================
373              
374             =head2 load_account_key($filename|$scalar_ref)
375              
376             Loads the private account key from the file or scalar in PEM or DER formats.
377              
378             Returns: OK | READ_ERROR | LOAD_ERROR | INVALID_DATA.
379              
380             =cut
381              
382             sub load_account_key {
383 4     4 1 733 my ($self, $file) = @_;
384 4         11 $self->_reset_key;
385 4         11 my $key = $self->_file($file);
386 4 100       16 return $self->_status(READ_ERROR, "Key reading error.") unless $key;
387 3         5 eval {
388 3         8 $key = Crypt::OpenSSL::RSA->new_private_key($self->_convert($key, 'RSA PRIVATE KEY'));
389             };
390 3 100       12 return $self->_status(LOAD_ERROR, "Key loading error.") if $@;
391 2         6 return $self->_set_key($key, "Account key loaded.");
392             }
393              
394             =head2 generate_account_key()
395              
396             Generates a new private account key of the $keysize bits (4096 by default). The key is additionally validated for not being divisible by small primes.
397              
398             Returns: OK | INVALID_DATA.
399              
400             =cut
401              
402             sub generate_account_key {
403 1     1 1 3 my $self = shift;
404 1         3 my ($pk, $err, $code) = _key();
405 1 50 0     4 return $self->_status(INVALID_DATA, $err||"Could not generate account key") unless $pk;
406 1         221 my $key = Crypt::OpenSSL::RSA->new_private_key(Net::SSLeay::PEM_get_string_PrivateKey($pk));
407 1         6 _free(k => $pk);
408 1         6 return $self->_set_key($key, "Account key generated.");
409             }
410              
411             =head2 account_key()
412              
413             Returns: A previously loaded or generated private key in PEM format or undef.
414              
415             =cut
416              
417             sub account_key {
418 3     3 1 427 return shift->{pem};
419             }
420              
421             =head2 load_csr($filename|$scalar_ref [, $domains])
422              
423             Loads Certificate Signing Requests from the file or scalar. Domains list can be omitted or it can be given as a string of comma-separated names or as an array reference.
424             If omitted, then names will be loaded from the CSR. If it is given, then the list of names will be verified against those found on CSR.
425              
426             Returns: OK | READ_ERROR | LOAD_ERROR | INVALID_DATA | DATA_MISMATCH.
427              
428             =cut
429              
430             sub load_csr {
431 11     11 1 492 my $self = shift;
432 11         20 my ($file, $domains) = @_;
433 11         27 $self->_reset_csr;
434 11         21 my $csr = $self->_file($file);
435 11 100       47 return $self->_status(READ_ERROR, "CSR reading error.") unless $csr;
436 10         42 my $bio = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem());
437 10 50       18 return $self->_status(LOAD_ERROR, "Could not allocate memory for the CSR") unless $bio;
438 10         12 my ($in, $cn, $san, $i);
439 10 100 66     480 unless (Net::SSLeay::BIO_write($bio, $csr) and $in = Net::SSLeay::PEM_read_bio_X509_REQ($bio)) {
440 1         3 _free(b => $bio);
441 1         2 return $self->_status(LOAD_ERROR, "Could not load the CSR");
442             }
443 9         27 $cn = Net::SSLeay::X509_REQ_get_subject_name($in);
444 9 50       15 if ($cn) {
445 9         92 $cn = Net::SSLeay::X509_NAME_print_ex($cn, $flag_rfc22536_utf8, 1);
446 9 50 33     96 $cn = lc($1) if ($cn and $cn=~/^.*?\bCN=([^\s,]+).*$/);
447             }
448 9         12 my @list = @{$self->_get_list($domains)};
  9         18  
449 9         216 $i = Net::SSLeay::X509_REQ_get_attr_by_NID($in, &Net::SSLeay::NID_ext_req, -1);
450 9 50       136 if ($i > -1) {
451 9         23 my $o = Net::SSLeay::P_X509_REQ_get_attr($in, $i);
452 9 50       15 if ($o) {
453 9         30 my $exts = $asn->find("Extensions");
454 9         143 my $dec = $exts->decode(Net::SSLeay::P_ASN1_STRING_get($o));
455 9 50       2159 if ($dec) {
456 9         10 foreach my $ext (@{$dec}) {
  9         17  
457 9 50 33     31 if ($ext->{extnID} and $ext->{extnID} eq SAN) {
458 9         20 $exts = $asn->find("SubjectAltName");
459 9         126 $san = $exts->decode($ext->{extnValue});
460 9         1377 last;
461             }
462             }
463             }
464             }
465             }
466 9         20 my @loaded_domains = ();
467 9         12 my %seen = ();
468 9         10 my $san_broken;
469 9 50       17 if ($cn) {
470 9         16 push @loaded_domains, $cn;
471 9         15 $seen{$cn} = 1;
472             }
473 9 50       18 if ($san) {
474 9         9 foreach my $ext (@{$san}) {
  9         13  
475 21 50       35 if ($ext->{dNSName}) {
476 21         35 $cn = lc($ext->{dNSName});
477 21 100       47 push @loaded_domains, $cn unless $seen{$cn}++;
478             } else {
479 0         0 $san_broken++;
480             }
481             }
482             }
483 9         21 _free(b => $bio);
484 9 50       21 if ($san_broken) {
485 0         0 return $self->_status(INVALID_DATA, "CSR contains $san_broken non-DNS record(s) in SAN");
486             }
487 9 50       13 unless (@loaded_domains) {
488 0         0 return $self->_status(INVALID_DATA, "No domains found on CSR.");
489             } else {
490 9 100       20 if (my $odd = $self->_verify_list(\@loaded_domains)) {
491 1         2 return $self->_status(INVALID_DATA, "Unsupported domain names on CSR: " . join(", ", @{$odd}));
  1         5  
492             }
493 8         26 $self->_debug("Loaded domain names from CSR: " . join(', ', @loaded_domains));
494             }
495 8 100       16 if (@list) {
496 4 100       23 return $self->_status(DATA_MISMATCH, "The list of provided domains does not match the one on the CSR.") unless (join(',', sort @loaded_domains) eq join(',', sort @list));
497 2         5 @loaded_domains = @list; # Use the command line domain order if those were listed along with CSR.
498             }
499 6         15 $self->_set_csr($csr, undef, \@loaded_domains);
500 6         11 return $self->_status(OK, "CSR loaded.");
501             }
502              
503             =head2 generate_csr($domains, [$key_type], [$key_attr])
504              
505             Generates a new Certificate Signing Request. Optionally accepts key type and key attribute parameters, where key type should
506             be either KEY_RSA or KEY_ECC (if supported on your system) and key attribute is either the key size (for RSA) or the curve (for ECC).
507             By default an RSA key of 4096 bits will be used.
508             Domains list is mandatory and can be given as a string of comma-separated names or as an array reference.
509              
510             Returns: OK | ERROR | UNSUPPORTED | INVALID_DATA.
511              
512             =cut
513              
514             sub generate_csr {
515 14     14 1 151 my $self = shift;
516 14         26 my ($domains, $key_type, $key_attr) = @_;
517 14         37 $self->_reset_csr;
518 14         14 my @list = @{$self->_get_list($domains)};
  14         31  
519 14 100       39 return $self->_status(INVALID_DATA, "No domains provided.") unless @list;
520 13 100       50 if (my $odd = $self->_verify_list(\@list)) {
521 4         6 return $self->_status(INVALID_DATA, "Unsupported domain names provided: " . join(", ", @{$odd}));
  4         14  
522             }
523 9         25 my ($key, $err, $code) = _key($self->csr_key(), $key_type, $key_attr);
524 9 100 100     30 return $self->_status($code||ERROR, $err||"Key problem while creating CSR") unless $key;
      50        
525 6         37 my ($csr, $csr_key) = _csr($key, \@list, { O => '-', L => '-', ST => '-', C => 'GB' });
526 6 50       25 return $self->_status(ERROR, "Unexpected CSR error.") unless $csr;
527 6         26 $self->_set_csr($csr, $csr_key, \@list);
528 6         18 return $self->_status(OK, "CSR generated.");
529             }
530              
531             =head2 csr()
532              
533             Returns: A previously loaded or generated CSR in PEM format or undef.
534              
535             =cut
536              
537             sub csr {
538 3     3 1 12 return shift->{csr};
539             }
540              
541             =head2 load_csr_key($filename|$scalar_ref)
542              
543             Loads the CSR key from the file or scalar (to be used for generating a new CSR).
544              
545             Returns: OK | READ_ERROR.
546              
547             =cut
548              
549             sub load_csr_key {
550 3     3 1 543 my $self = shift;
551 3         5 my $file = shift;
552 3         5 undef $self->{csr_key};
553 3         8 my $key = $self->_file($file);
554 3 100       13 return $self->_status(READ_ERROR, "CSR key reading error.") unless $key;
555 1         2 $self->{csr_key} = $key;
556 1         3 return $self->_status(OK, "CSR key loaded");
557             }
558              
559             =head2 csr_key()
560              
561             Returns: A CSR key (either loaded or generated with CSR) or undef.
562              
563             =cut
564              
565             sub csr_key {
566 10     10 1 33 return shift->{csr_key};
567             }
568              
569             =head2 set_account_email([$email])
570              
571             Sets (or resets if no parameter is given) an email address that will be used for registration requests.
572              
573             Returns: OK | INVALID_DATA.
574              
575             =cut
576              
577             sub set_account_email {
578 0     0 1 0 my ($self, $email) = @_;
579 0 0       0 unless ($email) {
580 0         0 undef $self->{email};
581 0         0 return $self->_status(OK, "Account email has been reset");
582             }
583             # Note: We don't validate email, just removing some extra bits which may be present.
584 0         0 $email=~s/^\s*mail(?:to):\s*//i;
585 0         0 $email=~s/^<([^>]+)>/$1/;
586 0         0 $email=~s/^\s+$//;
587 0 0       0 return $self->_status(INVALID_DATA, "Invalid email provided") unless $email;
588 0         0 $self->{email} = $email;
589 0         0 return $self->_status(OK, "Account email has been set to '$email'");
590             }
591              
592             =head2 set_domains($domains)
593              
594             Sets the list of domains to be used for verification process. This call is optional if you load or generate a CSR, in which case the list of the domains will be set at that point.
595              
596             Returns: OK | INVALID_DATA.
597              
598             =cut
599              
600             sub set_domains {
601 9     9 1 4240 my ($self, $domains) = @_;
602 9         15 my @list = @{$self->_get_list($domains)};
  9         280  
603 9 100       25 return $self->_status(INVALID_DATA, "No domains provided.") unless @list;
604 8 100       18 if (my $odd = $self->_verify_list(\@list)) {
605 4         7 return $self->_status(INVALID_DATA, "Unsupported domain names provided: " . join(", ", @{$odd}));
  4         12  
606             }
607 4         10 $self->{loaded_domains} = \@list;
608 4         7 my %loaded_domains = map {$_, undef} @list;
  9         20  
609 4         10 $self->{domains} = \%loaded_domains;
610 4         10 return $self->_status(OK, "Domains list is set");
611             }
612              
613             =head2 set_version($version)
614              
615             Sets the API version to be used. To pick the version automatically, use 0, other accepted values are currently 1 and 2.
616              
617             Returns: OK | INVALID_DATA.
618              
619             =cut
620              
621             sub set_version {
622 0     0 1 0 my ($self, $version) = @_;
623 0 0 0     0 return $self->_status(INVALID_DATA, "Unsupported API version") unless (defined $version and $version=~/^\d+$/ and $version <= 2);
      0        
624 0         0 $self->{version} = $version;
625 0         0 return $self->_status(OK, "API version is set to $version.");
626             }
627              
628             =head2 version()
629              
630             Returns: The API version currently used (1 or 2). If 0 is returned, it means it is set to automatic detection and the directory has not yet been retrieved.
631              
632             =cut
633              
634             sub version {
635 0     0 1 0 my $self = shift;
636 0         0 return $self->{version};
637             }
638              
639             #====================================================================================================
640             # API Setup helpers
641             #====================================================================================================
642              
643             sub _reset_key {
644 4     4   5 my $self = shift;
645 4         35 undef $self->{$_} for qw<key_params key pem jwk fingerprint>;
646             }
647              
648             sub _set_key {
649 3     3   6 my $self = shift;
650 3         7 my ($key, $msg) = @_;
651 3         119 my $pem = $key->get_private_key_string;
652 3         75 my ($n, $e) = $key->get_key_parameters;
653 3 50       1282 return $self->_status(INVALID_DATA, "Key modulus is divisible by a small prime and will be rejected.") if $self->_is_divisible($n);
654 3         10 $key->use_pkcs1_padding;
655 3         9 $key->use_sha256_hash;
656 3         13 $self->{key_params} = { n => $n, e => $e };
657 3         5 $self->{key} = $key;
658 3         9 $self->{pem} = $pem;
659 3         9 $self->{jwk} = $self->_jwk();
660 3         106 $self->{fingerprint} = encode_base64url(sha256($j->encode($self->{jwk})));
661 3 50       33 if ($self->{autodir}) {
662 0         0 my $status = $self->directory;
663 0 0       0 return $status unless ($status == OK);
664             }
665 3         10 return $self->_status(OK, $msg);
666             }
667              
668             sub _is_divisible {
669 3     3   8 my ($self, $n) = @_;
670 3         4 my ($quotient, $remainder);
671 3         16 my $ctx = Crypt::OpenSSL::Bignum::CTX->new();
672 3         8 foreach my $prime (@primes) {
673 399         2570 ($quotient, $remainder) = $n->div($prime, $ctx);
674 399 50       1015 return 1 if $remainder->is_zero;
675             }
676 3         17 return 0;
677             }
678              
679             sub _reset_csr {
680 25     25   30 my $self = shift;
681 25         104 undef $self->{$_} for qw<domains loaded_domains csr>;
682             }
683              
684             sub _set_csr {
685 12     12   16 my $self = shift;
686 12         21 my ($csr, $pk, $domains) = @_;
687 12         32 $self->{csr} = $csr;
688 12         24 $self->{csr_key} = $pk;
689 12         15 my %loaded_domains = map {$_, undef} @{$domains};
  22         61  
  12         20  
690 12         21 $self->{loaded_domains} = $domains;
691 12         26 $self->{domains} = \%loaded_domains;
692             }
693              
694             sub _get_list {
695 32     32   62 my ($self, $list) = @_;
696 32 100       151 return [ map {lc $_} (ref $list eq 'ARRAY') ? @{$list} : $list ? split /\s*,\s*/, $list : () ];
  40 100       142  
  1         3  
697             }
698              
699             sub _verify_list {
700 30     30   51 my ($self, $list) = @_;
701 30 100 100     38 my @odd = grep { /[\s\[\{\(\<\@\>\)\}\]\/\\:]/ or /^[\d\.]+$/ or !/\./ } @{$list};
  49         369  
  30         46  
702 30 100       103 return @odd ? \@odd : undef;
703             }
704              
705             #====================================================================================================
706             # API Workflow functions
707             #====================================================================================================
708              
709             =head1 METHODS (API Workflow)
710              
711             The following methods are provided for the API workflow processing. All but C<accept_challenge()> methods interact with Let's Encrypt servers.
712              
713             =head2 directory([ $reload ])
714              
715             Loads resource pointers from Let's Encrypt. This method needs to be called before the registration. It
716             will be called automatically upon account key loading/generation unless you have reset the 'autodir'
717             parameter when creating a new Crypt::LE instance. If any true value is provided as a parameter, reloads
718             the directory even if it has been already retrieved, but preserves the 'reg' value (for example to pull
719             another Nonce for the current session).
720              
721             Returns: OK | INVALID_DATA | LOAD_ERROR.
722              
723             =cut
724              
725             sub directory {
726 1     1 1 2789 my ($self, $reload) = @_;
727 1 50 33     8 if (!$self->{directory} or $reload) {
728 1 50       7 my ($status, $content) = $self->{dir} ? $self->_request($self->{dir}) : $self->_request("https://$self->{server}/directory");
729 1 50 33     7 if ($status == SUCCESS and $content and (ref $content eq 'HASH')) {
      33        
730 0 0       0 if ($content->{newAccount}) {
    0          
731 0 0 0     0 unless ($self->version) {
732 0         0 $self->set_version(2);
733             } elsif ($self->version() != 2) {
734             return $self->_status(INVALID_DATA, "Resource directory is not compatible with the version set (required v1, got v2).");
735             }
736 0         0 $self->_compat($content);
737             } elsif ($content->{'new-reg'}) {
738 0 0 0     0 unless ($self->version) {
739 0         0 $self->set_version(1);
740             } elsif ($self->version() != 1) {
741             return $self->_status(INVALID_DATA, "Resource directory is not compatible with the version set (required v2, got v1).");
742             }
743             } else {
744 0         0 return $self->_status(INVALID_DATA, "Resource directory does not contain expected fields.");
745             }
746 0 0 0     0 $content->{reg} = $self->{directory}->{reg} if ($self->{directory} and $self->{directory}->{reg});
747 0         0 $self->{directory} = $content;
748 0 0       0 unless ($self->{nonce}) {
749 0 0       0 if ($self->{directory}->{'newNonce'}) {
750 0         0 $self->_request($self->{directory}->{'newNonce'}, undef, { method => 'head' });
751 0 0       0 return $self->_status(LOAD_ERROR, "Could not retrieve the Nonce value.") unless $self->{nonce};
752             } else {
753 0         0 return $self->_status(LOAD_ERROR, "Could not retrieve the Nonce value and there is no method to request it.")
754             }
755             }
756 0         0 return $self->_status(OK, "Directory loaded successfully.");
757             } else {
758 1         5 return $self->_status(LOAD_ERROR, $content);
759             }
760             }
761 0         0 return $self->_status(OK, "Directory has been already loaded.");
762             }
763              
764             =head2 new_nonce()
765              
766             Requests a new nonce by forcing the directory reload. Picks up the value from the returned headers if it
767             is present (API v1.0), otherwise uses newNonce method to get it (API v2.0) if one is provided.
768              
769             Returns: Nonce value or undef (if neither the value is in the headers nor newNonce method is available).
770              
771             =cut
772              
773             sub new_nonce {
774 0     0 1 0 my $self = shift;
775 0         0 undef $self->{nonce};
776 0         0 $self->directory(1);
777 0         0 return $self->{nonce};
778             }
779              
780             =head2 register([$kid, $mac])
781              
782             Registers an account key with Let's Encrypt. If the key is already registered, it will be handled automatically.
783             Accepts optional $kid (eab-kid) and $mac (eab-hmac-key) parameters - those are used for EAB (External Account Binding).
784              
785             Returns: OK | ERROR.
786              
787             =cut
788              
789             sub register {
790 0     0 1 0 my ($self, $kid, $mac) = @_;
791 0         0 my $req = { resource => 'new-reg' };
792 0 0       0 $req->{contact} = [ "mailto:$self->{email}" ] if $self->{email};
793 0         0 my ($status, $content) = $self->_request($self->{directory}->{'new-reg'}, $req, { kid => $kid, mac => $mac });
794 0 0       0 $self->{directory}->{reg} = $self->{location} if $self->{location};
795 0         0 $self->{$_} = undef for (qw<registration_id contact_details>);
796 0 0 0     0 if ($status == $self->_compat_response(ALREADY_DONE)) {
    0 0        
    0 0        
797 0         0 $self->{new_registration} = 0;
798 0         0 $self->_debug("Key is already registered, reg path: $self->{directory}->{reg}.");
799 0         0 ($status, $content) = $self->_request($self->{directory}->{'reg'}, { resource => 'reg' });
800 0 0       0 if ($status == $self->_compat_response(ACCEPTED)) {
801 0         0 $self->{registration_info} = $content;
802 0 0 0     0 if ($self->version() == 1 and $self->{links} and $self->{links}->{'terms-of-service'} and (!$content->{agreement} or ($self->{links}->{'terms-of-service'} ne $content->{agreement}))) {
      0        
      0        
      0        
803 0 0       0 $self->_debug($content->{agreement} ? "You need to accept TOS" : "TOS has changed, you may need to accept it again.");
804 0         0 $self->{tos_changed} = 1;
805             } else {
806 0         0 $self->{tos_changed} = 0;
807             }
808             } else {
809 0         0 return $self->_status(ERROR, $content);
810             }
811             } elsif ($status == CREATED) {
812 0         0 $self->{new_registration} = 1;
813 0         0 $self->{registration_info} = $content;
814 0         0 $self->{tos_changed} = 0;
815 0         0 my $tos_message = '';
816 0 0       0 if ($self->{links}->{'terms-of-service'}) {
817 0         0 $self->{tos_changed} = 1;
818 0         0 $tos_message = "You need to accept TOS at $self->{links}->{'terms-of-service'}";
819             }
820 0         0 $self->_debug("New key is now registered, reg path: $self->{directory}->{reg}. $tos_message");
821             } elsif ($status == BAD_REQUEST and $kid and $mac and $self->_pull_error($content)=~/not awaiting/) {
822             # EAB credentials were already associated with the key.
823 0 0       0 if ($self->{directory}->{reg}) {
824 0         0 $self->_debug("EAB credentials already associated. Account URL is: $self->{directory}->{reg}.");
825             } else {
826 0         0 return $self->_status(ERROR, "EAB credentials already associated and no EAB id was provided.");
827             }
828             } else {
829 0         0 return $self->_status(ERROR, $content);
830             }
831 0 0 0     0 if ($self->{registration_info} and ref $self->{registration_info} eq 'HASH') {
832 0         0 $self->{registration_id} = $self->{registration_info}->{id};
833 0 0 0     0 if ($self->{registration_info}->{contact} and (ref $self->{registration_info}->{contact} eq 'ARRAY') and @{$self->{registration_info}->{contact}}) {
  0   0     0  
834 0         0 $self->{contact_details} = $self->{registration_info}->{contact};
835             }
836             }
837 0 0 0     0 if (!$self->{registration_id} and $self->{directory}->{reg}=~/\/([^\/]+)$/) {
838 0         0 $self->{registration_id} = $1;
839             }
840 0 0       0 $self->_debug("Account ID: $self->{registration_id}") if $self->{registration_id};
841 0         0 return $self->_status(OK, "Registration success: TOS change status - $self->{tos_changed}, new registration flag - $self->{new_registration}.");
842             }
843              
844             =head2 accept_tos()
845              
846             Accepts Terms of Service set by Let's Encrypt.
847              
848             Returns: OK | ERROR.
849              
850             =cut
851              
852             sub accept_tos {
853 0     0 1 0 my $self = shift;
854 0 0       0 return $self->_status(OK, "TOS has NOT been changed, no need to accept again.") unless $self->tos_changed;
855 0         0 my ($status, $content) = $self->_request($self->{directory}->{'reg'}, { resource => 'reg', agreement => $self->{links}->{'terms-of-service'} });
856 0 0       0 return ($status == $self->_compat_response(ACCEPTED)) ? $self->_status(OK, "Accepted TOS.") : $self->_status(ERROR, $content);
857             }
858              
859             =head2 update_contacts($array_ref)
860              
861             Updates contact details for your Let's Encrypt account. Accepts an array reference of contacts.
862             Non-prefixed contacts will be automatically prefixed with 'mailto:'.
863              
864             Returns: OK | INVALID_DATA | ERROR.
865              
866             =cut
867              
868             sub update_contacts {
869 0     0 1 0 my ($self, $contacts) = @_;
870 0 0 0     0 return $self->_status(INVALID_DATA, "Invalid call parameters.") unless ($contacts and (ref $contacts eq 'ARRAY'));
871 0 0       0 my @set = map { /^\w+:/ ? $_ : "mailto:$_" } @{$contacts};
  0         0  
  0         0  
872 0         0 my ($status, $content) = $self->_request($self->{directory}->{'reg'}, { resource => 'reg', contact => \@set });
873 0 0       0 return ($status == $self->_compat_response(ACCEPTED)) ? $self->_status(OK, "Email has been updated.") : $self->_status(ERROR, $content);
874             }
875              
876             =head2 request_challenge()
877              
878             Requests challenges for domains on your CSR. On error you can call failed_domains() method, which returns an array reference to domain names for which
879             the challenge was not requested successfully.
880              
881             Returns: OK | ERROR.
882              
883             =cut
884              
885             sub request_challenge {
886 0     0 1 0 my $self = shift;
887 0 0       0 $self->_status(ERROR, "No domains are set.") unless $self->{domains};
888 0         0 my ($domains_requested, %domains_failed);
889             # For v2.0 API the 'new-authz' is optional. However, authz set is provided via newOrder request (also utilized by request_certificate call).
890             # We are keeping the flow compatible with older clients, so if that call has not been specifically made (as it would in le.pl), we do
891             # it at the point of requesting the challenge. Note that if certificate is already valid, we will skip most of the challenge-related
892             # calls, but will not be returning the cert early to avoid interrupting the established flow.
893 0 0       0 if ($self->version() > 1) {
894 0 0       0 unless ($self->{authz}) {
895 0         0 my ($status, $content) = $self->_request($self->{directory}->{'new-cert'}, { resource => 'new-cert' });
896 0 0 0     0 if ($status == CREATED and $content->{'identifiers'} and $content->{'authorizations'}) {
      0        
897 0         0 push @{$self->{authz}}, [ $_, '' ] for @{$content->{'authorizations'}};
  0         0  
  0         0  
898 0         0 $self->{finalize} = $content->{'finalize'};
899             } else {
900 0 0       0 unless ($self->{directory}->{'new-authz'}) {
901 0         0 return $self->_status(ERROR, "Cannot request challenges - " . $self->_pull_error($content) . "($status).");
902             }
903 0         0 $self->_get_authz();
904             }
905             }
906             } else {
907 0         0 $self->_get_authz();
908             }
909 0         0 foreach my $authz (@{$self->{authz}}) {
  0         0  
910 0         0 $self->_debug("Requesting challenge.");
911 0         0 my ($status, $content) = $self->_request(@{$authz});
  0         0  
912 0         0 $domains_requested++;
913 0 0       0 if ($status == $self->_compat_response(CREATED)) {
914 0         0 my $valid_challenge = 0;
915 0 0 0     0 return $self->_status(ERROR, "Missing identifier in the authz response.") unless ($content->{identifier} and $content->{identifier}->{value});
916 0         0 my $domain = $content->{identifier}->{value};
917 0 0       0 $domain = "*.$domain" if $content->{wildcard};
918 0         0 foreach my $challenge (@{$content->{challenges}}) {
  0         0  
919 0 0 0     0 unless ($challenge and (ref $challenge eq 'HASH') and $challenge->{type} and
      0        
      0        
      0        
      0        
      0        
920             ($challenge->{url} or $challenge->{uri}) and
921             ($challenge->{status} or $content->{status})) {
922 0         0 $self->_debug("Challenge for domain $domain does not contain required fields.");
923 0         0 next;
924             }
925 0         0 my $type = (split '-', delete $challenge->{type})[0];
926 0 0 0     0 unless ($challenge->{token} and $challenge->{token}=~$url_safe) {
927 0         0 $self->_debug("Challenge ($type) for domain $domain is missing a valid token.");
928 0         0 next;
929             }
930 0 0       0 $valid_challenge = 1 if ($challenge->{status} eq 'valid');
931 0   0     0 $challenge->{uri} ||= $challenge->{url};
932 0   0     0 $challenge->{status} ||= $content->{status};
933 0         0 $self->{challenges}->{$domain}->{$type} = $challenge;
934             }
935 0 0 0     0 if ($self->{challenges} and exists $self->{challenges}->{$domain}) {
936 0         0 $self->_debug("Received challenges for $domain.");
937 0         0 $self->{domains}->{$domain} = $valid_challenge;
938             } else {
939 0         0 $self->_debug("Received no valid challenges for $domain.");
940 0   0     0 $domains_failed{$domain} = $self->_pull_error($content)||'No valid challenges';
941             }
942             } else {
943             # NB: In API v2.0 you don't know which domain you are receiving a challenge for - you can only rely
944             # on the identifier in the response. Even though in v1.0 we could associate domain name with this error,
945             # we treat this uniformly and return.
946 0         0 my $err = $self->_pull_error($content);
947 0         0 return $self->_status(ERROR, "Failed to receive the challenge. $err");
948             }
949             }
950 0 0       0 if (%domains_failed) {
951 0         0 my @failed = sort keys %domains_failed;
952 0         0 $self->{failed_domains} = [ \@failed ];
953 0         0 my $status = join "\n", map { "$_: $domains_failed{$_}" } @failed;
  0         0  
954 0 0       0 my $info = @failed == $domains_requested ? "All domains failed" : "Some domains failed";
955 0         0 return $self->_status(ERROR, "$info\n$status");
956             } else {
957 0         0 $self->{failed_domains} = [ undef ];
958             }
959             # Domains not requested with authz are considered to be already validated.
960 0         0 for my $domain (@{$self->{loaded_domains}}) {
  0         0  
961 0 0       0 unless (defined $self->{domains}->{$domain}) {
962 0         0 $self->{domains}->{$domain} = 1;
963 0         0 $self->_debug("Domain $domain does not require a challenge at this time.");
964             }
965             }
966 0 0       0 return $self->_status(OK, $domains_requested ? "Requested challenges for $domains_requested domain(s)." : "There are no domains which were not yet requested for challenges.");
967             }
968              
969             =head2 accept_challenge($callback [, $params] [, $type])
970              
971             Sets up a callback, which will be called for each non-verified domain to satisfy the requested challenge. Each callback will receive two parameters -
972             a hash reference with the challenge data and a hash reference of parameters optionally passed to accept_challenge(). The challenge data has the following keys:
973              
974             =over 14
975              
976             =item C<domain>
977              
978             The domain name being processed (lower-case)
979              
980             =item C<host>
981              
982             The domain name without the wildcard part (if that was present)
983              
984             =item C<token>
985              
986             The challenge token
987              
988             =item C<fingerprint>
989            
990             The account key fingerprint
991              
992             =item C<file>
993              
994             The file name for HTTP verification (essentially the same as token)
995              
996             =item C<text>
997              
998             The text for HTTP verification
999              
1000             =item C<record>
1001              
1002             The value of the TXT record for DNS verification
1003              
1004             =item C<logger>
1005            
1006             Logger object.
1007              
1008             =back
1009              
1010             The type of the challenge accepted is optional and it is 'http' by default. The following values are currently available: 'http', 'tls', 'dns'.
1011             New values which might be added by Let's Encrypt will be supported automatically. While currently all domains being processed share the same type
1012             of challenge, it might be changed in the future versions.
1013              
1014             On error you can call failed_domains() method, which returns an array reference to domain names for which the challenge was not accepted successfully.
1015              
1016             The callback should return a true value on success.
1017              
1018             The callback could be either a code reference (for example to a subroutine in your program) or a blessed reference to a module handling
1019             the challenge. In the latter case the module should have methods defined for handling appropriate challenge type, such as:
1020              
1021             =over
1022              
1023             =item
1024              
1025             B<handle_challenge_http()>
1026              
1027             =item
1028              
1029             B<handle_challenge_tls()>
1030              
1031             =item
1032              
1033             B<handle_challenge_dns()>
1034              
1035             =back
1036              
1037             You can use L<Crypt::LE::Challenge::Simple> example module as a template.
1038            
1039             Returns: OK | INVALID_DATA | ERROR.
1040              
1041             =cut
1042              
1043             sub accept_challenge {
1044 0     0 1 0 my $self = shift;
1045 0         0 my ($cb, $params, $type) = @_;
1046 0 0 0     0 return $self->_status(ERROR, "Domains and challenges need to be set before accepting.") unless ($self->{domains} and $self->{challenges});
1047 0 0 0     0 my $mod_callback = ($cb and blessed $cb) ? 1 : 0;
1048 0   0     0 $type||='http';
1049 0         0 my $handler = "handle_challenge_$type";
1050 0 0 0     0 return $self->_status(INVALID_DATA, "Valid callback has not been provided.") unless ($cb and ((ref $cb eq 'CODE') or ($mod_callback and $cb->can($handler))));
      0        
1051 0 0 0     0 return $self->_status(INVALID_DATA, "Passed parameters are not pointing to a hash.") if ($params and (ref $params ne 'HASH'));
1052 0         0 my ($domains_accepted, @domains_failed);
1053 0         0 $self->{active_challenges} = undef;
1054 0         0 foreach my $domain (@{$self->{loaded_domains}}) {
  0         0  
1055 0 0 0     0 unless (defined $self->{domains}->{$domain} and !$self->{domains}->{$domain}) {
1056 0 0       0 $self->_debug($self->{domains}->{$domain} ? "Domain $domain has been already validated, skipping." : "Challenge has not yet been requested for domain $domain, skipping.");
1057 0         0 next;
1058             }
1059 0 0 0     0 unless ($self->{challenges}->{$domain} and $self->{challenges}->{$domain}->{$type}) {
1060 0         0 $self->_debug("Could not find a challenge of type $type for domain $domain.");
1061 0         0 push @domains_failed, $domain;
1062 0         0 next;
1063             }
1064 0         0 my $rv;
1065             my $callback_data = {
1066             domain => $domain,
1067             token => $self->{challenges}->{$domain}->{$type}->{token},
1068             fingerprint => $self->{fingerprint},
1069             logger => $self->{logger},
1070 0         0 };
1071 0         0 $self->_callback_extras($callback_data);
1072 0         0 eval {
1073 0 0       0 $rv = $mod_callback ? $cb->$handler($callback_data, $params) : &$cb($callback_data, $params);
1074             };
1075 0 0 0     0 if ($@ or !$rv) {
1076 0 0       0 $self->_debug("Challenge callback for domain $domain " . ($@ ? "thrown an error: $@" : "did not return a true value"));
1077 0         0 push @domains_failed, $domain;
1078             } else {
1079 0         0 $self->{active_challenges}->{$domain} = $type;
1080 0         0 $domains_accepted++;
1081             }
1082             }
1083 0 0       0 if (@domains_failed) {
1084 0         0 push @{$self->{failed_domains}}, \@domains_failed;
  0         0  
1085 0 0       0 return $self->_status(ERROR, $domains_accepted ? "Challenges failed for domains: " . join(", ", @domains_failed) : "All challenges failed");
1086             } else {
1087 0         0 push @{$self->{failed_domains}}, undef;
  0         0  
1088             }
1089 0 0       0 return $self->_status(OK, $domains_accepted ? "Accepted challenges for $domains_accepted domain(s)." : "There are no domains for which challenges need to be accepted.");
1090             }
1091              
1092             =head2 verify_challenge([$callback] [, $params] [, $type])
1093              
1094             Asks Let's Encrypt server to verify the results of the challenge. On error you can call failed_domains() method, which returns an array reference to domain names
1095             for which the challenge was not verified successfully.
1096              
1097             Optionally you can set up a callback, which will be called for each domain with the results of verification. The callback will receive two parameters -
1098             a hash reference with the results and a hash reference of parameters optionally passed to verify_challenge(). The results data has the following keys:
1099              
1100             =over 14
1101              
1102             =item C<domain>
1103              
1104             The domain name processed (lower-case)
1105              
1106             =item C<host>
1107              
1108             The domain name without the wildcard part (if that was present)
1109              
1110             =item C<token>
1111              
1112             The challenge token
1113              
1114             =item C<fingerprint>
1115            
1116             The account key fingerprint
1117              
1118             =item C<file>
1119              
1120             The file name for HTTP verification (essentially the same as token)
1121              
1122             =item C<text>
1123              
1124             The text for HTTP verification
1125              
1126             =item C<record>
1127              
1128             The value of the TXT record for DNS verification
1129              
1130             =item C<valid>
1131            
1132             Set to 1 if the domain has been verified successfully or set to 0 otherwise.
1133              
1134             =item C<error>
1135            
1136             Error message returned for domain on verification failure.
1137              
1138             =item C<logger>
1139            
1140             Logger object.
1141              
1142             =back
1143              
1144             The type of the challenge accepted is optional and it is 'http' by default. The following values are currently available: 'http', 'tls', 'dns'.
1145              
1146             The callback should return a true value on success.
1147              
1148             The callback could be either a code reference (for example to a subroutine in your program) or a blessed reference to a module handling
1149             the verification outcome. In the latter case the module should have methods defined for handling appropriate verification type, such as:
1150              
1151             =over
1152              
1153             =item
1154              
1155             B<handle_verification_http()>
1156              
1157             =item
1158              
1159             B<handle_verification_tls()>
1160              
1161             =item
1162              
1163             B<handle_verification_dns()>
1164              
1165             =back
1166              
1167             You can use L<Crypt::LE::Challenge::Simple> example module as a template.
1168              
1169             Returns: OK | INVALID_DATA | ERROR.
1170              
1171             =cut
1172              
1173             sub verify_challenge {
1174 0     0 1 0 my $self = shift;
1175 0         0 my ($cb, $params, $type) = @_;
1176 0 0 0     0 return $self->_status(ERROR, "Domains and challenges need to be set before verifying.") unless ($self->{domains} and $self->{challenges});
1177 0 0       0 return $self->_status(OK, "There are no active challenges to verify") unless $self->{active_challenges};
1178 0 0 0     0 my $mod_callback = ($cb and blessed $cb) ? 1 : 0;
1179 0   0     0 $type||='http';
1180 0         0 my $handler = "handle_verification_$type";
1181 0 0       0 if ($cb) {
1182 0 0 0     0 return $self->_status(INVALID_DATA, "Valid callback has not been provided.") unless ($cb and ((ref $cb eq 'CODE') or ($mod_callback and $cb->can($handler))));
      0        
1183 0 0 0     0 return $self->_status(INVALID_DATA, "Passed parameters are not pointing to a hash.") if ($params and (ref $params ne 'HASH'));
1184             }
1185 0         0 my ($domains_verified, @domains_failed);
1186 0         0 my $expected_status = $self->_compat_response(ACCEPTED);
1187 0         0 foreach my $domain (@{$self->{loaded_domains}}) {
  0         0  
1188 0 0 0     0 unless (defined $self->{domains}->{$domain} and !$self->{domains}->{$domain}) {
1189 0 0       0 $self->_debug($self->{domains}->{$domain} ? "Domain $domain has been already verified, skipping." : "Challenge has not yet been requested for domain $domain, skipping.");
1190 0         0 next;
1191             }
1192 0 0       0 unless ($self->{active_challenges}->{$domain}) {
1193 0         0 $self->_debug("Domain $domain is not set as having an active challenge (you may need to run 'accept_challenge'), skipping.");
1194 0         0 push @domains_failed, $domain;
1195 0         0 next;
1196             }
1197 0         0 my $type = delete $self->{active_challenges}->{$domain};
1198 0         0 my $token = $self->{challenges}->{$domain}->{$type}->{token};
1199 0         0 my ($status, $content) = $self->_request($self->{challenges}->{$domain}->{$type}->{uri}, { resource => 'challenge', keyAuthorization => "$token.$self->{fingerprint}" });
1200 0         0 my ($validated, $cb_reset) = (0, 0);
1201 0 0       0 if ($status == $expected_status) {
1202 0   0     0 $content->{uri} ||= $content->{url};
1203 0 0       0 if ($content->{uri}) {
1204 0         0 my @check = ($content->{uri});
1205 0 0       0 push @check, $self->version() > 1 ? '' : undef;
1206 0         0 ($status, $content) = $self->_await(@check, { status => $expected_status });
1207 0 0 0     0 if ($status == $expected_status and $content and $content->{status}) {
      0        
1208 0 0       0 if ($content->{status}=~/^(?:in)?valid$/) {
1209 0 0       0 if ($content->{status} eq 'valid') {
1210 0         0 $self->_debug("Domain $domain has been verified successfully.");
1211 0         0 $validated = 1;
1212             }
1213             }
1214             }
1215             }
1216             }
1217 0 0       0 if ($cb) {
1218 0         0 my $rv;
1219             my $callback_data = {
1220             domain => $domain,
1221             token => $self->{challenges}->{$domain}->{$type}->{token},
1222             fingerprint => $self->{fingerprint},
1223             valid => $validated,
1224             error => $self->_pull_error($content),
1225             logger => $self->{logger},
1226 0         0 };
1227 0         0 $self->_callback_extras($callback_data);
1228 0         0 eval {
1229 0 0       0 $rv = $mod_callback ? $cb->$handler($callback_data, $params) : &$cb($callback_data, $params);
1230             };
1231 0 0 0     0 if ($@ or !$rv) {
1232             # NB: Error in callback will propagate, even if validation process returned OK.
1233 0 0       0 $self->_debug("Verification callback for domain $domain " . ($@ ? "thrown an error: $@" : "did not return a true value"));
1234 0 0       0 $cb_reset = 1 if $validated;
1235 0         0 $validated = 0;
1236             }
1237             }
1238 0 0       0 if ($validated) {
1239 0         0 $self->{domains}->{$domain} = 1;
1240 0         0 $domains_verified++;
1241             } else {
1242 0 0       0 $self->_debug("Domain $domain has failed verification (status code $status).", $content) unless $cb_reset;
1243 0         0 push @domains_failed, $domain;
1244             }
1245             }
1246 0 0       0 if (@domains_failed) {
1247 0         0 push @{$self->{failed_domains}}, \@domains_failed;
  0         0  
1248 0 0       0 return $self->_status(ERROR, $domains_verified ? "Verification failed for domains: " . join(", ", @domains_failed) : "All verifications failed");
1249             } else {
1250 0         0 push @{$self->{failed_domains}}, undef;
  0         0  
1251             }
1252 0 0       0 return $self->_status(OK, $domains_verified ? "Verified challenges for $domains_verified domain(s)." : "There are no domains pending challenge verification.");
1253             }
1254              
1255             =head2 request_certificate()
1256              
1257             Requests the certificate for your CSR.
1258              
1259             Returns: OK | AUTH_ERROR | ERROR.
1260              
1261             =cut
1262              
1263             sub request_certificate {
1264 0     0 1 0 my $self = shift;
1265 0 0       0 return $self->_status(ERROR, "CSR is missing, make sure it has been either loaded or generated.") unless $self->{csr};
1266 0         0 my $csr = encode_base64url($self->pem2der($self->{csr}));
1267 0         0 my ($status, $content, $ready);
1268 0         0 delete $self->{authz};
1269 0         0 delete $self->{alternatives};
1270 0 0       0 unless ($self->{finalize}) {
1271 0         0 ($status, $content) = $self->_request($self->{directory}->{'new-cert'}, { resource => 'new-cert', csr => $csr });
1272 0 0       0 return $self->_status($status == AUTH_ERROR ? AUTH_ERROR : ERROR, $content) unless ($status == CREATED);
    0          
1273 0 0 0     0 if (ref $content eq 'HASH' and $content->{'identifiers'} and $content->{'authorizations'}) {
      0        
1274 0         0 push @{$self->{authz}}, [ $_, '' ] for @{$content->{'authorizations'}};
  0         0  
  0         0  
1275 0         0 $self->{finalize} = $content->{'finalize'};
1276             }
1277             }
1278 0 0       0 if ($self->{finalize}) {
1279             # v2. Let's attempt to finalize the order immediately.
1280 0         0 ($status, $content) = $self->_request($self->{finalize}, { csr => $csr });
1281 0 0 0     0 if (ref $content eq 'HASH' and $content->{status} and $content->{status} eq 'processing') {
      0        
1282             # The order is not ready yet - poll until it is (or we hit the retries set, with the default of 300).
1283 0         0 ($status, $content) = $self->_await($self->{location}, '');
1284             }
1285              
1286 0 0 0     0 if ($status == SUCCESS and $content and $content->{status}) {
      0        
1287 0 0       0 if ($content->{status} eq 'valid') {
    0          
    0          
1288 0 0       0 if ($content->{certificate}) {
1289 0         0 $self->_debug("The certificate is ready for download at $content->{certificate}.");
1290 0         0 my @cert = ($content->{certificate});
1291 0 0       0 push @cert, '' if ($self->version() > 1);
1292 0         0 ($status, $content) = $self->_request(@cert);
1293 0 0       0 return $self->_status(ERROR, "Certificate could not be downloaded from $content->{certificate}.") unless ($status == SUCCESS);
1294             # In v2 certificate is returned along with the chain.
1295 0         0 $ready = 1;
1296 0 0       0 if ($content=~/(\n\-+END CERTIFICATE\-+)[\s\r\n]+(.+)/s) {
1297 0         0 $self->_debug("Certificate is separated from the chain.");
1298 0         0 $self->{issuer} = $self->_convert($2, 'CERTIFICATE');
1299 0         0 $content = $` . $1;
1300             }
1301             # Save the links to alternative certificates.
1302 0   0     0 $self->{alternatives} = $self->{links}->{alternate} || [];
1303             } else {
1304 0         0 return $self->_status(ERROR, "The certificate is ready, but there was no download link provided.");
1305             }
1306             } elsif ($content->{status} eq 'invalid') {
1307 0         0 return $self->_status(ERROR, "Certificate cannot be issued.");
1308             } elsif ($content->{status} eq 'pending') {
1309 0         0 return $self->_status(AUTH_ERROR, "Order already exists but not yet completed.");
1310             } else {
1311 0         0 return $self->_status(ERROR, "Unknown order status: $content->{status}.");
1312             }
1313             } else {
1314 0         0 return $self->_status(AUTH_ERROR, "Could not finalize an order.");
1315             }
1316 0 0       0 return $self->_status(AUTH_ERROR, "Could not finalize an order.") unless $ready;
1317             }
1318 0         0 $self->{certificate} = $self->_convert($content, 'CERTIFICATE');
1319 0         0 $self->{certificate_url} = $self->{location};
1320 0 0 0     0 $self->{issuer_url} = ($self->{links} and $self->{links}->{up}) ? $self->{links}->{up} : undef;
1321 0 0       0 return $self->_status(OK, "Domain certificate has been received." . ($self->{issuer_url} ? " Issuer's certificate can be found at: $self->{issuer_url}" : ""));
1322             }
1323              
1324             =head2 request_alternatives()
1325              
1326             Requests alternative certificates if any are available.
1327              
1328             Returns: OK | ERROR.
1329              
1330             =cut
1331              
1332             sub request_alternatives {
1333 0     0 1 0 my $self = shift;
1334 0 0       0 return $self->_status(ERROR, "The default certificate must be requested before the alternatives.") unless $self->{alternatives};
1335 0         0 my ($status, $content);
1336 0         0 delete $self->{alternative_certificates};
1337 0         0 foreach my $link (@{$self->{alternatives}}) {
  0         0  
1338 0         0 $self->_debug("Alternative certificate is available at $link.");
1339 0         0 my @cert = ($link);
1340 0 0       0 push @cert, '' if ($self->version() > 1);
1341 0         0 ($status, $content) = $self->_request(@cert);
1342 0 0       0 return $self->_status(ERROR, "Certificate could not be downloaded from $link.") unless ($status == SUCCESS);
1343             # In v2 certificate is returned along with the chain.
1344 0 0       0 if ($content=~/(\n\-+END CERTIFICATE\-+)[\s\r\n]+(.+)/s) {
1345 0         0 $self->_debug("Certificate is separated from the chain.");
1346 0         0 push @{$self->{alternative_certificates}}, [ $self->_convert($` . $1, 'CERTIFICATE'), $self->_convert($2, 'CERTIFICATE') ];
  0         0  
1347             } else {
1348 0         0 push @{$self->{alternative_certificates}}, [ $self->_convert($content, 'CERTIFICATE') ];
  0         0  
1349             }
1350             }
1351 0         0 return $self->_status(OK, "Alternative certificates have been received.");
1352             }
1353              
1354             =head2 request_issuer_certificate()
1355              
1356             Requests the issuer's certificate.
1357              
1358             Returns: OK | ERROR.
1359              
1360             =cut
1361              
1362             sub request_issuer_certificate {
1363 0     0 1 0 my $self = shift;
1364 0 0       0 return $self->_status(OK, "Issuer's certificate has been already received.") if $self->issuer();
1365 0 0       0 return $self->_status(ERROR, "The URL of issuer certificate is not set.") unless $self->{issuer_url};
1366 0         0 my ($status, $content) = $self->_request($self->{issuer_url});
1367 0 0       0 if ($status == SUCCESS) {
1368 0         0 $self->{issuer} = $self->_convert($content, 'CERTIFICATE');
1369 0         0 return $self->_status(OK, "Issuer's certificate has been received.");
1370             }
1371 0         0 return $self->_status(ERROR, $content);
1372             }
1373              
1374             =head2 revoke_certificate($certificate_file|$scalar_ref)
1375              
1376             Revokes a certificate.
1377              
1378             Returns: OK | READ_ERROR | ALREADY_DONE | ERROR.
1379              
1380             =cut
1381              
1382             sub revoke_certificate {
1383 0     0 1 0 my $self = shift;
1384 0         0 my $file = shift;
1385 0         0 my $crt = $self->_file($file);
1386 0 0       0 return $self->_status(READ_ERROR, "Certificate reading error.") unless $crt;
1387 0         0 my ($status, $content) = $self->_request($self->{directory}->{'revoke-cert'},
1388             { resource => 'revoke-cert', certificate => encode_base64url($self->pem2der($crt)) },
1389             { jwk => 0 });
1390 0 0       0 if ($status == SUCCESS) {
    0          
1391 0         0 return $self->_status(OK, "Certificate has been revoked.");
1392             } elsif ($status == ALREADY_DONE) {
1393 0         0 return $self->_status(ALREADY_DONE, "Certificate has been already revoked.");
1394             }
1395 0         0 return $self->_status(ERROR, $content);
1396             }
1397              
1398             #====================================================================================================
1399             # API Workflow helpers
1400             #====================================================================================================
1401              
1402             =head1 METHODS (Other)
1403              
1404             The following methods are the common getters you can use to get more details about the outcome of the workflow run and return some retrieved data, such as
1405             registration info and certificates for your domains.
1406              
1407             =head2 tos()
1408              
1409             Returns: The link to a Terms of Service document or undef.
1410              
1411             =cut
1412              
1413             sub tos {
1414 0     0 1 0 my $self = shift;
1415 0 0 0     0 return ($self->{links} and $self->{links}->{'terms-of-service'}) ? $self->{links}->{'terms-of-service'} : undef;
1416             }
1417              
1418             =head2 tos_changed()
1419              
1420             Returns: True if Terms of Service have been changed (or you haven't yet accepted them). Otherwise returns false.
1421              
1422             =cut
1423              
1424             sub tos_changed {
1425 0     0 1 0 return shift->{tos_changed};
1426             }
1427              
1428             =head2 new_registration()
1429              
1430             Returns: True if new key has been registered. Otherwise returns false.
1431              
1432             =cut
1433              
1434             sub new_registration {
1435 0     0 1 0 return shift->{new_registration};
1436             }
1437              
1438             =head2 registration_info()
1439              
1440             Returns: Registration information structure returned by Let's Encrypt for your key or undef.
1441              
1442             =cut
1443              
1444             sub registration_info {
1445 0     0 1 0 return shift->{registration_info};
1446             }
1447              
1448             =head2 registration_id()
1449              
1450             Returns: Registration ID returned by Let's Encrypt for your key or undef.
1451              
1452             =cut
1453              
1454             sub registration_id {
1455 0     0 1 0 return shift->{registration_id};
1456             }
1457              
1458             =head2 contact_details()
1459              
1460             Returns: Contact details returned by Let's Encrypt for your key or undef.
1461              
1462             =cut
1463              
1464             sub contact_details {
1465 0     0 1 0 return shift->{contact_details};
1466             }
1467              
1468             =head2 certificate()
1469              
1470             Returns: The last received certificate or undef.
1471              
1472             =cut
1473              
1474             sub certificate {
1475 0     0 1 0 return shift->{certificate};
1476             }
1477              
1478             =head2 alternative_certificate()
1479              
1480             Returns: Specific alternative certificate as an arrayref (domain, issuer) or undef.
1481              
1482             =cut
1483              
1484             sub alternative_certificate {
1485 0     0 1 0 my ($self, $idx) = @_;
1486 0 0 0     0 if ($self->{alternative_certificates} and defined $idx and $idx < @{$self->{alternative_certificates}}) {
  0   0     0  
1487 0         0 return $self->{alternative_certificates}->[$idx];
1488             }
1489 0         0 return undef;
1490             }
1491              
1492             =head2 alternative_certificates()
1493              
1494             Returns: All available alternative certificates (as an arrayref of arrayrefs) or undef.
1495              
1496             =cut
1497              
1498             sub alternative_certificates {
1499 0     0 1 0 my ($self) = @_;
1500 0 0       0 if ($self->{alternative_certificates}) {
1501             # Prevent them from being accidentally changed (using the core module to avoid adding more dependencies).
1502 0         0 return dclone $self->{alternative_certificates};
1503             }
1504 0         0 return undef;
1505             }
1506              
1507             =head2 certificate_url()
1508              
1509             Returns: The URL of the last received certificate or undef.
1510              
1511             =cut
1512              
1513             sub certificate_url {
1514 0     0 1 0 return shift->{certificate_url};
1515             }
1516              
1517             =head2 issuer()
1518              
1519             Returns: The issuer's certificate or undef.
1520              
1521             =cut
1522              
1523             sub issuer {
1524 0     0 1 0 return shift->{issuer};
1525             }
1526              
1527             =head2 issuer_url()
1528              
1529             Returns: The URL of the issuer's certificate or undef.
1530              
1531             =cut
1532              
1533             sub issuer_url {
1534 0     0 1 0 return shift->{issuer_url};
1535             }
1536              
1537             =head2 domains()
1538              
1539             Returns: An array reference to the loaded domain names or undef.
1540              
1541             =cut
1542              
1543             sub domains {
1544 5     5 1 36 return shift->{loaded_domains};
1545             }
1546              
1547             =head2 failed_domains([$all])
1548              
1549             Returns: An array reference to the domain names for which processing has failed or undef. If any true value is passed as a parameter, then the list
1550             will contain domain names which failed on any of the request/accept/verify steps. Otherwise the list will contain the names of the domains failed on
1551             the most recently called request/accept/verify step.
1552              
1553             =cut
1554              
1555             sub failed_domains {
1556 2     2 1 5 my ($self, $all) = @_;
1557 2 50 33     8 return undef unless ($self->{failed_domains} and @{$self->{failed_domains}});
  2         20  
1558 2 100       9 return $self->{failed_domains}->[-1] unless $all;
1559 1         2 my %totals;
1560 1         1 foreach my $proc (@{$self->{failed_domains}}) {
  1         3  
1561 2 100       5 if ($proc) {
1562 1         1 $totals{$_} = undef for @{$proc};
  1         5  
1563             }
1564             }
1565 1         5 my @rv = sort keys %totals;
1566 1 50       7 return @rv ? \@rv : undef;
1567             }
1568              
1569             =head2 verified_domains()
1570              
1571             Returns: An array reference to the successfully verified domain names.
1572              
1573             =cut
1574              
1575             sub verified_domains {
1576 1     1 1 2 my $self = shift;
1577 1 50 33     4 return undef unless ($self->{domains} and %{$self->{domains}});
  1         4  
1578 1         2 my @list = grep { $self->{domains}->{$_} } keys %{$self->{domains}};
  3         6  
  1         4  
1579 1 50       6 return @list ? \@list : undef;
1580             }
1581              
1582             =head2 ca_list()
1583              
1584             Returns: An array of names of the directly supported CAs.
1585              
1586             =cut
1587              
1588             sub ca_list {
1589 1     1 1 940 return keys %{$cas};
  1         5  
1590             }
1591              
1592             =head2 ca_supported($name)
1593              
1594             Returns: True if CA is directly supported, or false otherwise.
1595              
1596             =cut
1597              
1598             sub ca_supported {
1599 2     2 1 5 my ($self, $ca) = @_;
1600 2 50       5 return undef unless $ca;
1601 2 100       11 return $cas->{lc $ca} ? 1 : 0;
1602             }
1603              
1604             =head2 ca_supported_staging($name)
1605              
1606             Returns: True if CA is directly supported and has staging environment, or false otherwise.
1607              
1608             =cut
1609              
1610             sub ca_supported_staging {
1611 2     2 1 6 my ($self, $ca) = @_;
1612 2 50       5 return undef unless $ca;
1613 2         4 $ca = lc $ca;
1614 2 100 66     23 return ($cas->{$ca} and $cas->{$ca}->{stage}) ? 1 : 0;
1615             }
1616              
1617             =head2 check_expiration($certificate_file|$scalar_ref|$url, [ \%params ])
1618              
1619             Checks the expiration of the certificate. Accepts an URL, a full path to the certificate file or a
1620             scalar reference to a certificate in memory. Optionally a hash ref of parameters can be provided with the
1621             timeout key set to the amount of seconds to wait for the https checks (by default set to 10 seconds).
1622              
1623             Returns: Days left until certificate expiration or undef on error. Note - zero and negative values can be
1624             returned for the already expired certificates. On error the status is set accordingly to one of the following:
1625             INVALID_DATA, LOAD_ERROR or ERROR, and the 'error_details' call can be used to get more information about the problem.
1626              
1627             =cut
1628              
1629             sub check_expiration {
1630 2     2 1 279 my ($self, $res, $params) = @_;
1631 2         5 my ($load_error, $exp);
1632 2 50 33     6 my $timeout = $params->{timeout} if ($params and (ref $params eq 'HASH'));
1633 2 50 0     16 if (!$res or ($timeout and ($timeout!~/^\d+/ or $timeout < 1))) {
    50 33        
      33        
      33        
1634 0         0 $self->_status(INVALID_DATA, "Invalid parameters");
1635 0         0 return undef;
1636             } elsif (ref $res or $res!~m~^\w+://~i) {
1637 2         4 my $bio;
1638 2 50       5 if (ref $res) {
1639 2         20 $bio = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem());
1640 2 50 33     14 $load_error = 1 unless ($bio and Net::SSLeay::BIO_write($bio, $$res));
1641             } else {
1642 0         0 $bio = Net::SSLeay::BIO_new_file($res, 'r');
1643 0 0       0 $load_error = 1 unless $bio;
1644             }
1645 2 50       4 unless ($load_error) {
1646 2         52 my $cert = Net::SSLeay::PEM_read_bio_X509($bio);
1647 2         6 Net::SSLeay::BIO_free($bio);
1648 2 100       5 unless ($cert) {
1649 1         3 $self->_status(LOAD_ERROR, "Could not parse the certificate");
1650 1         6 return undef;
1651             }
1652 1         5 _verify_crt(\$exp)->(0, 0, 0, 0, $cert, 0);
1653             } else {
1654 0         0 $self->_status(LOAD_ERROR, "Could not load the certificate");
1655 0         0 return undef;
1656             }
1657             } else {
1658 0         0 $res=~s/^[^:]+/https/;
1659 0   0     0 my $probe = HTTP::Tiny->new(
1660             agent => "Mozilla/5.0 (compatible; Crypt::LE v$VERSION agent; https://Do-Know.com/)",
1661             verify_SSL => 1,
1662             timeout => $timeout || 10,
1663             SSL_options => { SSL_verify_callback => _verify_crt(\$exp) },
1664             );
1665 0         0 my $response = $probe->head($res);
1666 0 0 0     0 $self->_status(ERROR, "Connection error: $response->{status} " . ($response->{reason}||'')) unless $response->{success};
1667             }
1668 1         9 return $exp;
1669             }
1670              
1671             =head2 pem2der($pem)
1672              
1673             Returns: DER form of the provided PEM content
1674              
1675             =cut
1676              
1677             sub pem2der {
1678 3     3 1 317 my ($self, $pem) = @_;
1679 3 50       8 return unless $pem;
1680 3 50       197 $pem = $1 if $pem=~/(?:^|\s+)-+BEGIN[^-]*-+\s+(.*?)\s+-+END/s;
1681 3         14 $pem=~s/\s+//;
1682 3         36 return decode_base64($pem);
1683             }
1684              
1685             =head2 der2pem($der, $type)
1686              
1687             Returns: PEM form of the provided DER content of the given type (for example 'CERTIFICATE REQUEST') or undef.
1688              
1689             =cut
1690              
1691             sub der2pem {
1692 3     3 1 6 my ($self, $der, $type) = @_;
1693 3 50 33     73 return ($der and $type) ? "-----BEGIN $type-----$/" . encode_base64($der) . "-----END $type-----" : undef;
1694             }
1695              
1696             =head2 export_pfx($file, $pass, $cert, $key, [ $ca ], [ $tag ])
1697              
1698             Exports given certificate, CA chain and a private key into a PFX/P12 format with a given password.
1699             Optionally you can specify a text to go into pfx instead of the default "Crypt::LE exported".
1700              
1701             Returns: OK | UNSUPPORTED | INVALID_DATA | ERROR.
1702              
1703             =cut
1704              
1705             sub export_pfx {
1706 1     1 1 306 my ($self, $file, $pass, $cert, $key, $ca, $tag) = @_;
1707 1         2 my $unsupported = "PFX export is not supported (requires specific build of PKCS12 library for Windows).";
1708 1 50       5 return $self->_status(UNSUPPORTED, $unsupported) unless $pkcs12_available;
1709 0 0       0 return $self->_status(INVALID_DATA, "Password is required") unless $pass;
1710 0         0 my $pkcs12 = Crypt::OpenSSL::PKCS12->new();
1711 0         0 eval {
1712 0   0     0 $pkcs12->create($cert, $key, $pass, $file, $ca, $tag || "Crypt::LE exported");
1713             };
1714 0 0 0     0 return $self->_status(UNSUPPORTED, $unsupported) if ($@ and $@=~/Usage/);
1715 0 0       0 return $self->_status(ERROR, $@) if $@;
1716 0         0 return $self->_status(OK, "PFX exported to $file.");
1717             }
1718              
1719             =head2 error()
1720              
1721             Returns: Last error (can be a code or a structure) or undef.
1722              
1723             =cut
1724              
1725             sub error {
1726 0     0 1 0 return shift->{error};
1727             }
1728              
1729             =head2 error_details()
1730              
1731             Returns: Last error details if available or a generic 'error' string otherwise. Empty string if the last called method returned OK.
1732              
1733             =cut
1734              
1735             sub error_details {
1736 2     2 1 10 my $self = shift;
1737 2 50       4 if ($self->{error}) {
1738 2         6 my $err = $self->_pull_error($self->{error});
1739 2 50       19 return $err ? $err : (ref $self->{error}) ? 'error' : $self->{error};
    50          
1740             }
1741 0         0 return '';
1742             }
1743              
1744             #====================================================================================================
1745             # Internal Crypto helpers
1746             #====================================================================================================
1747              
1748             sub _key {
1749 10     10   20 my ($key, $type, $attr) = @_;
1750 10         13 my $pk;
1751 10   100     32 $type||=KEY_RSA;
1752 10 50 33     57 return (undef, "Unsupported key type", INVALID_DATA) unless ($type=~/^\d+$/ and $type <= KEY_ECC);
1753 10 100       24 if ($type == KEY_RSA) {
    50          
1754 8   66     36 $attr||=$keysize;
1755 8 100 100     35 return (undef, "Unsupported key size", INVALID_DATA) if ($attr < 2048 or $attr%1024);
1756             } elsif ($type == KEY_ECC) {
1757 2 100 66     10 $attr = $keycurve unless ($attr and $attr ne 'default');
1758 2 50       7 return (undef, "Unsupported key type - upgrade Net::SSLeay to version 1.75 or better", UNSUPPORTED) unless defined &Net::SSLeay::EC_KEY_generate_key;
1759             }
1760 8 100       14 if ($key) {
1761 4         21 my $bio = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem());
1762 4 50       11 return (undef, "Could not allocate memory for the key") unless $bio;
1763 4 50       34 return _free(b => $bio, error => "Could not load the key data") unless Net::SSLeay::BIO_write($bio, $key);
1764 4         271 $pk = Net::SSLeay::PEM_read_bio_PrivateKey($bio);
1765 4         13 _free(b => $bio);
1766 4 50       9 return (undef, "Could not read the private key") unless $pk;
1767             } else {
1768 4         27 $pk = Net::SSLeay::EVP_PKEY_new();
1769 4 50       8 return (undef, "Could not allocate memory for the key") unless $pk;
1770 4         7 my $gen;
1771 4         5 eval {
1772 4 100       157 $gen = ($type == KEY_RSA) ? Net::SSLeay::RSA_generate_key($attr, &Net::SSLeay::RSA_F4) : Net::SSLeay::EC_KEY_generate_key($attr);
1773             };
1774 4 100       1816446 $@=~s/ at \S+ line \d+.$// if $@;
1775 4 50       17 return _free(k => $pk, error => "Could not generate the private key '$attr'" . ($@ ? " - $@" : "")) unless $gen;
    100          
1776 3 100       30 ($type == KEY_RSA) ? Net::SSLeay::EVP_PKEY_assign_RSA($pk, $gen) : Net::SSLeay::EVP_PKEY_assign_EC_KEY($pk, $gen);
1777             }
1778 7         21 return ($pk);
1779             }
1780              
1781             sub _csr {
1782 6     6   34 my ($pk, $domains, $attrib) = @_;
1783 6         16 my $ref = ref $domains;
1784 6 50 33     41 return unless ($domains and (!$ref or $ref eq 'ARRAY'));
      33        
1785 6 50 33     25 return if ($attrib and (ref $attrib ne 'HASH'));
1786 6         36 my $req = Net::SSLeay::X509_REQ_new();
1787 6 50       12 return _free(k => $pk) unless $req;
1788 6 50       73 return _free(k => $pk, r => $req) unless (Net::SSLeay::X509_REQ_set_pubkey($req, $pk));
1789 6 50       10 my @names = $ref ? @{$domains} : split(/\s*,\s*/, $domains);
  6         14  
1790 6 50 33     43 $attrib->{CN} = $names[0] unless ($attrib and ($attrib->{CN} or $attrib->{commonName}));
      33        
1791 6         12 my $list = join ',', map { 'DNS:' . encode_utf8($_) } @names;
  8         43  
1792 6 50       154 return _free(k => $pk, r => $req) unless Net::SSLeay::P_X509_REQ_add_extensions($req, &Net::SSLeay::NID_subject_alt_name => $list);
1793 6         218 my $n = Net::SSLeay::X509_NAME_new();
1794 6 50       14 return _free(k => $pk, r => $req) unless $n;
1795 6         7 foreach my $key (keys %{$attrib}) {
  6         23  
1796             # Can use long or short names
1797 30 50       785 return _free(k => $pk, r => $req) unless Net::SSLeay::X509_NAME_add_entry_by_txt($n, $key, MBSTRING_UTF8, encode_utf8($attrib->{$key}));
1798             }
1799 6 50       218 return _free(k => $pk, r => $req) unless Net::SSLeay::X509_REQ_set_subject_name($req, $n);
1800             # Handle old openssl and set the version explicitly unless it is set already to greater than v1 (0 value).
1801             # NB: get_version will return 0 regardless of whether version is set to v1 or not set at all.
1802 6 50       21 unless (Net::SSLeay::X509_REQ_get_version($req)) {
1803 6 50       17 return _free(k => $pk, r => $req) unless Net::SSLeay::X509_REQ_set_version($req, 0);
1804             }
1805 6         24 my $md = Net::SSLeay::EVP_get_digestbyname('sha256');
1806 6 50 33     64327 return _free(k => $pk, r => $req) unless ($md and Net::SSLeay::X509_REQ_sign($req, $pk, $md));
1807 6         682 my @rv = (Net::SSLeay::PEM_get_string_X509_REQ($req), Net::SSLeay::PEM_get_string_PrivateKey($pk));
1808 6         28 _free(k => $pk, r => $req);
1809 6         25 return @rv;
1810             }
1811              
1812             sub _free {
1813 22     22   58 my %data = @_;
1814 22 100       84 Net::SSLeay::X509_REQ_free($data{r}) if $data{r};
1815 22 100       90 Net::SSLeay::BIO_free($data{b}) if $data{b};
1816 22 100       72 Net::SSLeay::EVP_PKEY_free($data{k}) if $data{k};
1817 22 100       62 return wantarray ? (undef, $data{'error'}) : undef;
1818             }
1819              
1820             sub _to_hex {
1821 6     6   53 my $val = shift;
1822 6         30 $val = $val->to_hex;
1823 6         11 $val =~s/^0x//;
1824 6 50       14 $val = "0$val" if length($val) % 2;
1825 6         55 return $val;
1826             }
1827              
1828             #====================================================================================================
1829             # Internal Service helpers
1830             #====================================================================================================
1831              
1832             sub _request {
1833 1     1   1 my $self = shift;
1834 1         3 my ($url, $payload, $opts) = @_;
1835 1 50       2 unless ($url) {
1836 0         0 my $rv = 'Resource directory does not contain expected fields.';
1837 0 0       0 return wantarray ? (INVALID_DATA, $rv) : $rv;
1838             }
1839 1         5 $self->_debug("Connecting to $url");
1840 1         3 $payload = $self->_translate($payload);
1841 1         2 my $resp;
1842 1   50     5 $opts ||= {};
1843 1   50     5 my $method = lc($opts->{method} || 'get');
1844 1 50 33     5 if (defined $payload or $method eq 'post') {
1845             $resp = defined $payload ? $self->{ua}->post($url, { headers => $headers, content => $self->_jws($payload, $url, $opts) }) :
1846 0 0       0 $self->{ua}->post($url, { headers => $headers });
1847             } else {
1848 1         23 $resp = $self->{ua}->$method($url);
1849             }
1850 1 50 33     312735 my $slurp = ($resp->{headers}->{'content-type'} and $resp->{headers}->{'content-type'}=~/^application\/(?:problem\+)?json/) ? 0 : 1;
1851 1 50       10 $self->_debug($slurp ? $resp->{headers} : $resp);
1852 1 50       5 $self->{nonce} = $resp->{headers}->{'replay-nonce'} if $resp->{headers}->{'replay-nonce'};
1853 1         3 my ($status, $rv) = ($resp->{status}, $resp->{content});
1854 1 50       4 unless ($slurp) {
1855 0         0 eval {
1856 0         0 $rv = $j->decode($rv);
1857             };
1858 0 0       0 if ($@) {
1859 0         0 ($status, $rv) = (ERROR, $@);
1860             }
1861             }
1862 1 50       4 $self->{links} = $resp->{headers}->{link} ? $self->_links($resp->{headers}->{link}) : undef;
1863 1 50       4 $self->{location} = $resp->{headers}->{location} ? $resp->{headers}->{location} : undef;
1864 1 50 33     5 if ($resp->{headers}->{'retry-after'} and $resp->{headers}->{'retry-after'}=~/^(\d+)$/) {
1865 0         0 $self->{retry} = $1; # Set retry based on the last request where it was present, do not reset.
1866             }
1867              
1868 1 50       7 return wantarray ? ($status, $rv) : $rv;
1869             }
1870              
1871             sub _await {
1872 0     0   0 my $self = shift;
1873 0         0 my ($url, $payload, $opts) = @_;
1874 0         0 my ($ready, $try, $status, $content) = (0, 0);
1875 0   0     0 $opts ||= {};
1876 0   0     0 my $expected_status = $opts->{status} || SUCCESS;
1877 0         0 ($status, $content) = $self->_request($url, $payload, $opts);
1878 0   0     0 while ($status == $expected_status and $content and $content->{status} and $content->{status}=~/^(?:pending|processing)$/) {
      0        
      0        
1879 0   0     0 select(undef, undef, undef, $self->{retry} || $self->{delay});
1880 0         0 ($status, $content) = $self->_request($url, $payload, $opts);
1881 0 0 0     0 last if ($self->{try} and (++$try == $self->{try}));
1882             }
1883 0         0 return ($status, $content);
1884             }
1885              
1886             sub _jwk {
1887 3     3   5 my $self = shift;
1888 3 50       8 return unless $self->{key_params};
1889             return {
1890             kty => "RSA",
1891             n => encode_base64url(pack("H*", _to_hex($self->{key_params}->{n}))),
1892 3         8 e => encode_base64url(pack("H*", _to_hex($self->{key_params}->{e}))),
1893             };
1894             }
1895              
1896             sub _jws {
1897 0     0   0 my $self = shift;
1898 0         0 my ($obj, $url, $opts) = @_;
1899 0 0       0 return unless (defined $obj);
1900 0 0       0 my $json = ref $obj ? encode_base64url($j->encode($obj)) : "";
1901 0         0 my $protected = { alg => "RS256", jwk => $self->{jwk}, nonce => $self->{nonce} };
1902 0   0     0 $opts ||= {};
1903 0 0 0     0 if ($url and $self->version() > 1) {
1904 0 0 0     0 if ($self->{directory}->{reg} and !$opts->{jwk}) {
1905 0         0 $protected->{kid} = $self->{directory}->{reg};
1906 0         0 delete $protected->{jwk};
1907             }
1908 0         0 $protected->{url} = $url;
1909             # EAB registration
1910 0 0 0     0 if ($opts->{kid} and $opts->{mac}) {
1911 0         0 my $eab_protected = { alg => "HS256", kid => $opts->{kid}, url => $url };
1912 0         0 my $eab_header = encode_base64url($j->encode($eab_protected));
1913 0         0 my $eab_payload = encode_base64url($j->encode($self->{jwk}));
1914 0         0 my $mac = decode_base64url($opts->{mac});
1915 0         0 my $eab_sig = encode_base64url(hmac_sha256("$eab_header.$eab_payload", $mac));
1916             $obj->{externalAccountBinding} = {
1917 0         0 protected => $eab_header,
1918             payload => $eab_payload,
1919             signature => $eab_sig,
1920             };
1921             }
1922             }
1923 0 0       0 $json = ref $obj ? encode_base64url($j->encode($obj)) : "";
1924 0         0 my $header = encode_base64url($j->encode($protected));
1925 0         0 my $sig = encode_base64url($self->{key}->sign("$header.$json"));
1926 0         0 my $jws = $j->encode({ protected => $header, payload => $json, signature => $sig });
1927 0         0 return $jws;
1928             }
1929              
1930             sub _links {
1931 0     0   0 my $self = shift;
1932 0         0 my ($links) = @_;
1933 0 0       0 return unless $links;
1934 0         0 my $rv;
1935 0 0       0 foreach my $link ((ref $links eq 'ARRAY') ? @{$links} : ($links)) {
  0         0  
1936 0 0 0     0 next unless ($link and $link=~/^<([^>]+)>;rel="([^"]+)"$/i);
1937 0 0       0 if ($2 eq 'alternate') {
1938             # We might have more than one alternate link.
1939 0         0 push @{$rv->{$2}}, $1;
  0         0  
1940             } else {
1941 0         0 $rv->{$2} = $1;
1942             }
1943             }
1944 0         0 return $rv;
1945             }
1946              
1947             sub _compat {
1948 0     0   0 my ($self, $content) = @_;
1949 0 0       0 return unless $content;
1950 0         0 foreach (keys %{$content}) {
  0         0  
1951 0 0       0 if (my $name = $compat->{$_}) {
1952 0         0 $content->{$name} = delete $content->{$_};
1953             }
1954             }
1955             }
1956              
1957             sub _compat_response {
1958 0     0   0 my ($self, $code) = @_;
1959 0 0       0 return ($self->version() == 2) ? SUCCESS : $code;
1960             }
1961              
1962             sub _translate {
1963 1     1   3 my ($self, $req) = @_;
1964 1 0 33     12 return $req if (!$req or $self->version() == 1 or !$req->{'resource'});
      33        
1965 0 0       0 return $req unless my $res = delete $req->{'resource'};
1966 0 0 0     0 if ($res eq 'new-reg' or $res eq 'reg') {
    0          
1967 0         0 delete $req->{'agreement'};
1968 0         0 $req->{'termsOfServiceAgreed'} = \1;
1969             } elsif ($res eq 'new-cert') {
1970 0         0 delete $req->{'csr'};
1971 0         0 push @{$req->{'identifiers'}}, { type => 'dns', value => $_ } for @{$self->{loaded_domains}};
  0         0  
  0         0  
1972             }
1973 0         0 return $req;
1974             }
1975              
1976             sub _callback_extras {
1977 0     0   0 my ($self, $data) = @_;
1978 0 0 0     0 return unless ($data and $data->{domain});
1979 0         0 $data->{domain}=~/^(\*\.)?(.+)$/;
1980 0         0 $data->{host} = $2;
1981 0         0 $data->{file} = $data->{token};
1982 0         0 $data->{text} = "$data->{token}.$data->{fingerprint}";
1983 0         0 $data->{record} = encode_base64url(sha256($data->{text}));
1984             }
1985              
1986             sub _debug {
1987 55     55   73 my $self = shift;
1988 55 50       118 return unless $self->{debug};
1989 0         0 foreach (@_) {
1990 0 0       0 if (!ref $_) {
    0          
1991 0 0       0 $self->{logger} ? $self->{logger}->debug($_) : print "$_\n";
1992             } elsif ($self->{debug} > 1) {
1993 0 0       0 $self->{logger} ? $self->{logger}->debug(Dumper($_)) : print Dumper($_);
1994             }
1995             }
1996             }
1997              
1998             sub _status {
1999 45     45   91 my $self = shift;
2000 45         77 my ($code, $data) = @_;
2001 45 100       79 if ($code == OK) {
2002 20         34 undef $self->{error};
2003             } else {
2004 25 50 33     59 if (ref $data eq 'HASH' and $data->{error}) {
2005 0         0 $self->{error} = $data->{error};
2006             } else {
2007 25   33     56 $self->{error} = $data||$code;
2008             }
2009             }
2010 45 50       115 $self->_debug($data) if $data;
2011 45         207 return $code;
2012             }
2013              
2014             sub _pull_error {
2015 2     2   3 my $self = shift;
2016 2         4 my ($err) = @_;
2017 2 50 33     14 if ($err and ref $err eq 'HASH') {
2018 0 0 0     0 return $err->{error}->{detail} if ($err->{error} and $err->{error}->{detail});
2019 0 0       0 return $err->{detail} if $err->{detail};
2020             }
2021 2         3 return '';
2022             }
2023              
2024             sub _get_authz {
2025 0     0   0 my $self = shift;
2026 0 0       0 return unless $self->{loaded_domains};
2027 0         0 $self->{authz} = [];
2028 0         0 foreach my $domain (@{$self->{loaded_domains}}) {
  0         0  
2029 0         0 push @{$self->{authz}}, [ $self->{directory}->{'new-authz'}, { resource => 'new-authz', identifier => { type => 'dns', value => $domain } } ];
  0         0  
2030             }
2031             }
2032              
2033             sub _file {
2034 18     18   20 my $self = shift;
2035 18         31 my ($file) = @_;
2036 18 50       35 return unless $file;
2037 18 100       35 unless (ref $file) {
2038 4         24 my ($fh, $content) = (new IO::File "<$file");
2039 4 50       265 if (defined $fh) {
2040 4         13 local $/;
2041 4         13 $fh->binmode;
2042 4         139 $content = <$fh>;
2043 4         23 $fh->close;
2044             }
2045 4         85 return $content;
2046             }
2047 14 50       38 return (ref $file eq 'SCALAR') ? $$file : undef;
2048             }
2049              
2050             sub _verify_crt {
2051 1     1   33 my $exp = shift;
2052             return sub {
2053 1 50 33 1   7 unless (defined $_[CRT_DEPTH] and $_[CRT_DEPTH]) {
2054 1         1 my ($t, $s);
2055 1         2 eval {
2056 1         20 $t = Net::SSLeay::X509_get_notAfter($_[PEER_CRT]);
2057 1         17 $t = Time::Piece->strptime(Net::SSLeay::P_ASN1_TIME_get_isotime($t), "%Y-%m-%dT%H:%M:%SZ");
2058             };
2059 1 50       95 unless ($@) {
2060 1         4 $s = $t - localtime;
2061 1         166 $s = int($s->days);
2062 1 50 33     44 $$exp = $s unless ($$exp and $s > $$exp);
2063             }
2064             }
2065 1         7 };
2066             }
2067              
2068             sub _convert {
2069 3     3   4 my $self = shift;
2070 3         6 my ($content, $type) = @_;
2071 3 100 66     120 return (!$content or $content=~/^\-+BEGIN/) ? $content : $self->der2pem($content, $type);
2072             }
2073              
2074             1;
2075              
2076             =head1 AUTHOR
2077              
2078             Alexander Yezhov, C<< <leader at cpan.org> >>
2079             Domain Knowledge Ltd.
2080             L<https://do-know.com/>
2081              
2082             =head1 BUGS
2083              
2084             Considering that this module has been written in a rather quick manner after I decided to give a go to Let's Encrypt certificates
2085             and found that CPAN seems to be lacking some easy ways to leverage LE API from Perl, expect some (hopefully minor) bugs.
2086             The initial goal was to make this work, make it easy to use and possibly remove the need to use openssl command line.
2087              
2088             Please report any bugs or feature requests to C<bug-crypt-le at rt.cpan.org>, or through
2089             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Crypt-LE>. I will be notified, and then you'll
2090             automatically be notified of progress on your bug as I make changes.
2091              
2092             =head1 SUPPORT
2093              
2094             You can find documentation for this module with the perldoc command.
2095              
2096             perldoc Crypt::LE
2097              
2098              
2099             You can also look for information at:
2100              
2101             =over 4
2102              
2103             =item * RT: CPAN's request tracker (report bugs here)
2104              
2105             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Crypt-LE>
2106              
2107             =item * AnnoCPAN: Annotated CPAN documentation
2108              
2109             L<http://annocpan.org/dist/Crypt-LE>
2110              
2111             =item * CPAN Ratings
2112              
2113             L<http://cpanratings.perl.org/d/Crypt-LE>
2114              
2115             =item * Search CPAN
2116              
2117             L<http://search.cpan.org/dist/Crypt-LE/>
2118              
2119             =item * Project homepage
2120              
2121             L<https://Do-Know.com/>
2122              
2123              
2124              
2125             =back
2126              
2127             =head1 LICENSE AND COPYRIGHT
2128              
2129             Copyright 2016-2023 Alexander Yezhov.
2130              
2131             This program is free software; you can redistribute it and/or modify it
2132             under the terms of the Artistic License (2.0). You may obtain a
2133             copy of the full license at:
2134              
2135             L<http://www.perlfoundation.org/artistic_license_2_0>
2136              
2137             Any use, modification, and distribution of the Standard or Modified
2138             Versions is governed by this Artistic License. By using, modifying or
2139             distributing the Package, you accept this license. Do not use, modify,
2140             or distribute the Package, if you do not accept this license.
2141              
2142             If your Modified Version has been derived from a Modified Version made
2143             by someone other than you, you are nevertheless required to ensure that
2144             your Modified Version complies with the requirements of this license.
2145              
2146             This license does not grant you the right to use any trademark, service
2147             mark, tradename, or logo of the Copyright Holder.
2148              
2149             This license includes the non-exclusive, worldwide, free-of-charge
2150             patent license to make, have made, use, offer to sell, sell, import and
2151             otherwise transfer the Package with respect to any patent claims
2152             licensable by the Copyright Holder that are necessarily infringed by the
2153             Package. If you institute patent litigation (including a cross-claim or
2154             counterclaim) against any party alleging that the Package constitutes
2155             direct or contributory patent infringement, then this Artistic License
2156             to you shall terminate on the date that such litigation is filed.
2157              
2158             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
2159             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
2160             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
2161             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
2162             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
2163             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
2164             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
2165             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2166              
2167              
2168             =cut
2169