File Coverage

blib/lib/Crypt/PK/Ed448.pm
Criterion Covered Total %
statement 20 70 28.5
branch 1 62 1.6
condition 0 40 0.0
subroutine 7 12 58.3
pod 5 5 100.0
total 33 189 17.4


line stmt bran cond sub pod time code
1             package Crypt::PK::Ed448;
2              
3 3     3   89230 use strict;
  3         6  
  3         93  
4 3     3   16 use warnings;
  3         5  
  3         428  
5             our $VERSION = '0.089';
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 3     3   19 use Carp;
  3         7  
  3         249  
13             $Carp::Internal{(__PACKAGE__)}++;
14 3     3   396 use CryptX;
  3         6  
  3         78  
15 3     3   352 use Crypt::PK;
  3         6  
  3         95  
16 3     3   386 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u pem_to_der der_to_pem);
  3         6  
  3         3935  
17              
18             sub new {
19 1     1 1 9625 my $self = shift->_new();
20 1 50       56 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) == 57;
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 0 0         if (ref($key) eq 'HASH') {
39 0 0 0       if ($key->{kty} && $key->{kty} eq "OKP" && $key->{crv} && $key->{crv} eq 'Ed448') {
      0        
      0        
40 0 0         return $self->_import_raw(decode_b64u($key->{d}), 1) if $key->{d};
41 0 0         return $self->_import_raw(decode_b64u($key->{x}), 0) if $key->{x};
42             }
43 0 0 0       if ($key->{curve} && $key->{curve} eq "ed448" && ($key->{priv} || $key->{pub})) {
      0        
      0        
44 0 0         return $self->_import_raw(pack("H*", $key->{priv}), 1) if $key->{priv};
45 0 0         return $self->_import_raw(pack("H*", $key->{pub}), 0) if $key->{pub};
46             }
47 0           croak "FATAL: unexpected Ed448 key hash";
48             }
49              
50 0           my $data;
51 0 0         if (ref($key) eq 'SCALAR') {
    0          
52 0           $data = $$key;
53             }
54             elsif (-f $key) {
55 0           $data = read_rawfile($key);
56             }
57             else {
58 0           croak "FATAL: non-existing file '$key'";
59             }
60 0 0         croak "FATAL: invalid key data" unless $data;
61              
62 0 0         if ($data =~ /-----BEGIN (PUBLIC|PRIVATE|ENCRYPTED PRIVATE) KEY-----(.+?)-----END (PUBLIC|PRIVATE|ENCRYPTED PRIVATE) KEY-----/s) {
    0          
    0          
    0          
63 0           return $self->_import_pem($data, $password);
64             }
65             elsif ($data =~ /-----BEGIN CERTIFICATE-----(.+?)-----END CERTIFICATE-----/s) {
66 0           return $self->_import_x509(pem_to_der($data));
67             }
68             elsif ($data =~ /^\s*(\{.*?\})\s*$/s) {
69 0   0       my $h = CryptX::_decode_json("$1") || {};
70 0 0 0       if ($h->{kty} && $h->{kty} eq "OKP" && $h->{crv} && $h->{crv} eq 'Ed448') {
      0        
      0        
71 0 0         return $self->_import_raw(decode_b64u($h->{d}), 1) if $h->{d};
72 0 0         return $self->_import_raw(decode_b64u($h->{x}), 0) if $h->{x};
73             }
74             }
75             elsif (length($data) == 57) {
76 0           croak "FATAL: use import_key_raw() to load raw (57 bytes) Ed448 key";
77             }
78             else {
79             my $rv = eval { $self->_import($data) } ||
80             eval { $self->_import_pkcs8($data, $password) } ||
81 0   0       eval { $self->_import_x509($data) };
82 0 0         return $rv if $rv;
83             }
84 0           croak "FATAL: invalid or unsupported Ed448 key format";
85             }
86              
87             sub export_key_pem {
88 0     0 1   my ($self, $type, $password, $cipher) = @_;
89 0           local $SIG{__DIE__} = \&CryptX::_croak;
90 0   0       my $key = $self->export_key_der($type||'');
91 0 0         return unless $key;
92 0 0         return der_to_pem($key, "PRIVATE KEY", $password, $cipher) if $type eq 'private';
93 0 0         return der_to_pem($key, "PUBLIC KEY") if $type eq 'public';
94             }
95              
96             sub export_key_jwk {
97 0     0 1   my ($self, $type, $wanthash) = @_;
98 0           local $SIG{__DIE__} = \&CryptX::_croak;
99 0           my $kh = $self->key2hash;
100 0 0         return unless $kh;
101 0           my $hash = { kty => "OKP", crv => "Ed448" };
102 0           $hash->{x} = encode_b64u(pack("H*", $kh->{pub}));
103 0 0 0       $hash->{d} = encode_b64u(pack("H*", $kh->{priv})) if $type && $type eq 'private' && $kh->{priv};
      0        
104 0 0         return $wanthash ? $hash : CryptX::_encode_json($hash);
105             }
106              
107 0     0     sub CLONE_SKIP { 1 } # prevent cloning
108              
109             1;
110              
111             =pod
112              
113             =head1 NAME
114              
115             Crypt::PK::Ed448 - Digital signature based on Ed448
116              
117             =head1 SYNOPSIS
118              
119             use Crypt::PK::Ed448;
120              
121             my $message = 'hello world';
122             my $signer = Crypt::PK::Ed448->new->generate_key;
123             my $signature = $signer->sign_message($message);
124              
125             my $public_der = $signer->export_key_der('public');
126             my $verifier = Crypt::PK::Ed448->new(\$public_der);
127             $verifier->verify_message($signature, $message) or die "ERROR";
128              
129             my $pk = Crypt::PK::Ed448->new;
130             $pk->import_key_raw(
131             pack("H*", "1b0055aad3b239a0fa1ed1ea8023151a5791d0bb556435299da6cf1aaa272d858b0238822654bc15f64adbab97f1bb9ec848d72cd8ad856800"),
132             "public",
133             );
134              
135             my $sk = Crypt::PK::Ed448->new;
136             $sk->import_key_raw(
137             pack("H*", "f82bd65291965de46d87c7447863924e8efb8da36993618a784cd3b69a6d66e61cdc0a48a31e66bd8e81e4d77cedc311aa0f72a322ef3e4fad"),
138             "private",
139             );
140              
141             =head1 DESCRIPTION
142              
143             I
144              
145             =head1 METHODS
146              
147             =head2 new
148              
149             my $source = Crypt::PK::Ed448->new();
150             $source->generate_key;
151              
152             my $public_der = $source->export_key_der('public');
153             my $pub = Crypt::PK::Ed448->new(\$public_der);
154              
155             my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
156             my $priv = Crypt::PK::Ed448->new(\$private_pem, 'secret');
157              
158             Passing C<$filename> or C<\$buffer> to C is equivalent: both forms
159             immediately import the key material into the new object.
160              
161             =head2 generate_key
162              
163             Returns the object itself (for chaining).
164              
165             $pk->generate_key;
166              
167             =head2 import_key
168              
169             Loads Ed448 private or public keys from DER, PEM, PKCS#8, X.509 certificates, SubjectPublicKeyInfo, or JWK.
170              
171             my $source = Crypt::PK::Ed448->new();
172             $source->generate_key;
173              
174             my $public_der = $source->export_key_der('public');
175             my $pub = Crypt::PK::Ed448->new();
176             $pub->import_key(\$public_der);
177             my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
178             my $priv = Crypt::PK::Ed448->new();
179             $priv->import_key(\$private_pem, 'secret');
180             $pk->import_key({
181             curve => "ed448",
182             pub => "1B0055AAD3B239A0FA1ED1EA8023151A5791D0BB556435299DA6CF1AAA272D858B0238822654BC15F64ADBAB97F1BB9EC848D72CD8AD856800",
183             priv => "F82BD65291965DE46D87C7447863924E8EFB8DA36993618A784CD3B69A6D66E61CDC0A48A31E66BD8E81E4D77CEDC311AA0F72A322EF3E4FAD",
184             });
185             $pk->import_key({
186             kty => "OKP",
187             crv => "Ed448",
188             d => "-CvWUpGWXeRth8dEeGOSTo77jaNpk2GKeEzTtpptZuYc3ApIox5mvY6B5Nd87cMRqg9yoyLvPk-t",
189             x => "GwBVqtOyOaD6HtHqgCMVGleR0LtVZDUpnabPGqonLYWLAjiCJlS8FfZK26uX8bueyEjXLNithWgA",
190             });
191              
192             The same method also accepts filenames instead of buffers.
193              
194             =head2 import_key_raw
195              
196             Import raw public/private key bytes.
197              
198             $pk->import_key_raw($key, 'public');
199             $pk->import_key_raw($key, 'private');
200              
201             The raw key must be exactly 57 bytes long.
202              
203             =head2 export_key_der
204              
205             Returns the key as a binary DER-encoded string.
206              
207             my $der = $pk->export_key_der('private');
208             my $der = $pk->export_key_der('public');
209              
210             =head2 export_key_pem
211              
212             Returns the key as a PEM-encoded string (ASCII).
213              
214             my $pem = $pk->export_key_pem('private');
215             my $pem = $pk->export_key_pem('public');
216             my $pem = $pk->export_key_pem('private', $password, 'AES-256-CBC');
217              
218             =head2 export_key_jwk
219              
220             Returns a JSON string, or a hashref if the optional second argument is true.
221              
222             my $json = $pk->export_key_jwk('private');
223             my $hash = $pk->export_key_jwk('public', 1);
224              
225             =head2 export_key_raw
226              
227             Returns the raw key as a binary string.
228              
229             my $raw = $pk->export_key_raw('private');
230             my $raw = $pk->export_key_raw('public');
231              
232             =head2 sign_message
233              
234             Returns the signature as a binary string. Ed448 uses a fixed hash internally
235             (SHAKE256); unlike RSA or ECDSA there is no C<$hash_name> parameter.
236              
237             my $signature = $priv->sign_message($message);
238              
239             =head2 verify_message
240              
241             Returns C<1> if the signature is valid, C<0> otherwise.
242              
243             my $valid = $pub->verify_message($signature, $message);
244              
245             =head2 sign_message_ctx
246              
247             Signs a message using Ed448ctx (RFC 8032 Section 5.2), which includes a
248             mandatory context string (at most 255 bytes). The context string makes the
249             signature domain-separated: the same key signing the same message with a
250             different context produces a different (and incompatible) signature.
251              
252             B In contrast to Ed25519, plain Ed448 (L) is formally
253             defined with an empty context string, so Ed448 and Ed448ctx with an empty
254             context produce the same signature.
255              
256             my $signature = $priv->sign_message_ctx($message, $context);
257              
258             =head2 verify_message_ctx
259              
260             Verifies a signature produced by L.
261              
262             my $valid = $pub->verify_message_ctx($signature, $message, $context);
263              
264             =head2 sign_message_ph
265              
266             Signs a message using Ed448ph (RFC 8032 Section 5.2), the "pre-hashed"
267             variant. The message is first hashed with SHAKE256 internally before signing.
268             This is useful when signing very large messages or when only a hash of the
269             message is available. An optional context string (at most 255 bytes) can be
270             provided; it defaults to the empty string if omitted.
271              
272             my $signature = $priv->sign_message_ph($message);
273             my $signature = $priv->sign_message_ph($message, $context);
274              
275             =head2 verify_message_ph
276              
277             Verifies a signature produced by L.
278              
279             my $valid = $pub->verify_message_ph($signature, $message);
280             my $valid = $pub->verify_message_ph($signature, $message, $context);
281              
282             =head2 is_private
283              
284             my $rv = $pk->is_private;
285              
286             =head2 key2hash
287              
288             Returns a hashref with the key components, or C if no key is loaded.
289              
290             my $hash = $pk->key2hash;
291              
292             Returns a hash like:
293              
294             {
295             curve => "ed448",
296             pub => "1B0055AAD3B239A0FA1ED1EA8023151A5791D0BB556435299DA6CF1AAA272D858B0238822654BC15F64ADBAB97F1BB9EC848D72CD8AD856800",
297             priv => "F82BD65291965DE46D87C7447863924E8EFB8DA36993618A784CD3B69A6D66E61CDC0A48A31E66BD8E81E4D77CEDC311AA0F72A322EF3E4FAD",
298             }
299              
300             =head1 OpenSSL interoperability
301              
302             # Generate a key with OpenSSL
303             # openssl genpkey -algorithm ed448 -out ed448_priv.pem
304             # openssl pkey -in ed448_priv.pem -pubout -out ed448_pub.pem
305              
306             # Load the OpenSSL-generated key in CryptX
307             use Crypt::PK::Ed448;
308             my $priv = Crypt::PK::Ed448->new("ed448_priv.pem");
309             my $pub = Crypt::PK::Ed448->new("ed448_pub.pem");
310              
311             # Sign in CryptX, verify with OpenSSL
312             my $message = "hello";
313             my $signature = $priv->sign_message($message);
314              
315             # Export CryptX key for OpenSSL
316             my $pem = $priv->export_key_pem('private');
317             # then: openssl pkey -in priv.pem -text -noout
318              
319             =head1 SEE ALSO
320              
321             =over
322              
323             =item * L
324              
325             =item * L
326              
327             =back
328              
329             =cut