File Coverage

lib/Crypt/Perl/ECDSA/KeyBase.pm
Criterion Covered Total %
statement 178 187 95.1
branch 14 20 70.0
condition 7 12 58.3
subroutine 50 51 98.0
pod 0 11 0.0
total 249 281 88.6


line stmt bran cond sub pod time code
1             package Crypt::Perl::ECDSA::KeyBase;
2              
3 7     7   2749 use strict;
  7         15  
  7         170  
4 7     7   31 use warnings;
  7         9  
  7         165  
5              
6 7     7   29 use Try::Tiny;
  7         9  
  7         384  
7              
8 7         33 use parent qw(
9             Crypt::Perl::KeyBase
10 7     7   37 );
  7         12  
11              
12 7     7   948 use Crypt::Format ();
  7         1051  
  7         96  
13              
14 7     7   1394 use Crypt::Perl::ASN1 ();
  7         18  
  7         120  
15 7     7   36 use Crypt::Perl::BigInt ();
  7         10  
  7         80  
16 7     7   337 use Crypt::Perl::Math ();
  7         41  
  7         94  
17 7     7   1032 use Crypt::Perl::ECDSA::EC::Curve ();
  7         13  
  7         89  
18 7     7   34 use Crypt::Perl::ECDSA::EC::DB ();
  7         21  
  7         80  
19 7     7   41 use Crypt::Perl::ECDSA::EC::Point ();
  7         13  
  7         78  
20 7     7   1629 use Crypt::Perl::ECDSA::ECParameters ();
  7         15  
  7         115  
21 7     7   1727 use Crypt::Perl::ECDSA::NIST ();
  7         14  
  7         105  
22 7     7   35 use Crypt::Perl::ECDSA::EncodedPoint ();
  7         11  
  7         91  
23 7     7   28 use Crypt::Perl::ECDSA::Utils ();
  7         12  
  7         78  
24 7     7   26 use Crypt::Perl::X ();
  7         10  
  7         126  
25              
26 7         507 use constant ASN1_SIGNATURE => q<
27             SEQUENCE {
28             r INTEGER,
29             s INTEGER
30             }
31 7     7   29 >;
  7         12  
32              
33 7         407 use constant ASN1_Params => Crypt::Perl::ECDSA::ECParameters::ASN1_ECParameters() . q<
34             EcpkParameters ::= CHOICE {
35             namedCurve OBJECT IDENTIFIER,
36             ecParameters ECParameters
37             }
38 7     7   40 >;
  7         12  
39              
40 7     7   39 use constant _JWK_THUMBPRINT_JSON_ORDER => qw( crv kty x y );
  7         9  
  7         361  
41              
42 7     7   36 use constant JWA_DIGEST_prime256v1 => 'sha256';
  7         10  
  7         341  
43 7     7   35 use constant JWA_DIGEST_secp384r1 => 'sha384';
  7         10  
  7         311  
44 7     7   36 use constant JWA_DIGEST_secp521r1 => 'sha512';
  7         12  
  7         382  
45              
46 7     7   40 use constant JWA_CURVE_ALG_prime256v1 => 'ES256';
  7         11  
  7         292  
47 7     7   37 use constant JWA_CURVE_ALG_secp384r1 => 'ES384';
  7         21  
  7         302  
48 7     7   35 use constant JWA_CURVE_ALG_secp521r1 => 'ES512';
  7         13  
  7         11376  
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 3450 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         365 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 248     248 0 8452742 my ($self, $msg, $sig) = @_;
76              
77 248         1553 my $struct = Crypt::Perl::ASN1->new()->prepare(ASN1_SIGNATURE)->decode($sig);
78              
79 248         3490330 return $self->_verify($msg, @{$struct}{ qw( r s ) });
  248         1672  
