From feeba51885a27fb3522ddc6fcf64061c5cf2203d Mon Sep 17 00:00:00 2001 From: Andrew Bennett Date: Thu, 18 May 2017 19:18:33 -0600 Subject: [PATCH] Add support for reading/writing PKIX for Curve25519 and Curve448. --- include/jose_public_key.hrl | 59 +++ src/jose_jwk_kty.erl | 18 +- src/jose_jwk_kty_okp_ed25519.erl | 55 ++- src/jose_jwk_kty_okp_ed448.erl | 55 ++- src/jose_jwk_kty_okp_x25519.erl | 55 ++- src/jose_jwk_kty_okp_x448.erl | 55 ++- src/jose_jwk_pem.erl | 21 +- src/jose_public_key.erl | 461 ++++++++++++++++++ test/jose_jwk_SUITE.erl | 28 ++ .../jose_jwk_kty_okp_ed25519_props.erl | 27 +- .../jose_jwk_kty_okp_ed448_props.erl | 27 +- .../jose_jwk_kty_okp_x25519_props.erl | 31 +- .../jose_jwk_kty_okp_x448_props.erl | 31 +- 13 files changed, 876 insertions(+), 47 deletions(-) create mode 100644 include/jose_public_key.hrl create mode 100644 src/jose_public_key.erl diff --git a/include/jose_public_key.hrl b/include/jose_public_key.hrl new file mode 100644 index 0000000..8717cec --- /dev/null +++ b/include/jose_public_key.hrl @@ -0,0 +1,59 @@ +%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*- +%% vim: ts=4 sw=4 ft=erlang noet +%%%------------------------------------------------------------------- +%%% @author Andrew Bennett +%%% @copyright 2014-2017, Andrew Bennett +%%% @doc +%%% +%%% @end +%%% Created : 12 May 2017 by Andrew Bennett +%%%------------------------------------------------------------------- + +-ifndef(JOSE_PUBLIC_KEY_HRL). + +-include_lib("public_key/include/public_key.hrl"). + +-define('jose_id-X25519', {1,3,101,110}). +-define('jose_id-X448', {1,3,101,111}). +-define('jose_id-EdDSA25519', {1,3,101,112}). +-define('jose_id-EdDSA448', {1,3,101,113}). + +-record(jose_EdDSA25519PublicKey, { + publicKey = undefined :: undefined | << _:256 >> +}). + +-record(jose_EdDSA25519PrivateKey, { + publicKey = undefined :: undefined | #jose_EdDSA25519PublicKey{}, + privateKey = undefined :: undefined | << _:256 >> +}). + +-record(jose_EdDSA448PublicKey, { + publicKey = undefined :: undefined | << _:456 >> +}). + +-record(jose_EdDSA448PrivateKey, { + publicKey = undefined :: undefined | #jose_EdDSA448PublicKey{}, + privateKey = undefined :: undefined | << _:456 >> +}). + +-record(jose_X25519PublicKey, { + publicKey = undefined :: undefined | << _:256 >> +}). + +-record(jose_X25519PrivateKey, { + publicKey = undefined :: undefined | #jose_X25519PublicKey{}, + privateKey = undefined :: undefined | << _:256 >> +}). + +-record(jose_X448PublicKey, { + publicKey = undefined :: undefined | << _:448 >> +}). + +-record(jose_X448PrivateKey, { + publicKey = undefined :: undefined | #jose_X448PublicKey{}, + privateKey = undefined :: undefined | << _:448 >> +}). + +-define(JOSE_PUBLIC_KEY_HRL, 1). + +-endif. diff --git a/src/jose_jwk_kty.erl b/src/jose_jwk_kty.erl index cd8d2d7..7c9d42b 100644 --- a/src/jose_jwk_kty.erl +++ b/src/jose_jwk_kty.erl @@ -10,7 +10,7 @@ %%%------------------------------------------------------------------- -module(jose_jwk_kty). --include_lib("public_key/include/public_key.hrl"). +-include_lib("jose_public_key.hrl"). -callback generate_key(Parameters) -> KTY when @@ -53,6 +53,22 @@ from_key(ECPrivateKey=#'ECPrivateKey'{}) -> {?KTY_EC_MODULE, ?KTY_EC_MODULE:from_key(ECPrivateKey)}; from_key(ECPublicKey={#'ECPoint'{}, _}) -> {?KTY_EC_MODULE, ?KTY_EC_MODULE:from_key(ECPublicKey)}; +from_key(EdDSA25519PrivateKey=#'jose_EdDSA25519PrivateKey'{}) -> + {?KTY_OKP_Ed25519_MODULE, ?KTY_OKP_Ed25519_MODULE:from_key(EdDSA25519PrivateKey)}; +from_key(EdDSA25519PublicKey=#'jose_EdDSA25519PublicKey'{}) -> + {?KTY_OKP_Ed25519_MODULE, ?KTY_OKP_Ed25519_MODULE:from_key(EdDSA25519PublicKey)}; +from_key(EdDSA448PrivateKey=#'jose_EdDSA448PrivateKey'{}) -> + {?KTY_OKP_Ed448_MODULE, ?KTY_OKP_Ed448_MODULE:from_key(EdDSA448PrivateKey)}; +from_key(EdDSA448PublicKey=#'jose_EdDSA448PublicKey'{}) -> + {?KTY_OKP_Ed448_MODULE, ?KTY_OKP_Ed448_MODULE:from_key(EdDSA448PublicKey)}; +from_key(X25519PrivateKey=#'jose_X25519PrivateKey'{}) -> + {?KTY_OKP_X25519_MODULE, ?KTY_OKP_X25519_MODULE:from_key(X25519PrivateKey)}; +from_key(X25519PublicKey=#'jose_X25519PublicKey'{}) -> + {?KTY_OKP_X25519_MODULE, ?KTY_OKP_X25519_MODULE:from_key(X25519PublicKey)}; +from_key(X448PrivateKey=#'jose_X448PrivateKey'{}) -> + {?KTY_OKP_X448_MODULE, ?KTY_OKP_X448_MODULE:from_key(X448PrivateKey)}; +from_key(X448PublicKey=#'jose_X448PublicKey'{}) -> + {?KTY_OKP_X448_MODULE, ?KTY_OKP_X448_MODULE:from_key(X448PublicKey)}; from_key(RSAPrivateKey=#'RSAPrivateKey'{}) -> {?KTY_RSA_MODULE, ?KTY_RSA_MODULE:from_key(RSAPrivateKey)}; from_key(RSAPublicKey=#'RSAPublicKey'{}) -> diff --git a/src/jose_jwk_kty_okp_ed25519.erl b/src/jose_jwk_kty_okp_ed25519.erl index b78bd88..23a1ddb 100644 --- a/src/jose_jwk_kty_okp_ed25519.erl +++ b/src/jose_jwk_kty_okp_ed25519.erl @@ -13,6 +13,8 @@ -behaviour(jose_jwk_kty). -behaviour(jose_jwk_use_sig). +-include_lib("jose_public_key.hrl"). + %% jose_jwk callbacks -export([from_map/1]). -export([to_key/1]). @@ -29,10 +31,15 @@ -export([verifier/2]). -export([verify/4]). %% API +-export([from_key/1]). -export([from_okp/1]). -export([from_openssh_key/1]). +-export([from_pem/1]). +-export([from_pem/2]). -export([to_okp/1]). -export([to_openssh_key/2]). +-export([to_pem/1]). +-export([to_pem/2]). %% Macros -define(crv, <<"Ed25519">>). @@ -60,10 +67,13 @@ from_map(F = #{ <<"kty">> := <<"OKP">>, <<"crv">> := ?crv, <<"x">> := X }) -> << PK:?publickeybytes/binary >> = base64url:decode(X), {PK, maps:without([<<"crv">>, <<"kty">>, <<"x">>], F)}. -to_key(PK = << _:?publickeybytes/binary >>) -> - PK; -to_key(SK = << _:?secretkeybytes/binary >>) -> - SK. +to_key(<< PublicKey:?publickeybytes/binary >>) -> + #'jose_EdDSA25519PublicKey'{ publicKey = PublicKey }; +to_key(<< PrivateKey:?secretbytes/binary, PublicKey:?publickeybytes/binary >>) -> + #'jose_EdDSA25519PrivateKey'{ + publicKey = #'jose_EdDSA25519PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }. to_map(PK = << _:?publickeybytes/binary >>, F) -> F#{ @@ -145,6 +155,11 @@ verify(Message, ALG, Signature, PK = << _:?publickeybytes/binary >>) %% API functions %%==================================================================== +from_key(#'jose_EdDSA25519PrivateKey'{publicKey=#'jose_EdDSA25519PublicKey'{publicKey=Public}, privateKey=Secret}) -> + {<< Secret/binary, Public/binary >>, #{}}; +from_key(#'jose_EdDSA25519PublicKey'{publicKey=Public}) -> + {Public, #{}}. + from_okp({'Ed25519', SK = << Secret:?secretbytes/binary, PK:?publickeybytes/binary >>}) -> case jose_curve25519:eddsa_secret_to_public(Secret) of PK -> @@ -164,6 +179,22 @@ from_openssh_key({<<"ssh-ed25519">>, _PK, SK, Comment}) -> {KTY, maps:merge(#{ <<"kid">> => Comment }, OtherFields)} end. +from_pem(PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + +from_pem(Password, PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(Password, PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + to_okp(SK = << _:?secretkeybytes/binary >>) -> {'Ed25519', SK}; to_okp(PK = << _:?publickeybytes/binary >>) -> @@ -173,6 +204,22 @@ to_openssh_key(SK = << _:?secretbytes/binary, PK:?publickeybytes/binary >>, F) - Comment = maps:get(<<"kid">>, F, <<>>), jose_jwk_openssh_key:to_binary([[{{<<"ssh-ed25519">>, PK}, {<<"ssh-ed25519">>, PK, SK, Comment}}]]). +to_pem(SK = << _:?secretkeybytes/binary >>) -> + EdDSA25519PrivateKey = to_key(SK), + PEMEntry = jose_public_key:pem_entry_encode('EdDSA25519PrivateKey', EdDSA25519PrivateKey), + jose_public_key:pem_encode([PEMEntry]); +to_pem(PK = << _:?publickeybytes/binary >>) -> + EdDSA25519PublicKey = to_key(PK), + PEMEntry = jose_public_key:pem_entry_encode('EdDSA25519PublicKey', EdDSA25519PublicKey), + jose_public_key:pem_encode([PEMEntry]). + +to_pem(Password, SK = << _:?secretkeybytes/binary >>) -> + EdDSA25519PrivateKey = to_key(SK), + jose_jwk_pem:to_binary(Password, 'EdDSA25519PrivateKey', EdDSA25519PrivateKey); +to_pem(Password, PK = << _:?publickeybytes/binary >>) -> + EdDSA25519PublicKey = to_key(PK), + jose_jwk_pem:to_binary(Password, 'EdDSA25519PublicKey', EdDSA25519PublicKey). + %%%------------------------------------------------------------------- %%% Internal functions %%%------------------------------------------------------------------- diff --git a/src/jose_jwk_kty_okp_ed448.erl b/src/jose_jwk_kty_okp_ed448.erl index 9a0b120..1bb2ebc 100644 --- a/src/jose_jwk_kty_okp_ed448.erl +++ b/src/jose_jwk_kty_okp_ed448.erl @@ -13,6 +13,8 @@ -behaviour(jose_jwk_kty). -behaviour(jose_jwk_use_sig). +-include_lib("jose_public_key.hrl"). + %% jose_jwk callbacks -export([from_map/1]). -export([to_key/1]). @@ -29,10 +31,15 @@ -export([verifier/2]). -export([verify/4]). %% API +-export([from_key/1]). -export([from_okp/1]). -export([from_openssh_key/1]). +-export([from_pem/1]). +-export([from_pem/2]). -export([to_okp/1]). -export([to_openssh_key/2]). +-export([to_pem/1]). +-export([to_pem/2]). %% Macros -define(crv, <<"Ed448">>). @@ -60,10 +67,13 @@ from_map(F = #{ <<"kty">> := <<"OKP">>, <<"crv">> := ?crv, <<"x">> := X }) -> << PK:?publickeybytes/binary >> = base64url:decode(X), {PK, maps:without([<<"crv">>, <<"kty">>, <<"x">>], F)}. -to_key(PK = << _:?publickeybytes/binary >>) -> - PK; -to_key(SK = << _:?secretkeybytes/binary >>) -> - SK. +to_key(<< PublicKey:?publickeybytes/binary >>) -> + #'jose_EdDSA448PublicKey'{ publicKey = PublicKey }; +to_key(<< PrivateKey:?secretbytes/binary, PublicKey:?publickeybytes/binary >>) -> + #'jose_EdDSA448PrivateKey'{ + publicKey = #'jose_EdDSA448PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }. to_map(PK = << _:?publickeybytes/binary >>, F) -> F#{ @@ -145,6 +155,11 @@ verify(Message, ALG, Signature, PK = << _:?publickeybytes/binary >>) %% API functions %%==================================================================== +from_key(#'jose_EdDSA448PrivateKey'{publicKey=#'jose_EdDSA448PublicKey'{publicKey=Public}, privateKey=Secret}) -> + {<< Secret/binary, Public/binary >>, #{}}; +from_key(#'jose_EdDSA448PublicKey'{publicKey=Public}) -> + {Public, #{}}. + from_okp({'Ed448', SK = << Secret:?secretbytes/binary, PK:?publickeybytes/binary >>}) -> case jose_curve448:eddsa_secret_to_public(Secret) of PK -> @@ -164,6 +179,22 @@ from_openssh_key({<<"ssh-ed448">>, _PK, SK, Comment}) -> {KTY, maps:merge(#{ <<"kid">> => Comment }, OtherFields)} end. +from_pem(PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + +from_pem(Password, PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(Password, PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + to_okp(SK = << _:?secretkeybytes/binary >>) -> {'Ed448', SK}; to_okp(PK = << _:?publickeybytes/binary >>) -> @@ -173,6 +204,22 @@ to_openssh_key(SK = << _:?secretbytes/binary, PK:?publickeybytes/binary >>, F) - Comment = maps:get(<<"kid">>, F, <<>>), jose_jwk_openssh_key:to_binary([[{{<<"ssh-ed448">>, PK}, {<<"ssh-ed448">>, PK, SK, Comment}}]]). +to_pem(SK = << _:?secretkeybytes/binary >>) -> + EdDSA448PrivateKey = to_key(SK), + PEMEntry = jose_public_key:pem_entry_encode('EdDSA448PrivateKey', EdDSA448PrivateKey), + jose_public_key:pem_encode([PEMEntry]); +to_pem(PK = << _:?publickeybytes/binary >>) -> + EdDSA448PublicKey = to_key(PK), + PEMEntry = jose_public_key:pem_entry_encode('EdDSA448PublicKey', EdDSA448PublicKey), + jose_public_key:pem_encode([PEMEntry]). + +to_pem(Password, SK = << _:?secretkeybytes/binary >>) -> + EdDSA448PrivateKey = to_key(SK), + jose_jwk_pem:to_binary(Password, 'EdDSA448PrivateKey', EdDSA448PrivateKey); +to_pem(Password, PK = << _:?publickeybytes/binary >>) -> + EdDSA448PublicKey = to_key(PK), + jose_jwk_pem:to_binary(Password, 'EdDSA448PublicKey', EdDSA448PublicKey). + %%%------------------------------------------------------------------- %%% Internal functions %%%------------------------------------------------------------------- diff --git a/src/jose_jwk_kty_okp_x25519.erl b/src/jose_jwk_kty_okp_x25519.erl index f442e15..3c2698a 100644 --- a/src/jose_jwk_kty_okp_x25519.erl +++ b/src/jose_jwk_kty_okp_x25519.erl @@ -13,6 +13,8 @@ -behaviour(jose_jwk_kty). -behaviour(jose_jwk_use_enc). +-include_lib("jose_public_key.hrl"). + %% jose_jwk callbacks -export([from_map/1]). -export([to_key/1]). @@ -27,10 +29,15 @@ -export([block_encryptor/2]). -export([derive_key/2]). %% API +-export([from_key/1]). -export([from_okp/1]). -export([from_openssh_key/1]). +-export([from_pem/1]). +-export([from_pem/2]). -export([to_okp/1]). -export([to_openssh_key/2]). +-export([to_pem/1]). +-export([to_pem/2]). %% Macros -define(crv, <<"X25519">>). @@ -58,10 +65,13 @@ from_map(F = #{ <<"kty">> := <<"OKP">>, <<"crv">> := ?crv, <<"x">> := X }) -> << PK:?publickeybytes/binary >> = base64url:decode(X), {PK, maps:without([<<"crv">>, <<"kty">>, <<"x">>], F)}. -to_key(PK = << _:?publickeybytes/binary >>) -> - PK; -to_key(SK = << _:?secretkeybytes/binary >>) -> - SK. +to_key(<< PublicKey:?publickeybytes/binary >>) -> + #'jose_X25519PublicKey'{ publicKey = PublicKey }; +to_key(<< PrivateKey:?secretbytes/binary, PublicKey:?publickeybytes/binary >>) -> + #'jose_X25519PrivateKey'{ + publicKey = #'jose_X25519PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }. to_map(PK = << _:?publickeybytes/binary >>, F) -> F#{ @@ -145,6 +155,11 @@ derive_key(PK = << _:?publickeybytes/binary >>, << Secret:?secretbytes/binary, _ %% API functions %%==================================================================== +from_key(#'jose_X25519PrivateKey'{publicKey=#'jose_X25519PublicKey'{publicKey=Public}, privateKey=Secret}) -> + {<< Secret/binary, Public/binary >>, #{}}; +from_key(#'jose_X25519PublicKey'{publicKey=Public}) -> + {Public, #{}}. + from_okp({'X25519', SK = << Secret:?secretbytes/binary, PK:?publickeybytes/binary >>}) -> case jose_curve25519:x25519_secret_to_public(Secret) of PK -> @@ -164,6 +179,22 @@ from_openssh_key({<<"ssh-x25519">>, _PK, SK, Comment}) -> {KTY, maps:merge(#{ <<"kid">> => Comment }, OtherFields)} end. +from_pem(PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + +from_pem(Password, PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(Password, PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + to_okp(SK = << _:?secretkeybytes/binary >>) -> {'X25519', SK}; to_okp(PK = << _:?publickeybytes/binary >>) -> @@ -173,6 +204,22 @@ to_openssh_key(SK = << _:?secretbytes/binary, PK:?publickeybytes/binary >>, F) - Comment = maps:get(<<"kid">>, F, <<>>), jose_jwk_openssh_key:to_binary([[{{<<"ssh-x25519">>, PK}, {<<"ssh-x25519">>, PK, SK, Comment}}]]). +to_pem(SK = << _:?secretkeybytes/binary >>) -> + X25519PrivateKey = to_key(SK), + PEMEntry = jose_public_key:pem_entry_encode('X25519PrivateKey', X25519PrivateKey), + jose_public_key:pem_encode([PEMEntry]); +to_pem(PK = << _:?publickeybytes/binary >>) -> + X25519PublicKey = to_key(PK), + PEMEntry = jose_public_key:pem_entry_encode('X25519PublicKey', X25519PublicKey), + jose_public_key:pem_encode([PEMEntry]). + +to_pem(Password, SK = << _:?secretkeybytes/binary >>) -> + X25519PrivateKey = to_key(SK), + jose_jwk_pem:to_binary(Password, 'X25519PrivateKey', X25519PrivateKey); +to_pem(Password, PK = << _:?publickeybytes/binary >>) -> + X25519PublicKey = to_key(PK), + jose_jwk_pem:to_binary(Password, 'X25519PublicKey', X25519PublicKey). + %%%------------------------------------------------------------------- %%% Internal functions %%%------------------------------------------------------------------- diff --git a/src/jose_jwk_kty_okp_x448.erl b/src/jose_jwk_kty_okp_x448.erl index b54a7f8..9dbacc8 100644 --- a/src/jose_jwk_kty_okp_x448.erl +++ b/src/jose_jwk_kty_okp_x448.erl @@ -13,6 +13,8 @@ -behaviour(jose_jwk_kty). -behaviour(jose_jwk_use_enc). +-include_lib("jose_public_key.hrl"). + %% jose_jwk callbacks -export([from_map/1]). -export([to_key/1]). @@ -27,10 +29,15 @@ -export([block_encryptor/2]). -export([derive_key/2]). %% API +-export([from_key/1]). -export([from_okp/1]). -export([from_openssh_key/1]). +-export([from_pem/1]). +-export([from_pem/2]). -export([to_okp/1]). -export([to_openssh_key/2]). +-export([to_pem/1]). +-export([to_pem/2]). %% Macros -define(crv, <<"X448">>). @@ -58,10 +65,13 @@ from_map(F = #{ <<"kty">> := <<"OKP">>, <<"crv">> := ?crv, <<"x">> := X }) -> << PK:?publickeybytes/binary >> = base64url:decode(X), {PK, maps:without([<<"crv">>, <<"kty">>, <<"x">>], F)}. -to_key(PK = << _:?publickeybytes/binary >>) -> - PK; -to_key(SK = << _:?secretkeybytes/binary >>) -> - SK. +to_key(<< PublicKey:?publickeybytes/binary >>) -> + #'jose_X448PublicKey'{ publicKey = PublicKey }; +to_key(<< PrivateKey:?secretbytes/binary, PublicKey:?publickeybytes/binary >>) -> + #'jose_X448PrivateKey'{ + publicKey = #'jose_X448PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }. to_map(PK = << _:?publickeybytes/binary >>, F) -> F#{ @@ -145,6 +155,11 @@ derive_key(PK = << _:?publickeybytes/binary >>, << Secret:?secretbytes/binary, _ %% API functions %%==================================================================== +from_key(#'jose_X448PrivateKey'{publicKey=#'jose_X448PublicKey'{publicKey=Public}, privateKey=Secret}) -> + {<< Secret/binary, Public/binary >>, #{}}; +from_key(#'jose_X448PublicKey'{publicKey=Public}) -> + {Public, #{}}. + from_okp({'X448', SK = << Secret:?secretbytes/binary, PK:?publickeybytes/binary >>}) -> case jose_curve448:x448_secret_to_public(Secret) of PK -> @@ -164,6 +179,22 @@ from_openssh_key({<<"ssh-x448">>, _PK, SK, Comment}) -> {KTY, maps:merge(#{ <<"kid">> => Comment }, OtherFields)} end. +from_pem(PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + +from_pem(Password, PEMBinary) when is_binary(PEMBinary) -> + case jose_jwk_pem:from_binary(Password, PEMBinary) of + {?MODULE, {Key, Fields}} -> + {Key, Fields}; + PEMError -> + PEMError + end. + to_okp(SK = << _:?secretkeybytes/binary >>) -> {'X448', SK}; to_okp(PK = << _:?publickeybytes/binary >>) -> @@ -173,6 +204,22 @@ to_openssh_key(SK = << _:?secretbytes/binary, PK:?publickeybytes/binary >>, F) - Comment = maps:get(<<"kid">>, F, <<>>), jose_jwk_openssh_key:to_binary([[{{<<"ssh-x448">>, PK}, {<<"ssh-x448">>, PK, SK, Comment}}]]). +to_pem(SK = << _:?secretkeybytes/binary >>) -> + X448PrivateKey = to_key(SK), + PEMEntry = jose_public_key:pem_entry_encode('X448PrivateKey', X448PrivateKey), + jose_public_key:pem_encode([PEMEntry]); +to_pem(PK = << _:?publickeybytes/binary >>) -> + X448PublicKey = to_key(PK), + PEMEntry = jose_public_key:pem_entry_encode('X448PublicKey', X448PublicKey), + jose_public_key:pem_encode([PEMEntry]). + +to_pem(Password, SK = << _:?secretkeybytes/binary >>) -> + X448PrivateKey = to_key(SK), + jose_jwk_pem:to_binary(Password, 'X448PrivateKey', X448PrivateKey); +to_pem(Password, PK = << _:?publickeybytes/binary >>) -> + X448PublicKey = to_key(PK), + jose_jwk_pem:to_binary(Password, 'X448PublicKey', X448PublicKey). + %%%------------------------------------------------------------------- %%% Internal functions %%%------------------------------------------------------------------- diff --git a/src/jose_jwk_pem.erl b/src/jose_jwk_pem.erl index fc788e9..c6b1036 100644 --- a/src/jose_jwk_pem.erl +++ b/src/jose_jwk_pem.erl @@ -24,33 +24,33 @@ %%==================================================================== from_binary(PEMBinary) when is_binary(PEMBinary) -> - case public_key:pem_decode(PEMBinary) of + case jose_public_key:pem_decode(PEMBinary) of [CertificatePEMEntry={'Certificate', _, not_encrypted}] -> from_certificate(CertificatePEMEntry); [PEMEntry] -> - jose_jwk_kty:from_key(public_key:pem_entry_decode(PEMEntry)); + jose_jwk_kty:from_key(jose_public_key:pem_entry_decode(PEMEntry)); PEMDecodeError -> PEMDecodeError end. from_binary(Password, EncryptedPEMBinary) when is_binary(EncryptedPEMBinary) -> - case public_key:pem_decode(EncryptedPEMBinary) of + case jose_public_key:pem_decode(EncryptedPEMBinary) of [EncryptedPEMEntry] -> PasswordString = unicode:characters_to_list(Password), - jose_jwk_kty:from_key(public_key:pem_entry_decode(EncryptedPEMEntry, PasswordString)); + jose_jwk_kty:from_key(jose_public_key:pem_entry_decode(EncryptedPEMEntry, PasswordString)); PEMDecodeError -> PEMDecodeError end. from_certificate(CertificateBinary) when is_binary(CertificateBinary) -> - case public_key:pem_decode(CertificateBinary) of + case jose_public_key:pem_decode(CertificateBinary) of [CertificatePEMEntry={'Certificate', _, not_encrypted}] -> from_certificate(CertificatePEMEntry); PEMDecodeError -> {error, {pem_decode, PEMDecodeError}} end; from_certificate(CertificatePEMEntry={'Certificate', _, not_encrypted}) -> - case public_key:pem_entry_decode(CertificatePEMEntry) of + case jose_public_key:pem_entry_decode(CertificatePEMEntry) of Certificate=#'Certificate'{} -> from_certificate(Certificate); PEMEntryDecodeError -> @@ -60,15 +60,16 @@ from_certificate(#'Certificate'{tbsCertificate=#'TBSCertificate'{subjectPublicKe from_public_key_info(SubjectPublicKeyInfo). from_public_key_info(#'SubjectPublicKeyInfo'{algorithm=#'AlgorithmIdentifier'{}}=SubjectPublicKeyInfo) -> - from_public_key_info(public_key:pem_entry_encode('SubjectPublicKeyInfo', SubjectPublicKeyInfo)); + from_public_key_info(jose_public_key:pem_entry_encode('SubjectPublicKeyInfo', SubjectPublicKeyInfo)); from_public_key_info(PEMEntry={'SubjectPublicKeyInfo', DER, not_encrypted}) when is_binary(DER) -> - jose_jwk_kty:from_key(public_key:pem_entry_decode(PEMEntry)). + jose_jwk_kty:from_key(jose_public_key:pem_entry_decode(PEMEntry)). to_binary(Password, KeyType, Key) -> + % CipherInfo = {"AES-128-CBC", crypto:strong_rand_bytes(16)}, CipherInfo = {"DES-EDE3-CBC", crypto:strong_rand_bytes(8)}, PasswordString = binary_to_list(iolist_to_binary(Password)), - PEMEntry = public_key:pem_entry_encode(KeyType, Key, {CipherInfo, PasswordString}), - public_key:pem_encode([PEMEntry]). + PEMEntry = jose_public_key:pem_entry_encode(KeyType, Key, {CipherInfo, PasswordString}), + jose_public_key:pem_encode([PEMEntry]). %%%------------------------------------------------------------------- %%% Internal functions diff --git a/src/jose_public_key.erl b/src/jose_public_key.erl new file mode 100644 index 0000000..03d90f9 --- /dev/null +++ b/src/jose_public_key.erl @@ -0,0 +1,461 @@ +%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*- +%% vim: ts=4 sw=4 ft=erlang noet +%%%------------------------------------------------------------------- +%%% @author Andrew Bennett +%%% @copyright 2014-2017, Andrew Bennett +%%% @doc +%%% +%%% @end +%%% Created : 18 May 2017 by Andrew Bennett +%%%------------------------------------------------------------------- +-module(jose_public_key). + +-include("jose_public_key.hrl"). + +%% API +-export([der_decode/2]). +-export([der_encode/2]). +-export([pem_decode/1]). +-export([pem_encode/1]). +-export([pem_entry_decode/1]). +-export([pem_entry_decode/2]). +-export([pem_entry_encode/2]). +-export([pem_entry_encode/3]). + +%%==================================================================== +%% API functions +%%==================================================================== + +der_decode(ASN1Type, DER) when is_atom(ASN1Type) andalso is_binary(DER) -> + public_key:der_decode(ASN1Type, DER). + +der_encode(ASN1Type, Entity) when is_atom(ASN1Type) -> + public_key:der_encode(ASN1Type, Entity). + +pem_decode(PEMBinary) when is_binary(PEMBinary) -> + public_key:pem_decode(PEMBinary). + +pem_encode(PEMEntries) when is_list(PEMEntries) -> + % public_key:pem_encode(PEMEntries). + try + public_key:pem_encode(PEMEntries) + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + case pem_enc(PEMEntries) of + {true, PEMBinary} -> + PEMBinary; + false -> + erlang:raise(Class, Reason, ST) + end + end. + +pem_entry_decode(PEMEntry) -> + % public_key:pem_entry_decode(PEMEntry). + Result = + try + public_key:pem_entry_decode(PEMEntry) + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + case pem_entry_dec(PEMEntry) of + {true, DecodedPEMEntry} -> + DecodedPEMEntry; + false -> + erlang:raise(Class, Reason, ST) + end + end, + case Result of + PrivateKeyInfo=#'PrivateKeyInfo'{} -> + i2k(PrivateKeyInfo); + SubjectPublicKeyInfo=#'SubjectPublicKeyInfo'{} -> + i2k(SubjectPublicKeyInfo); + Other -> + Other + end. + +pem_entry_decode(PEMEntry, Password) -> + % public_key:pem_entry_decode(PEMEntry, Password). + Result = + try + public_key:pem_entry_decode(PEMEntry, Password) + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + case pem_entry_dec(PEMEntry) of + {true, DecodedPEMEntry} -> + DecodedPEMEntry; + false -> + erlang:raise(Class, Reason, ST) + end + end, + case Result of + PrivateKeyInfo=#'PrivateKeyInfo'{} -> + i2k(PrivateKeyInfo); + SubjectPublicKeyInfo=#'SubjectPublicKeyInfo'{} -> + i2k(SubjectPublicKeyInfo); + Other -> + Other + end. + +pem_entry_encode(ASN1Type, Entity) -> + % public_key:pem_entry_encode(ASN1Type, Entity). + try + public_key:pem_entry_encode(ASN1Type, Entity) + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + case pem_entry_enc(ASN1Type, Entity) of + {true, PEMEntry} -> + PEMEntry; + false -> + erlang:raise(Class, Reason, ST) + end + end. + +pem_entry_encode(ASN1Type, Entity, Password) -> + % public_key:pem_entry_encode(ASN1Type, Entity, Password). + try + public_key:pem_entry_encode(ASN1Type, Entity, Password) + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + case pem_entry_enc(ASN1Type, Entity, Password) of + {true, PEMEntry} -> + PEMEntry; + false -> + erlang:raise(Class, Reason, ST) + end + end. + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +%% @private +pem_enc(Entries) -> + pem_enc(Entries, []). + +%% @private +pem_enc([Entry={'PrivateKeyInfo', _, _} | Entries], Acc) -> + Encoded = + try + public_key:pem_encode([Entry]) + catch + _:_ -> + pem_entry_enc(Entry) + end, + pem_enc(Entries, [Encoded | Acc]); +pem_enc([Entry | Entries], Acc) -> + Encoded = public_key:pem_encode([Entry]), + pem_enc(Entries, [Encoded | Acc]); +pem_enc([], Acc) -> + {true, erlang:iolist_to_binary(lists:reverse(Acc))}. + +%% @private +pem_entry_dec({ASN1Type='PrivateKeyInfo', Der, not_encrypted}) -> + Entity = der_decode(ASN1Type, Der), + {true, i2k(Entity)}; +pem_entry_dec({ASN1Type='SubjectPublicKeyInfo', Der, not_encrypted}) -> + Entity = der_decode(ASN1Type, Der), + {true, i2k(Entity)}; +pem_entry_dec(_) -> + false. + +%% @private +pem_entry_enc({'PrivateKeyInfo', Der, EncParams}) -> + EncodedPEM = public_key:pem_encode([{'ECPrivateKey', Der, EncParams}]), + erlang:iolist_to_binary(binary:split(EncodedPEM, <<" EC">>, [global, trim_all])); +pem_entry_enc(Entry) -> + Entry. + +%% @private +pem_entry_enc('EdDSA25519PrivateKey', K=#'jose_EdDSA25519PrivateKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('PrivateKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('EdDSA25519PublicKey', K=#'jose_EdDSA25519PublicKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('SubjectPublicKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('EdDSA448PrivateKey', K=#'jose_EdDSA448PrivateKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('PrivateKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('EdDSA448PublicKey', K=#'jose_EdDSA448PublicKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('SubjectPublicKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('X25519PrivateKey', K=#'jose_X25519PrivateKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('PrivateKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('X25519PublicKey', K=#'jose_X25519PublicKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('SubjectPublicKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('X448PrivateKey', K=#'jose_X448PrivateKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('PrivateKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc('X448PublicKey', K=#'jose_X448PublicKey'{}) -> + EncodedPEMEntry = public_key:pem_entry_encode('SubjectPublicKeyInfo', k2i(K)), + {true, EncodedPEMEntry}; +pem_entry_enc(_, _) -> + false. + +%% @private +pem_entry_enc('EdDSA25519PrivateKey', K=#'jose_EdDSA25519PrivateKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('PrivateKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('EdDSA25519PublicKey', K=#'jose_EdDSA25519PublicKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('SubjectPublicKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('EdDSA448PrivateKey', K=#'jose_EdDSA448PrivateKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('PrivateKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('EdDSA448PublicKey', K=#'jose_EdDSA448PublicKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('SubjectPublicKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('X25519PrivateKey', K=#'jose_X25519PrivateKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('PrivateKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('X25519PublicKey', K=#'jose_X25519PublicKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('SubjectPublicKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('X448PrivateKey', K=#'jose_X448PrivateKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('PrivateKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc('X448PublicKey', K=#'jose_X448PublicKey'{}, Password) -> + EncodedPEMEntry = pem_entry_enc0('SubjectPublicKeyInfo', k2i(K), Password), + {true, EncodedPEMEntry}; +pem_entry_enc(_, _, _) -> + false. + +%% @private +pem_entry_enc0(ASN1Type, Entry, Cipher) -> + try + public_key:pem_entry_encode(ASN1Type, Entry, Cipher) + catch + Class:Reason -> + ST = erlang:get_stacktrace(), + case pem_entry_enc1(ASN1Type, Entry, Cipher) of + {true, Encoded} -> + Encoded; + false -> + erlang:raise(Class, Reason, ST) + end + end. + +%% @private +pem_entry_enc1(ASN1Type, Entry, {CipherInfo={C, _}, Password}) when C == "AES-128-CBC" -> + Der = der_encode(ASN1Type, Entry), + DecryptDer = pem_cipher(Der, CipherInfo, Password), + {true, {ASN1Type, DecryptDer, CipherInfo}}; +pem_entry_enc1(_, _, _) -> + false. + +%% @private +pem_cipher(Data, {Cipher = "AES-128-CBC", IV}, Password) -> + << Salt:8/binary, _/binary >> = IV, + {Key, _} = password_to_key_and_iv(Password, Cipher, Salt), + crypto:block_encrypt(aes_cbc128, Key, IV, jose_jwa_pkcs7:pad(Data)). + +%% @private +ceiling(Float) -> + erlang:round(Float + 0.5). + +%% @private +derived_key_length(_, Len) when is_integer(Len) -> + Len; +derived_key_length(Cipher, _) when (Cipher == "AES-128-CBC") -> + 16. + +%% @private +password_to_key_and_iv(Password, Cipher, Salt) -> + KeyLen = derived_key_length(Cipher, undefined), + << Key:KeyLen/binary, _/binary >> = + pem_encrypt(<<>>, Password, Salt, ceiling(KeyLen div 16), <<>>, md5), + %% Old PEM encryption does not use standard encryption method + %% pbdkdf1 and uses then salt as IV + {Key, Salt}. + +% %% @private +% pbe_pad(Data) -> +% N = 8 - (erlang:byte_size(Data) rem 8), +% Pad = binary:copy(<< N >>, N), +% <>. + +%% @private +pem_encrypt(_, _, _, 0, Acc, _) -> + Acc; +pem_encrypt(Prev, Password, Salt, Count, Acc, Hash) -> + Result = crypto:hash(Hash, [Prev, Password, Salt]), + pem_encrypt(Result, Password, Salt, Count-1 , <>, Hash). + +%% @private +i2k(#'PrivateKeyInfo'{ + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-EdDSA25519' + }, + privateKey = + << 4, 32:8/integer, PrivateKey:32/binary >> +}) -> + PublicKey = jose_curve25519:eddsa_secret_to_public(PrivateKey), + #'jose_EdDSA25519PrivateKey'{ + publicKey = #'jose_EdDSA25519PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }; +i2k(#'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-EdDSA25519' + }, + subjectPublicKey = << PublicKey:32/binary >> +}) -> + #'jose_EdDSA25519PublicKey'{ publicKey = PublicKey }; +i2k(#'PrivateKeyInfo'{ + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-EdDSA448' + }, + privateKey = + << 4, 57:8/integer, PrivateKey:57/binary >> +}) -> + PublicKey = jose_curve448:eddsa_secret_to_public(PrivateKey), + #'jose_EdDSA448PrivateKey'{ + publicKey = #'jose_EdDSA448PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }; +i2k(#'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-EdDSA448' + }, + subjectPublicKey = << PublicKey:57/binary >> +}) -> + #'jose_EdDSA448PublicKey'{ publicKey = PublicKey }; +i2k(#'PrivateKeyInfo'{ + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-X25519' + }, + privateKey = + << 4, 32:8/integer, PrivateKey:32/binary >> +}) -> + PublicKey = jose_curve25519:x25519_secret_to_public(PrivateKey), + #'jose_X25519PrivateKey'{ + publicKey = #'jose_X25519PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }; +i2k(#'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-X25519' + }, + subjectPublicKey = << PublicKey:32/binary >> +}) -> + #'jose_X25519PublicKey'{ publicKey = PublicKey }; +i2k(#'PrivateKeyInfo'{ + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-X448' + }, + privateKey = + << 4, 56:8/integer, PrivateKey:56/binary >> +}) -> + PublicKey = jose_curve448:x448_secret_to_public(PrivateKey), + #'jose_X448PrivateKey'{ + publicKey = #'jose_X448PublicKey'{ publicKey = PublicKey }, + privateKey = PrivateKey + }; +i2k(#'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-X448' + }, + subjectPublicKey = << PublicKey:56/binary >> +}) -> + #'jose_X448PublicKey'{ publicKey = PublicKey }; +i2k(Info) -> + Info. + +%% @private +k2i(#'jose_EdDSA25519PrivateKey'{privateKey=PrivateKey}) -> + #'PrivateKeyInfo'{ + version = v1, + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-EdDSA25519', + parameters = asn1_NOVALUE + }, + privateKey = + << 4, 32:8/integer, PrivateKey:32/binary >>, + attributes = asn1_NOVALUE + }; +k2i(#'jose_EdDSA25519PublicKey'{publicKey=PublicKey}) -> + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-EdDSA25519', + parameters = asn1_NOVALUE + }, + subjectPublicKey = << PublicKey:32/binary >> + }; +k2i(#'jose_EdDSA448PrivateKey'{privateKey=PrivateKey}) -> + #'PrivateKeyInfo'{ + version = v1, + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-EdDSA448', + parameters = asn1_NOVALUE + }, + privateKey = + << 4, 57:8/integer, PrivateKey:57/binary >>, + attributes = asn1_NOVALUE + }; +k2i(#'jose_EdDSA448PublicKey'{publicKey=PublicKey}) -> + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-EdDSA448', + parameters = asn1_NOVALUE + }, + subjectPublicKey = << PublicKey:57/binary >> + }; +k2i(#'jose_X25519PrivateKey'{privateKey=PrivateKey}) -> + #'PrivateKeyInfo'{ + version = v1, + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-X25519', + parameters = asn1_NOVALUE + }, + privateKey = + << 4, 32:8/integer, PrivateKey:32/binary >>, + attributes = asn1_NOVALUE + }; +k2i(#'jose_X25519PublicKey'{publicKey=PublicKey}) -> + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-X25519', + parameters = asn1_NOVALUE + }, + subjectPublicKey = << PublicKey:32/binary >> + }; +k2i(#'jose_X448PrivateKey'{privateKey=PrivateKey}) -> + #'PrivateKeyInfo'{ + version = v1, + privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{ + algorithm = ?'jose_id-X448', + parameters = asn1_NOVALUE + }, + privateKey = + << 4, 56:8/integer, PrivateKey:56/binary >>, + attributes = asn1_NOVALUE + }; +k2i(#'jose_X448PublicKey'{publicKey=PublicKey}) -> + #'SubjectPublicKeyInfo'{ + algorithm = + #'AlgorithmIdentifier'{ + algorithm = ?'jose_id-X448', + parameters = asn1_NOVALUE + }, + subjectPublicKey = << PublicKey:56/binary >> + }. diff --git a/test/jose_jwk_SUITE.erl b/test/jose_jwk_SUITE.erl index d5dd254..c13403e 100644 --- a/test/jose_jwk_SUITE.erl +++ b/test/jose_jwk_SUITE.erl @@ -24,16 +24,20 @@ -export([kty_oct_block_encrypt_and_block_decrypt/1]). -export([kty_oct_sign_and_verify/1]). -export([kty_okp_ed25519_from_map_and_to_map/1]). +-export([kty_okp_ed25519_from_pem_and_to_pem/1]). -export([kty_okp_ed25519_sign_and_verify/1]). -export([kty_okp_ed25519ph_from_map_and_to_map/1]). -export([kty_okp_ed25519ph_sign_and_verify/1]). -export([kty_okp_ed448_from_map_and_to_map/1]). +-export([kty_okp_ed448_from_pem_and_to_pem/1]). -export([kty_okp_ed448_sign_and_verify/1]). -export([kty_okp_ed448ph_from_map_and_to_map/1]). -export([kty_okp_ed448ph_sign_and_verify/1]). -export([kty_okp_x25519_from_map_and_to_map/1]). +-export([kty_okp_x25519_from_pem_and_to_pem/1]). -export([kty_okp_x25519_box_encrypt_and_box_decrypt/1]). -export([kty_okp_x448_from_map_and_to_map/1]). +-export([kty_okp_x448_from_pem_and_to_pem/1]). -export([kty_okp_x448_box_encrypt_and_box_decrypt/1]). -export([kty_rsa_convert_sfm_to_crt/1]). -export([kty_rsa_from_map_and_to_map/1]). @@ -75,6 +79,7 @@ groups() -> ]}, {jose_jwk_kty_okp_ed25519, [parallel], [ kty_okp_ed25519_from_map_and_to_map, + kty_okp_ed25519_from_pem_and_to_pem, kty_okp_ed25519_sign_and_verify ]}, {jose_jwk_kty_okp_ed25519ph, [parallel], [ @@ -83,6 +88,7 @@ groups() -> ]}, {jose_jwk_kty_okp_ed448, [parallel], [ kty_okp_ed448_from_map_and_to_map, + kty_okp_ed448_from_pem_and_to_pem, kty_okp_ed448_sign_and_verify ]}, {jose_jwk_kty_okp_ed448ph, [parallel], [ @@ -91,10 +97,12 @@ groups() -> ]}, {jose_jwk_kty_okp_x25519, [parallel], [ kty_okp_x25519_from_map_and_to_map, + kty_okp_x25519_from_pem_and_to_pem, kty_okp_x25519_box_encrypt_and_box_decrypt ]}, {jose_jwk_kty_okp_x448, [parallel], [ kty_okp_x448_from_map_and_to_map, + kty_okp_x448_from_pem_and_to_pem, kty_okp_x448_box_encrypt_and_box_decrypt ]}, {jose_jwk_kty_rsa, [parallel], [ @@ -176,6 +184,11 @@ kty_okp_ed25519_from_map_and_to_map(Config) -> jose_jwk_kty_okp_ed25519_props:prop_from_map_and_to_map(), Config). +kty_okp_ed25519_from_pem_and_to_pem(Config) -> + ct_property_test:quickcheck( + jose_jwk_kty_okp_ed25519_props:prop_from_pem_and_to_pem(), + Config). + kty_okp_ed25519_sign_and_verify(Config) -> ct_property_test:quickcheck( jose_jwk_kty_okp_ed25519_props:prop_sign_and_verify(), @@ -196,6 +209,11 @@ kty_okp_ed448_from_map_and_to_map(Config) -> jose_jwk_kty_okp_ed448_props:prop_from_map_and_to_map(), Config). +kty_okp_ed448_from_pem_and_to_pem(Config) -> + ct_property_test:quickcheck( + jose_jwk_kty_okp_ed448_props:prop_from_pem_and_to_pem(), + Config). + kty_okp_ed448_sign_and_verify(Config) -> ct_property_test:quickcheck( jose_jwk_kty_okp_ed448_props:prop_sign_and_verify(), @@ -216,6 +234,11 @@ kty_okp_x25519_from_map_and_to_map(Config) -> jose_jwk_kty_okp_x25519_props:prop_from_map_and_to_map(), Config). +kty_okp_x25519_from_pem_and_to_pem(Config) -> + ct_property_test:quickcheck( + jose_jwk_kty_okp_x25519_props:prop_from_pem_and_to_pem(), + Config). + kty_okp_x25519_box_encrypt_and_box_decrypt(Config) -> ct_property_test:quickcheck( jose_jwk_kty_okp_x25519_props:prop_box_encrypt_and_box_decrypt(), @@ -226,6 +249,11 @@ kty_okp_x448_from_map_and_to_map(Config) -> jose_jwk_kty_okp_x448_props:prop_from_map_and_to_map(), Config). +kty_okp_x448_from_pem_and_to_pem(Config) -> + ct_property_test:quickcheck( + jose_jwk_kty_okp_x448_props:prop_from_pem_and_to_pem(), + Config). + kty_okp_x448_box_encrypt_and_box_decrypt(Config) -> ct_property_test:quickcheck( jose_jwk_kty_okp_x448_props:prop_box_encrypt_and_box_decrypt(), diff --git a/test/property_test/jose_jwk_kty_okp_ed25519_props.erl b/test/property_test/jose_jwk_kty_okp_ed25519_props.erl index a235276..0b76d5a 100644 --- a/test/property_test/jose_jwk_kty_okp_ed25519_props.erl +++ b/test/property_test/jose_jwk_kty_okp_ed25519_props.erl @@ -2,7 +2,7 @@ %% vim: ts=4 sw=4 ft=erlang noet -module(jose_jwk_kty_okp_ed25519_props). --include_lib("public_key/include/public_key.hrl"). +-include_lib("jose/include/jose_public_key.hrl"). -include_lib("triq/include/triq.hrl"). @@ -22,8 +22,10 @@ ed25519_secret() -> binary(32). ed25519_keypair(Secret) -> - {PK, SK} = jose_curve25519:eddsa_keypair(Secret), - {SK, PK}. + {PK, << S:32/binary, _:32/binary >>} = jose_curve25519:eddsa_keypair(Secret), + PublicKey = #'jose_EdDSA25519PublicKey'{publicKey=PK}, + SecretKey = #'jose_EdDSA25519PrivateKey'{publicKey=PublicKey, privateKey=S}, + {SecretKey, PublicKey}. jwk_map() -> ?LET({AliceSecret, BobSecret}, @@ -31,7 +33,7 @@ jwk_map() -> begin AliceKeys = {AlicePrivateKey, _} = ed25519_keypair(AliceSecret), BobKeys = ed25519_keypair(BobSecret), - AlicePrivateJWK = jose_jwk:from_okp({'Ed25519', AlicePrivateKey}), + AlicePrivateJWK = jose_jwk:from_key(AlicePrivateKey), {_, AlicePrivateJWKMap} = jose_jwk:to_map(AlicePrivateJWK), Keys = {AliceKeys, BobKeys}, {Keys, AlicePrivateJWKMap} @@ -59,6 +61,23 @@ prop_from_map_and_to_map() -> andalso AlicePublicThumbprint =:= jose_jwk:thumbprint(AlicePrivateJWK) end). +prop_from_pem_and_to_pem() -> + ?FORALL({_Keys, AlicePrivateJWK, Password}, + ?LET({{Keys, AlicePrivateJWK}, Bytes}, + {jwk_gen(), binary()}, + {Keys, AlicePrivateJWK, base64url:encode(Bytes)}), + begin + AlicePrivatePEM = element(2, jose_jwk:to_pem(AlicePrivateJWK)), + EncryptedAlicePrivatePEM = element(2, jose_jwk:to_pem(Password, AlicePrivateJWK)), + AlicePublicJWK = jose_jwk:to_public(AlicePrivateJWK), + AlicePublicPEM = element(2, jose_jwk:to_pem(AlicePublicJWK)), + EncryptedAlicePublicPEM = element(2, jose_jwk:to_pem(Password, AlicePublicJWK)), + AlicePrivateJWK =:= jose_jwk:from_pem(AlicePrivatePEM) + andalso AlicePrivateJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePrivatePEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(AlicePublicPEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePublicPEM) + end). + prop_sign_and_verify() -> ?FORALL({_Keys, JWK, Message}, ?LET({Keys, JWK}, diff --git a/test/property_test/jose_jwk_kty_okp_ed448_props.erl b/test/property_test/jose_jwk_kty_okp_ed448_props.erl index b1b89ea..fb01223 100644 --- a/test/property_test/jose_jwk_kty_okp_ed448_props.erl +++ b/test/property_test/jose_jwk_kty_okp_ed448_props.erl @@ -2,7 +2,7 @@ %% vim: ts=4 sw=4 ft=erlang noet -module(jose_jwk_kty_okp_ed448_props). --include_lib("public_key/include/public_key.hrl"). +-include_lib("jose/include/jose_public_key.hrl"). -include_lib("triq/include/triq.hrl"). @@ -22,8 +22,10 @@ ed448_secret() -> binary(57). ed448_keypair(Secret) -> - {PK, SK} = jose_curve448:eddsa_keypair(Secret), - {SK, PK}. + {PK, << S:57/binary, _:57/binary >>} = jose_curve448:eddsa_keypair(Secret), + PublicKey = #'jose_EdDSA448PublicKey'{publicKey=PK}, + SecretKey = #'jose_EdDSA448PrivateKey'{publicKey=PublicKey, privateKey=S}, + {SecretKey, PublicKey}. jwk_map() -> ?LET({AliceSecret, BobSecret}, @@ -31,7 +33,7 @@ jwk_map() -> begin AliceKeys = {AlicePrivateKey, _} = ed448_keypair(AliceSecret), BobKeys = ed448_keypair(BobSecret), - AlicePrivateJWK = jose_jwk:from_okp({'Ed448', AlicePrivateKey}), + AlicePrivateJWK = jose_jwk:from_key(AlicePrivateKey), {_, AlicePrivateJWKMap} = jose_jwk:to_map(AlicePrivateJWK), Keys = {AliceKeys, BobKeys}, {Keys, AlicePrivateJWKMap} @@ -59,6 +61,23 @@ prop_from_map_and_to_map() -> andalso AlicePublicThumbprint =:= jose_jwk:thumbprint(AlicePrivateJWK) end). +prop_from_pem_and_to_pem() -> + ?FORALL({_Keys, AlicePrivateJWK, Password}, + ?LET({{Keys, AlicePrivateJWK}, Bytes}, + {jwk_gen(), binary()}, + {Keys, AlicePrivateJWK, base64url:encode(Bytes)}), + begin + AlicePrivatePEM = element(2, jose_jwk:to_pem(AlicePrivateJWK)), + EncryptedAlicePrivatePEM = element(2, jose_jwk:to_pem(Password, AlicePrivateJWK)), + AlicePublicJWK = jose_jwk:to_public(AlicePrivateJWK), + AlicePublicPEM = element(2, jose_jwk:to_pem(AlicePublicJWK)), + EncryptedAlicePublicPEM = element(2, jose_jwk:to_pem(Password, AlicePublicJWK)), + AlicePrivateJWK =:= jose_jwk:from_pem(AlicePrivatePEM) + andalso AlicePrivateJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePrivatePEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(AlicePublicPEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePublicPEM) + end). + prop_sign_and_verify() -> ?FORALL({_Keys, JWK, Message}, ?LET({Keys, JWK}, diff --git a/test/property_test/jose_jwk_kty_okp_x25519_props.erl b/test/property_test/jose_jwk_kty_okp_x25519_props.erl index 38acd6b..82c034d 100644 --- a/test/property_test/jose_jwk_kty_okp_x25519_props.erl +++ b/test/property_test/jose_jwk_kty_okp_x25519_props.erl @@ -2,7 +2,7 @@ %% vim: ts=4 sw=4 ft=erlang noet -module(jose_jwk_kty_okp_x25519_props). --include_lib("public_key/include/public_key.hrl"). +-include_lib("jose/include/jose_public_key.hrl"). -include_lib("triq/include/triq.hrl"). @@ -22,8 +22,10 @@ x25519_secret() -> binary(32). x25519_keypair(Secret) -> - {PK, Secret} = jose_curve25519:x25519_keypair(Secret), - {<< Secret/binary, PK/binary >>, PK}. + {PK, S} = jose_curve25519:x25519_keypair(Secret), + PublicKey = #'jose_X25519PublicKey'{publicKey=PK}, + SecretKey = #'jose_X25519PrivateKey'{publicKey=PublicKey, privateKey=S}, + {SecretKey, PublicKey}. jwk_map() -> ?LET({AliceSecret, BobSecret}, @@ -31,7 +33,7 @@ jwk_map() -> begin AliceKeys = {AlicePrivateKey, _} = x25519_keypair(AliceSecret), BobKeys = x25519_keypair(BobSecret), - AlicePrivateJWK = jose_jwk:from_okp({'X25519', AlicePrivateKey}), + AlicePrivateJWK = jose_jwk:from_key(AlicePrivateKey), {_, AlicePrivateJWKMap} = jose_jwk:to_map(AlicePrivateJWK), Keys = {AliceKeys, BobKeys}, {Keys, AlicePrivateJWKMap} @@ -59,12 +61,29 @@ prop_from_map_and_to_map() -> andalso AlicePublicThumbprint =:= jose_jwk:thumbprint(AlicePrivateJWK) end). +prop_from_pem_and_to_pem() -> + ?FORALL({_Keys, AlicePrivateJWK, Password}, + ?LET({{Keys, AlicePrivateJWK}, Bytes}, + {jwk_gen(), binary()}, + {Keys, AlicePrivateJWK, base64url:encode(Bytes)}), + begin + AlicePrivatePEM = element(2, jose_jwk:to_pem(AlicePrivateJWK)), + EncryptedAlicePrivatePEM = element(2, jose_jwk:to_pem(Password, AlicePrivateJWK)), + AlicePublicJWK = jose_jwk:to_public(AlicePrivateJWK), + AlicePublicPEM = element(2, jose_jwk:to_pem(AlicePublicJWK)), + EncryptedAlicePublicPEM = element(2, jose_jwk:to_pem(Password, AlicePublicJWK)), + AlicePrivateJWK =:= jose_jwk:from_pem(AlicePrivatePEM) + andalso AlicePrivateJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePrivatePEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(AlicePublicPEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePublicPEM) + end). + prop_box_encrypt_and_box_decrypt() -> ?FORALL({{{_, {BobPrivateKey, BobPublicKey}}, AlicePrivateJWK}, PlainText}, {jwk_gen(), binary()}, begin - BobPrivateJWK = jose_jwk:from_okp({'X25519', BobPrivateKey}), - BobPublicJWK = jose_jwk:from_okp({'X25519', BobPublicKey}), + BobPrivateJWK = jose_jwk:from_key(BobPrivateKey), + BobPublicJWK = jose_jwk:from_key(BobPublicKey), Encrypted = jose_jwk:box_encrypt(PlainText, BobPublicJWK, AlicePrivateJWK), CompactEncrypted = jose_jwe:compact(Encrypted), Decrypted = {_, JWE} = jose_jwk:box_decrypt(Encrypted, BobPrivateJWK), diff --git a/test/property_test/jose_jwk_kty_okp_x448_props.erl b/test/property_test/jose_jwk_kty_okp_x448_props.erl index 9c2757a..da0e38f 100644 --- a/test/property_test/jose_jwk_kty_okp_x448_props.erl +++ b/test/property_test/jose_jwk_kty_okp_x448_props.erl @@ -2,7 +2,7 @@ %% vim: ts=4 sw=4 ft=erlang noet -module(jose_jwk_kty_okp_x448_props). --include_lib("public_key/include/public_key.hrl"). +-include_lib("jose/include/jose_public_key.hrl"). -include_lib("triq/include/triq.hrl"). @@ -22,8 +22,10 @@ x448_secret() -> binary(56). x448_keypair(Secret) -> - {PK, Secret} = jose_curve448:x448_keypair(Secret), - {<< Secret/binary, PK/binary >>, PK}. + {PK, S} = jose_curve448:x448_keypair(Secret), + PublicKey = #'jose_X448PublicKey'{publicKey=PK}, + SecretKey = #'jose_X448PrivateKey'{publicKey=PublicKey, privateKey=S}, + {SecretKey, PublicKey}. jwk_map() -> ?LET({AliceSecret, BobSecret}, @@ -31,7 +33,7 @@ jwk_map() -> begin AliceKeys = {AlicePrivateKey, _} = x448_keypair(AliceSecret), BobKeys = x448_keypair(BobSecret), - AlicePrivateJWK = jose_jwk:from_okp({'X448', AlicePrivateKey}), + AlicePrivateJWK = jose_jwk:from_key(AlicePrivateKey), {_, AlicePrivateJWKMap} = jose_jwk:to_map(AlicePrivateJWK), Keys = {AliceKeys, BobKeys}, {Keys, AlicePrivateJWKMap} @@ -59,12 +61,29 @@ prop_from_map_and_to_map() -> andalso AlicePublicThumbprint =:= jose_jwk:thumbprint(AlicePrivateJWK) end). +prop_from_pem_and_to_pem() -> + ?FORALL({_Keys, AlicePrivateJWK, Password}, + ?LET({{Keys, AlicePrivateJWK}, Bytes}, + {jwk_gen(), binary()}, + {Keys, AlicePrivateJWK, base64url:encode(Bytes)}), + begin + AlicePrivatePEM = element(2, jose_jwk:to_pem(AlicePrivateJWK)), + EncryptedAlicePrivatePEM = element(2, jose_jwk:to_pem(Password, AlicePrivateJWK)), + AlicePublicJWK = jose_jwk:to_public(AlicePrivateJWK), + AlicePublicPEM = element(2, jose_jwk:to_pem(AlicePublicJWK)), + EncryptedAlicePublicPEM = element(2, jose_jwk:to_pem(Password, AlicePublicJWK)), + AlicePrivateJWK =:= jose_jwk:from_pem(AlicePrivatePEM) + andalso AlicePrivateJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePrivatePEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(AlicePublicPEM) + andalso AlicePublicJWK =:= jose_jwk:from_pem(Password, EncryptedAlicePublicPEM) + end). + prop_box_encrypt_and_box_decrypt() -> ?FORALL({{{_, {BobPrivateKey, BobPublicKey}}, AlicePrivateJWK}, PlainText}, {jwk_gen(), binary()}, begin - BobPrivateJWK = jose_jwk:from_okp({'X448', BobPrivateKey}), - BobPublicJWK = jose_jwk:from_okp({'X448', BobPublicKey}), + BobPrivateJWK = jose_jwk:from_key(BobPrivateKey), + BobPublicJWK = jose_jwk:from_key(BobPublicKey), Encrypted = jose_jwk:box_encrypt(PlainText, BobPublicJWK, AlicePrivateJWK), CompactEncrypted = jose_jwe:compact(Encrypted), Decrypted = {_, JWE} = jose_jwk:box_decrypt(Encrypted, BobPrivateJWK),