diff --git a/inc/CryptX_PK_Ed25519.xs.inc b/inc/CryptX_PK_Ed25519.xs.inc index 5c467a6..bdbc269 100644 --- a/inc/CryptX_PK_Ed25519.xs.inc +++ b/inc/CryptX_PK_Ed25519.xs.inc @@ -185,7 +185,7 @@ CODE: { int rv; - unsigned char out[4096]; + unsigned char out[32]; unsigned long int out_len = sizeof(out); RETVAL = newSVpvn(NULL, 0); /* undef */ diff --git a/inc/CryptX_PK_X25519.xs.inc b/inc/CryptX_PK_X25519.xs.inc index e2c50a3..6eb6e00 100644 --- a/inc/CryptX_PK_X25519.xs.inc +++ b/inc/CryptX_PK_X25519.xs.inc @@ -116,7 +116,7 @@ int is_private(Crypt::PK::X25519 self) CODE: - if (self->key.type == -1) XSRETURN_UNDEF; + if (self->initialized == 0 || self->key.type == -1) XSRETURN_UNDEF; RETVAL = (self->key.type == PK_PRIVATE) ? 1 : 0; OUTPUT: RETVAL @@ -164,8 +164,8 @@ RETVAL = newSVpvn(NULL, 0); /* undef */ if (strnEQ(type, "private", 7)) { - rv = x25519_export(out, &out_len, PK_PRIVATE, &self->key); - if (rv != CRYPT_OK) croak("FATAL: x25519_export(PK_PRIVATE) failed: %s", error_to_string(rv)); + rv = x25519_export(out, &out_len, PK_PRIVATE|PK_STD, &self->key); + if (rv != CRYPT_OK) croak("FATAL: x25519_export(PK_PRIVATE|PK_STD) failed: %s", error_to_string(rv)); RETVAL = newSVpvn((char*)out, out_len); } else if (strnEQ(type, "public", 6)) { @@ -180,6 +180,32 @@ OUTPUT: RETVAL +SV* +export_key_raw(Crypt::PK::X25519 self, char * type) + CODE: + { + int rv; + unsigned char out[32]; + unsigned long int out_len = sizeof(out); + + RETVAL = newSVpvn(NULL, 0); /* undef */ + if (strnEQ(type, "private", 7)) { + rv = x25519_export(out, &out_len, PK_PRIVATE, &self->key); + if (rv != CRYPT_OK) croak("FATAL: x25519_export(PK_PRIVATE) failed: %s", error_to_string(rv)); + RETVAL = newSVpvn((char*)out, out_len); + } + else if (strnEQ(type, "public", 6)) { + rv = x25519_export(out, &out_len, PK_PUBLIC, &self->key); + if (rv != CRYPT_OK) croak("FATAL: x25519_export(PK_PUBLIC) failed: %s", error_to_string(rv)); + RETVAL = newSVpvn((char*)out, out_len); + } + else { + croak("FATAL: export_key_raw invalid type '%s'", type); + } + } + OUTPUT: + RETVAL + SV * shared_secret(Crypt::PK::X25519 self, Crypt::PK::X25519 pubkey) CODE: @@ -199,4 +225,3 @@ DESTROY(Crypt::PK::X25519 self) CODE: Safefree(self); - diff --git a/lib/Crypt/PK/Ed25519.pm b/lib/Crypt/PK/Ed25519.pm index 23e4312..32f4d08 100644 --- a/lib/Crypt/PK/Ed25519.pm +++ b/lib/Crypt/PK/Ed25519.pm @@ -163,7 +163,128 @@ =head2 import_key -TODO +Loads private or public key in DER or PEM format. + + $pk->import_key($filename); + #or + $pk->import_key(\$buffer_containing_key); + +Support for password protected PEM keys: + + $pk->import_key($filename, $password); + #or + $pk->import_key(\$buffer_containing_key, $password); + +Loading private or public keys form perl hash: + + $pk->import_key($hashref); + + # the $hashref is either a key exported via key2hash + $pk->import_key({ + curve => "ed25519", + pub => "A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D", + priv => "45C109BA6FD24E8B67D23EFB6B92D99CD457E2137172C0D749FE2B5A0C142DAD", + }); + + # or a hash with items corresponding to JWK (JSON Web Key) + $pk->import_key({ + kty => "OKP", + crv => "Ed25519", + d => "RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0", + x => "oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0", + }); + +Supported key formats: + + # all formats can be loaded from a file + my $pk = Crypt::PK::Ed25519->new($filename); + + # or from a buffer containing the key + my $pk = Crypt::PK::Ed25519->new(\$buffer_with_key); + +=over + +=item * Ed25519 private keys in PEM format + + -----BEGIN ED25519 PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIEXBCbpv0k6LZ9I++2uS2ZzUV+ITcXLA10n+K1oMFC2t + -----END ED25519 PRIVATE KEY----- + +=item * Ed25519 private keys in password protected PEM format + + -----BEGIN ED25519 PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-CBC,6A64D756D49C1EFF + + 8xQ7OyfQ10IITNEKcJGZA53Z1yk+NJQU7hrKqXwChZtgWNInhMBJRl9pozLKDSkH + v7u6EOve8NY= + -----END ED25519 PRIVATE KEY----- + +=item * PKCS#8 private keys + + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEIEXBCbpv0k6LZ9I++2uS2ZzUV+ITcXLA10n+K1oMFC2t + -----END PRIVATE KEY----- + +=item * PKCS#8 encrypted private keys + + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAjPx9JkdpRH2QICCAAw + DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIWWieQojaWTcEOGj43SxqHUys4Eb2M27N + AkhqpmhosOxKrpGi0L3h8m8ipHE8EwI94NeOMsjfVw60aJuCrssY5vKN + -----END ENCRYPTED PRIVATE KEY----- + +=item * Ed25519 public keys in PEM format + + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VwAyEAoF0a6lgwrJplzfs4RmDUl+NpfEa0Gc8s7IXei9JFRZ0= + -----END PUBLIC KEY----- + +=item * Ed25519 public key from X509 certificate + + -----BEGIN CERTIFICATE----- + MIIBODCB66ADAgECAhRWDU9FZBBUZ7KTdX8f7Bco8jsoaTAFBgMrZXAwETEPMA0G + A1UEAwwGQ3J5cHRYMCAXDTIwMDExOTEzMDIwMloYDzIyOTMxMTAyMTMwMjAyWjAR + MQ8wDQYDVQQDDAZDcnlwdFgwKjAFBgMrZXADIQCgXRrqWDCsmmXN+zhGYNSX42l8 + RrQZzyzshd6L0kVFnaNTMFEwHQYDVR0OBBYEFHCGFtVibAxxWYyRt5wazMpqSZDV + MB8GA1UdIwQYMBaAFHCGFtVibAxxWYyRt5wazMpqSZDVMA8GA1UdEwEB/wQFMAMB + Af8wBQYDK2VwA0EAqG/+98smzqF/wmFX3zHXSaA67as202HnBJod1Tiurw1f+lr3 + BX6OMtsDpgRq9O77IF1Qyx/MdJEwwErczOIbAA== + -----END CERTIFICATE----- + +=item * SSH public Ed25519 keys + + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++ + +=item * SSH public Ed25519 keys (RFC-4716 format) + + ---- BEGIN SSH2 PUBLIC KEY ---- + Comment: "256-bit ED25519, converted from OpenSSH" + AAAAC3NzaC1lZDI1NTE5AAAAIL0XsiFcRDp6Hpsoak8OdiiBMJhM2UKszNTxoGS7dJ++ + ---- END SSH2 PUBLIC KEY ---- + +=item * Ed25519 private keys in JSON Web Key (JWK) format + +See L + + { + "kty":"OKP", + "crv":"Ed25519", + "x":"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0", + "d":"RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0", + } + +B For JWK support you need to have L, L or L module. + +=item * Ed25519 public keys in JSON Web Key (JWK) format + + { + "kty":"OKP", + "crv":"Ed25519", + "x":"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0", + } + +B For JWK support you need to have L, L or L module. =head2 import_key_raw diff --git a/lib/Crypt/PK/X25519.pm b/lib/Crypt/PK/X25519.pm index 4725a9c..b0d7a5f 100644 --- a/lib/Crypt/PK/X25519.pm +++ b/lib/Crypt/PK/X25519.pm @@ -23,6 +23,7 @@ sub import_key_raw { my ($self, $key, $type) = @_; croak "FATAL: undefined key" unless $key; + croak "FATAL: invalid key" unless length($key) == 32; croak "FATAL: undefined type" unless $type; return $self->_import_raw($key, 1) if $type eq 'private'; return $self->_import_raw($key, 0) if $type eq 'public'; @@ -135,22 +136,196 @@ =head2 generate_key +Uses Yarrow-based cryptographically strong random number generator seeded with +random data taken from C (UNIX) or C (Win32). + + $pk->generate_key; + =head2 import_key +Loads private or public key in DER or PEM format. + + $pk->import_key($filename); + #or + $pk->import_key(\$buffer_containing_key); + +Support for password protected PEM keys: + + $pk->import_key($filename, $password); + #or + $pk->import_key(\$buffer_containing_key, $password); + +Loading private or public keys form perl hash: + + $pk->import_key($hashref); + + # the $hashref is either a key exported via key2hash + $pk->import_key({ + curve => "x25519", + pub => "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41", + priv => "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651", + }); + + # or a hash with items corresponding to JWK (JSON Web Key) + $pk->import_key({ + kty => "OKP", + crv => "X25519", + d => "AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE", + x => "6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE", + }); + +Supported key formats: + + # all formats can be loaded from a file + my $pk = Crypt::PK::X25519->new($filename); + + # or from a buffer containing the key + my $pk = Crypt::PK::X25519->new(\$buffer_with_key); + +=over + +=item * X25519 private keys in PEM format + + -----BEGIN X25519 PRIVATE KEY----- + MC4CAQAwBQYDK2VuBCIEIAAvk9ELpXKNjdjpUnch2royYcC7G+/ee0u9rGMdRUZR + -----END X25519 PRIVATE KEY----- + +=item * X25519 private keys in password protected PEM format + + -----BEGIN X25519 PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: DES-CBC,DEEFD3D6B714E75A + + dfFWP5bKn49aZ993NVAhQQPdFWgsTb4j8CWhRjGBVTPl6ITstAL17deBIRBwZb7h + pAyIka81Kfs= + -----END X25519 PRIVATE KEY----- + +=item * X25519 public keys in PEM format + + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VuAyEA6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE= + -----END PUBLIC KEY----- + +=item * PKCS#8 private keys + + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VuBCIEIAAvk9ELpXKNjdjpUnch2royYcC7G+/ee0u9rGMdRUZR + -----END PRIVATE KEY----- + +=item * PKCS#8 encrypted private keys + + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAiS0NOFZmjJswICCAAw + DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIGd40Hdso8Y4EONSRCTrqvftl9hl3zbH9 + 2QmHF1KJ4HDMdLDRxD7EynonCw2SV7BO+XNRHzw2yONqiTybfte7nk9t + -----END ENCRYPTED PRIVATE KEY----- + +=item * X25519 private keys in JSON Web Key (JWK) format + +See L + + { + "kty":"OKP", + "crv":"X25519", + "x":"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE", + "d":"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE", + } + +B For JWK support you need to have L, L or L module. + +=item * X25519 public keys in JSON Web Key (JWK) format + + { + "kty":"OKP", + "crv":"X25519", + "x":"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE", + } + +B For JWK support you need to have L, L or L module. + =head2 import_key_raw +Import raw public/private key - can load raw key data exported by L. + + $pk->import_key_raw($key, 'public'); + $pk->import_key_raw($key, 'private'); + =head2 export_key_der + my $private_der = $pk->export_key_der('private'); + #or + my $public_der = $pk->export_key_der('public'); + =head2 export_key_pem + my $private_pem = $pk->export_key_pem('private'); + #or + my $public_pem = $pk->export_key_pem('public'); + =head2 export_key_jwk +Exports public/private keys as a JSON Web Key (JWK). + + my $private_json_text = $pk->export_key_jwk('private'); + #or + my $public_json_text = $pk->export_key_jwk('public'); + +Also exports public/private keys as a perl HASH with JWK structure. + + my $jwk_hash = $pk->export_key_jwk('private', 1); + #or + my $jwk_hash = $pk->export_key_jwk('public', 1); + +B For JWK support you need to have L, L or L module. + =head2 export_key_raw +Export raw public/private key + + my $private_pem = $pk->export_key_raw('private'); + #or + my $public_pem = $pk->export_key_raw('public'); + =head2 shared_secret + # Alice having her priv key $pk and Bob's public key $pkb + my $pk = Crypt::PK::X25519->new($priv_key_filename); + my $pkb = Crypt::PK::X25519->new($pub_key_filename); + my $shared_secret = $pk->shared_secret($pkb); + + # Bob having his priv key $pk and Alice's public key $pka + my $pk = Crypt::PK::X25519->new($priv_key_filename); + my $pka = Crypt::PK::X25519->new($pub_key_filename); + my $shared_secret = $pk->shared_secret($pka); # same value as computed by Alice + =head2 is_private + my $rv = $pk->is_private; + # 1 .. private key loaded + # 0 .. public key loaded + # undef .. no key loaded + =head2 key2hash + my $hash = $pk->key2hash; + + # returns hash like this (or undef if no key loaded): + { + curve => "x25519", + # raw public key as a hexadecimal string + pub => "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41", + # raw private key as a hexadecimal string. undef if key is public only + priv => "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651", + } + +=head1 SEE ALSO + +=over + +=item * L + +=item * L + +=back + =cut diff --git a/t/pk_ed25519.t b/t/pk_ed25519.t index 531add3..a86bfd1 100644 --- a/t/pk_ed25519.t +++ b/t/pk_ed25519.t @@ -1,12 +1,12 @@ use strict; use warnings; -use Test::More tests => 74; +use Test::More tests => 78; use Crypt::PK::Ed25519; use Crypt::Misc qw(read_rawfile); { - my $k; + my ($k, $k2); # t/data/openssl_ed25519_sk.pem # ED25519 Private-Key: @@ -21,12 +21,20 @@ is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} raw-priv'); is($k->export_key_raw('private'), $sk_data, 'export_key_raw private'); + $k2 = Crypt::PK::Ed25519->new->import_key($k->key2hash); + ok($k2->is_private, 'is_private raw-priv'); + is($k->export_key_der('private'), $k2->export_key_der('private'), 'import_key hash'); + my $pk_data = pack("H*", "A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D"); $k = Crypt::PK::Ed25519->new->import_key_raw($pk_data, 'public'); ok($k, 'new+import_key_raw raw-pub'); ok(!$k->is_private, '!is_private raw-pub'); is(uc($k->key2hash->{pub}), 'A05D1AEA5830AC9A65CDFB384660D497E3697C46B419CF2CEC85DE8BD245459D', 'key2hash->{pub} raw-pub'); is($k->export_key_raw('public'), $pk_data, 'export_key_raw public'); + + $k2 = Crypt::PK::Ed25519->new->import_key($k->key2hash); + ok(!$k2->is_private, 'is_private raw-priv'); + is($k->export_key_der('public'), $k2->export_key_der('public'), 'import_key hash'); my $sk_jwk = { kty=>"OKP",crv=>"Ed25519",d=>"RcEJum_STotn0j77a5LZnNRX4hNxcsDXSf4rWgwULa0",x=>"oF0a6lgwrJplzfs4RmDUl-NpfEa0Gc8s7IXei9JFRZ0" }; $k = Crypt::PK::Ed25519->new($sk_jwk); diff --git a/t/pk_x25519.t b/t/pk_x25519.t index 8494b1e..46bffa1 100644 --- a/t/pk_x25519.t +++ b/t/pk_x25519.t @@ -1,37 +1,54 @@ use strict; use warnings; -use Test::More tests => 46; +use Test::More tests => 65; use Crypt::PK::X25519; +use Crypt::Misc qw(read_rawfile); { - my $k; + my ($k, $k2); # t/data/openssl_x25519_sk.pem # X25519 Private-Key: # priv = 002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651 == AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE # pub = EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41 == 6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE - $k = Crypt::PK::X25519->new->import_key_raw(pack("H*", "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651"), 'private'); + my $sk_data = pack("H*", "002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651"); + $k = Crypt::PK::X25519->new->import_key_raw($sk_data, 'private'); ok($k, 'new+import_key_raw raw-priv'); ok($k->is_private, 'is_private raw-priv'); is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} raw-priv'); is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} raw-priv'); + is($k->export_key_raw('private'), $sk_data, 'export_key_raw private'); - $k = Crypt::PK::X25519->new->import_key_raw(pack("H*", "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41"), 'public'); + $k2 = Crypt::PK::X25519->new->import_key($k->key2hash); + ok($k2->is_private, 'is_private raw-priv'); + is($k->export_key_der('private'), $k2->export_key_der('private'), 'import_key hash'); + + my $pk_data = pack("H*", "EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41"); + $k = Crypt::PK::X25519->new->import_key_raw($pk_data, 'public'); ok($k, 'new+import_key_raw raw-pub'); ok(!$k->is_private, '!is_private raw-pub'); is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} raw-pub'); + is($k->export_key_raw('public'), $pk_data, 'export_key_raw public'); - $k = Crypt::PK::X25519->new({ kty=>"OKP",crv=>"X25519",d=>"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE"}); + $k2 = Crypt::PK::X25519->new->import_key($k->key2hash); + ok(!$k2->is_private, 'is_private raw-priv'); + is($k->export_key_der('public'), $k2->export_key_der('public'), 'import_key hash'); + + my $sk_jwk = { kty=>"OKP",crv=>"X25519",d=>"AC-T0Qulco2N2OlSdyHaujJhwLsb7957S72sYx1FRlE",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE" }; + $k = Crypt::PK::X25519->new($sk_jwk); ok($k, 'new JWKHASH/priv'); ok($k->is_private, 'is_private JWKHASH/priv'); is(uc($k->key2hash->{priv}), '002F93D10BA5728D8DD8E9527721DABA3261C0BB1BEFDE7B4BBDAC631D454651', 'key2hash->{priv} JWKHASH/priv'); + ok(eq_hash($sk_jwk, $k->export_key_jwk('private', 1)), 'JWKHASH export private'); - $k = Crypt::PK::X25519->new({ kty=>"OKP",crv=>"X25519",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE"}); + my $pk_jwk = { kty=>"OKP",crv=>"X25519",x=>"6ngG9yGoVwUSyPbvtOjWIMSaUp5N9eqnfexkb7HofkE"}; + $k = Crypt::PK::X25519->new($pk_jwk); ok($k, 'new JWKHASH/pub'); ok(!$k->is_private, '!is_private JWKHASH/pub'); is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} JWKHASH/pub'); + ok(eq_hash($pk_jwk, $k->export_key_jwk('public', 1)), 'JWKHASH export public'); $k = Crypt::PK::X25519->new('t/data/jwk_x25519-priv1.json'); ok($k, 'new JWK/priv'); @@ -88,3 +105,42 @@ ok(!$k->is_private, '!is_private openssl_x25519_pk.pem'); is(uc($k->key2hash->{pub}), 'EA7806F721A8570512C8F6EFB4E8D620C49A529E4DF5EAA77DEC646FB1E87E41', 'key2hash->{pub} openssl_x25519_pk.pem'); } + +{ + my $k = Crypt::PK::X25519->new; + $k->generate_key; + ok($k, 'generate_key'); + ok($k->is_private, 'is_private'); + ok($k->export_key_der('private'), 'export_key_der pri'); + ok($k->export_key_der('public'), 'export_key_der pub'); +} + +{ + for (qw( openssl_x25519_pk.der openssl_x25519_pk.pem )) { + my $k = Crypt::PK::X25519->new("t/data/$_"); + is($k->export_key_der('public'), read_rawfile("t/data/$_"), 'export_key_der public') if (substr($_, -3) eq "der"); + is($k->export_key_pem('public'), read_rawfile("t/data/$_"), 'export_key_pem public') if (substr($_, -3) eq "pem"); + } + + for (qw( openssl_x25519_sk.der openssl_x25519_sk_t.pem )) { + my $k = Crypt::PK::X25519->new("t/data/$_"); + is($k->export_key_der('private'), read_rawfile("t/data/$_"), 'export_key_der private') if (substr($_, -3) eq "der"); + is($k->export_key_pem('private'), read_rawfile("t/data/$_"), 'export_key_pem private') if (substr($_, -3) eq "pem"); + } +} + +{ + my $sk1 = Crypt::PK::X25519->new; + $sk1->import_key('t/data/openssl_x25519_sk.der'); + my $pk1 = Crypt::PK::X25519->new->import_key_raw($sk1->export_key_raw('public'), 'public'); + ok(!$pk1->is_private, '!is_private'); + + my $sk2 = Crypt::PK::X25519->new; + $sk2->generate_key; + my $pk2 = Crypt::PK::X25519->new->import_key_raw($sk2->export_key_raw('public'), 'public'); + ok(!$pk2->is_private, '!is_private'); + + my $ss1 = $sk1->shared_secret($pk2); + my $ss2 = $sk2->shared_secret($pk1); + is(unpack("H*",$ss1), unpack("H*",$ss2), 'shared_secret'); +}