Codebase list libcryptx-perl / 3037385
Crypt::PK::X25519 + Crypt::PK::Ed25519 (.pm + .t) key loading and basic stuff Karel Miko 4 years ago
12 changed file(s) with 657 addition(s) and 70 deletion(s). Raw diff Collapse all Expand all
147147 ecc_key key;
148148 } *Crypt__PK__ECC;
149149
150 struct curve25519_struct {
150 typedef struct ed25519_struct { /* used by Crypt::PK::Ed25519 */
151151 prng_state pstate;
152152 int pindex;
153153 curve25519_key key;
154 } ;
155 typedef struct curve25519_struct *Crypt__PK__Ed25519; /* used by Crypt::PK::Ed25519 */
156 typedef struct curve25519_struct *Crypt__PK__X25519; /* used by Crypt::PK::X25519 */
154 int initialized;
155 } *Crypt__PK__Ed25519;
156
157 typedef struct x25519_struct { /* used by Crypt::PK::X25519 */
158 prng_state pstate;
159 int pindex;
160 curve25519_key key;
161 int initialized;
162 } *Crypt__PK__X25519;
157163
158164 int mp_tohex_with_leading_zero(mp_int * a, char *str, int maxlen, int minlen) {
159165 int len, rv;
66 CODE:
77 {
88 int rv;
9 Newz(0, RETVAL, 1, struct curve25519_struct);
9 Newz(0, RETVAL, 1, struct ed25519_struct);
1010 if (!RETVAL) croak("FATAL: Newz failed");
11 RETVAL->initialized = 0;
1112 RETVAL->pindex = find_prng("chacha20");
1213 RETVAL->key.type = -1;
1314 if (RETVAL->pindex == -1) {
3132 /* gen the key */
3233 rv = ed25519_make_key(&self->pstate, self->pindex, &self->key);
3334 if (rv != CRYPT_OK) croak("FATAL: ed25519_make_key failed: %s", error_to_string(rv));
35 self->initialized = 1;
3436 XPUSHs(ST(0)); /* return self */
3537 }
3638
4648 self->key.type = -1;
4749 rv = ed25519_import(data, (unsigned long)data_len, &self->key);
4850 if (rv != CRYPT_OK) croak("FATAL: ed25519_import failed: %s", error_to_string(rv));
51 self->initialized = 1;
4952 XPUSHs(ST(0)); /* return self */
5053 }
5154
6467 self->key.type = -1;
6568 rv = ed25519_import_pkcs8(data, (unsigned long)data_len, pwd, (unsigned long)pwd_len, &self->key);
6669 if (rv != CRYPT_OK) croak("FATAL: ed25519_import_pkcs8 failed: %s", error_to_string(rv));
70 self->initialized = 1;
6771 XPUSHs(ST(0)); /* return self */
6872 }
6973
7983 self->key.type = -1;
8084 rv = ed25519_import_x509(data, (unsigned long)data_len, &self->key);
8185 if (rv != CRYPT_OK) croak("FATAL: ed25519_import_x509 failed: %s", error_to_string(rv));
82 XPUSHs(ST(0)); /* return self */
83 }
84
85 void
86 _import_key_data(Crypt::PK::Ed25519 self, SV * priv, SV * pub)
87 PPCODE:
88 {
89 int rv, type;
90 unsigned char *priv_data=NULL, *pub_data=NULL;
91 STRLEN priv_len=0, pub_len=0;
92
93 if (SvOK(priv)) {
94 priv_data = (unsigned char *)SvPVbyte(priv, priv_len);
95 }
96 if (SvOK(pub)) {
97 pub_data = (unsigned char *)SvPVbyte(pub, pub_len);
98 }
99 self->key.type = -1;
100 rv = ed25519_set_key(priv_data, (unsigned long)priv_len, pub_data, (unsigned long)pub_len, &self->key);
101 if (rv != CRYPT_OK) croak("FATAL: ed25519_set_key failed: %s", error_to_string(rv));
86 self->initialized = 1;
87 XPUSHs(ST(0)); /* return self */
88 }
89
90 void
91 _import_raw(Crypt::PK::Ed25519 self, SV * key, int which)
92 PPCODE:
93 {
94 int rv;
95 unsigned char *key_data=NULL;
96 STRLEN key_len=0;
97
98 if (SvOK(key)) {
99 key_data = (unsigned char *)SvPVbyte(key, key_len);
100 }
101 if (which == 0) {
102 rv = ed25519_import_raw(key_data, (unsigned long)key_len, PK_PUBLIC, &self->key);
103 }
104 else if (which == 1) {
105 rv = ed25519_import_raw(key_data, (unsigned long)key_len, PK_PRIVATE, &self->key);
106 }
107 else {
108 croak("FATAL: import_raw invalid type '%d'", which);
109 }
110 if (rv != CRYPT_OK) croak("FATAL: ed25519_import_raw failed: %s", error_to_string(rv));
111 self->initialized = 1;
102112 XPUSHs(ST(0)); /* return self */
103113 }
104114
105115 int
106116 is_private(Crypt::PK::Ed25519 self)
107117 CODE:
108 if (self->key.type == -1) XSRETURN_UNDEF;
118 if (self->initialized == 0 || self->key.type == -1) XSRETURN_UNDEF;
109119 RETVAL = (self->key.type == PK_PRIVATE) ? 1 : 0;
110120 OUTPUT:
111121 RETVAL
114124 key2hash(Crypt::PK::Ed25519 self)
115125 PREINIT:
116126 HV *rv_hash;
117 char buf[20001];
127 char buf[256];
128 unsigned long blen;
118129 SV **not_used;
119 CODE:
120 if (self->key.type == -1) XSRETURN_UNDEF;
130 int rv;
131 CODE:
132 if (self->initialized == 0) XSRETURN_UNDEF;
121133 rv_hash = newHV();
122134 /* priv */
123135 if (self->key.type == PK_PRIVATE) {
124 not_used = hv_store(rv_hash, "priv", 4, newSVpv(self->key.priv, sizeof(self->key.priv)), 0);
136 blen = sizeof(buf);
137 rv = base16_encode(self->key.priv, sizeof(self->key.priv), buf, &blen, 0);
138 if (rv != CRYPT_OK) croak("FATAL: base16_encode failed: %s", error_to_string(rv));
139 not_used = hv_store(rv_hash, "priv", 4, newSVpv(buf, blen), 0);
125140 }
126141 else {
127142 not_used = hv_store(rv_hash, "priv", 4, &PL_sv_undef, 0);
128143 }
129144 /* pub */
130 not_used = hv_store(rv_hash, "pub", 3, newSVpv(self->key.pub, sizeof(self->key.pub)), 0);
131 /* algo */
132 not_used = hv_store(rv_hash, "algo", 4, newSVpv("ed25519", 0), 0);
145 blen = sizeof(buf);
146 rv = base16_encode(self->key.pub, sizeof(self->key.pub), buf, &blen, 0);
147 if (rv != CRYPT_OK) croak("FATAL: base16_encode failed: %s", error_to_string(rv));
148 not_used = hv_store(rv_hash, "pub", 3, newSVpv(buf, blen), 0);
149 /* curve */
150 not_used = hv_store(rv_hash, "curve", 5, newSVpv("ed25519", 0), 0);
133151 LTC_UNUSED_PARAM(not_used);
134152 RETVAL = newRV_noinc((SV*)rv_hash);
135153 OUTPUT:
150168 RETVAL = newSVpvn((char*)out, out_len);
151169 }
152170 else if (strnEQ(type, "public", 6)) {
153 rv = ed25519_export(out, &out_len, PK_PUBLIC|PK_STD, &self->key);
154 if (rv != CRYPT_OK) croak("FATAL: ed25519_export(PK_PUBLIC|PK_STD) failed: %s", error_to_string(rv));
171 rv = ed25519_export(out, &out_len, PK_PUBLIC, &self->key);
172 if (rv != CRYPT_OK) croak("FATAL: ed25519_export(PK_PUBLIC) failed: %s", error_to_string(rv));
155173 RETVAL = newSVpvn((char*)out, out_len);
156174 }
157175 else {
221239 DESTROY(Crypt::PK::Ed25519 self)
222240 CODE:
223241 Safefree(self);
224
66 CODE:
77 {
88 int rv;
9 Newz(0, RETVAL, 1, struct curve25519_struct);
9 Newz(0, RETVAL, 1, struct x25519_struct);
1010 if (!RETVAL) croak("FATAL: Newz failed");
11 RETVAL->initialized = 0;
1112 RETVAL->pindex = find_prng("chacha20");
1213 RETVAL->key.type = -1;
1314 if (RETVAL->pindex == -1) {
3132 /* gen the key */
3233 rv = x25519_make_key(&self->pstate, self->pindex, &self->key);
3334 if (rv != CRYPT_OK) croak("FATAL: x25519_make_key failed: %s", error_to_string(rv));
35 self->initialized = 1;
3436 XPUSHs(ST(0)); /* return self */
3537 }
3638
4648 self->key.type = -1;
4749 rv = x25519_import(data, (unsigned long)data_len, &self->key);
4850 if (rv != CRYPT_OK) croak("FATAL: x25519_import failed: %s", error_to_string(rv));
51 self->initialized = 1;
4952 XPUSHs(ST(0)); /* return self */
5053 }
5154
6467 self->key.type = -1;
6568 rv = x25519_import_pkcs8(data, (unsigned long)data_len, pwd, (unsigned long)pwd_len, &self->key);
6669 if (rv != CRYPT_OK) croak("FATAL: x25519_import_pkcs8 failed: %s", error_to_string(rv));
70 self->initialized = 1;
6771 XPUSHs(ST(0)); /* return self */
6872 }
6973
7983 self->key.type = -1;
8084 rv = x25519_import_x509(data, (unsigned long)data_len, &self->key);
8185 if (rv != CRYPT_OK) croak("FATAL: x25519_import_x509 failed: %s", error_to_string(rv));
82 XPUSHs(ST(0)); /* return self */
83 }
84
85 void
86 _import_key_data(Crypt::PK::X25519 self, SV * priv, SV * pub)
87 PPCODE:
88 {
89 int rv, type;
90 unsigned char *priv_data=NULL, *pub_data=NULL;
91 STRLEN priv_len=0, pub_len=0;
92
93 if (SvOK(priv)) {
94 priv_data = (unsigned char *)SvPVbyte(priv, priv_len);
95 }
96 if (SvOK(pub)) {
97 pub_data = (unsigned char *)SvPVbyte(pub, pub_len);
98 }
99 self->key.type = -1;
100 rv = x25519_set_key(priv_data, (unsigned long)priv_len, pub_data, (unsigned long)pub_len, &self->key);
101 if (rv != CRYPT_OK) croak("FATAL: x25519_set_key failed: %s", error_to_string(rv));
86 self->initialized = 1;
87 XPUSHs(ST(0)); /* return self */
88 }
89
90 void
91 _import_raw(Crypt::PK::X25519 self, SV * key, int which)
92 PPCODE:
93 {
94 int rv;
95 unsigned char *key_data=NULL;
96 STRLEN key_len=0;
97
98 if (SvOK(key)) {
99 key_data = (unsigned char *)SvPVbyte(key, key_len);
100 }
101 if (which == 0) {
102 rv = x25519_import_raw(key_data, (unsigned long)key_len, PK_PUBLIC, &self->key);
103 }
104 else if (which == 1) {
105 rv = x25519_import_raw(key_data, (unsigned long)key_len, PK_PRIVATE, &self->key);
106 }
107 else {
108 croak("FATAL: import_raw invalid type '%d'", which);
109 }
110 if (rv != CRYPT_OK) croak("FATAL: x25519_import_raw failed: %s", error_to_string(rv));
111 self->initialized = 1;
102112 XPUSHs(ST(0)); /* return self */
103113 }
104114
114124 key2hash(Crypt::PK::X25519 self)
115125 PREINIT:
116126 HV *rv_hash;
117 char buf[20001];
127 char buf[256];
128 unsigned long blen;
118129 SV **not_used;
119 CODE:
120 if (self->key.type == -1) XSRETURN_UNDEF;
130 int rv;
131 CODE:
132 if (self->initialized == 0) XSRETURN_UNDEF;
121133 rv_hash = newHV();
122134 /* priv */
123135 if (self->key.type == PK_PRIVATE) {
124 not_used = hv_store(rv_hash, "priv", 4, newSVpv(self->key.priv, sizeof(self->key.priv)), 0);
136 blen = sizeof(buf);
137 rv = base16_encode(self->key.priv, sizeof(self->key.priv), buf, &blen, 0);
138 if (rv != CRYPT_OK) croak("FATAL: base16_encode failed: %s", error_to_string(rv));
139 not_used = hv_store(rv_hash, "priv", 4, newSVpv(buf, blen), 0);
125140 }
126141 else {
127142 not_used = hv_store(rv_hash, "priv", 4, &PL_sv_undef, 0);
128143 }
129144 /* pub */
130 not_used = hv_store(rv_hash, "pub", 3, newSVpv(self->key.pub, sizeof(self->key.pub)), 0);
131 /* algo */
132 not_used = hv_store(rv_hash, "algo", 4, newSVpv("x25519", 0), 0);
145 blen = sizeof(buf);
146 rv = base16_encode(self->key.pub, sizeof(self->key.pub), buf, &blen, 0);
147 if (rv != CRYPT_OK) croak("FATAL: base16_encode failed: %s", error_to_string(rv));
148 not_used = hv_store(rv_hash, "pub", 3, newSVpv(buf, blen), 0);
149 /* curve */
150 not_used = hv_store(rv_hash, "curve", 5, newSVpv("x25519", 0), 0);
133151 LTC_UNUSED_PARAM(not_used);
134152 RETVAL = newRV_noinc((SV*)rv_hash);
135153 OUTPUT:
150168 RETVAL = newSVpvn((char*)out, out_len);
151169 }
152170 else if (strnEQ(type, "public", 6)) {
153 rv = x25519_export(out, &out_len, PK_PUBLIC|PK_STD, &self->key);
154 if (rv != CRYPT_OK) croak("FATAL: x25519_export(PK_PUBLIC|PK_STD) failed: %s", error_to_string(rv));
171 rv = x25519_export(out, &out_len, PK_PUBLIC, &self->key);
172 if (rv != CRYPT_OK) croak("FATAL: x25519_export(PK_PUBLIC) failed: %s", error_to_string(rv));
155173 RETVAL = newSVpvn((char*)out, out_len);
156174 }
157175 else {
0 package Crypt::PK::Ed25519;
1
2 use strict;
3 use warnings;
4 our $VERSION = '0.066_001';
5
6 require Exporter; our @ISA = qw(Exporter); ### use Exporter 'import';
7 our %EXPORT_TAGS = ( all => [qw( )] );
8 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
9 our @EXPORT = qw();
10
11 use Carp;
12 $Carp::Internal{(__PACKAGE__)}++;
13 use CryptX;
14 use Crypt::PK;
15 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
16
17 sub new {
18 my $self = shift->_new();
19 return @_ > 0 ? $self->import_key(@_) : $self;
20 }
21
22 sub import_key_raw {
23 my ($self, $key, $type) = @_;
24 croak "FATAL: undefined key" unless $key;
25 croak "FATAL: undefined type" unless $type;
26 return $self->_import_raw($key, 1) if $type eq 'private';
27 return $self->_import_raw($key, 0) if $type eq 'public';
28 croak "FATAL: invalid key type '$type'";
29 }
30
31 sub import_key {
32 my ($self, $key, $password) = @_;
33 local $SIG{__DIE__} = \&CryptX::_croak;
34 croak "FATAL: undefined key" unless $key;
35
36 # special case
37 if (ref($key) eq 'HASH') {
38 if ($key->{kty} && $key->{kty} eq "OKP" && $key->{crv} && $key->{crv} eq 'Ed25519') {
39 # JWK-like structure e.g.
40 # {"kty":"OKP","crv":"Ed25519","d":"...","x":"..."}
41 return $self->_import_raw(decode_b64u($key->{d}), 1) if $key->{d}; # private
42 return $self->_import_raw(decode_b64u($key->{x}), 0) if $key->{x}; # public
43 }
44 if ($key->{curve} && $key->{curve} eq "ed25519" && ($key->{priv} || $key->{pub})) {
45 # hash exported via key2hash
46 return $self->_import_raw(pack("H*", $key->{priv}), 1) if $key->{priv};
47 return $self->_import_raw(pack("H*", $key->{pub}), 0) if $key->{pub};
48 }
49 croak "FATAL: unexpected Ed25519 key hash";
50 }
51
52 my $data;
53 if (ref($key) eq 'SCALAR') {
54 $data = $$key;
55 }
56 elsif (-f $key) {
57 $data = read_rawfile($key);
58 }
59 else {
60 croak "FATAL: non-existing file '$key'";
61 }
62 croak "FATAL: invalid key data" unless $data;
63
64 if ($data =~ /-----BEGIN PUBLIC KEY-----(.*?)-----END/sg) {
65 $data = pem_to_der($data, $password);
66 return $self->_import($data);
67 }
68 elsif ($data =~ /-----BEGIN PRIVATE KEY-----(.*?)-----END/sg) {
69 $data = pem_to_der($data, $password);
70 return $self->_import_pkcs8($data, $password);
71 }
72 elsif ($data =~ /-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END/sg) {
73 $data = pem_to_der($data, $password);
74 return $self->_import_pkcs8($data, $password);
75 }
76 elsif ($data =~ /-----BEGIN ED25519 PRIVATE KEY-----(.*?)-----END/sg) {
77 $data = pem_to_der($data, $password);
78 return $self->_import_pkcs8($data, $password);
79 }
80 elsif ($data =~ /^\s*(\{.*?\})\s*$/s) { # JSON
81 my $h = CryptX::_decode_json("$1");
82 if ($h->{kty} && $h->{kty} eq "OKP" && $h->{crv} && $h->{crv} eq 'Ed25519') {
83 return $self->_import_raw(decode_b64u($h->{d}), 1) if $h->{d}; # private
84 return $self->_import_raw(decode_b64u($h->{x}), 0) if $h->{x}; # public
85 }
86 }
87 elsif ($data =~ /-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----/sg) {
88 $data = pem_to_der($data);
89 return $self->_import_x509($data);
90 }
91 elsif ($data =~ /-----BEGIN OPENSSH PRIVATE KEY-----(.*?)-----END/sg) {
92 #XXX-FIXME-TODO
93 # https://crypto.stackexchange.com/questions/71789/openssh-ed2215-private-key-format
94 # https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
95 croak "FATAL: OPENSSH PRIVATE KEY not supported";
96 }
97 elsif ($data =~ /(ssh-ed25519)\s+(\S+)/) {
98 $data = decode_b64("$2");
99 my ($typ, $pubkey) = Crypt::PK::_ssh_parse($data);
100 return $self->_import_raw($pubkey, 0) if $typ eq 'ssh-ed25519' && length($pubkey) == 32;
101 }
102 elsif (length($data) == 32) {
103 croak "FATAL: use import_key_raw() to load raw (32 bytes) Ed25519 key";
104 }
105 else {
106 my $rv = eval { $self->_import($data) } ||
107 eval { $self->_import_pkcs8($data, $password) } ||
108 eval { $self->_import_x509($data) };
109 return $rv if $rv;
110 }
111 croak "FATAL: invalid or unsupported Ed25519 key format";
112 }
113
114 sub export_key_pem {
115 my ($self, $type, $password, $cipher) = @_;
116 local $SIG{__DIE__} = \&CryptX::_croak;
117 my $key = $self->export_key_der($type||'');
118 return unless $key;
119 return der_to_pem($key, "ED25519 PRIVATE KEY", $password, $cipher) if substr($type, 0, 7) eq 'private';
120 return der_to_pem($key, "PUBLIC KEY") if substr($type,0, 6) eq 'public';
121 }
122
123 sub export_key_jwk {
124 my ($self, $type, $wanthash) = @_;
125 local $SIG{__DIE__} = \&CryptX::_croak;
126 my $kh = $self->key2hash;
127 return unless $kh;
128 my $hash = { kty => "OKP", crv => "Ed25519" };
129 $hash->{x} = encode_b64u(pack("H*", $kh->{pub}));
130 $hash->{d} = encode_b64u(pack("H*", $kh->{priv})) if $type && $type eq 'private' && $kh->{priv};
131 return $wanthash ? $hash : CryptX::_encode_json($hash);
132 }
133
134 sub CLONE_SKIP { 1 } # prevent cloning
135
136 1;
137
138 =pod
139
140 =head1 NAME
141
142 Crypt::PK::Ed25519 - Digital signature based on Ed25519
143
144 =head1 SYNOPSIS
145
146 =head1 METHODS
147
148 =head2 new
149
150 =head2 generate_key
151
152 =head2 import_key
153
154 =head2 import_key_raw
155
156 =head2 export_key_der
157
158 =head2 export_key_pem
159
160 =head2 export_key_jwk
161
162 =head2 export_key_raw
163
164 =head2 sign_message
165
166 =head2 verify_message
167
168 =head2 is_private
169
170 =head2 key2hash
171
172 =head1 SEE ALSO
173
174 =over
175
176 =item * L<https://en.wikipedia.org/wiki/EdDSA#Ed25519>
177
178 =item * L<https://en.wikipedia.org/wiki/Curve25519>
179
180 =item * L<https://tools.ietf.org/html/rfc8032>
181
182 =back
183
184 =cut
0 package Crypt::PK::X25519;
1
2 use strict;
3 use warnings;
4 our $VERSION = '0.066_001';
5
6 require Exporter; our @ISA = qw(Exporter); ### use Exporter 'import';
7 our %EXPORT_TAGS = ( all => [qw( )] );
8 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
9 our @EXPORT = qw();
10
11 use Carp;
12 $Carp::Internal{(__PACKAGE__)}++;
13 use CryptX;
14 use Crypt::PK;
15 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
16
17 sub new {
18 my $self = shift->_new();
19 return @_ > 0 ? $self->import_key(@_) : $self;
20 }
21
22 sub import_key_raw {
23 my ($self, $key, $type) = @_;
24 croak "FATAL: undefined key" unless $key;
25 croak "FATAL: undefined type" unless $type;
26 return $self->_import_raw($key, 1) if $type eq 'private';
27 return $self->_import_raw($key, 0) if $type eq 'public';
28 croak "FATAL: invalid key type '$type'";
29 }
30
31 sub import_key {
32 my ($self, $key, $password) = @_;
33 local $SIG{__DIE__} = \&CryptX::_croak;
34 croak "FATAL: undefined key" unless $key;
35
36 # special case
37 if (ref($key) eq 'HASH') {
38 if ($key->{kty} && $key->{kty} eq "OKP" && $key->{crv} && $key->{crv} eq 'X25519') {
39 # JWK-like structure e.g.
40 # {"kty":"OKP","crv":"X25519","d":"...","x":"..."}
41 return $self->_import_raw(decode_b64u($key->{d}), 1) if $key->{d}; # private
42 return $self->_import_raw(decode_b64u($key->{x}), 0) if $key->{x}; # public
43 }
44 if ($key->{curve} && $key->{curve} eq "x25519" && ($key->{priv} || $key->{pub})) {
45 # hash exported via key2hash
46 return $self->_import_raw(pack("H*", $key->{priv}), 1) if $key->{priv};
47 return $self->_import_raw(pack("H*", $key->{pub}), 0) if $key->{pub};
48 }
49 croak "FATAL: unexpected X25519 key hash";
50 }
51
52 my $data;
53 if (ref($key) eq 'SCALAR') {
54 $data = $$key;
55 }
56 elsif (-f $key) {
57 $data = read_rawfile($key);
58 }
59 else {
60 croak "FATAL: non-existing file '$key'";
61 }
62 croak "FATAL: invalid key data" unless $data;
63
64 if ($data =~ /-----BEGIN PUBLIC KEY-----(.*?)-----END/sg) {
65 $data = pem_to_der($data, $password);
66 return $self->_import($data);
67 }
68 elsif ($data =~ /-----BEGIN PRIVATE KEY-----(.*?)-----END/sg) {
69 $data = pem_to_der($data, $password);
70 return $self->_import_pkcs8($data, $password);
71 }
72 elsif ($data =~ /-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END/sg) {
73 $data = pem_to_der($data, $password);
74 return $self->_import_pkcs8($data, $password);
75 }
76 elsif ($data =~ /-----BEGIN X25519 PRIVATE KEY-----(.*?)-----END/sg) {
77 $data = pem_to_der($data, $password);
78 return $self->_import_pkcs8($data, $password);
79 }
80 elsif ($data =~ /^\s*(\{.*?\})\s*$/s) { # JSON
81 my $h = CryptX::_decode_json("$1");
82 if ($h->{kty} && $h->{kty} eq "OKP" && $h->{crv} && $h->{crv} eq 'X25519') {
83 return $self->_import_raw(decode_b64u($h->{d}), 1) if $h->{d}; # private
84 return $self->_import_raw(decode_b64u($h->{x}), 0) if $h->{x}; # public
85 }
86 }
87 elsif (length($data) == 32) {
88 croak "FATAL: use import_key_raw() to load raw (32 bytes) X25519 key";
89 }
90 else {
91 my $rv = eval { $self->_import($data) } ||
92 eval { $self->_import_pkcs8($data, $password) } ||
93 eval { $self->_import_x509($data) };
94 return $rv if $rv;
95 }
96 croak "FATAL: invalid or unsupported X25519 key format";
97 }
98
99 sub export_key_pem {
100 my ($self, $type, $password, $cipher) = @_;
101 local $SIG{__DIE__} = \&CryptX::_croak;
102 my $key = $self->export_key_der($type||'');
103 return unless $key;
104 return der_to_pem($key, "X25519 PRIVATE KEY", $password, $cipher) if substr($type, 0, 7) eq 'private';
105 return der_to_pem($key, "PUBLIC KEY") if substr($type,0, 6) eq 'public';
106 }
107
108 sub export_key_jwk {
109 my ($self, $type, $wanthash) = @_;
110 local $SIG{__DIE__} = \&CryptX::_croak;
111 my $kh = $self->key2hash;
112 return unless $kh;
113 my $hash = { kty => "OKP", crv => "X25519" };
114 $hash->{x} = encode_b64u(pack("H*", $kh->{pub}));
115 $hash->{d} = encode_b64u(pack("H*", $kh->{priv})) if $type && $type eq 'private' && $kh->{priv};
116 return $wanthash ? $hash : CryptX::_encode_json($hash);
117 }
118
119 sub CLONE_SKIP { 1 } # prevent cloning
120
121 1;
122
123 =pod
124
125 =head1 NAME
126
127 Crypt::PK::X25519 - Asymmetric cryptography based on X25519
128
129 =head1 SYNOPSIS
130
131 =head1 METHODS
132
133 =head2 new
134
135 =head2 generate_key
136
137 =head2 import_key
138
139 =head2 import_key_raw
140
141 =head2 export_key_der
142
143 =head2 export_key_pem
144
145 =head2 export_key_jwk
146
147 =head2 export_key_raw
148
149 =head2 shared_secret
150
151 =head2 is_private
152
153 =head2 key2hash
154
155 =cut
109109
110110 =item * Public key cryptography
111111
112 L<Crypt::PK::RSA>, L<Crypt::PK::DSA>, L<Crypt::PK::ECC>, L<Crypt::PK::DH>
112 L<Crypt::PK::RSA>, L<Crypt::PK::DSA>, L<Crypt::PK::ECC>, L<Crypt::PK::DH>, L<Crypt::PK::Ed25519>, L<Crypt::PK::X25519>
113113
114114 =item * Cryptographically secure random number generators - see L<Crypt::PRNG> and related modules
115115
100100 use Crypt::PK::DSA;
101101 use Crypt::PK::ECC;
102102 use Crypt::PK::RSA;
103 use Crypt::PK::X25519;
104 use Crypt::PK::Ed25519;
103105 use Crypt::PK;
104106 use Crypt::PRNG::ChaCha20;
105107 use Crypt::PRNG::Fortuna;
55 plan skip_all => "set AUTHOR_MODE to enable this test (developer only!)" unless $ENV{AUTHOR_MODE};
66 plan skip_all => "File::Find not installed" unless eval { require File::Find };
77 plan skip_all => "Test::Pod not installed" unless eval { require Test::Pod };
8 plan tests => 107;
8 plan tests => 109;
99
1010 my @files;
1111 File::Find::find({ wanted=>sub { push @files, $_ if /\.pm$/ }, no_chdir=>1 }, 'lib');
2121 },
2222 );
2323
24 plan tests => 107;
24 plan tests => 109;
2525
2626 my @files;
2727 File::Find::find({ wanted=>sub { push @files, $_ if /\.pm$/ }, no_chdir=>1 }, 'lib');
55 plan skip_all => "set AUTHOR_MODE to enable this test (developer only!)" unless $ENV{AUTHOR_MODE};
66 plan skip_all => "Pod::Coverage not installed" unless eval { require Pod::Coverage };
77 plan skip_all => "File::Find not installed" unless eval { require File::Find };
8 plan tests => 107;
8 plan tests => 109;
99
1010 my @files;
1111 File::Find::find({ wanted=>sub { push @files, $_ if /\.pm$/ }, no_chdir=>1 }, 'lib');
0 use strict;
1 use warnings;
2 use Test::More tests => 55;
3
4 use Crypt::PK::Ed25519;
5
6 {
7 my $k;
8
9 # t/data/openssl_ed25519_sk.pem
10 # ED25519 Private-Key:
11 # priv = 45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD == RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0
12 # pub = A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D == oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0
13
14 $k = Crypt::PK::Ed25519->new->import_key_raw(pack("H*", "45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD"), 'private');
15 ok($k, 'new+import_key_raw raw-priv');
16 ok($k->is_private, 'is_private raw-priv');
17 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} raw-priv');
18 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} raw-priv');
19
20 $k = Crypt::PK::Ed25519->new->import_key_raw(pack("H*", "A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D"), 'public');
21 ok($k, 'new+import_key_raw raw-pub');
22 ok(!$k->is_private, '!is_private raw-pub');
23 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} raw-pub');
24
25 $k = Crypt::PK::Ed25519->new({ kty=>"OKP",crv=>"Ed25519",d=>"RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0",x=>"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0"});
26 ok($k, 'new JWKHASH/priv');
27 ok($k->is_private, 'is_private JWKHASH/priv');
28 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} JWKHASH/priv');
29
30 $k = Crypt::PK::Ed25519->new({ kty=>"OKP",crv=>"Ed25519",x=>"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0"});
31 ok($k, 'new JWKHASH/pub');
32 ok(!$k->is_private, '!is_private JWKHASH/pub');
33 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} JWKHASH/pub');
34
35 $k = Crypt::PK::Ed25519->new('t/data/jwk_ed25519-priv1.json');
36 ok($k, 'new JWK/priv');
37 ok($k->is_private, 'is_private JWK/priv');
38 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} JWK/priv');
39
40 $k = Crypt::PK::Ed25519->new('t/data/jwk_ed25519-pub1.json');
41 ok($k, 'new JWK/pub');
42 ok(!$k->is_private, '!is_private JWK/pub');
43 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} JWK/pub');
44
45 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk.der');
46 ok($k, 'new openssl_ed25519_sk.der');
47 ok($k->is_private, 'is_private openssl_ed25519_sk.der');
48 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk.der');
49
50 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk.pem');
51 ok($k, 'new openssl_ed25519_sk.pem');
52 ok($k->is_private, 'is_private openssl_ed25519_sk.pem');
53 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk.pem');
54
55 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk_t.pem');
56 ok($k, 'new openssl_ed25519_sk_t.pem');
57 ok($k->is_private, 'is_private openssl_ed25519_sk_t.pem');
58 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk_t.pem');
59
60 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk.pkcs8');
61 ok($k, 'new openssl_ed25519_sk.pkcs8');
62 ok($k->is_private, 'is_private openssl_ed25519_sk.pkcs8');
63 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk.pkcs8');
64
65 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk_pbes1.pkcs8', 'secret');
66 ok($k, 'new openssl_ed25519_sk_pbes1.pkcs8');
67 ok($k->is_private, 'is_private openssl_ed25519_sk_pbes1.pkcs8');
68 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk_pbes1.pkcs8');
69
70 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk_pbes2.pkcs8', 'secret');
71 ok($k, 'new openssl_ed25519_sk_pbes2.pkcs8');
72 ok($k->is_private, 'is_private openssl_ed25519_sk_pbes2.pkcs8');
73 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk_pbes2.pkcs8');
74
75 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk_pw.pem', 'secret');
76 ok($k, 'new openssl_ed25519_sk_pw.pem');
77 ok($k->is_private, 'is_private openssl_ed25519_sk_pw.pem');
78 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk_pw.pem');
79
80 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_sk_pw_t.pem', 'secret');
81 ok($k, 'new openssl_ed25519_sk_pw_t.pem');
82 ok($k->is_private, 'is_private openssl_ed25519_sk_pw_t.pem');
83 is(uc($k->key2hash->{priv}), '45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD', 'key2hash->{priv} openssl_ed25519_sk_pw_t.pem');
84
85 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_pk.pem');
86 ok($k, 'new openssl_ed25519_pk.pem');
87 ok(!$k->is_private, '!is_private openssl_ed25519_pk.pem');
88 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} openssl_ed25519_pk.pem');
89
90 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_x509.pem');
91 ok($k, 'new openssl_ed25519_x509.pem');
92 ok(!$k->is_private, '!is_private openssl_ed25519_x509.pem');
93 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} openssl_ed25519_x509.pem');
94
95 $k = Crypt::PK::Ed25519->new('t/data/openssl_ed25519_x509.der');
96 ok($k, 'new openssl_ed25519_x509.der');
97 ok(!$k->is_private, '!is_private openssl_ed25519_x509.der');
98 is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} openssl_ed25519_x509.der');
99
100 $k = Crypt::PK::Ed25519->new('t/data/ssh/ssh_ed25519.pub');
101 ok($k, 'new ssh_ed25519.pub');
102 ok(!$k->is_private, '!is_private ssh_ed25519.pub');
103 is(uc($k->key2hash->{pub}), 'BD17B2215C443A7A1E9B286A4F0E76288130984CD942ACCCD4F1A064BB749FBE', 'key2hash->{pub} ssh_ed25519.pub');
104
105 ### $k = Crypt::PK::Ed25519->new('t/data/ssh/ssh_ed25519.priv');
106 ### ok($k, 'new ssh_ed25519.priv');
107 ### ok($k->is_private, 'is_private ssh_ed25519.priv');
108 ###
109 ### $k = Crypt::PK::Ed25519->new('t/data/ssh/ssh_ed25519_pw.priv', 'secret');
110 ### ok($k, 'new ssh_ed25519_pw.priv');
111 ### ok($k->is_private, 'is_private ssh_ed25519_pw.priv');
112 }
0 use strict;
1 use warnings;
2 use Test::More tests => 46;
3
4 use Crypt::PK::X25519;
5
6 {
7 my $k;
8
9 # t/data/openssl_x25519_sk.pem
10 # X25519 Private-Key:
11 # priv = 002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651 == AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE
12 # pub = EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41 == 6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE
13
14 $k = Crypt::PK::X25519->new->import_key_raw(pack("H*", "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651"), 'private');
15 ok($k, 'new+import_key_raw raw-priv');
16 ok($k->is_private, 'is_private raw-priv');
17 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} raw-priv');
18 is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} raw-priv');
19
20 $k = Crypt::PK::X25519->new->import_key_raw(pack("H*", "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41"), 'public');
21 ok($k, 'new+import_key_raw raw-pub');
22 ok(!$k->is_private, '!is_private raw-pub');
23 is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} raw-pub');
24
25 $k = Crypt::PK::X25519->new({ kty=>"OKP",crv=>"X25519",d=>"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE"});
26 ok($k, 'new JWKHASH/priv');
27 ok($k->is_private, 'is_private JWKHASH/priv');
28 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} JWKHASH/priv');
29
30 $k = Crypt::PK::X25519->new({ kty=>"OKP",crv=>"X25519",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE"});
31 ok($k, 'new JWKHASH/pub');
32 ok(!$k->is_private, '!is_private JWKHASH/pub');
33 is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} JWKHASH/pub');
34
35 $k = Crypt::PK::X25519->new('t/data/jwk_x25519-priv1.json');
36 ok($k, 'new JWK/priv');
37 ok($k->is_private, 'is_private JWK/priv');
38 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} JWK/priv');
39
40 $k = Crypt::PK::X25519->new('t/data/jwk_x25519-pub1.json');
41 ok($k, 'new JWK/pub');
42 ok(!$k->is_private, '!is_private JWK/pub');
43 is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} JWK/pub');
44
45 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk.der');
46 ok($k, 'new openssl_x25519_sk.der');
47 ok($k->is_private, 'is_private openssl_x25519_sk.der');
48 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk.der');
49
50 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk.pem');
51 ok($k, 'new openssl_x25519_sk.pem');
52 ok($k->is_private, 'is_private openssl_x25519_sk.pem');
53 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk.pem');
54
55 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_t.pem');
56 ok($k, 'new openssl_x25519_sk_t.pem');
57 ok($k->is_private, 'is_private openssl_x25519_sk_t.pem');
58 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_t.pem');
59
60 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk.pkcs8');
61 ok($k, 'new openssl_x25519_sk.pkcs8');
62 ok($k->is_private, 'is_private openssl_x25519_sk.pkcs8');
63 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk.pkcs8');
64
65 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pbes1.pkcs8', 'secret');
66 ok($k, 'new openssl_x25519_sk_pbes1.pkcs8');
67 ok($k->is_private, 'is_private openssl_x25519_sk_pbes1.pkcs8');
68 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pbes1.pkcs8');
69
70 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pbes2.pkcs8', 'secret');
71 ok($k, 'new openssl_x25519_sk_pbes2.pkcs8');
72 ok($k->is_private, 'is_private openssl_x25519_sk_pbes2.pkcs8');
73 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pbes2.pkcs8');
74
75 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pw.pem', 'secret');
76 ok($k, 'new openssl_x25519_sk_pw.pem');
77 ok($k->is_private, 'is_private openssl_x25519_sk_pw.pem');
78 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pw.pem');
79
80 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_sk_pw_t.pem', 'secret');
81 ok($k, 'new openssl_x25519_sk_pw_t.pem');
82 ok($k->is_private, 'is_private openssl_x25519_sk_pw_t.pem');
83 is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} openssl_x25519_sk_pw_t.pem');
84
85 $k = Crypt::PK::X25519->new('t/data/openssl_x25519_pk.pem');
86 ok($k, 'new openssl_x25519_pk.pem');
87 ok(!$k->is_private, '!is_private openssl_x25519_pk.pem');
88 is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} openssl_x25519_pk.pem');
89 }