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   131 use strict;
  7         14  
  7         189  
77 7     7   32 use warnings;
  7         11  
  7         223  
78              
79 7     7   35 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  7         11  
  7         44  
80              
81 7     7   305 use Try::Tiny;
  7         17  
  7         311  
82              
83 7     7   37 use Bytes::Random::Secure::Tiny ();
  7         15  
  7         104  
84              
85 7     7   29 use Crypt::Perl::ASN1 ();
  7         11  
  7         69  
86 7     7   25 use Crypt::Perl::BigInt ();
  7         18  
  7         104  
87 7     7   41 use Crypt::Perl::Math ();
  7         14  
  7         99  
88 7     7   1334 use Crypt::Perl::ToDER ();
  7         14  
  7         97  
89 7     7   34 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         457 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   32 >;
  7         63  
103              
104 7     7   48 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  7         12  
  7         323  
105              
106 7     7   32 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  7         40  
  7         5022  
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 2117 my ($class, $key_parts, $curve_parts) = @_;
114              
115 544 50       2358 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         1972 version => $key_parts->{'version'},
121             };
122              
123 544         1665 bless $self, $class;
124              
125 544         3840 $self->_set_public( $key_parts->{'public'} );
126              
127 544         2327 for my $k ( qw( private ) ) {
128 544 50   544   5206 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  544         14348  
129 544         6644 $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         3478 return $self->_add_params( $curve_parts );
137             }
138              
139             sub sign {
140 284     284 0 304873 my ($self, $whatsit) = @_;
141              
142 284         2833 my ($r, $s) = $self->_sign($whatsit);
143 242         3367 return $self->_serialize_sig( $r, $s );
144             }
145              
146             #cf. RFC 7518, page 8
147             sub sign_jwa {
148 3     3 0 1865 my ($self, $whatsit) = @_;
149              
150 3         20 my $dgst_cr = $self->_get_jwk_digest_cr();
151              
152 3         73 my ($r, $s) = map { $_->as_bytes() } $self->_sign($dgst_cr->($whatsit));
  6         24  
153              
154 3         29 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         15 return $r . $s;
159             }
160              
161             sub get_public_key {
162 13     13 0 114 my ($self) = @_;
163              
164 13         1053 require Crypt::Perl::ECDSA::PublicKey;
165              
166 13         87 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
167 13         69 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
168 13         42 $ccurve_hr->{'seed'} = [ $ccurve_hr->{'seed'} ];
169              
170 13         129 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 1063 my ($self) = @_;
178              
179 1         5 my $hr = $self->get_struct_for_public_jwk();
180              
181 1         27 require MIME::Base64;
182              
183 1         7 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
184              
185 1         15 return $hr;
186             }
187              
188             #----------------------------------------------------------------------
189              
190             #$whatsit is probably a message digest, e.g., from SHA256
191             sub _sign {
192 287     287   1385 my ($self, $whatsit) = @_;
193              
194 287         2084 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
195              
196 287         135087 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
197              
198 287         1819 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
199              
200 287         2331 my $key_len = $self->max_sign_bits();
201 287         234805 my $dgst_len = $dgst->bit_length();
202 287 100       132743 if ( $dgst_len > $key_len ) {
203 42         428 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
204             }
205              
206             #isa ECPoint
207 245         2256 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         726 my ($k, $r);
213              
214 245         1360 do {
215 245         2510 $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         2245 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         1103 $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         32710 my $s = $k->bmodinv($n);
234              
235             #$s *= ( $dgst + ( $priv_num * $r ) );
236 245         5180946 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
237              
238 245         211765 $s->bmod($n);
239              
240 245         216477 return ($r, $s);
241             }
242              
243             sub _get_asn1_parts {
244 250     250   885 my ($self, $curve_parts, @params) = @_;
245              
246 250         1062 my $private_str = $self->{'private'}->as_bytes();
247              
248 250         3474 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   1087 my ($self, $r, $s) = @_;
262              
263 242         2257 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
264 242         1595 return $asn1->encode( r => $r, s => $s );
265             }
266              
267             1;