File Coverage

lib/Crypt/Perl/ECDSA/PrivateKey.pm
Criterion Covered Total %
statement 97 99 97.9
branch 4 6 66.6
condition n/a
subroutine 23 23 100.0
pod 0 5 0.0
total 124 133 93.2


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 7     7   77 use strict;
  7         15  
  7         180  
77 7     7   31 use warnings;
  7         12  
  7         196  
78              
79 7     7   34 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  7         16  
  7         47  
80              
81 7     7   301 use Try::Tiny;
  7         11  
  7         291  
82              
83 7     7   35 use Bytes::Random::Secure::Tiny ();
  7         13  
  7         82  
84              
85 7     7   28 use Crypt::Perl::ASN1 ();
  7         14  
  7         96  
86 7     7   25 use Crypt::Perl::BigInt ();
  7         14  
  7         118  
87 7     7   1518 use Crypt::Perl::PKCS8 ();
  7         14  
  7         107  
88 7     7   33 use Crypt::Perl::Math ();
  7         11  
  7         89  
89 7     7   1331 use Crypt::Perl::ToDER ();
  7         14  
  7         115  
90 7     7   34 use Crypt::Perl::X ();
  7         9  
  7         227  
91              
92             #This is not the standard ASN.1 template as found in RFC 5915,
93             #but it seems to generate equivalent results.
94             #
95 7         491 use constant ASN1_PRIVATE => Crypt::Perl::ECDSA::KeyBase->ASN1_Params() . q<
96              
97             ECPrivateKey ::= SEQUENCE {
98             version INTEGER,
99             privateKey OCTET STRING,
100             parameters [0] EXPLICIT EcpkParameters OPTIONAL,
101             publicKey [1] EXPLICIT BIT STRING
102             }
103 7     7   32 >;
  7         73  
104              
105 7     7   40 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  7         11  
  7         282  
106              
107 7     7   36 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  7         37  
  7         5310  