80             }
81              
82             #cf. RFC 7518, page 8
83             sub verify_jwa {
84 3     3 0 1858 my ($self, $msg, $sig) = @_;
85              
86 3         15 my $dgst_cr = $self->_get_jwk_digest_cr();
87              
88 3         11 my $half_len = (length $sig) / 2;
89              
90 3         9 my $r = substr($sig, 0, $half_len);
91 3         9 my $s = substr($sig, $half_len);
92              
93 3         19 $_ = Crypt::Perl::BigInt->from_bytes($_) for ($r, $s);
94              
95 3         4163 return $self->_verify($dgst_cr->($msg), $r, $s);
96             }
97              
98             sub to_der_with_curve_name {
99 58     58 0 853 my ($self, %params) = @_;
100              
101 58 50       372 if ($params{'seed'}) {
102 0         0 die Crypt::Perl::X::create('Generic', '“seed” is meaningless to a named-curve export.');
103             }
104              
105 58         526 return $self->_get_asn1_parts($self->_named_curve_parameters(), %params);
106             }
107              
108             sub to_der_with_explicit_curve {
109 444     444 0 110730 my ($self, @params) = @_;
110              
111 444         5417 return $self->_get_asn1_parts($self->_explicit_curve_parameters(@params), @params);
112             }
113              
114             sub to_pem_with_curve_name {
115 42     42 0 64799 my ($self, @params) = @_;
116              
117 42         355 my $der = $self->to_der_with_curve_name(@params);
118              
119 42         15228 return Crypt::Format::der2pem($der, $self->_PEM_HEADER());
120             }
121              
122             sub to_pem_with_explicit_curve {
123 434     434 0 1877301 my ($self, @params) = @_;
124              
125 434         5964 my $der = $self->to_der_with_explicit_curve(@params);
126              
127 434         16141870 return Crypt::Format::der2pem($der, $self->_PEM_HEADER());
128             }
129              
130             sub max_sign_bits {
131 297     297 0 1030 my ($self) = @_;
132              
133 297         2114 return $self->_get_curve_obj()->keylen();
134             }
135              
136             sub get_curve_name {
137 69     69 0 226 my ($self) = @_;
138              
139 69         330 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 34 my ($self) = @_;
144              
145 5         48 my ($xb, $yb) = Crypt::Perl::ECDSA::Utils::split_G_or_public( $self->_decompress_public_point() );
146              
147 5         34 require MIME::Base64;
148              
149             return {
150 5         28 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 574     574   2002 my ($self, $pub_in) = @_;
175              
176 574         8779 $self->{'_public'} = Crypt::Perl::ECDSA::EncodedPoint->new($pub_in);
177              
178 574         1570 return;
179             }
180              
181             sub _compress_public_point {
182 7     7   13 my ($self) = @_;
183              
184 7         27 return $self->{'_public'}->get_compressed();
185             }
186              
187             sub _decompress_public_point {
188 764     764   3163 my ($self) = @_;
189              
190 764         3604 return $self->{'_public'}->get_uncompressed( $self->_curve() );
191             }
192              
193             sub _get_jwk_digest_cr {
194 6     6   15 my ($self) = @_;
195              
196 6         31 my $name = $self->get_curve_name();
197              
198 6 50       59 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 6         29 require Digest::SHA;
204              
205 6         60 return Digest::SHA->can( $getter_cr->() );
206             }
207              
208             sub _get_jwk_curve_name {
209 5     5   12 my ($self) = @_;
210              
211 5         15 my $name = $self->get_curve_name();
212              
213 5         24 return Crypt::Perl::ECDSA::NIST::get_nist_for_curve_name($name);
214             }
215              
216             sub _verify {
217 251     251   912 my ($self, $msg, $r, $s) = @_;
218              
219 251 50 33     1536 if ($r->is_positive() && $s->is_positive()) {
220 251         13271 my ($x, $y) = Crypt::Perl::ECDSA::Utils::split_G_or_public( $self->_decompress_public_point() );
221 251         3014 $_ = Crypt::Perl::BigInt->from_bytes($_) for ($x, $y);
222              
223 251         222216 my $curve = $self->_get_curve_obj();
224              
225 251         2037 my $Q = Crypt::Perl::ECDSA::EC::Point->new(
226             $curve,
227             $curve->from_bigint($x),
228             $curve->from_bigint($y),
229             );
230              
231 251         952 my $e = Crypt::Perl::BigInt->from_bytes($msg);
232              
233             #----------------------------------------------------------------------
234              
235 251         114661 my $n = $self->_curve()->{'n'};
236              
237 251 50 33     905 if ($r->blt($n) && $s->blt($n)) {
238 251         19103 my $c = $s->copy()->bmodinv($n);
239              
240 251         6135703 my $u1 = $e->copy()->bmul($c)->bmod($n);
241 251         174729 my $u2 = $r->copy()->bmul($c)->bmod($n);
242              
243 251         237279 my $point = $self->_G()->multiply($u1)->add( $Q->multiply($u2) );
244              
245 251         4281 my $v = $point->get_x()->to_bigint()->copy()->bmod($n);
246              
247 251 100       35580 return 1 if $v->beq($r);
248             }
249             }
250              
251 8         631 return 0;
252             }
253              
254             #return isa EC::Point
255             sub _G {
256 496     496   1924 my ($self) = @_;
257 496         1799 return $self->_get_curve_obj()->decode_point( @{$self->_curve()}{ qw( gx gy ) } );
  496         1520  
258             }
259              
260             sub _pad_bytes_for_asn1 {
261 914     914   2658 my ($self, $bytes) = @_;
262              
263 914         2637 return Crypt::Perl::ECDSA::Utils::pad_bytes_for_asn1( $bytes, $self->_curve()->{'p'} );
264             }
265              
266             sub _named_curve_parameters {
267 58     58   187 my ($self) = @_;
268              
269 58         288 my $curve_name = $self->get_curve_name();
270              
271             return {
272 58         310 namedCurve => Crypt::Perl::ECDSA::EC::DB::get_oid_for_curve_name($curve_name),
273             };
274             }
275              
276             #The idea is to emulate what Convert::ASN1 gives us as the parse.
277             sub _explicit_curve_parameters {
278 457     457   2206 my ($self, %params) = @_;
279              
280 457         4026 my $curve_hr = $self->_curve();
281              
282 457         2217 my ($gx, $gy) = map { $_->as_bytes() } @{$curve_hr}{'gx', 'gy'};
  914         7049  
  457         3590  
283              
284 457         4586 for my $str ( $gx, $gy ) {
285 914         6036 $str = $self->_pad_bytes_for_asn1($str);
286             }
287              
288             my %curve = (
289             a => $curve_hr->{'a'}->as_bytes(),
290 457         2775 b => $curve_hr->{'b'}->as_bytes(),
291             );
292              
293 457 100 100     2814 if ($params{'seed'} && $curve_hr->{'seed'}) {
294              
295             #We don’t care about the bit count. (Right?)
296 13         596 $curve{'seed'} = $curve_hr->{'seed'}->as_bytes();
297             }
298              
299 457         3352 my $base = "\x{04}$gx$gy";
300 457 100       1755 if ( $params{'compressed'} ) {
301 4         17 $base = Crypt::Perl::ECDSA::Utils::compress_point($base);
302             }
303              
304             return {
305             ecParameters => {
306             version => 1,
307             fieldID => {
308             fieldType => Crypt::Perl::ECDSA::ECParameters::OID_prime_field(),
309             parameters => {
310             'prime-field' => $curve_hr->{'p'},
311             },
312             },
313             curve => \%curve,
314             base => $base,
315             order => $curve_hr->{'n'},
316 457         20827 cofactor => $curve_hr->{'h'},
317             },
318             };
319             }
320              
321             sub __to_der {
322 502     502   4937 my ($self, $macro, $template, $data_hr, %params) = @_;
323              
324 502 100       4167 my $pub_bin = $params{'compressed'} ? $self->_compress_public_point() : $self->_decompress_public_point();
325              
326 502         3160 local $data_hr->{'publicKey'} = $pub_bin;
327              
328 502         6569 require Crypt::Perl::ASN1;
329 502         8835 my $asn1 = Crypt::Perl::ASN1->new()->prepare($template);
330              
331 502         3716 return $asn1->find($macro)->encode( $data_hr );
332             }
333              
334             #return isa EC::Curve
335             sub _get_curve_obj {
336 1044     1044   2801 my ($self) = @_;
337              
338 1044   66     6934 return $self->{'_curve_obj'} ||= Crypt::Perl::ECDSA::EC::Curve->new( @{$self->_curve()}{ qw( p a b ) } );
  296         925  
339             }
340              
341             sub _add_params {
342 574     574   1794 my ($self, $params_struct) = @_;
343              
344 574 100       2208 if (my $params = $params_struct->{'ecParameters'}) {
345              
346             #Convert::ASN1 returns bit strings as an array of [ $content, $length ].
347             #Since normalize() wants that, we local() it here.
348             #local $params->{'curve'}{'seed'} = [$params->{'curve'}{'seed'}] if $params->{'curve'} && $params->{'curve'}{'seed'};
349              
350 258         2004 $self->{'curve'} = Crypt::Perl::ECDSA::ECParameters::normalize($params);
351             }
352             else {
353 316         1694 $self->{'curve'} = $self->_curve_params_for_OID($params_struct->{'namedCurve'});
354             }
355              
356 334         5273 return $self;
357             }
358              
359             sub _curve_params_for_OID {
360 316     316   1432 my ($self, $oid) = @_;
361              
362 316         2852 return Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_oid($oid);
363             }
364              
365             sub _curve {
366 3534     3534   8345 my ($self) = @_;
367              
368 3534         31633 return $self->{'curve'};
369             }
370              
371             #----------------------------------------------------------------------
372              
373             1;