File Coverage

lib/Crypt/Perl/ECDSA/KeyBase.pm
Criterion Covered Total %
statement 180 189 95.2
branch 15 20 75.0
condition 9 12 75.0
subroutine 51 52 98.0
pod 0 11 0.0
total 255 284 89.7


line stmt bran cond sub pod time code
1             package Crypt::Perl::ECDSA::KeyBase;
2              
3 8     8   8113 use strict;
  8         29  
  8         238  
4 8     8   40 use warnings;
  8         28  
  8         183  
5              
6 8     8   41 use Try::Tiny;
  8         18  
  8         448  
7              
8 8         67 use parent qw(
9             Crypt::Perl::KeyBase
10 8     8   57 );
  8         18  
11              
12 8     8   1791 use Crypt::Format ();
  8         2049  
  8         154  
13              
14 8     8   2128 use Crypt::Perl::ASN1 ();
  8         20  
  8         167  
15 8     8   43 use Crypt::Perl::BigInt ();
  8         17  
  8         128  
16 8     8   455 use Crypt::Perl::Math ();
  8         23  
  8         113  
17 8     8   1425 use Crypt::Perl::ECDSA::EC::Curve ();
  8         20  
  8         117  
18 8     8   44 use Crypt::Perl::ECDSA::EC::DB ();
  8         18  
  8         105  
19 8     8   37 use Crypt::Perl::ECDSA::EC::Point ();
  8         14  
  8         108  
20 8     8   2510 use Crypt::Perl::ECDSA::ECParameters ();
  8         18  
  8         139  
21 8     8   2690 use Crypt::Perl::ECDSA::NIST ();
  8         16  
  8         160  
22 8     8   51 use Crypt::Perl::ECDSA::EncodedPoint ();
  8         16  
  8         158  
23 8     8   41 use Crypt::Perl::ECDSA::Utils ();
  8         13  
  8         113  
24 8     8   50 use Crypt::Perl::X ();
  8         13  
  8         194  
25              
26 8         582 use constant ASN1_SIGNATURE => q<
27             SEQUENCE {
28             r INTEGER,
29             s INTEGER
30             }
31 8     8   39 >;
  8         15  
32              
33 8         571 use constant ASN1_Params => Crypt::Perl::ECDSA::ECParameters::ASN1_ECParameters() . q<
34             EcpkParameters ::= CHOICE {
35             namedCurve OBJECT IDENTIFIER,
36             ecParameters ECParameters
37             }
38 8     8   52 >;
  8         17  
39              
40 8     8   56 use constant _JWK_THUMBPRINT_JSON_ORDER => qw( crv kty x y );
  8         22  
  8         539  
41              
42 8     8   56 use constant JWA_DIGEST_prime256v1 => 'sha256';
  8         16  
  8         431  
43 8     8   48 use constant JWA_DIGEST_secp384r1 => 'sha384';
  8         15  
  8         464  
44 8     8   55 use constant JWA_DIGEST_secp521r1 => 'sha512';
  8         16  
  8         507  
45              
46 8     8   51 use constant JWA_CURVE_ALG_prime256v1 => 'ES256';
  8         15  
  8         435  
47 8     8   51 use constant JWA_CURVE_ALG_secp384r1 => 'ES384';
  8         16  
  8         527  
48 8     8   50 use constant JWA_CURVE_ALG_secp521r1 => 'ES512';
  8         17  
  8         17106  
