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   84450 use strict;
  3         6  
  3         80  
4 3     3   13 use warnings;
  3         4  
  3         330  
5             our $VERSION = '0.088_004';
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   13 use Carp;
  3         4  
  3         180  
13             $Carp::Internal{(__PACKAGE__)}++;
14 3     3   377 use CryptX;
  3         5  
  3         63  
15 3     3   343 use Crypt::PK;
  3         5  
  3         88  
16 3     3   378 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u pem_to_der der_to_pem);
  3         4  
  3         2630  
17              
18             sub new {
19 1     1 1 6838 my $self = shift->_new();
20 1 50       13 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(pack("H*", "1b0055aad3b239a0fa1ed1ea8023151a5791d0bb556435299da6cf1aaa272d858b0238822654bc15f64adbab97f1bb9ec848d72cd8ad856800"), "public");
131              
132             my $sk = Crypt::PK::Ed448->new;
133             $sk->import_key_raw(pack("H*", "f82bd65291965de46d87c7447863924e8efb8da36993618a784cd3b69a6d66e61cdc0a48a31e66bd8e81e4d77cedc311aa0f72a322ef3e4fad"), "private");
134              
135             =head1 DESCRIPTION
136              
137             I
138              
139             =head1 METHODS
140              
141             =head2 new
142              
143             my $source = Crypt::PK::Ed448->new();
144             $source->generate_key;
145              
146             my $public_der = $source->export_key_der('public');
147             my $pub = Crypt::PK::Ed448->new(\$public_der);
148              
149             my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
150             my $priv = Crypt::PK::Ed448->new(\$private_pem, 'secret');
151              
152             Passing C<$filename> or C<\$buffer> to C is equivalent: both forms
153             immediately import the key material into the new object.
154              
155             =head2 generate_key
156              
157             Returns the object itself (for chaining).
158              
159             $pk->generate_key;
160              
161             =head2 import_key
162              
163             Loads Ed448 private or public keys from DER, PEM, PKCS#8, X.509 certificates, SubjectPublicKeyInfo, or JWK.
164              
165             my $source = Crypt::PK::Ed448->new();
166             $source->generate_key;
167              
168             my $public_der = $source->export_key_der('public');
169             my $pub = Crypt::PK::Ed448->new();
170             $pub->import_key(\$public_der);
171             my $private_pem = $source->export_key_pem('private', 'secret', 'AES-256-CBC');
172             my $priv = Crypt::PK::Ed448->new();
173             $priv->import_key(\$private_pem, 'secret');
174             $pk->import_key({
175             curve => "ed448",
176             pub => "1B0055AAD3B239A0FA1ED1EA8023151A5791D0BB556435299DA6CF1AAA272D858B0238822654BC15F64ADBAB97F1BB9EC848D72CD8AD856800",
177             priv => "F82BD65291965DE46D87C7447863924E8EFB8DA36993618A784CD3B69A6D66E61CDC0A48A31E66BD8E81E4D77CEDC311AA0F72A322EF3E4FAD",
178             });
179             $pk->import_key({
180             kty => "OKP",
181             crv => "Ed448",
182             d => "-CvWUpGWXeRth8dEeGOSTo77jaNpk2GKeEzTtpptZuYc3ApIox5mvY6B5Nd87cMRqg9yoyLvPk-t",
183             x => "GwBVqtOyOaD6HtHqgCMVGleR0LtVZDUpnabPGqonLYWLAjiCJlS8FfZK26uX8bueyEjXLNithWgA",
184             });
185              
186             The same method also accepts filenames instead of buffers.
187              
188             =head2 import_key_raw
189              
190             Import raw public/private key bytes.
191              
192             $pk->import_key_raw($key, 'public');
193             $pk->import_key_raw($key, 'private');
194              
195             The raw key must be exactly 57 bytes long.
196              
197             =head2 export_key_der
198              
199             Returns the key as a binary DER-encoded string.
200              
201             my $der = $pk->export_key_der('private');
202             my $der = $pk->export_key_der('public');
203              
204             =head2 export_key_pem
205              
206             Returns the key as a PEM-encoded string (ASCII).
207              
208             my $pem = $pk->export_key_pem('private');
209             my $pem = $pk->export_key_pem('public');
210             my $pem = $pk->export_key_pem('private', $password, 'AES-256-CBC');
211              
212             =head2 export_key_jwk
213              
214             Returns a JSON string, or a hashref if the optional second argument is true.
215              
216             my $json = $pk->export_key_jwk('private');
217             my $hash = $pk->export_key_jwk('public', 1);
218              
219             =head2 export_key_raw
220              
221             Returns the raw key as a binary string.
222              
223             my $raw = $pk->export_key_raw('private');
224             my $raw = $pk->export_key_raw('public');
225              
226             =head2 sign_message
227              
228             Returns the signature as a binary string. Ed448 uses a fixed hash internally
229             (SHAKE256); unlike RSA or ECDSA there is no C<$hash_name> parameter.
230              
231             my $signature = $priv->sign_message($message);
232              
233             =head2 verify_message
234              
235             Returns C<1> if the signature is valid, C<0> otherwise.
236              
237             my $valid = $pub->verify_message($signature, $message);
238              
239             =head2 sign_message_ctx
240              
241             I
242              
243             Signs a message using Ed448ctx (RFC 8032 Section 5.2), which includes a
244             mandatory context string (at most 255 bytes). The context string makes the
245             signature domain-separated: the same key signing the same message with a
246             different context produces a different (and incompatible) signature.
247              
248             B In contrast to Ed25519, plain Ed448 (L) is formally
249             defined with an empty context string, so Ed448 and Ed448ctx with an empty
250             context produce the same signature.
251              
252             my $signature = $priv->sign_message_ctx($message, $context);
253              
254             =head2 verify_message_ctx
255              
256             I
257              
258             Verifies a signature produced by L.
259              
260             my $valid = $pub->verify_message_ctx($signature, $message, $context);
261              
262             =head2 sign_message_ph
263              
264             I
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             I
278              
279             Verifies a signature produced by L.
280              
281             my $valid = $pub->verify_message_ph($signature, $message);
282             my $valid = $pub->verify_message_ph($signature, $message, $context);
283              
284             =head2 is_private
285              
286             my $rv = $pk->is_private;
287              
288             =head2 key2hash
289              
290             Returns a hashref with the key components, or C if no key is loaded.
291              
292             my $hash = $pk->key2hash;
293              
294             Returns a hash like:
295              
296             {
297             curve => "ed448",
298             pub => "1B0055AAD3B239A0FA1ED1EA8023151A5791D0BB556435299DA6CF1AAA272D858B0238822654BC15F64ADBAB97F1BB9EC848D72CD8AD856800",
299             priv => "F82BD65291965DE46D87C7447863924E8EFB8DA36993618A784CD3B69A6D66E61CDC0A48A31E66BD8E81E4D77CEDC311AA0F72A322EF3E4FAD",
300             }
301              
302             =head1 OpenSSL interoperability
303              
304             # Generate a key with OpenSSL
305             # openssl genpkey -algorithm ed448 -out ed448_priv.pem
306             # openssl pkey -in ed448_priv.pem -pubout -out ed448_pub.pem
307              
308             # Load the OpenSSL-generated key in CryptX
309             use Crypt::PK::Ed448;
310             my $priv = Crypt::PK::Ed448->new("ed448_priv.pem");
311             my $pub = Crypt::PK::Ed448->new("ed448_pub.pem");
312              
313             # Sign in CryptX, verify with OpenSSL
314             my $message = "hello";
315             my $signature = $priv->sign_message($message);
316              
317             # Export CryptX key for OpenSSL
318             my $pem = $priv->export_key_pem('private');
319             # then: openssl pkey -in priv.pem -text -noout
320              
321             =head1 SEE ALSO
322              
323             =over
324              
325             =item * L
326              
327             =item * L
328              
329             =back
330              
331             =cut