File Coverage

lib/Crypt/Perl/ECDSA/PrivateKey.pm
Criterion Covered Total %
statement 94 96 97.9
branch 4 6 66.6
condition n/a
subroutine 22 22 100.0
pod 0 5 0.0
total 120 129 93.0


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   41 use strict;
  7         14  
  7         164  
77 7     7   31 use warnings;
  7         21  
  7         179  
78              
79 7     7   27 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  7         14  
  7         57  
80              
81 7     7   286 use Try::Tiny;
  7         13  
  7         263  
82              
83 7     7   32 use Bytes::Random::Secure::Tiny ();
  7         11  
  7         92  
84              
85 7     7   24 use Crypt::Perl::ASN1 ();
  7         11  
  7         62  
86 7     7   25 use Crypt::Perl::BigInt ();
  7         9  
  7         114  
87 7     7   34 use Crypt::Perl::Math ();
  7         13  
  7         83  
88 7     7   1430 use Crypt::Perl::ToDER ();
  7         10  
  7         90  
89 7     7   29 use Crypt::Perl::X ();
  7         12  
  7         212  
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 7         526 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 7     7   31 >;
  7         55  
103              
104 7     7   37 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  7         11  
  7         342  
105              
106 7     7   32 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  7         33  
  7         4895  
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 544     544 0 1790 my ($class, $key_parts, $curve_parts) = @_;
114              
115 544 50       2605 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 544         1975 version => $key_parts->{'version'},
121             };
122              
123 544         1183 bless $self, $class;
124              
125 544         4319 $self->_set_public( $key_parts->{'public'} );
126              
127 544         2327 for my $k ( qw( private ) ) {
128 544 50   544   5481 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  544         15348  
129 544         7633 $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 544         2458 return $self->_add_params( $curve_parts );
137             }
138              
139             sub sign {
140 284     284 0 332550 my ($self, $whatsit) = @_;
141              
142 284         2981 my ($r, $s) = $self->_sign($whatsit);
143 242         2744 return $self->_serialize_sig( $r, $s );
144             }
145              
146             #cf. RFC 7518, page 8
147             sub sign_jwa {
148 3     3 0 1597 my ($self, $whatsit) = @_;
149              
150 3         17 my $dgst_cr = $self->_get_jwk_digest_cr();
151              
152 3         65 my ($r, $s) = map { $_->as_bytes() } $self->_sign($dgst_cr->($whatsit));
  6         31  
153              
154 3         34 my $octet_length = Crypt::Perl::Math::ceil($self->max_sign_bits() / 8);
155              
156 3         17 substr( $_, 0, 0 ) = "\0" x ($octet_length - length) for ($r, $s);
157              
158 3         19 return $r . $s;
159             }
160              
161             sub get_public_key {
162 13     13 0 156 my ($self) = @_;
163              
164 13         1057 require Crypt::Perl::ECDSA::PublicKey;
165              
166 13         119 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
167 13         57 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
168 13         47 $ccurve_hr->{'seed'} = [ $ccurve_hr->{'seed'} ];
169              
170 13         91 return Crypt::Perl::ECDSA::PublicKey->new(
171             $self->_decompress_public_point(),
172             $curve_hr,
173             );
174             }
175              
176             sub get_struct_for_private_jwk {
177 1     1 0 727 my ($self) = @_;
178              
179 1         5 my $hr = $self->get_struct_for_public_jwk();
180              
181 1         20 require MIME::Base64;
182              
183 1         6 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
184              
185 1         12 return $hr;
186             }
187              
188             #----------------------------------------------------------------------
189              
190             #$whatsit is probably a message digest, e.g., from SHA256
191             sub _sign {
192 287     287   1306 my ($self, $whatsit) = @_;
193              
194 287         2119 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
195              
196 287         140562 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
197              
198 287         2442 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
199              
200 287         2827 my $key_len = $self->max_sign_bits();
201 287         245334 my $dgst_len = $dgst->bit_length();
202 287 100       133445 if ( $dgst_len > $key_len ) {
203 42         509 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
204             }
205              
206             #isa ECPoint
207 245         2567 my $G = $self->_G();
208             #printf "G.x: %s\n", $G->{'x'}->to_bigint()->as_hex();
209             #printf "G.y: %s\n", $G->{'y'}->to_bigint()->as_hex();
210             #printf "G.z: %s\n", $G->{'z'}->as_hex();
211              
212 245         860 my ($k, $r);
213              
214 245         538 do {
215 245         3271 $k = Crypt::Perl::Math::randint($n);
216             #print "once\n";
217             #printf "big random: %s\n", $k->as_hex();
218             #$k = Crypt::Perl::BigInt->new("98452900523450592996995215574085435893040452563985855319633891614520662229711");
219             #printf "k: %s\n", $k->bstr();
220 245         2675 my $Q = $G->multiply($k); #$Q isa ECPoint
221             #printf "Q.x: %s\n", $Q->{'x'}->to_bigint()->as_hex();
222             #printf "Q.y: %s\n", $Q->{'y'}->to_bigint()->as_hex();
223             #printf "Q.z: %s\n", $Q->{'z'}->as_hex();
224 245         1108 $r = $Q->get_x()->to_bigint()->copy()->bmod($n);
225             } while !$r->is_positive();
226              
227             #printf "k: %s\n", $k->as_hex();
228             #printf "n: %s\n", $n->as_hex();
229             #printf "e: %s\n", $dgst->as_hex();
230             #printf "d: %s\n", $priv_num->as_hex();
231             #printf "r: %s\n", $r->as_hex();
232              
233 245         34738 my $s = $k->bmodinv($n);
234              
235             #$s *= ( $dgst + ( $priv_num * $r ) );
236 245         5425134 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
237              
238 245         220019 $s->bmod($n);
239              
240 245         230815 return ($r, $s);
241             }
242              
243             sub _get_asn1_parts {
244 250     250   802 my ($self, $curve_parts, @params) = @_;
245              
246 250         1042 my $private_str = $self->{'private'}->as_bytes();
247              
248 250         2340 return $self->__to_der(
249             'ECPrivateKey',
250             ASN1_PRIVATE(),
251             {
252             version => 1,
253             privateKey => $private_str,
254             parameters => $curve_parts,
255             },
256             @params,
257             );
258             }
259              
260             sub _serialize_sig {
261 242     242   1583 my ($self, $r, $s) = @_;
262              
263 242         2705 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
264 242         2656 return $asn1->encode( r => $r, s => $s );
265             }
266              
267             1;