49              
50             #Expects $key_parts to be a hash ref:
51             #
52             # version - AFAICT unused
53             # private - BigInt or its byte-string representation
54             # public - ^^
55             #
56             sub new_by_curve_name {
57 55     55 0 705 my ($class, $key_parts, $curve_name) = @_;
58              
59             #We could store the curve name on here if looking it up
60             #in to_der_with_curve_name() proves prohibitive.
61 55         458 return $class->new(
62             $key_parts,
63              
64             #“Fake out” the $curve_parts attribute by recreating
65             #the structure that ASN.1 would give from a named curve.
66             {
67             namedCurve => Crypt::Perl::ECDSA::EC::DB::get_oid_for_curve_name($curve_name),
68             },
69             );
70             }
71              
72             #$msg has to be small enough that the key could have signed it.
73             #It’s probably a digest rather than the original message.
74             sub verify {
75 464     464 0 16035971 my ($self, $msg, $sig) = @_;
76              
77 464         10639 my $struct = Crypt::Perl::ASN1->new()->prepare(ASN1_SIGNATURE)->decode($sig);
78              
79 464         7756682 return $self->_verify($msg, @{$struct}{ qw( r s ) });
  464         5902  
80             }
81              
82             #cf. RFC 7518, page 8
83             sub verify_jwa {
84 3     3 0 3696 my ($self, $msg, $sig) = @_;
85              
86 3         22 my $dgst_cr = $self->_get_jwk_digest_cr();
87              
88 3         11 my $half_len = (length $sig) / 2;
89              
90 3         10 my $r = substr($sig, 0, $half_len);
91 3         31 my $s = substr($sig, $half_len);
92              
93 3         25 $_ = Crypt::Perl::BigInt->from_bytes($_) for ($r, $s);
94              
95 3         853 return $self->_verify($dgst_cr->($msg), $r, $s);
96             }
97              
98             sub to_der_with_curve_name {
99 29     29 0 1092 my ($self, %params) = @_;
100              
101 29 50       210 if ($params{'seed'}) {
102 0         0 die Crypt::Perl::X::create('Generic', '“seed” is meaningless to a named-curve export.');
103             }
104              
105 29         292 return $self->_get_asn1_parts($self->_named_curve_parameters(), %params);
106             }
107              
108             sub to_der_with_explicit_curve {
109 414     414 0 127517 my ($self, @params) = @_;
110              
111 414         3120 return $self->_get_asn1_parts($self->_explicit_curve_parameters(@params), @params);
112             }
113              
114             sub to_pem_with_curve_name {
115 13     13 0 102095 my ($self, @params) = @_;
116              
117 13         320 my $der = $self->to_der_with_curve_name(@params);
118              
119 13         5745 return Crypt::Format::der2pem($der, $self->_PEM_HEADER());
120             }
121              
122             sub to_pem_with_explicit_curve {
123 404     404 0 22224 my ($self, @params) = @_;
124              
125 404         5774 my $der = $self->to_der_with_explicit_curve(@params);
126              
127 404         16386843 return Crypt::Format::der2pem($der, $self->_PEM_HEADER());
128             }
129              
130             sub max_sign_bits {
131 506     506 0 1470 my ($self) = @_;
132              
133 506         3765 return $self->_get_curve_obj()->keylen();
134             }
135              
136             sub get_curve_name {
137 43     43 0 196 my ($self) = @_;
138              
139 43         260 return Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data( $self->_curve() );
140             }
141              
142             sub get_struct_for_public_jwk {
143 5     5 0 41 my ($self) = @_;
144              
145 5         21 my ($xb, $yb) = Crypt::Perl::ECDSA::Utils::split_G_or_public( $self->_decompress_public_point() );
146              
147 5         32 require MIME::Base64;
148              
149             return {
150 5         38 kty => 'EC',
151             crv => $self->_get_jwk_curve_name(),
152             x => MIME::Base64::encode_base64url($xb),
153             y => MIME::Base64::encode_base64url($yb),
154             }
155             }
156              
157             sub get_jwa_alg {
158 0     0 0 0 my ($self) = @_;
159              
160 0         0 my $name = $self->get_curve_name();
161              
162 0 0       0 my $getter_cr = __PACKAGE__->can("JWA_CURVE_ALG_$name") or do {
163 0         0 my $err = sprintf( "“%s” knows of no JWA “alg” for the curve “%s”!", ref($self), $name);
164              
165 0         0 die Crypt::Perl::X::create('Generic', $err);
166             };
167              
168 0         0 return $getter_cr->();
169             }
170              
171             #----------------------------------------------------------------------
172              
173             sub _set_public {
174 587     587   2207 my ($self, $pub_in) = @_;
175              
176 587         9825 $self->{'_public'} = Crypt::Perl::ECDSA::EncodedPoint->new($pub_in);
177              
178 587         1822 return;
179             }
180              
181             sub _compress_public_point {
182 7     7   26 my ($self) = @_;
183              
184 7         48 return $self->{'_public'}->get_compressed();
185             }
186              
187             sub _decompress_public_point {
188 921     921   3809 my ($self) = @_;
189              
190 921         6973 return $self->{'_public'}->get_uncompressed( $self->_curve() );
191             }
192              
193             sub _get_jwk_digest_name {
194 9     9   31 my ($self) = @_;
195              
196 9         35 my $name = $self->get_curve_name();
197              
198 9 50       99 my $getter_cr = $self->can("JWA_DIGEST_$name") or do {
199 0         0 my $err = sprintf( "“%s” knows of no digest to use for JWA with the curve “%s”!", ref($self), $name);
200 0         0 die Crypt::Perl::X::create('Generic', $err);
201             };
202              
203 9         70 return $getter_cr->();
204             }
205              
206             sub _get_jwk_digest_cr {
207              
208 3     3   37 require Digest::SHA;
209 3         67 return Digest::SHA->can( $_[0]->_get_jwk_digest_name() );
210             }
211              
212             sub _get_jwk_curve_name {
213 5     5   13 my ($self) = @_;
214              
215 5         16 my $name = $self->get_curve_name();
216              
217 5         29 return Crypt::Perl::ECDSA::NIST::get_nist_for_curve_name($name);
218             }
219              
220             sub _verify {
221 467     467   4408 my ($self, $msg, $r, $s) = @_;
222              
223 467         3594 $_ = Crypt::Perl::BigInt->new($_) for ($r, $s);
224              
225 467 50 33     62135 if ($r->is_positive() && $s->is_positive()) {
226 467         40464 my ($x, $y) = Crypt::Perl::ECDSA::Utils::split_G_or_public( $self->_decompress_public_point() );
227 467         8505 $_ = Crypt::Perl::BigInt->from_bytes($_) for ($x, $y);
228              
229 467         102322 my $curve = $self->_get_curve_obj();
230              
231 467         6562 my $Q = Crypt::Perl::ECDSA::EC::Point->new(
232             $curve,
233             $curve->from_bigint($x),
234             $curve->from_bigint($y),
235             );
236              
237 467         3078 my $e = Crypt::Perl::BigInt->from_bytes($msg);
238              
239             #----------------------------------------------------------------------
240              
241 467         93834 my $n = $self->_curve()->{'n'};
242              
243 467 100 100     5174 if ($r->blt($n) && $s->blt($n)) {
244 465         40757 my $c = $s->copy()->bmodinv($n);
245              
246 465         134608 my $u1 = $e->copy()->bmul($c)->bmod($n);
247 465         74247 my $u2 = $r->copy()->bmul($c)->bmod($n);
248              
249 465         72775 my $point = $self->_G()->multiply($u1)->add( $Q->multiply($u2) );
250              
251 465         17918 my $v = $point->get_x()->to_bigint()->copy()->bmod($n);
252              
253 465 100       57646 return 1 if $v->beq($r);
254             }
255             }
256              
257 10         906 return 0;
258             }
259              
260             #return isa EC::Point
261             sub _G {
262 916     916   4699 my ($self) = @_;
263 916         3763 return $self->_get_curve_obj()->decode_point( @{$self->_curve()}{ qw( gx gy ) } );
  916         3465  
264             }
265              
266             sub _pad_bytes_for_asn1 {
267 854     854   3257 my ($self, $bytes) = @_;
268              
269 854         4189 return Crypt::Perl::ECDSA::Utils::pad_bytes_for_asn1( $bytes, $self->_curve()->{'p'} );
270             }
271              
272             sub _named_curve_parameters {
273 29     29   122 my ($self) = @_;
274              
275 29         229 my $curve_name = $self->get_curve_name();
276              
277             return {
278 29         199 namedCurve => Crypt::Perl::ECDSA::EC::DB::get_oid_for_curve_name($curve_name),
279             };
280             }
281              
282             #The idea is to emulate what Convert::ASN1 gives us as the parse.
283             sub _explicit_curve_parameters {
284 427     427   1643 my ($self, %params) = @_;
285              
286 427         3816 my $curve_hr = $self->_curve();
287              
288 427         1212 my ($gx, $gy) = map { $_->as_bytes() } @{$curve_hr}{'gx', 'gy'};
  854         4650  
  427         1749  
289              
290 427         5149 for my $str ( $gx, $gy ) {
291 854         4467 $str = $self->_pad_bytes_for_asn1($str);
292             }
293              
294             my %curve = (
295             a => $curve_hr->{'a'}->as_bytes(),
296 427         4979 b => $curve_hr->{'b'}->as_bytes(),
297             );
298              
299 427 100 100     3762 if ($params{'seed'} && $curve_hr->{'seed'}) {
300              
301             #We don’t care about the bit count. (Right?)
302 13         617 $curve{'seed'} = $curve_hr->{'seed'}->as_bytes();
303             }
304              
305 427         2861 my $base = "\x{04}$gx$gy";
306 427 100       1818 if ( $params{'compressed'} ) {
307 4         30 $base = Crypt::Perl::ECDSA::Utils::compress_point($base);
308             }
309              
310             return {
311             ecParameters => {
312             version => 1,
313             fieldID => {
314             fieldType => Crypt::Perl::ECDSA::ECParameters::OID_prime_field(),
315             parameters => {
316             'prime-field' => $curve_hr->{'p'},
317             },
318             },
319             curve => \%curve,
320             base => $base,
321             order => $curve_hr->{'n'},
322 427         21844 cofactor => $curve_hr->{'h'},
323             },
324             };
325             }
326              
327             sub __to_der {
328 443     443   4496 my ($self, $macro, $template, $data_hr, %params) = @_;
329              
330 443 100       4139 my $pub_bin = $params{'compressed'} ? $self->_compress_public_point() : $self->_decompress_public_point();
331              
332 443         3258 local $data_hr->{'publicKey'} = $pub_bin;
333              
334 443         8385 require Crypt::Perl::ASN1;
335 443         4227 my $asn1 = Crypt::Perl::ASN1->new()->prepare($template);
336              
337 443         3752 return $asn1->find($macro)->encode( $data_hr );
338             }
339              
340             #return isa EC::Curve
341             sub _get_curve_obj {
342 1889     1889   6586 my ($self) = @_;
343              
344 1889   66     12890 return $self->{'_curve_obj'} ||= Crypt::Perl::ECDSA::EC::Curve->new( @{$self->_curve()}{ qw( p a b ) } );
  309         1203  
345             }
346              
347             sub _add_params {
348 587     587   2259 my ($self, $params_struct) = @_;
349              
350 587 100       3055 if (my $params = $params_struct->{'ecParameters'}) {
351              
352             #Convert::ASN1 returns bit strings as an array of [ $content, $length ].
353             #Since normalize() wants that, we local() it here.
354             #local $params->{'curve'}{'seed'} = [$params->{'curve'}{'seed'}] if $params->{'curve'} && $params->{'curve'}{'seed'};
355              
356 258         2272 $self->{'curve'} = Crypt::Perl::ECDSA::ECParameters::normalize($params);
357             }
358             else {
359 329         1472 $self->{'curve'} = $self->_curve_params_for_OID($params_struct->{'namedCurve'});
360             }
361              
362 347         5837 return $self;
363             }
364              
365             sub _curve_params_for_OID {
366 329     329   1540 my ($self, $oid) = @_;
367              
368 329         3759 return Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_oid($oid);
369             }
370              
371             sub _curve {
372 4430     4430   10564 my ($self) = @_;
373              
374 4430         38867 return $self->{'curve'};
375             }
376              
377             #----------------------------------------------------------------------
378              
379             1;