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   37 use strict;
  7         10  
  7         162  
77 7     7   27 use warnings;
  7         12  
  7         173  
78              
79 7     7   29 use parent qw( Crypt::Perl::ECDSA::KeyBase );
  7         13  
  7         41  
80              
81 7     7   292 use Try::Tiny;
  7         13  
  7         299  
82              
83 7     7   33 use Bytes::Random::Secure::Tiny ();
  7         13  
  7         92  
84              
85 7     7   29 use Crypt::Perl::ASN1 ();
  7         9  
  7         74  
86 7     7   28 use Crypt::Perl::BigInt ();
  7         10  
  7         109  
87 7     7   29 use Crypt::Perl::Math ();
  7         10  
  7         94  
88 7     7   1428 use Crypt::Perl::ToDER ();
  7         11  
  7         86  
89 7     7   30 use Crypt::Perl::X ();
  7         8  
  7         200  
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         426 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         54  
103              
104 7     7   38 use constant _PEM_HEADER => 'EC PRIVATE KEY';
  7         12  
  7         306  
105              
106 7     7   31 use constant NUMBER_CLASS => 'Crypt::Perl::BigInt';
  7         38  
  7         4753  
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 2077 my ($class, $key_parts, $curve_parts) = @_;
114              
115 544 50       2881 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         2137 version => $key_parts->{'version'},
121             };
122              
123 544         1367 bless $self, $class;
124              
125 544         3953 $self->_set_public( $key_parts->{'public'} );
126              
127 544         2220 for my $k ( qw( private ) ) {
128 544 50   544   6317 if ( try { $key_parts->{$k}->isa(NUMBER_CLASS()) } ) {
  544         14580  
129 544         6891 $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         3264 return $self->_add_params( $curve_parts );
137             }
138              
139             sub sign {
140 284     284 0 308556 my ($self, $whatsit) = @_;
141              
142 284         2527 my ($r, $s) = $self->_sign($whatsit);
143 242         3031 return $self->_serialize_sig( $r, $s );
144             }
145              
146             #cf. RFC 7518, page 8
147             sub sign_jwa {
148 3     3 0 2439 my ($self, $whatsit) = @_;
149              
150 3         29 my $dgst_cr = $self->_get_jwk_digest_cr();
151              
152 3         67 my ($r, $s) = map { $_->as_bytes() } $self->_sign($dgst_cr->($whatsit));
  6         33  
153              
154 3         28 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         17 return $r . $s;
159             }
160              
161             sub get_public_key {
162 13     13 0 107 my ($self) = @_;
163              
164 13         1080 require Crypt::Perl::ECDSA::PublicKey;
165              
166 13         126 my $curve_hr = $self->_explicit_curve_parameters( seed => 1 );
167 13         44 my $ccurve_hr = $curve_hr->{'ecParameters'}{'curve'};
168 13         40 $ccurve_hr->{'seed'} = [ $ccurve_hr->{'seed'} ];
169              
170 13         88 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 1153 my ($self) = @_;
178              
179 1         6 my $hr = $self->get_struct_for_public_jwk();
180              
181 1         40 require MIME::Base64;
182              
183 1         17 $hr->{'d'} = MIME::Base64::encode_base64url( $self->{'private'}->as_bytes() );
184              
185 1         20 return $hr;
186             }
187              
188             #----------------------------------------------------------------------
189              
190             #$whatsit is probably a message digest, e.g., from SHA256
191             sub _sign {
192 287     287   1439 my ($self, $whatsit) = @_;
193              
194 287         2610 my $dgst = Crypt::Perl::BigInt->from_bytes( $whatsit );
195              
196 287         134912 my $priv_num = $self->{'private'}; #Math::BigInt->from_hex( $priv_hex );
197              
198 287         1593 my $n = $self->_curve()->{'n'}; #$curve_data->{'n'};
199              
200 287         3018 my $key_len = $self->max_sign_bits();
201 287         232696 my $dgst_len = $dgst->bit_length();
202 287 100       134316 if ( $dgst_len > $key_len ) {
203 42         466 die Crypt::Perl::X::create('TooLongToSign', $key_len, $dgst_len );
204             }
205              
206             #isa ECPoint
207 245         2247 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         679 my ($k, $r);
213              
214 245         462 do {
215 245         2857 $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         2301 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         1263 $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         33673 my $s = $k->bmodinv($n);
234              
235             #$s *= ( $dgst + ( $priv_num * $r ) );
236 245         5221729 $s->bmul( $priv_num->copy()->bmuladd( $r, $dgst ) );
237              
238 245         214390 $s->bmod($n);
239              
240 245         215678 return ($r, $s);
241             }
242              
243             sub _get_asn1_parts {
244 250     250   822 my ($self, $curve_parts, @params) = @_;
245              
246 250         1013 my $private_str = $self->{'private'}->as_bytes();
247              
248 250         3714 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   990 my ($self, $r, $s) = @_;
262              
263 242         2410 my $asn1 = Crypt::Perl::ASN1->new()->prepare( $self->ASN1_SIGNATURE() );
264 242         1615 return $asn1->encode( r => $r, s => $s );
265             }
266              
267             1;