File Coverage

blib/lib/Crypt/PK/X25519.pm
Criterion Covered Total %
statement 18 76 23.6
branch 0 74 0.0
condition 0 38 0.0
subroutine 6 12 50.0
pod 5 5 100.0
total 29 205 14.1


line stmt bran cond sub pod time code
1             package Crypt::PK::X25519;
2              
3 2     2   67681 use strict;
  2         12  
  2         85  
4 2     2   11 use warnings;
  2         3  
  2         217  
5             our $VERSION = '0.080_001';
6              
7             require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
8             our %EXPORT_TAGS = ( all => [qw( )] );
9             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
10             our @EXPORT = qw();
11              
12 2     2   21 use Carp;
  2         4  
  2         140  
13             $Carp::Internal{(__PACKAGE__)}++;
14 2     2   416 use CryptX;
  2         19  
  2         78  
15 2     2   389 use Crypt::PK;
  2         14  
  2         57  
16 2     2   462 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
  2         9  
  2         2634  
17              
18             sub new {
19 0     0 1   my $self = shift->_new();
20 0 0         return @_ > 0 ? $self->import_key(@_) : $self;
21             }
22              
23             sub import_key_raw {
24 0     0 1   my ($self, $key, $type) = @_;
25 0 0         croak "FATAL: undefined key" unless $key;
26 0 0         croak "FATAL: invalid key" unless length($key) == 32;
27 0 0         croak "FATAL: undefined type" unless $type;
28 0 0         return $self->_import_raw($key, 1) if $type eq 'private';
29 0 0         return $self->_import_raw($key, 0) if $type eq 'public';
30 0           croak "FATAL: invalid key type '$type'";
31             }
32              
33             sub import_key {
34 0     0 1   my ($self, $key, $password) = @_;
35 0           local $SIG{__DIE__} = \&CryptX::_croak;
36 0 0         croak "FATAL: undefined key" unless $key;
37              
38             # special case
39 0 0         if (ref($key) eq 'HASH') {
40 0 0 0       if ($key->{kty} && $key->{kty} eq "OKP" && $key->{crv} && $key->{crv} eq 'X25519') {
      0        
      0        
41             # JWK-like structure e.g.
42             # {"kty":"OKP","crv":"X25519","d":"...","x":"..."}
43 0 0         return $self->_import_raw(decode_b64u($key->{d}), 1) if $key->{d}; # private
44 0 0         return $self->_import_raw(decode_b64u($key->{x}), 0) if $key->{x}; # public
45             }
46 0 0 0       if ($key->{curve} && $key->{curve} eq "x25519" && ($key->{priv} || $key->{pub})) {
      0        
      0        
47             # hash exported via key2hash
48 0 0         return $self->_import_raw(pack("H*", $key->{priv}), 1) if $key->{priv};
49 0 0         return $self->_import_raw(pack("H*", $key->{pub}), 0) if $key->{pub};
50             }
51 0           croak "FATAL: unexpected X25519 key hash";
52             }
53              
54 0           my $data;
55 0 0         if (ref($key) eq 'SCALAR') {
    0          
56 0           $data = $$key;
57             }
58             elsif (-f $key) {
59 0           $data = read_rawfile($key);
60             }
61             else {
62 0           croak "FATAL: non-existing file '$key'";
63             }
64 0 0         croak "FATAL: invalid key data" unless $data;
65              
66 0 0         if ($data =~ /-----BEGIN PUBLIC KEY-----(.*?)-----END/sg) {
    0          
    0          
    0          
    0          
    0          
67 0 0         $data = pem_to_der($data, $password) or croak "FATAL: PEM/key decode failed";
68 0           return $self->_import($data);
69             }
70             elsif ($data =~ /-----BEGIN PRIVATE KEY-----(.*?)-----END/sg) {
71 0 0         $data = pem_to_der($data, $password) or croak "FATAL: PEM/key decode failed";
72 0           return $self->_import_pkcs8($data, $password);
73             }
74             elsif ($data =~ /-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END/sg) {
75 0 0         $data = pem_to_der($data, $password) or croak "FATAL: PEM/key decode failed";
76 0           return $self->_import_pkcs8($data, $password);
77             }
78             elsif ($data =~ /-----BEGIN X25519 PRIVATE KEY-----(.*?)-----END/sg) {
79 0 0         $data = pem_to_der($data, $password) or croak "FATAL: PEM/key decode failed";
80 0           return $self->_import_pkcs8($data, $password);
81             }
82             elsif ($data =~ /^\s*(\{.*?\})\s*$/s) { # JSON
83 0           my $h = CryptX::_decode_json("$1");
84 0 0 0       if ($h->{kty} && $h->{kty} eq "OKP" && $h->{crv} && $h->{crv} eq 'X25519') {
      0        
      0        
85 0 0         return $self->_import_raw(decode_b64u($h->{d}), 1) if $h->{d}; # private
86 0 0         return $self->_import_raw(decode_b64u($h->{x}), 0) if $h->{x}; # public
87             }
88             }
89             elsif (length($data) == 32) {
90 0           croak "FATAL: use import_key_raw() to load raw (32 bytes) X25519 key";
91             }
92             else {
93             my $rv = eval { $self->_import($data) } ||
94             eval { $self->_import_pkcs8($data, $password) } ||
95 0   0       eval { $self->_import_x509($data) };
96 0 0         return $rv if $rv;
97             }
98 0           croak "FATAL: invalid or unsupported X25519 key format";
99             }
100              
101             sub export_key_pem {
102 0     0 1   my ($self, $type, $password, $cipher) = @_;
103 0           local $SIG{__DIE__} = \&CryptX::_croak;
104 0   0       my $key = $self->export_key_der($type||'');
105 0 0         return unless $key;
106 0 0         return der_to_pem($key, "X25519 PRIVATE KEY", $password, $cipher) if substr($type, 0, 7) eq 'private';
107 0 0         return der_to_pem($key, "PUBLIC KEY") if substr($type,0, 6) eq 'public';
108             }
109              
110             sub export_key_jwk {
111 0     0 1   my ($self, $type, $wanthash) = @_;
112 0           local $SIG{__DIE__} = \&CryptX::_croak;
113 0           my $kh = $self->key2hash;
114 0 0         return unless $kh;
115 0           my $hash = { kty => "OKP", crv => "X25519" };
116 0           $hash->{x} = encode_b64u(pack("H*", $kh->{pub}));
117 0 0 0       $hash->{d} = encode_b64u(pack("H*", $kh->{priv})) if $type && $type eq 'private' && $kh->{priv};
      0        
118 0 0         return $wanthash ? $hash : CryptX::_encode_json($hash);
119             }
120              
121 0     0     sub CLONE_SKIP { 1 } # prevent cloning
122              
123             1;
124              
125             =pod
126              
127             =head1 NAME
128              
129             Crypt::PK::X25519 - Asymmetric cryptography based on X25519
130              
131             =head1 SYNOPSIS
132              
133             use Crypt::PK::X25519;
134              
135             #Shared secret
136             my $priv = Crypt::PK::X25519->new('Alice_priv_x25519.der');
137             my $pub = Crypt::PK::X25519->new('Bob_pub_x25519.der');
138             my $shared_secret = $priv->shared_secret($pub);
139              
140             #Load key
141             my $pk = Crypt::PK::X25519->new;
142             my $pk_hex = "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41";
143             $pk->import_key_raw(pack("H*", $pk_hex), "public");
144             my $sk = Crypt::PK::X25519->new;
145             my $sk_hex = "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651";
146             $sk->import_key_raw(pack("H*", $sk_hex), "private");
147              
148             #Key generation
149             my $pk = Crypt::PK::X25519->new->generate_key;
150             my $private_der = $pk->export_key_der('private');
151             my $public_der = $pk->export_key_der('public');
152             my $private_pem = $pk->export_key_pem('private');
153             my $public_pem = $pk->export_key_pem('public');
154             my $private_raw = $pk->export_key_raw('private');
155             my $public_raw = $pk->export_key_raw('public');
156             my $private_jwk = $pk->export_key_jwk('private');
157             my $public_jwk = $pk->export_key_jwk('public');
158              
159             =head1 DESCRIPTION
160              
161             I
162              
163             =head1 METHODS
164              
165             =head2 new
166              
167             my $pk = Crypt::PK::X25519->new();
168             #or
169             my $pk = Crypt::PK::X25519->new($priv_or_pub_key_filename);
170             #or
171             my $pk = Crypt::PK::X25519->new(\$buffer_containing_priv_or_pub_key);
172              
173             Support for password protected PEM keys
174              
175             my $pk = Crypt::PK::X25519->new($priv_pem_key_filename, $password);
176             #or
177             my $pk = Crypt::PK::X25519->new(\$buffer_containing_priv_pem_key, $password);
178              
179             =head2 generate_key
180              
181             Uses Yarrow-based cryptographically strong random number generator seeded with
182             random data taken from C (UNIX) or C (Win32).
183              
184             $pk->generate_key;
185              
186             =head2 import_key
187              
188             Loads private or public key in DER or PEM format.
189              
190             $pk->import_key($filename);
191             #or
192             $pk->import_key(\$buffer_containing_key);
193              
194             Support for password protected PEM keys:
195              
196             $pk->import_key($filename, $password);
197             #or
198             $pk->import_key(\$buffer_containing_key, $password);
199              
200             Loading private or public keys form perl hash:
201              
202             $pk->import_key($hashref);
203              
204             # the $hashref is either a key exported via key2hash
205             $pk->import_key({
206             curve => "x25519",
207             pub => "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41",
208             priv => "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651",
209             });
210              
211             # or a hash with items corresponding to JWK (JSON Web Key)
212             $pk->import_key({
213             kty => "OKP",
214             crv => "X25519",
215             d => "AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",
216             x => "6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE",
217             });
218              
219             Supported key formats:
220              
221             # all formats can be loaded from a file
222             my $pk = Crypt::PK::X25519->new($filename);
223              
224             # or from a buffer containing the key
225             my $pk = Crypt::PK::X25519->new(\$buffer_with_key);
226              
227             =over
228              
229             =item * X25519 private keys in PEM format
230              
231             -----BEGIN X25519 PRIVATE KEY-----
232             MC4CAQAwBQYDK2VuBCIEIAAvk9ELpXKNjdjpUnch2royYcC7G+/ee0u9rGMdRUZR
233             -----END X25519 PRIVATE KEY-----
234              
235             =item * X25519 private keys in password protected PEM format
236              
237             -----BEGIN X25519 PRIVATE KEY-----
238             Proc-Type: 4,ENCRYPTED
239             DEK-Info: DES-CBC,DEEFD3D6B714E75A
240              
241             dfFWP5bKn49aZ993NVAhQQPdFWgsTb4j8CWhRjGBVTPl6ITstAL17deBIRBwZb7h
242             pAyIka81Kfs=
243             -----END X25519 PRIVATE KEY-----
244              
245             =item * X25519 public keys in PEM format
246              
247             -----BEGIN PUBLIC KEY-----
248             MCowBQYDK2VuAyEA6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE=
249             -----END PUBLIC KEY-----
250              
251             =item * PKCS#8 private keys
252              
253             -----BEGIN PRIVATE KEY-----
254             MC4CAQAwBQYDK2VuBCIEIAAvk9ELpXKNjdjpUnch2royYcC7G+/ee0u9rGMdRUZR
255             -----END PRIVATE KEY-----
256              
257             =item * PKCS#8 encrypted private keys
258              
259             -----BEGIN ENCRYPTED PRIVATE KEY-----
260             MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAiS0NOFZmjJswICCAAw
261             DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIGd40Hdso8Y4EONSRCTrqvftl9hl3zbH9
262             2QmHF1KJ4HDMdLDRxD7EynonCw2SV7BO+XNRHzw2yONqiTybfte7nk9t
263             -----END ENCRYPTED PRIVATE KEY-----
264              
265             =item * X25519 private keys in JSON Web Key (JWK) format
266              
267             See L
268              
269             {
270             "kty":"OKP",
271             "crv":"X25519",
272             "x":"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE",
273             "d":"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",
274             }
275              
276             B For JWK support you need to have L module installed.
277              
278             =item * X25519 public keys in JSON Web Key (JWK) format
279              
280             {
281             "kty":"OKP",
282             "crv":"X25519",
283             "x":"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE",
284             }
285              
286             B For JWK support you need to have L module installed.
287              
288             =back
289              
290             =head2 import_key_raw
291              
292             Import raw public/private key - can load raw key data exported by L.
293              
294             $pk->import_key_raw($key, 'public');
295             $pk->import_key_raw($key, 'private');
296              
297             =head2 export_key_der
298              
299             my $private_der = $pk->export_key_der('private');
300             #or
301             my $public_der = $pk->export_key_der('public');
302              
303             =head2 export_key_pem
304              
305             my $private_pem = $pk->export_key_pem('private');
306             #or
307             my $public_pem = $pk->export_key_pem('public');
308              
309             Support for password protected PEM keys
310              
311             my $private_pem = $pk->export_key_pem('private', $password);
312             #or
313             my $private_pem = $pk->export_key_pem('private', $password, $cipher);
314              
315             # supported ciphers: 'DES-CBC'
316             # 'DES-EDE3-CBC'
317             # 'SEED-CBC'
318             # 'CAMELLIA-128-CBC'
319             # 'CAMELLIA-192-CBC'
320             # 'CAMELLIA-256-CBC'
321             # 'AES-128-CBC'
322             # 'AES-192-CBC'
323             # 'AES-256-CBC' (DEFAULT)
324              
325             =head2 export_key_jwk
326              
327             Exports public/private keys as a JSON Web Key (JWK).
328              
329             my $private_json_text = $pk->export_key_jwk('private');
330             #or
331             my $public_json_text = $pk->export_key_jwk('public');
332              
333             Also exports public/private keys as a perl HASH with JWK structure.
334              
335             my $jwk_hash = $pk->export_key_jwk('private', 1);
336             #or
337             my $jwk_hash = $pk->export_key_jwk('public', 1);
338              
339             B For JWK support you need to have L module installed.
340              
341             =head2 export_key_raw
342              
343             Export raw public/private key
344              
345             my $private_bytes = $pk->export_key_raw('private');
346             #or
347             my $public_bytes = $pk->export_key_raw('public');
348              
349             =head2 shared_secret
350              
351             # Alice having her priv key $pk and Bob's public key $pkb
352             my $pk = Crypt::PK::X25519->new($priv_key_filename);
353             my $pkb = Crypt::PK::X25519->new($pub_key_filename);
354             my $shared_secret = $pk->shared_secret($pkb);
355              
356             # Bob having his priv key $pk and Alice's public key $pka
357             my $pk = Crypt::PK::X25519->new($priv_key_filename);
358             my $pka = Crypt::PK::X25519->new($pub_key_filename);
359             my $shared_secret = $pk->shared_secret($pka); # same value as computed by Alice
360              
361             =head2 is_private
362              
363             my $rv = $pk->is_private;
364             # 1 .. private key loaded
365             # 0 .. public key loaded
366             # undef .. no key loaded
367              
368             =head2 key2hash
369              
370             my $hash = $pk->key2hash;
371              
372             # returns hash like this (or undef if no key loaded):
373             {
374             curve => "x25519",
375             # raw public key as a hexadecimal string
376             pub => "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41",
377             # raw private key as a hexadecimal string. undef if key is public only
378             priv => "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651",
379             }
380              
381             =head1 SEE ALSO
382              
383             =over
384              
385             =item * L
386              
387             =item * L
388              
389             =back
390              
391             =cut