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         181  
77 7     7   30 use warnings;
  7         10  
  7         186  
78              
79 7     7   32 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  7         12  
  7         34  
80              
81 7     7   298 use Try::Tiny;
  7         14  
  7         275  
82              
83 7     7   37 use Bytes::Random::Secure::Tiny ();
  7         11  
  7         106  
84              
85 7     7   26 use Crypt::Perl::ASN1 ();
  7         12  
  7         80  
86 7     7   33 use Crypt::Perl::BigInt ();
  7         11  
  7         107  
87 7     7   38 use Crypt::Perl::Math ();
  7         13  
  7         86  
88 7     7   1433 use Crypt::Perl::ToDER ();
  7         15  
  7         114  
89 7     7   33 use Crypt::Perl::X ();
  7         11  
  7         193  
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         514 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   30 >;
  7         60  
103              
104 7     7   38 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  7         20  
  7         380  
105              
106 7     7   37 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  7         46  
  7         5363  
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 1892 my ($class, $key_parts, $curve_parts) = @_;
114              
115 545 50       2717 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         1812 version => $key_parts->{'version'},
121             };
122              
123 545         1403 bless $self, $class;
124              
125 545         4940 $self->_set_public( $key_parts->{'public'} );
126              
127 545         2877 for my $k ( qw( private ) ) {
128 545 50   545   5427 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  545         16488  
129 545         7609 $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         3536 return $self->_add_params( $curve_parts );
137             }
138              
139             sub sign {
140 284     284 0 327715 my ($self, $whatsit) = @_;
141              
142 284         1989 my ($r, $s) = $self->_sign($whatsit);
143 242         2548 return $self->_serialize_sig( $r, $s );
144             }
145              
146             #cf. RFC 7518, page 8
147             sub sign_jwa {
148 3     3 0 2135 my ($self, $whatsit) = @_;
149              
150 3         19 my $dgst_cr = $self->_get_jwk_digest_cr();
151              
152 3         64 my ($r, $s) = map { $_->as_bytes() } $self->_sign($dgst_cr->($whatsit));
  6         52  
153              
154 3         27 my $octet_length = Crypt::Perl::Math::ceil($self->max_sign_bits() / 8);
155              
156 3         21 substr( $_, 0, 0 ) = "\0" x ($octet_length - length) for ($r, $s);
157              
158 3         16 return $r . $s;
159             }
160              
161             sub get_public_key {
162 13     13 0 141 my ($self) = @_;
163              
164 13         1231 require Crypt::Perl::ECDSA::PublicKey;
165              
166 13         140 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
167 13         46 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
168 13         41 $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 1170 my ($self) = @_;
178              
179 1         4 my $hr = $self->get_struct_for_public_jwk();
180              
181 1         30 require MIME::Base64;
182              
183 1         6 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
184              
185 1         16 return $hr;
186             }
187              
188             #----------------------------------------------------------------------
189              
190             #$whatsit is probably a message digest, e.g., from SHA256
191             sub _sign {
192 287     287   1894 my ($self, $whatsit) = @_;
193              
194 287         2482 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
195              
196 287         157513 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
197              
198 287         2467 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
199              
200 287         3622 my $key_len = $self->max_sign_bits();
201 287         271680 my $dgst_len = $dgst->bit_length();
202 287 100       157631 if ( $dgst_len > $key_len ) {
203 42         462 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
204             }
205              
206             #isa ECPoint
207 245         2609 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         797 my ($k, $r);
213              
214 245         491 do {
215 245         3221 $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         2655 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         1416 $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         37363 my $s = $k->bmodinv($n);
234              
235             #$s *= ( $dgst + ( $priv_num * $r ) );
236 245         6074192 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
237              
238 245         236737 $s->bmod($n);
239              
240 245         253419 return ($r, $s);
241             }
242              
243             sub _get_asn1_parts {
244 478     478   2268 my ($self, $curve_parts, @params) = @_;
245              
246 478         2526 my $private_str = $self->{'private'}->as_bytes();
247              
248 478         6737 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   938 my ($self, $r, $s) = @_;
262              
263 242         3402 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
264 242         1683 return $asn1->encode( r => $r, s => $s );
265             }
266              
267             1;