File Coverage

blib/lib/Net/ACME/Crypt.pm
Criterion Covered Total %
statement 62 64 96.8
branch 7 12 58.3
condition n/a
subroutine 17 17 100.0
pod 0 2 0.0
total 86 95 90.5


line stmt bran cond sub pod time code
1             package Net::ACME::Crypt;
2              
3             #----------------------------------------------------------------------
4             # This module exists because of a desire to do these computations
5             # in environments where a compiler may not be available.
6             # (Otherwise, CryptX would be ideal.)
7             #----------------------------------------------------------------------
8              
9 14     14   254429 use strict;
  14         34  
  14         419  
10 14     14   69 use warnings;
  14         26  
  14         337  
11              
12 14     14   3996 use JSON ();
  14         49476  
  14         271  
13 14     14   2777 use MIME::Base64 ();
  14         3895  
  14         287  
14              
15 14     14   4911 use Crypt::Perl::PK ();
  14         30310  
  14         272  
16              
17 14     14   5311 use Net::ACME::X ();
  14         40  
  14         326  
18              
19             #As per the ACME spec
20 14     14   86 use constant JWK_THUMBPRINT_DIGEST => 'sha256';
  14         27  
  14         1026  
21              
22 14     14   79 use constant JWT_RSA_SIG => 'RS256';
  14         44  
  14         10015  
23              
24             *parse_key = \&Crypt::Perl::PK::parse_key;
25              
26             sub get_jwk_thumbprint {
27 5     5 0 18667844 my ($jwk_hr) = @_;
28              
29             #We could generate the thumbprint directly from the JWK,
30             #but there’d be more code to maintain. For now the speed hit
31             #seems acceptable … ?
32              
33 5         28 my $key_obj = Crypt::Perl::PK::parse_jwk($jwk_hr);
34              
35 5         103375 return $key_obj->get_jwk_thumbprint(JWK_THUMBPRINT_DIGEST());
36             }
37              
38             *_encode_b64u = \&MIME::Base64::encode_base64url;
39              
40             #expects:
41             # key - object
42             # payload
43             # extra_headers (optional, hashref)
44             sub create_jwt {
45 10     10 0 407656732 my (%args) = @_;
46              
47 10 100       159 if ($args{'key'}->isa('Crypt::Perl::RSA::PrivateKey')) {
    50          
48 9         59 return _create_rs256_jwt(%args);
49             }
50             elsif ($args{'key'}->isa('Crypt::Perl::ECDSA::PrivateKey')) {
51 1         7 return _create_ecc_jwt(%args);
52             }
53              
54 0         0 die "Unrecognized “key”: “$args{'key'}”";
55             }
56              
57             #----------------------------------------------------------------------
58              
59             #Based on Crypt::JWT::encode_jwt(), but focused on this particular
60             #protocol’s needs. Note that UTF-8 might get mangled in here,
61             #but that’s not a problem since ACME shouldn’t require sending raw UTF-8.
62             #(Maybe with registration??)
63             sub _create_rs256_jwt {
64 9     9   35 my ( %args ) = @_;
65              
66 9         22 my $alg = JWT_RSA_SIG();
67              
68 9         24 my $key = $args{'key'};
69              
70             my $signer_cr = sub {
71 9     9   103 return $key->can("sign_$alg")->($key, @_);
72 9         74 };
73              
74 9         47 return _create_jwt(
75             %args,
76             alg => $alg,
77             signer_cr => $signer_cr,
78             );
79             }
80              
81             sub _create_ecc_jwt {
82 1     1   4 my (%args) = @_;
83              
84 1         4 my $key = $args{'key'};
85              
86             my $signer_cr = sub {
87 1     1   7 return $key->sign_jwa(@_);
88 1         5 };
89              
90 1         9 return _create_jwt(
91             %args,
92             alg => $key->get_jwa_alg(),
93             signer_cr => $signer_cr,
94             );
95             }
96              
97             sub _create_jwt {
98 10     10   13645 my ( %args ) = @_;
99              
100             # key
101 10 50       50 die "JWS: missing 'key'" if !$args{key};
102              
103 10         29 my $payload = $args{payload};
104 10         26 my $alg = $args{'alg'};
105              
106 10 50       48 my $header = $args{extra_headers} ? { %{$args{extra_headers}} } : {};
  10         45  
107              
108             # serialize payload
109 10         64 $payload = _payload_enc($payload);
110              
111             # encode payload
112 10         45 my $b64u_payload = _encode_b64u($payload);
113              
114             # prepare header
115 10         175 $header->{alg} = $alg;
116              
117             # encode header
118 10         43 my $json_header = _encode_json($header);
119 10         40 my $b64u_header = _encode_b64u($json_header);
120              
121 10         147 my $signer_cr = $args{'signer_cr'};
122              
123 10         75 my $b64u_signature = _encode_b64u( $signer_cr->("$b64u_header.$b64u_payload", $args{key}) );
124              
125 10         92733595 return join('.', $b64u_header, $b64u_payload, $b64u_signature);
126             }
127              
128             sub _encode_json {
129 10     10   32 my ($payload) = @_;
130              
131             #Always do a canonical encode so that we can test more easily.
132             #Note that JWS itself does NOT require this.
133 10         266 return JSON->new()->canonical(1)->encode($payload);
134             }
135              
136             #Taken from Crypt::JWT
137             sub _payload_enc {
138 10     10   36 my ($payload) = @_;
139              
140 10 50       50 if (ref($payload) =~ /^(?:HASH|ARRAY)$/) {
141 0         0 $payload = _encode_json($payload);
142             }
143             else {
144 10 50       65 utf8::downgrade($payload, 1) or die "JWT: payload cannot contain wide character";
145             }
146              
147 10         30 return $payload;
148             }
149              
150             1;