108              
109             #$curve_parts is also a hash ref, defined as whatever the ASN.1
110             #parse of the main key’s “parameters” returned, whether that be
111             #explicit key parameters or a named curve.
112             #
113             sub new {
114 544     544 0 1991 my ($class, $key_parts, $curve_parts) = @_;
115              
116 544 50       2580 if (!length $key_parts->{'version'}) {
117 0         0 die Crypt::Perl::X::create('Generic', 'Need a “version”! (Try 1)');
118             }
119              
120             my $self = {
121 544         1981 version => $key_parts->{'version'},
122             };
123              
124 544         1492 bless $self, $class;
125              
126 544         4298 $self->_set_public( $key_parts->{'public'} );
127              
128 544         2073 for my $k ( qw( private ) ) {
129 544 50   544   5594 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  544         15680  
130 544         7669 $self->{$k} = $key_parts->{$k};
131             }
132             else {
133 0         0 die Crypt::Perl::X::create('Generic', sprintf "“$k” must be “%s”, not “$key_parts->{$k}”!", NUMBER_CLASS());
134             }
135             }
136              
137 544         3399 return $self->_add_params( $curve_parts );
138             }
139              
140             sub sign {
141 284     284 0 353795 my ($self, $whatsit) = @_;
142              
143 284         2368 my ($r, $s) = $self->_sign($whatsit);
144 242         2808 return $self->_serialize_sig( $r, $s );
145             }
146              
147             #cf. RFC 7518, page 8
148             sub sign_jwa {
149 3     3 0 1944 my ($self, $whatsit) = @_;
150              
151 3         18 my $dgst_cr = $self->_get_jwk_digest_cr();
152              
153 3         73 my ($r, $s) = map { $_->as_bytes() } $self->_sign($dgst_cr->($whatsit));
  6         26  
154              
155 3         37 my $octet_length = Crypt::Perl::Math::ceil($self->max_sign_bits() / 8);
156              
157 3         20 substr( $_, 0, 0 ) = "\0" x ($octet_length - length) for ($r, $s);
158              
159 3         21 return $r . $s;
160             }
161              
162             sub get_public_key {
163 13     13 0 136 my ($self) = @_;
164              
165 13         1186 require Crypt::Perl::ECDSA::PublicKey;
166              
167 13         138 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
168 13         49 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
169 13         41 $ccurve_hr->{'seed'} = [ $ccurve_hr->{'seed'} ];
170              
171 13         120 return Crypt::Perl::ECDSA::PublicKey->new(
172             $self->_decompress_public_point(),
173             $curve_hr,
174             );
175             }
176              
177             sub get_struct_for_private_jwk {
178 1     1 0 676 my ($self) = @_;
179              
180 1         4 my $hr = $self->get_struct_for_public_jwk();
181              
182 1         23 require MIME::Base64;
183              
184 1         5 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
185              
186 1         14 return $hr;
187             }
188              
189             #----------------------------------------------------------------------
190              
191             #$whatsit is probably a message digest, e.g., from SHA256
192             sub _sign {
193 287     287   1761 my ($self, $whatsit) = @_;
194              
195 287         2911 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
196              
197 287         144153 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
198              
199 287         2492 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
200              
201 287         3118 my $key_len = $self->max_sign_bits();
202 287         249914 my $dgst_len = $dgst->bit_length();
203 287 100       138053 if ( $dgst_len > $key_len ) {
204 42         514 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
205             }
206              
207             #isa ECPoint
208 245         2456 my $G = $self->_G();
209             #printf "G.x: %s\n", $G->{'x'}->to_bigint()->as_hex();
210             #printf "G.y: %s\n", $G->{'y'}->to_bigint()->as_hex();
211             #printf "G.z: %s\n", $G->{'z'}->as_hex();
212              
213 245         1307 my ($k, $r);
214              
215 245         1351 do {
216 245         3848 $k = Crypt::Perl::Math::randint($n);
217             #print "once\n";
218             #printf "big random: %s\n", $k->as_hex();
219             #$k = Crypt::Perl::BigInt->new("98452900523450592996995215574085435893040452563985855319633891614520662229711");
220             #printf "k: %s\n", $k->bstr();
221 245         2910 my $Q = $G->multiply($k); #$Q isa ECPoint
222             #printf "Q.x: %s\n", $Q->{'x'}->to_bigint()->as_hex();
223             #printf "Q.y: %s\n", $Q->{'y'}->to_bigint()->as_hex();
224             #printf "Q.z: %s\n", $Q->{'z'}->as_hex();
225 245         1175 $r = $Q->get_x()->to_bigint()->copy()->bmod($n);
226             } while !$r->is_positive();
227              
228             #printf "k: %s\n", $k->as_hex();
229             #printf "n: %s\n", $n->as_hex();
230             #printf "e: %s\n", $dgst->as_hex();
231             #printf "d: %s\n", $priv_num->as_hex();
232             #printf "r: %s\n", $r->as_hex();
233              
234 245         35770 my $s = $k->bmodinv($n);
235              
236             #$s *= ( $dgst + ( $priv_num * $r ) );
237 245         5467096 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
238              
239 245         218740 $s->bmod($n);
240              
241 245         227068 return ($r, $s);
242             }
243              
244             sub _get_asn1_parts {
245 250     250   1067 my ($self, $curve_parts, @params) = @_;
246              
247 250         1162 my $private_str = $self->{'private'}->as_bytes();
248              
249 250         3063 return $self->__to_der(
250             'ECPrivateKey',
251             ASN1_PRIVATE(),
252             {
253             version => 1,
254             privateKey => $private_str,
255             parameters => $curve_parts,
256             },
257             @params,
258             );
259             }
260              
261             sub _serialize_sig {
262 242     242   1213 my ($self, $r, $s) = @_;
263              
264 242         2518 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
265 242         1729 return $asn1->encode( r => $r, s => $s );
266             }
267              
268             1;