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   46 use strict;
  7         13  
  7         193  
77 7     7   36 use warnings;
  7         14  
  7         177  
78              
79 7     7   33 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  7         17  
  7         36  
80              
81 7     7   316 use Try::Tiny;
  7         15  
  7         288  
82              
83 7     7   49 use Bytes::Random::Secure::Tiny ();
  7         15  
  7         114  
84              
85 7     7   32 use Crypt::Perl::ASN1 ();
  7         9  
  7         92  
86 7     7   41 use Crypt::Perl::BigInt ();
  7         11  
  7         122  
87 7     7   31 use Crypt::Perl::Math ();
  7         12  
  7         99  
88 7     7   1468 use Crypt::Perl::ToDER ();
  7         15  
  7         116  
89 7     7   34 use Crypt::Perl::X ();
  7         13  
  7         206  
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   38 >;
  7         66  
103              
104 7     7   42 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  7         16  
  7         358  
105              
106 7     7   40 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  7         48  
  7         6017  
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 545     545 0 1968 my ($class, $key_parts, $curve_parts) = @_;
114              
115 545 50       2388 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 545         2263 version => $key_parts->{'version'},
121             };
122              
123 545         1350 bless $self, $class;
124              
125 545         4746 $self->_set_public( $key_parts->{'public'} );
126              
127 545         2561 for my $k ( qw( private ) ) {
128 545 50   545   5075 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  545         16327  
129 545         7976 $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 545         3739 return $self->_add_params( $curve_parts );
137             }
138              
139             sub sign {
140 284     284 0 341805 my ($self, $whatsit) = @_;
141              
142 284         3029 my ($r, $s) = $self->_sign($whatsit);
143 242         3198 return $self->_serialize_sig( $r, $s );
144             }
145              
146             #cf. RFC 7518, page 8
147             sub sign_jwa {
148 3     3 0 2860 my ($self, $whatsit) = @_;
149              
150 3         17 my $dgst_cr = $self->_get_jwk_digest_cr();
151              
152 3         66 my ($r, $s) = map { $_->as_bytes() } $self->_sign($dgst_cr->($whatsit));
  6         32  
153              
154 3         30 my $octet_length = Crypt::Perl::Math::ceil($self->max_sign_bits() / 8);
155              
156 3         20 substr( $_, 0, 0 ) = "\0" x ($octet_length - length) for ($r, $s);
157              
158 3         18 return $r . $s;
159             }
160              
161             sub get_public_key {
162 13     13 0 117 my ($self) = @_;
163              
164 13         1096 require Crypt::Perl::ECDSA::PublicKey;
165              
166 13         134 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
167 13         46 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
168 13         46 $ccurve_hr->{'seed'} = [ $ccurve_hr->{'seed'} ];
169              
170 13         81 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 893 my ($self) = @_;
178              
179 1         5 my $hr = $self->get_struct_for_public_jwk();
180              
181 1         28 require MIME::Base64;
182              
183 1         6 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
184              
185 1         18 return $hr;
186             }
187              
188             #----------------------------------------------------------------------
189              
190             #$whatsit is probably a message digest, e.g., from SHA256
191             sub _sign {
192 287     287   1614 my ($self, $whatsit) = @_;
193              
194 287         2259 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
195              
196 287         157096 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
197              
198 287         2439 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
199              
200 287         2561 my $key_len = $self->max_sign_bits();
201 287         275121 my $dgst_len = $dgst->bit_length();
202 287 100       158077 if ( $dgst_len > $key_len ) {
203 42         399 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
204             }
205              
206             #isa ECPoint
207 245         3033 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         724 my ($k, $r);
213              
214 245         633 do {
215 245         3081 $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         2527 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         1154 $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         37855 my $s = $k->bmodinv($n);
234              
235             #$s *= ( $dgst + ( $priv_num * $r ) );
236 245         6069375 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
237              
238 245         238689 $s->bmod($n);
239              
240 245         254061 return ($r, $s);
241             }
242              
243             sub _get_asn1_parts {
244 478     478   1960 my ($self, $curve_parts, @params) = @_;
245              
246 478         2859 my $private_str = $self->{'private'}->as_bytes();
247              
248 478         5493 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   1122 my ($self, $r, $s) = @_;
262              
263 242         2700 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
264 242         1883 return $asn1->encode( r => $r, s => $s );
265             }
266              
267             1;