File Coverage

lib/Crypt/Perl/ECDSA/PrivateKey.pm
Criterion Covered Total %
statement 114 116 98.2
branch 6 8 75.0
condition n/a
subroutine 29 29 100.0
pod 0 10 0.0
total 149 163 91.4


line stmt bran cond sub pod time code
1             package Crypt::Perl::ECDSA::PrivateKey;
2              
3             =encoding utf-8
4              
5             =head1 NAME
6              
7             Crypt::Perl::ECDSA::PrivateKey - object representation of ECDSA private key
8              
9             =head1 SYNOPSIS
10              
11             #Use Generate.pm or Parse.pm rather
12             #than instantiating this class directly.
13              
14             #This works even if the object came from a key file that doesn’t
15             #contain the curve name.
16             $prkey->get_curve_name();
17              
18             if ($payload > ($prkey->max_sign_bits() / 8)) {
19             die "Payload too long!";
20             }
21              
22             #$payload is probably a hash (e.g., SHA-256) of your original message.
23             my $sig = $prkey->sign($payload);
24              
25             #For JSON Web Algorithms (JWT et al.), cf. RFC 7518 page 8
26             #This will also apply the appropriate SHA algorithm before signing.
27             my $sig_jwa = $prkey->sign_jwa($payload);
28              
29             $prkey->verify($payload, $sig) or die "Invalid signature!";
30             $prkey->verify_jwa($payload, $sig_jwa) or die "Invalid signature!";
31              
32             #Corresponding “der” methods exist as well.
33             my $cn_pem = $prkey->to_pem_with_curve_name();
34             my $expc_pem = $prkey->to_pem_with_explicit_curve();
35              
36             #----------------------------------------------------------------------
37              
38             my $pbkey = $prkey->get_public_key();
39              
40             #----------------------------------------------------------------------
41              
42             #Includes “kty”, “crv”, “x”, “y”, and (for private) “d”.
43             #Add in whatever else your application needs afterward.
44             #
45             #These will die() if you try to run it with a curve that
46             #doesn’t have a known JWK “crv” value.
47             #
48             my $prv_jwk = $prkey->get_struct_for_private_jwk();
49             my $pub_jwk = $prkey->get_struct_for_public_jwk();
50              
51             #Useful for JWTs
52             my $jwt_alg = $pbkey->get_jwa_alg();
53              
54             =head1 DISCUSSION
55              
56             The SYNOPSIS above should be illustration enough of how to use this class.
57              
58             =head1 SECURITY
59              
60             The security advantages of elliptic-curve cryptography (ECC) are a matter of
61             some controversy. While the math itself is apparently bulletproof, there are
62             varying opinions about the integrity of the various curves that are recommended
63             for ECC. Some believe that some curves contain “backdoors” that would allow
64             L to sniff a transmission.
65              
66             That said, RSA will eventually no longer be viable: as the keys get bigger, the
67             security advantage of increasing their size diminishes.
68              
69             =head1 TODO
70              
71             This minimal set of functionality can be augmented as feature requests come in.
72             Patches are welcome—particularly with tests!
73              
74             =cut
75              
76 8     8   58 use strict;
  8         24  
  8         253  
77 8     8   56 use warnings;
  8         32  
  8         243  
78              
79 8     8   47 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  8         24  
  8         97  
80              
81 8     8   452 use Try::Tiny;
  8         22  
  8         430  
82              
83 8     8   55 use Bytes::Random::Secure::Tiny ();
  8         21  
  8         116  
84              
85 8     8   40 use Crypt::Perl::ASN1 ();
  8         17  
  8         132  
86 8     8   36 use Crypt::Perl::BigInt ();
  8         27  
  8         229  
87 8     8   42 use Crypt::Perl::Math ();
  8         20  
  8         159  
88 8     8   2299 use Crypt::Perl::ToDER ();
  8         54  
  8         162  
89 8     8   54 use Crypt::Perl::X ();
  8         72  
  8         281  
