File Coverage

blib/lib/Crypt/PK/Ed25519.pm
Criterion Covered Total %
statement 18 85 21.1
branch 0 90 0.0
condition 0 44 0.0
subroutine 6 12 50.0
pod 5 5 100.0
total 29 236 12.2


line stmt bran cond sub pod time code
1             package Crypt::PK::Ed25519;
2              
3 2     2   53315 use strict;
  2         12  
  2         47  
4 2     2   9 use warnings;
  2         2  
  2         180  
5             our $VERSION = '0.080';
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   12 use Carp;
  2         9  
  2         128  
13             $Carp::Internal{(__PACKAGE__)}++;
14 2     2   303 use CryptX;
  2         4  
  2         48  
15 2     2   320 use Crypt::PK;
  2         5  
  2         59  
16 2     2   353 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
  2         4  
  2         2151  
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 'Ed25519') {
      0        
      0        
41             # JWK-like structure e.g.
42             # {"kty":"OKP","crv":"Ed25519","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 "ed25519" && ($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 Ed25519 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          
    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 ED25519 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 'Ed25519') {
      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 ($data =~ /-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----/sg) {
90 0 0         $data = pem_to_der($data) or croak "FATAL: PEM/cert decode failed";
91 0           return $self->_import_x509($data);
92             }
93             elsif ($data =~ /-----BEGIN OPENSSH PRIVATE KEY-----(.*?)-----END/sg) {
94             #XXX-FIXME-TODO
95             # https://crypto.stackexchange.com/questions/71789/openssh-ed2215-private-key-format
96             # https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
97 0           croak "FATAL: OPENSSH PRIVATE KEY not supported";
98             }
99             elsif ($data =~ /---- BEGIN SSH2 PUBLIC KEY ----(.*?)---- END SSH2 PUBLIC KEY ----/sg) {
100 0 0         $data = pem_to_der($data) or croak "FATAL: PEM/key decode failed";
101 0           my ($typ, $pubkey) = Crypt::PK::_ssh_parse($data);
102 0 0 0       return $self->_import_raw($pubkey, 0) if $typ eq 'ssh-ed25519' && length($pubkey) == 32;
103             }
104             elsif ($data =~ /(ssh-ed25519)\s+(\S+)/) {
105 0           $data = decode_b64("$2");
106 0           my ($typ, $pubkey) = Crypt::PK::_ssh_parse($data);
107 0 0 0       return $self->_import_raw($pubkey, 0) if $typ eq 'ssh-ed25519' && length($pubkey) == 32;
108             }
109             elsif (length($data) == 32) {
110 0           croak "FATAL: use import_key_raw() to load raw (32 bytes) Ed25519 key";
111             }
112             else {
113             my $rv = eval { $self->_import($data) } ||
114             eval { $self->_import_pkcs8($data, $password) } ||
115 0   0       eval { $self->_import_x509($data) };
116 0 0         return $rv if $rv;
117             }
118 0           croak "FATAL: invalid or unsupported Ed25519 key format";
119             }
120              
121             sub export_key_pem {
122 0     0 1   my ($self, $type, $password, $cipher) = @_;
123 0           local $SIG{__DIE__} = \&CryptX::_croak;
124 0   0       my $key = $self->export_key_der($type||'');
125 0 0         return unless $key;
126 0 0         return der_to_pem($key, "ED25519 PRIVATE KEY", $password, $cipher) if substr($type, 0, 7) eq 'private';
127 0 0         return der_to_pem($key, "PUBLIC KEY") if substr($type,0, 6) eq 'public';
128             }
129              
130             sub export_key_jwk {
131 0     0 1   my ($self, $type, $wanthash) = @_;
132 0           local $SIG{__DIE__} = \&CryptX::_croak;
133 0           my $kh = $self->key2hash;
134 0 0         return unless $kh;
135 0           my $hash = { kty => "OKP", crv => "Ed25519" };
136 0           $hash->{x} = encode_b64u(pack("H*", $kh->{pub}));
137 0 0 0       $hash->{d} = encode_b64u(pack("H*", $kh->{priv})) if $type && $type eq 'private' && $kh->{priv};
      0        
138 0 0         return $wanthash ? $hash : CryptX::_encode_json($hash);
139             }
140              
141 0     0     sub CLONE_SKIP { 1 } # prevent cloning
142              
143             1;
144              
145             =pod
146              
147             =head1 NAME
148              
149             Crypt::PK::Ed25519 - Digital signature based on Ed25519
150              
151             =head1 SYNOPSIS
152              
153             use Crypt::PK::Ed25519;
154              
155             #Signature: Alice
156             my $priv = Crypt::PK::Ed25519->new('Alice_priv_ed25519.der');
157             my $sig = $priv->sign_message($message);
158              
159             #Signature: Bob (received $message + $sig)
160             my $pub = Crypt::PK::Ed25519->new('Alice_pub_ed25519.der');
161             $pub->verify_message($sig, $message) or die "ERROR";
162              
163             #Load key
164             my $pk = Crypt::PK::Ed25519->new;
165             my $pk_hex = "A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D";
166             $pk->import_key_raw(pack("H*", $pk_hex), "public");
167             my $sk = Crypt::PK::Ed25519->new;
168             my $sk_hex = "45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD";
169             $sk->import_key_raw(pack("H*", $sk_hex), "private");
170              
171             #Key generation
172             my $pk = Crypt::PK::Ed25519->new->generate_key;
173             my $private_der = $pk->export_key_der('private');
174             my $public_der = $pk->export_key_der('public');
175             my $private_pem = $pk->export_key_pem('private');
176             my $public_pem = $pk->export_key_pem('public');
177             my $private_raw = $pk->export_key_raw('private');
178             my $public_raw = $pk->export_key_raw('public');
179             my $private_jwk = $pk->export_key_jwk('private');
180             my $public_jwk = $pk->export_key_jwk('public');
181              
182             =head1 DESCRIPTION
183              
184             I
185              
186             =head1 METHODS
187              
188             =head2 new
189              
190             my $pk = Crypt::PK::Ed25519->new();
191             #or
192             my $pk = Crypt::PK::Ed25519->new($priv_or_pub_key_filename);
193             #or
194             my $pk = Crypt::PK::Ed25519->new(\$buffer_containing_priv_or_pub_key);
195              
196             Support for password protected PEM keys
197              
198             my $pk = Crypt::PK::Ed25519->new($priv_pem_key_filename, $password);
199             #or
200             my $pk = Crypt::PK::Ed25519->new(\$buffer_containing_priv_pem_key, $password);
201              
202             =head2 generate_key
203              
204             Uses Yarrow-based cryptographically strong random number generator seeded with
205             random data taken from C (UNIX) or C (Win32).
206              
207             $pk->generate_key;
208              
209             =head2 import_key
210              
211             Loads private or public key in DER or PEM format.
212              
213             $pk->import_key($filename);
214             #or
215             $pk->import_key(\$buffer_containing_key);
216              
217             Support for password protected PEM keys:
218              
219             $pk->import_key($filename, $password);
220             #or
221             $pk->import_key(\$buffer_containing_key, $password);
222              
223             Loading private or public keys form perl hash:
224              
225             $pk->import_key($hashref);
226              
227             # the $hashref is either a key exported via key2hash
228             $pk->import_key({
229             curve => "ed25519",
230             pub => "A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D",
231             priv => "45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD",
232             });
233              
234             # or a hash with items corresponding to JWK (JSON Web Key)
235             $pk->import_key({
236             kty => "OKP",
237             crv => "Ed25519",
238             d => "RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0",
239             x => "oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0",
240             });
241              
242             Supported key formats:
243              
244             # all formats can be loaded from a file
245             my $pk = Crypt::PK::Ed25519->new($filename);
246              
247             # or from a buffer containing the key
248             my $pk = Crypt::PK::Ed25519->new(\$buffer_with_key);
249              
250             =over
251              
252             =item * Ed25519 private keys in PEM format
253              
254             -----BEGIN ED25519 PRIVATE KEY-----
255             MC4CAQAwBQYDK2VwBCIEIEXBCbpv0k6LZ9I++2uS2ZzUV+ITcXLA10n+K1oMFC2t
256             -----END ED25519 PRIVATE KEY-----
257              
258             =item * Ed25519 private keys in password protected PEM format
259              
260             -----BEGIN ED25519 PRIVATE KEY-----
261             Proc-Type: 4,ENCRYPTED
262             DEK-Info: DES-CBC,6A64D756D49C1EFF
263              
264             8xQ7OyfQ10IITNEKcJGZA53Z1yk+NJQU7hrKqXwChZtgWNInhMBJRl9pozLKDSkH
265             v7u6EOve8NY=
266             -----END ED25519 PRIVATE KEY-----
267              
268             =item * PKCS#8 private keys
269              
270             -----BEGIN PRIVATE KEY-----
271             MC4CAQAwBQYDK2VwBCIEIEXBCbpv0k6LZ9I++2uS2ZzUV+ITcXLA10n+K1oMFC2t
272             -----END PRIVATE KEY-----
273              
274             =item * PKCS#8 encrypted private keys
275              
276             -----BEGIN ENCRYPTED PRIVATE KEY-----
277             MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAjPx9JkdpRH2QICCAAw
278             DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIWWieQojaWTcEOGj43SxqHUys4Eb2M27N
279             AkhqpmhosOxKrpGi0L3h8m8ipHE8EwI94NeOMsjfVw60aJuCrssY5vKN
280             -----END ENCRYPTED PRIVATE KEY-----
281              
282             =item * Ed25519 public keys in PEM format
283              
284             -----BEGIN PUBLIC KEY-----
285             MCowBQYDK2VwAyEAoF0a6lgwrJplzfs4RmDUl+NpfEa0Gc8s7IXei9JFRZ0=
286             -----END PUBLIC KEY-----
287              
288             =item * Ed25519 public key from X509 certificate
289              
290             -----BEGIN CERTIFICATE-----
291             MIIBODCB66ADAgECAhRWDU9FZBBUZ7KTdX8f7Bco8jsoaTAFBgMrZXAwETEPMA0G
292             A1UEAwwGQ3J5cHRYMCAXDTIwMDExOTEzMDIwMloYDzIyOTMxMTAyMTMwMjAyWjAR
293             MQ8wDQYDVQQDDAZDcnlwdFgwKjAFBgMrZXADIQCgXRrqWDCsmmXN+zhGYNSX42l8
294             RrQZzyzshd6L0kVFnaNTMFEwHQYDVR0OBBYEFHCGFtVibAxxWYyRt5wazMpqSZDV
295             MB8GA1UdIwQYMBaAFHCGFtVibAxxWYyRt5wazMpqSZDVMA8GA1UdEwEB/wQFMAMB
296             Af8wBQYDK2VwA0EAqG/+98smzqF/wmFX3zHXSaA67as202HnBJod1Tiurw1f+lr3
297             BX6OMtsDpgRq9O77IF1Qyx/MdJEwwErczOIbAA==
298             -----END CERTIFICATE-----
299              
300             =item * SSH public Ed25519 keys
301              
302             ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++
303              
304             =item * SSH public Ed25519 keys (RFC-4716 format)
305              
306             ---- BEGIN SSH2 PUBLIC KEY ----
307             Comment: "256-bit ED25519, converted from OpenSSH"
308             AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++
309             ---- END SSH2 PUBLIC KEY ----
310              
311             =item * Ed25519 private keys in JSON Web Key (JWK) format
312              
313             See L
314              
315             {
316             "kty":"OKP",
317             "crv":"Ed25519",
318             "x":"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0",
319             "d":"RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0",
320             }
321              
322             B For JWK support you need to have L module installed.
323              
324             =item * Ed25519 public keys in JSON Web Key (JWK) format
325              
326             {
327             "kty":"OKP",
328             "crv":"Ed25519",
329             "x":"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0",
330             }
331              
332             B For JWK support you need to have L module installed.
333              
334             =back
335              
336             =head2 import_key_raw
337              
338             Import raw public/private key - can load raw key data exported by L.
339              
340             $pk->import_key_raw($key, 'public');
341             $pk->import_key_raw($key, 'private');
342              
343             =head2 export_key_der
344              
345             my $private_der = $pk->export_key_der('private');
346             #or
347             my $public_der = $pk->export_key_der('public');
348              
349             =head2 export_key_pem
350              
351             my $private_pem = $pk->export_key_pem('private');
352             #or
353             my $public_pem = $pk->export_key_pem('public');
354              
355             Support for password protected PEM keys
356              
357             my $private_pem = $pk->export_key_pem('private', $password);
358             #or
359             my $private_pem = $pk->export_key_pem('private', $password, $cipher);
360              
361             # supported ciphers: 'DES-CBC'
362             # 'DES-EDE3-CBC'
363             # 'SEED-CBC'
364             # 'CAMELLIA-128-CBC'
365             # 'CAMELLIA-192-CBC'
366             # 'CAMELLIA-256-CBC'
367             # 'AES-128-CBC'
368             # 'AES-192-CBC'
369             # 'AES-256-CBC' (DEFAULT)
370              
371             =head2 export_key_jwk
372              
373             Exports public/private keys as a JSON Web Key (JWK).
374              
375             my $private_json_text = $pk->export_key_jwk('private');
376             #or
377             my $public_json_text = $pk->export_key_jwk('public');
378              
379             Also exports public/private keys as a perl HASH with JWK structure.
380              
381             my $jwk_hash = $pk->export_key_jwk('private', 1);
382             #or
383             my $jwk_hash = $pk->export_key_jwk('public', 1);
384              
385             B For JWK support you need to have L module installed.
386              
387             =head2 export_key_raw
388              
389             Export raw public/private key
390              
391             my $private_bytes = $pk->export_key_raw('private');
392             #or
393             my $public_bytes = $pk->export_key_raw('public');
394              
395             =head2 sign_message
396              
397             my $signature = $priv->sign_message($message);
398              
399             =head2 verify_message
400              
401             my $valid = $pub->verify_message($signature, $message)
402              
403             =head2 is_private
404              
405             my $rv = $pk->is_private;
406             # 1 .. private key loaded
407             # 0 .. public key loaded
408             # undef .. no key loaded
409              
410             =head2 key2hash
411              
412             my $hash = $pk->key2hash;
413              
414             # returns hash like this (or undef if no key loaded):
415             {
416             curve => "ed25519",
417             # raw public key as a hexadecimal string
418             pub => "A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D",
419             # raw private key as a hexadecimal string. undef if key is public only
420             priv => "45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD",
421             }
422              
423             =head1 SEE ALSO
424              
425             =over
426              
427             =item * L
428              
429             =item * L
430              
431             =item * L
432              
433             =back
434              
435             =cut