90              
91             #This is not the standard ASN.1 template as found in RFC 5915,
92             #but it seems to generate equivalent results.
93             #
94 8         742 use constant ASN1_PRIVATE => Crypt::Perl::ECDSA::KeyBase->ASN1_Params() . q<
95              
96             ECPrivateKey ::= SEQUENCE {
97             version INTEGER,
98             privateKey OCTET STRING,
99             parameters [0] EXPLICIT EcpkParameters OPTIONAL,
100             publicKey [1] EXPLICIT BIT STRING
101             }
102 8     8   43 >;
  8         18  
103              
104 8     8   63 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  8         16  
  8         397  
105              
106 8     8   48 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  8         16  
  8         11011  
107              
108             #$curve_parts is also a hash ref, defined as whatever the ASN.1
109             #parse of the main key’s “parameters” returned, whether that be
110             #explicit key parameters or a named curve.
111             #
112             sub new {
113 546     546 0 2399 my ($class, $key_parts, $curve_parts) = @_;
114              
115 546 50       3141 if (!length $key_parts->{'version'}) {
116 0         0 die Crypt::Perl::X::create('Generic', 'Need a “version”! (Try 1)');
117             }
118              
119             my $self = {
120 546         2309 version => $key_parts->{'version'},
121             };
122              
123 546         1452 bless $self, $class;
124              
125 546         5302 $self->_set_public( $key_parts->{'public'} );
126              
127 546         2907 for my $k ( qw( private ) ) {
128 546 50   546   6177 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  546         18144  
129 546         8753 $self->{$k} = $key_parts->{$k};
130             }
131             else {
132 0         0 die Crypt::Perl::X::create('Generic', sprintf "“$k” must be “%s”, not “$key_parts->{$k}”!", NUMBER_CLASS());
133             }
134             }
135              
136 546         3819 return $self->_add_params( $curve_parts );
137             }
138              
139             sub sign {
140 274     274 0 117072 return $_[0]->_sign_and_serialize($_[1]);
141             }
142              
143             sub _sign_and_serialize {
144 487     487   3337 my ($self, $whatsit, $hashfn) = @_;
145              
146 487         4168 my ($r, $s) = $self->_sign($whatsit, $hashfn);
147 445         5083 return $self->_serialize_sig( $r, $s );
148             }
149              
150             sub _hash_sign_and_serialize {
151 213     213   2885 my ($self, $whatsit, $hashfn) = @_;
152              
153 213         3348 require Digest::SHA;
154 213         9198 $whatsit = Digest::SHA->can($hashfn)->($whatsit);
155              
156 213         3073 return $self->_sign_and_serialize($whatsit, $hashfn);
157             }
158              
159             sub sign_sha1 {
160 199     199 0 7853850 my ($self, $whatsit) = @_;
161              
162 199         1120 return $_[0]->_hash_sign_and_serialize($whatsit, 'sha1');
163             }
164              
165             sub sign_sha224 {
166 3     3 0 1121 my ($self, $whatsit) = @_;
167              
168 3         34 return $_[0]->_hash_sign_and_serialize($whatsit, 'sha224');
169             }
170              
171             sub sign_sha256 {
172 6     6 0 1146 my ($self, $whatsit) = @_;
173              
174 6         60 return $_[0]->_hash_sign_and_serialize($whatsit, 'sha256');
175             }
176              
177             sub sign_sha384 {
178 2     2 0 1152 my ($self, $whatsit) = @_;
179              
180 2         50 return $_[0]->_hash_sign_and_serialize($whatsit, 'sha384');
181             }
182              
183             sub sign_sha512 {
184 3     3 0 1081 my ($self, $whatsit) = @_;
185              
186 3         63 return $_[0]->_hash_sign_and_serialize($whatsit, 'sha512');
187             }
188              
189             #cf. RFC 7518, page 8
190             sub sign_jwa {
191 6     6 0 4000 my ($self, $whatsit) = @_;
192              
193             # As of version 0.34 this method creates deterministic signatures.
194              
195 6         34 my $dgst_name = $self->_get_jwk_digest_name();
196              
197 6         35 require Digest::SHA;
198 6         200 $whatsit = Digest::SHA->can($dgst_name)->($whatsit);
199              
200 6         36 my ($r, $s) = map { $_->as_bytes() } $self->_sign($whatsit, $dgst_name);
  12         60  
201              
202 6         72 my $octet_length = Crypt::Perl::Math::ceil($self->max_sign_bits() / 8);
203              
204 6         43 substr( $_, 0, 0 ) = "\0" x ($octet_length - length) for ($r, $s);
205              
206 6         46 return $r . $s;
207             }
208              
209             sub get_public_key {
210 13     13 0 202 my ($self) = @_;
211              
212 13         1333 require Crypt::Perl::ECDSA::PublicKey;
213              
214 13         186 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
215 13         83 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
216 13         52 $ccurve_hr->{'seed'} = [ $ccurve_hr->{'seed'} ];
217              
218 13         110 return Crypt::Perl::ECDSA::PublicKey->new(
219             $self->_decompress_public_point(),
220             $curve_hr,
221             );
222             }
223              
224             sub get_struct_for_private_jwk {
225 1     1 0 839 my ($self) = @_;
226              
227 1         8 my $hr = $self->get_struct_for_public_jwk();
228              
229 1         32 require MIME::Base64;
230              
231 1         7 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
232              
233 1         18 return $hr;
234             }
235              
236             #----------------------------------------------------------------------
237              
238             #$whatsit is probably a message digest, e.g., from SHA256
239             sub _sign {
240 493     493   2277 my ($self, $whatsit, $det_hashfuncname) = @_;
241              
242 493         3409 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
243              
244 493         117050 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
245              
246 493         3383 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
247              
248 493         3020 my $key_len = $self->max_sign_bits();
249 493         24705 my $dgst_len = $dgst->bit_length();
250 493 100       15223 if ( $dgst_len > $key_len ) {
251 42         453 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
252             }
253              
254             #isa ECPoint
255 451         4869 my $G = $self->_G();
256 451         1839 my ($k, $r);
257              
258 451         1244 do {
259 451 100       3561 if ($det_hashfuncname) {
260 219         4397 require Crypt::Perl::ECDSA::Deterministic;
261 219         4364 $k = Crypt::Perl::ECDSA::Deterministic::generate_k(
262             $n,
263             $priv_num,
264             $whatsit,
265             $det_hashfuncname,
266             );
267             }
268             else {
269 232         3725 $k = Crypt::Perl::Math::randint($n);
270             }
271              
272 451         4419 my $Q = $G->multiply($k); #$Q isa ECPoint
273              
274 451         4350 $r = $Q->get_x()->to_bigint()->copy()->bmod($n);
275             } while !$r->is_positive();
276              
277 451         65628 my $s = $k->bmodinv($n);
278              
279             #$s *= ( $dgst + ( $priv_num * $r ) );
280 451         89685 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
281              
282 451         70316 $s->bmod($n);
283              
284 451         44002 return ($r, $s);
285             }
286              
287             sub _get_asn1_parts {
288 418     418   2495 my ($self, $curve_parts, @params) = @_;
289              
290 418         2652 my $private_str = $self->{'private'}->as_bytes();
291              
292 418         7767 return $self->__to_der(
293             'ECPrivateKey',
294             ASN1_PRIVATE(),
295             {
296             version => 1,
297             privateKey => $private_str,
298             parameters => $curve_parts,
299             },
300             @params,
301             );
302             }
303              
304             sub _serialize_sig {
305 445     445   2116 my ($self, $r, $s) = @_;
306              
307 445         4406 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
308 445         5052 return $asn1->encode( r => $r, s => $s );
309             }
310              
311             1;