Packages
JSON Object Signing and Encryption (JOSE) for Erlang and Elixir.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/jose_jwk.erl
%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
%% vim: ts=4 sw=4 ft=erlang noet
%%%-------------------------------------------------------------------
%%% @author Andrew Bennett <andrew@pixid.com>
%%% @copyright 2014-2015, Andrew Bennett
%%% @doc
%%%
%%% @end
%%% Created : 21 Jul 2015 by Andrew Bennett <andrew@pixid.com>
%%%-------------------------------------------------------------------
-module(jose_jwk).
-include("jose_jwe.hrl").
-include("jose_jwk.hrl").
-include("jose_jws.hrl").
-callback from_map(Fields) -> KTY
when
Fields :: map(),
KTY :: any().
-callback to_key(KTY) -> Key
when
KTY :: any(),
Key :: any().
-callback to_map(KTY, Fields) -> Map
when
KTY :: any(),
Fields :: map(),
Map :: map().
-callback to_public_map(KTY, Fields) -> Map
when
KTY :: any(),
Fields :: map(),
Map :: map().
-callback to_thumbprint_map(KTY, Fields) -> Map
when
KTY :: any(),
Fields :: map(),
Map :: map().
%% Decode API
-export([from/1]).
-export([from/2]).
-export([from_binary/1]).
-export([from_binary/2]).
-export([from_file/1]).
-export([from_file/2]).
-export([from_firebase/1]).
-export([from_key/1]).
-export([from_map/1]).
-export([from_map/2]).
-export([from_oct/1]).
-export([from_oct/2]).
-export([from_oct_file/1]).
-export([from_oct_file/2]).
-export([from_okp/1]).
-export([from_openssh_key/1]).
-export([from_openssh_key_file/1]).
-export([from_pem/1]).
-export([from_pem/2]).
-export([from_pem_file/1]).
-export([from_pem_file/2]).
%% Encode API
-export([to_binary/1]).
-export([to_binary/2]).
-export([to_binary/3]).
-export([to_file/2]).
-export([to_file/3]).
-export([to_file/4]).
-export([to_key/1]).
-export([to_map/1]).
-export([to_map/2]).
-export([to_map/3]).
-export([to_oct/1]).
-export([to_oct/2]).
-export([to_oct/3]).
-export([to_oct_file/2]).
-export([to_oct_file/3]).
-export([to_oct_file/4]).
-export([to_okp/1]).
-export([to_openssh_key/1]).
-export([to_openssh_key_file/2]).
-export([to_pem/1]).
-export([to_pem/2]).
-export([to_pem_file/2]).
-export([to_pem_file/3]).
-export([to_public/1]).
-export([to_public_file/2]).
-export([to_public_key/1]).
-export([to_public_map/1]).
-export([to_thumbprint_map/1]).
%% API
-export([block_decrypt/2]).
-export([block_encrypt/2]).
-export([block_encrypt/3]).
-export([block_encryptor/1]).
-export([box_decrypt/2]).
-export([box_encrypt/2]).
-export([box_encrypt/3]).
-export([box_encrypt/4]).
-export([generate_key/1]).
-export([merge/2]).
-export([shared_secret/2]).
-export([sign/2]).
-export([sign/3]).
-export([signer/1]).
-export([thumbprint/1]).
-export([thumbprint/2]).
-export([verifier/1]).
-export([verify/2]).
-export([verify_strict/3]).
%% Types
-type key() :: #jose_jwk{}.
-export_type([key/0]).
-define(KEYS_MODULE, jose_jwk_set).
-define(KTY_EC_MODULE, jose_jwk_kty_ec).
-define(KTY_OCT_MODULE, jose_jwk_kty_oct).
-define(KTY_RSA_MODULE, jose_jwk_kty_rsa).
-define(KTY_OKP_Ed25519_MODULE, jose_jwk_kty_okp_ed25519).
-define(KTY_OKP_Ed25519ph_MODULE, jose_jwk_kty_okp_ed25519ph).
-define(KTY_OKP_X25519_MODULE, jose_jwk_kty_okp_x25519).
-define(KTY_OKP_Ed448_MODULE, jose_jwk_kty_okp_ed448).
-define(KTY_OKP_Ed448ph_MODULE, jose_jwk_kty_okp_ed448ph).
-define(KTY_OKP_X448_MODULE, jose_jwk_kty_okp_x448).
%%====================================================================
%% Decode API functions
%%====================================================================
from(List) when is_list(List) ->
[from(Element) || Element <- List];
from({Modules, Map}) when is_map(Modules) andalso is_map(Map) ->
from_map({Modules, Map});
from({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) ->
from_binary({Modules, Binary});
from(JWK=#jose_jwk{}) ->
JWK;
from(Other) when is_map(Other) orelse is_binary(Other) ->
from({#{}, Other}).
from(Key, List) when is_list(List) ->
[from(Key, Element) || Element <- List];
from(Key, {Modules, EncryptedMap}) when is_map(Modules) andalso is_map(EncryptedMap) ->
from_map(Key, {Modules, EncryptedMap});
from(Key, {Modules, EncryptedBinary}) when is_map(Modules) andalso is_binary(EncryptedBinary) ->
from_binary(Key, {Modules, EncryptedBinary});
from(_Key, JWK=#jose_jwk{}) ->
JWK;
from(Key, Other) when is_map(Other) orelse is_binary(Other) ->
from(Key, {#{}, Other}).
from_binary(List) when is_list(List) ->
[from_binary(Element) || Element <- List];
from_binary({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) ->
from_map({Modules, jose:decode(Binary)});
from_binary(Binary) when is_binary(Binary) ->
from_binary({#{}, Binary}).
from_binary(Key, List) when is_list(List) ->
[from_binary(Key, Element) || Element <- List];
from_binary(Key, {Modules, Encrypted = << ${, _/binary >>}) when is_map(Modules) andalso is_binary(Encrypted) ->
EncrypedMap = jose:decode(Encrypted),
from_map(Key, {Modules, EncrypedMap});
from_binary(Key, {Modules, Encrypted}) when is_map(Modules) andalso is_binary(Encrypted) ->
{JWKBinary, JWE=#jose_jwe{}} = jose_jwe:block_decrypt(Key, {Modules, Encrypted}),
{JWE, from_binary({Modules, JWKBinary})};
from_binary(Key, Encrypted) when is_binary(Encrypted) ->
from_binary(Key, {#{}, Encrypted}).
from_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_binary({Modules, Binary});
ReadError ->
ReadError
end;
from_file(File) when is_binary(File) orelse is_list(File) ->
from_file({#{}, File}).
from_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_binary(Key, {Modules, Binary});
ReadError ->
ReadError
end;
from_file(Key, File) when is_binary(File) orelse is_list(File) ->
from_file(Key, {#{}, File}).
from_firebase(Binary) when is_binary(Binary) ->
from_firebase({#{}, Binary});
from_firebase({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) ->
Map = jose:decode(Binary),
from_firebase({Modules, Map});
from_firebase(Map) when is_map(Map) ->
from_firebase({#{}, Map});
from_firebase({Modules, Map}) when is_map(Modules) andalso is_map(Map) ->
Folder = fun (Key, Val, Acc) ->
JWK = jose_jwk:merge(jose_jwk:from_pem({Modules, Val}), #{ <<"kid">> => Key }),
Acc#{ Key => JWK }
end,
maps:fold(Folder, #{}, Map).
from_key(List) when is_list(List) ->
[from_key(Element) || Element <- List];
from_key(Key) ->
case jose_jwk_kty:from_key(Key) of
{KTYModule, {KTY, Fields}} when KTYModule =/= error ->
#jose_jwk{ kty = {KTYModule, KTY}, fields = Fields };
FromKeyError ->
FromKeyError
end.
from_map(List) when is_list(List) ->
[from_map(Element) || Element <- List];
from_map(Map) when is_map(Map) ->
from_map({#{}, Map});
from_map({Modules, Map}) when is_map(Modules) andalso is_map(Map) ->
from_map({#jose_jwk{}, Modules, Map});
from_map({JWK, Modules=#{ keys := Module }, Map=#{ <<"keys">> := _ }}) ->
{KEYS, Fields} = Module:from_map(Map),
from_map({JWK#jose_jwk{ keys = {Module, KEYS} }, maps:remove(keys, Modules), Fields});
from_map({JWK, Modules=#{ kty := Module }, Map=#{ <<"kty">> := _ }}) ->
{KTY, Fields} = Module:from_map(Map),
from_map({JWK#jose_jwk{ kty = {Module, KTY} }, maps:remove(kty, Modules), Fields});
from_map({JWK, Modules, Map=#{ <<"keys">> := _ }}) ->
from_map({JWK, Modules#{ keys => ?KEYS_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"EC">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_EC_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"oct">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OCT_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed25519">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed25519_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed25519ph">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed25519ph_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"X25519">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OKP_X25519_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed448">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed448_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"Ed448ph">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OKP_Ed448ph_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"OKP">>, <<"crv">> := <<"X448">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_OKP_X448_MODULE }, Map});
from_map({JWK, Modules, Map=#{ <<"kty">> := <<"RSA">> }}) ->
from_map({JWK, Modules#{ kty => ?KTY_RSA_MODULE }, Map});
from_map({#jose_jwk{ keys = undefined, kty = undefined }, _Modules, _Map}) ->
{error, {missing_required_keys, [<<"keys">>, <<"kty">>]}};
from_map({JWK, _Modules, Fields}) ->
JWK#jose_jwk{ fields = Fields }.
from_map(Key, List) when is_list(List) ->
[from_map(Key, Element) || Element <- List];
from_map(Key, {Modules, Encrypted}) when is_map(Modules) andalso is_map(Encrypted) ->
{JWKBinary, JWE=#jose_jwe{}} = jose_jwe:block_decrypt(Key, {Modules, Encrypted}),
{JWE, from_binary({Modules, JWKBinary})};
from_map(Key, Encrypted) when is_map(Encrypted) ->
from_map(Key, {#{}, Encrypted}).
from_oct(List) when is_list(List) ->
[from_oct(Element) || Element <- List];
from_oct({#{ kty := Module }, Binary}) when is_binary(Binary) ->
{KTY, Fields} = Module:from_oct(Binary),
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_oct({#{}, Binary}) when is_binary(Binary) ->
case jose_jwk_oct:from_binary(Binary) of
{Module, {KTY, Fields}} when Module =/= error ->
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
OCTError ->
OCTError
end;
from_oct(Binary) when is_binary(Binary) ->
from_oct({#{}, Binary}).
from_oct(Key, List) when is_list(List) ->
[from_oct(Key, Element) || Element <- List];
from_oct(Key, {Modules, Encrypted}) when is_map(Modules) andalso is_binary(Encrypted) ->
{OCTBinary, JWE=#jose_jwe{}} = jose_jwe:block_decrypt(Key, {Modules, Encrypted}),
{JWE, from_oct({Modules, OCTBinary})};
from_oct(Key, Encrypted) when is_binary(Encrypted) ->
from_oct(Key, {#{}, Encrypted}).
from_oct_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_oct({Modules, Binary});
ReadError ->
ReadError
end;
from_oct_file(File) when is_binary(File) orelse is_list(File) ->
from_oct_file({#{}, File}).
from_oct_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_oct(Key, {Modules, Binary});
ReadError ->
ReadError
end;
from_oct_file(Key, File) when is_binary(File) orelse is_list(File) ->
from_oct_file(Key, {#{}, File}).
from_okp(List) when is_list(List) ->
[from_okp(Element) || Element <- List];
from_okp({#{ kty := Module }, OKP}) ->
{KTY, Fields} = Module:from_okp(OKP),
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_okp(P={'Ed25519', Binary}) when is_binary(Binary) ->
from_okp({#{ kty => ?KTY_OKP_Ed25519_MODULE }, P});
from_okp(P={'Ed25519ph', Binary}) when is_binary(Binary) ->
from_okp({#{ kty => ?KTY_OKP_Ed25519ph_MODULE }, P});
from_okp(P={'X25519', Binary}) when is_binary(Binary) ->
from_okp({#{ kty => ?KTY_OKP_X25519_MODULE }, P});
from_okp(P={'Ed448', Binary}) when is_binary(Binary) ->
from_okp({#{ kty => ?KTY_OKP_Ed448_MODULE }, P});
from_okp(P={'Ed448ph', Binary}) when is_binary(Binary) ->
from_okp({#{ kty => ?KTY_OKP_Ed448ph_MODULE }, P});
from_okp(P={'X448', Binary}) when is_binary(Binary) ->
from_okp({#{ kty => ?KTY_OKP_X448_MODULE }, P}).
from_openssh_key(List) when is_list(List) ->
[from_openssh_key(Element) || Element <- List];
from_openssh_key({#{ kty := Module }, Binary}) when is_binary(Binary) ->
{KTY, Fields} = Module:from_openssh_key(Binary),
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_openssh_key({#{}, Binary}) when is_binary(Binary) ->
case jose_jwk_openssh_key:from_binary(Binary) of
CurrentKey = [[{{Type, PK}, Key = {Type, PK, _SK, _Comment}} | _] | _] ->
Module = case Type of
<<"ssh-ed25519">> ->
?KTY_OKP_Ed25519_MODULE;
<<"ssh-ed25519ph">> ->
?KTY_OKP_Ed25519ph_MODULE;
<<"ssh-x25519">> ->
?KTY_OKP_X25519_MODULE;
<<"ssh-ed448">> ->
?KTY_OKP_Ed448_MODULE;
<<"ssh-ed448ph">> ->
?KTY_OKP_Ed448ph_MODULE;
<<"ssh-x448">> ->
?KTY_OKP_X448_MODULE;
_ ->
{error, {unknown_key, CurrentKey}}
end,
case is_atom(Module) of
true ->
{KTY, Fields} = Module:from_openssh_key(Key),
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
false ->
Module
end;
UnknownKey ->
{error, {unknown_key, UnknownKey}}
end;
from_openssh_key(Binary) when is_binary(Binary) ->
from_openssh_key({#{}, Binary}).
from_openssh_key_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_openssh_key({Modules, Binary});
ReadError ->
ReadError
end;
from_openssh_key_file(File) when is_binary(File) orelse is_list(File) ->
from_openssh_key_file({#{}, File}).
from_pem(List) when is_list(List) ->
[from_pem(Element) || Element <- List];
from_pem({#{ kty := Module }, Binary}) when is_binary(Binary) ->
{KTY, Fields} = Module:from_pem(Binary),
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_pem({#{}, Binary}) when is_binary(Binary) ->
case jose_jwk_pem:from_binary(Binary) of
{Module, {KTY, Fields}} when Module =/= error ->
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
PEMError ->
PEMError
end;
from_pem(Binary) when is_binary(Binary) ->
from_pem({#{}, Binary}).
from_pem(Key, List) when is_list(List) ->
[from_pem(Key, Element) || Element <- List];
from_pem(Key, {#{ kty := Module }, Binary}) when is_binary(Binary) ->
{KTY, Fields} = Module:from_pem(Key, Binary),
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
from_pem(Key, {#{}, Binary}) when is_binary(Binary) ->
case jose_jwk_pem:from_binary(Key, Binary) of
{Module, {KTY, Fields}} when Module =/= error ->
#jose_jwk{ kty = {Module, KTY}, fields = Fields };
PEMError ->
PEMError
end;
from_pem(Key, Binary) when is_binary(Binary) ->
from_pem(Key, {#{}, Binary}).
from_pem_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_pem({Modules, Binary});
ReadError ->
ReadError
end;
from_pem_file(File) when is_binary(File) orelse is_list(File) ->
from_pem_file({#{}, File}).
from_pem_file(Key, {Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) ->
case file:read_file(File) of
{ok, Binary} ->
from_pem(Key, {Modules, Binary});
ReadError ->
ReadError
end;
from_pem_file(Key, File) when is_binary(File) orelse is_list(File) ->
from_pem_file(Key, {#{}, File}).
%%====================================================================
%% Encode API functions
%%====================================================================
to_binary(List) when is_list(List) ->
[to_binary(Element) || Element <- List];
to_binary(JWK=#jose_jwk{}) ->
{Modules, Map} = to_map(JWK),
{Modules, jose:encode(Map)};
to_binary(Other) ->
to_binary(from(Other)).
to_binary(Key, List) when is_list(List) ->
[to_binary(Key, Element) || Element <- List];
to_binary(Key, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
to_binary(Key, Module:key_encryptor(KTY, Fields, Key), JWK);
to_binary(Key, Other) ->
to_binary(Key, from(Other)).
to_binary(Key, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
{Modules, EncryptedMap} = to_map(Key, JWE, JWK),
{Modules, jose:encode(EncryptedMap)};
to_binary(Key, JWEOther, JWKOther) ->
to_binary(Key, jose_jwe:from(JWEOther), from(JWKOther)).
to_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, Binary} = to_binary(JWK),
case file:write_file(File, Binary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_file(File, Other) when is_binary(File) orelse is_list(File) ->
to_file(File, from(Other)).
to_file(Key, File, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) when is_binary(File) orelse is_list(File) ->
to_file(Key, File, Module:key_encryptor(KTY, Fields, Key), JWK);
to_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
to_file(Key, File, from(Other)).
to_file(Key, File, JWE=#jose_jwe{}, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, EncryptedBinary} = to_binary(Key, JWE, JWK),
case file:write_file(File, EncryptedBinary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_file(Key, File, JWEOther, JWKOther) when is_binary(File) orelse is_list(File) ->
to_file(Key, File, jose_jwe:from(JWEOther), from(JWKOther)).
to_key(List) when is_list(List) ->
[to_key(Element) || Element <- List];
to_key(#jose_jwk{kty={Module, KTY}}) ->
{#{ kty => Module }, Module:to_key(KTY)};
to_key(Other) ->
to_key(from(Other)).
to_map(List) when is_list(List) ->
[to_map(Element) || Element <- List];
to_map(JWK=#jose_jwk{fields=Fields}) ->
record_to_map(JWK, #{}, Fields);
to_map(Other) ->
to_map(from(Other)).
to_map(Key, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
to_map(Key, Module:key_encryptor(KTY, Fields, Key), JWK);
to_map(Key, Other) ->
to_map(Key, from(Other)).
to_map(Key, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
{Modules0, JWKBinary} = to_binary(JWK),
{Modules1, Encrypted} = jose_jwe:block_encrypt(Key, JWKBinary, JWE),
{maps:merge(Modules0, Modules1), Encrypted};
to_map(Key, JWEOther, JWKOther) ->
to_map(Key, jose_jwe:from(JWEOther), from(JWKOther)).
to_oct(List) when is_list(List) ->
[to_oct(Element) || Element <- List];
to_oct(#jose_jwk{kty={Module, KTY}}) ->
{#{ kty => Module }, Module:to_oct(KTY)};
to_oct(Other) ->
to_oct(from(Other)).
to_oct(Key, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
to_oct(Key, Module:key_encryptor(KTY, Fields, Key), JWK);
to_oct(Key, Other) ->
to_oct(Key, from(Other)).
to_oct(Key, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
{Modules0, OCTBinary} = to_oct(JWK),
{Modules1, EncryptedMap} = jose_jwe:block_encrypt(Key, OCTBinary, JWE),
jose_jwe:compact({maps:merge(Modules0, Modules1), EncryptedMap});
to_oct(Key, JWEOther, JWKOther) ->
to_oct(Key, jose_jwe:from(JWEOther), from(JWKOther)).
to_oct_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, Binary} = to_oct(JWK),
case file:write_file(File, Binary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_oct_file(File, Other) when is_binary(File) orelse is_list(File) ->
to_oct_file(File, from(Other)).
to_oct_file(Key, File, JWK=#jose_jwk{kty={Module, KTY}, fields=Fields}) when is_binary(File) orelse is_list(File) ->
to_oct_file(Key, File, Module:key_encryptor(KTY, Fields, Key), JWK);
to_oct_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
to_oct_file(Key, File, from(Other)).
to_oct_file(Key, File, JWE=#jose_jwe{}, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, EncryptedBinary} = to_oct(Key, JWE, JWK),
case file:write_file(File, EncryptedBinary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_oct_file(Key, File, JWEOther, JWKOther) when is_binary(File) orelse is_list(File) ->
to_oct_file(Key, File, jose_jwe:from(JWEOther), from(JWKOther)).
to_okp(List) when is_list(List) ->
[to_okp(Element) || Element <- List];
to_okp(#jose_jwk{kty={Module, KTY}}) ->
{#{ kty => Module }, Module:to_okp(KTY)};
to_okp(Other) ->
to_okp(from(Other)).
to_openssh_key(List) when is_list(List) ->
[to_openssh_key(Element) || Element <- List];
to_openssh_key(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
{#{ kty => Module }, Module:to_openssh_key(KTY, Fields)};
to_openssh_key(Other) ->
to_openssh_key(from(Other)).
to_openssh_key_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, Binary} = to_openssh_key(JWK),
case file:write_file(File, Binary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_openssh_key_file(File, Other) when is_binary(File) orelse is_list(File) ->
to_openssh_key_file(File, from(Other)).
to_pem(List) when is_list(List) ->
[to_pem(Element) || Element <- List];
to_pem(#jose_jwk{kty={Module, KTY}}) ->
{#{ kty => Module }, Module:to_pem(KTY)};
to_pem(Other) ->
to_pem(from(Other)).
to_pem(Key, #jose_jwk{kty={Module, KTY}}) ->
{#{ kty => Module }, Module:to_pem(Key, KTY)};
to_pem(Key, Other) ->
to_pem(Key, from(Other)).
to_pem_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, Binary} = to_pem(JWK),
case file:write_file(File, Binary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_pem_file(File, Other) when is_binary(File) orelse is_list(File) ->
to_pem_file(File, from(Other)).
to_pem_file(Key, File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
{Modules, Binary} = to_pem(Key, JWK),
case file:write_file(File, Binary) of
ok ->
{Modules, File};
WriteError ->
WriteError
end;
to_pem_file(Key, File, Other) when is_binary(File) orelse is_list(File) ->
to_pem_file(Key, File, from(Other)).
to_public(List) when is_list(List) ->
[to_public(Element) || Element <- List];
to_public(JWK=#jose_jwk{}) ->
from_map(to_public_map(JWK));
to_public(Other) ->
to_public(from(Other)).
to_public_file(File, JWK=#jose_jwk{}) when is_binary(File) orelse is_list(File) ->
to_file(File, to_public(JWK));
to_public_file(File, Other) when is_binary(File) orelse is_list(File) ->
to_public_file(File, from(Other)).
to_public_key(List) when is_list(List) ->
[to_public_key(Element) || Element <- List];
to_public_key(JWT=#jose_jwk{}) ->
to_key(to_public(JWT));
to_public_key(Other) ->
to_public_key(from(Other)).
to_public_map(List) when is_list(List) ->
[to_public_map(Element) || Element <- List];
to_public_map(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
{#{ kty => Module }, Module:to_public_map(KTY, Fields)};
to_public_map(Other) ->
to_public_map(from(Other)).
to_thumbprint_map(List) when is_list(List) ->
[to_thumbprint_map(Element) || Element <- List];
to_thumbprint_map(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
{#{ kty => Module }, Module:to_thumbprint_map(KTY, Fields)};
to_thumbprint_map(Other) ->
to_thumbprint_map(from(Other)).
%%====================================================================
%% API functions
%%====================================================================
block_decrypt(Encrypted, JWK=#jose_jwk{}) ->
jose_jwe:block_decrypt(JWK, Encrypted);
block_decrypt(Encrypted, Other) ->
block_decrypt(Encrypted, from(Other)).
block_encrypt(PlainText, JWK=#jose_jwk{}) ->
block_encrypt(PlainText, block_encryptor(JWK), JWK);
block_encrypt(PlainText, Other) ->
block_encrypt(PlainText, from(Other)).
block_encrypt(PlainText, JWE=#jose_jwe{}, JWK=#jose_jwk{}) ->
jose_jwe:block_encrypt(JWK, PlainText, JWE);
block_encrypt(PlainText, JWEOther, JWKOther) ->
block_encrypt(PlainText, jose_jwe:from(JWEOther), from(JWKOther)).
block_encryptor(List) when is_list(List) ->
[block_encryptor(Element) || Element <- List];
block_encryptor(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
Module:block_encryptor(KTY, Fields);
block_encryptor(Other) ->
block_encryptor(from(Other)).
box_decrypt(Encrypted, MyPrivateJWK=#jose_jwk{}) ->
jose_jwe:block_decrypt(MyPrivateJWK, Encrypted);
box_decrypt(Encrypted, Other) ->
box_decrypt(Encrypted, from(Other)).
%% @doc Generates an ephemeral private key based on other public key curve.
box_encrypt(PlainText, OtherPublicJWK=#jose_jwk{}) ->
MyPrivateJWK = generate_key(OtherPublicJWK),
{box_encrypt(PlainText, OtherPublicJWK, MyPrivateJWK), MyPrivateJWK};
box_encrypt(PlainText, JWKOtherPublic) ->
box_encrypt(PlainText, from(JWKOtherPublic)).
box_encrypt(PlainText, OtherPublicJWK=#jose_jwk{}, MyPrivateJWK=#jose_jwk{}) ->
MyPublicJWK0 = #jose_jwk{fields=Fields0} = to_public(MyPrivateJWK),
Fields = maps:put(Fields0, <<"epk">>, element(2, to_map(MyPublicJWK0))),
MyPublicJWK = MyPublicJWK0#jose_jwk{fields=Fields},
JWEFields = block_encryptor(MyPublicJWK),
box_encrypt(PlainText, JWEFields, OtherPublicJWK, MyPrivateJWK);
box_encrypt(PlainText, JWKOtherPublic, JWKMyPrivate) ->
box_encrypt(PlainText, from(JWKOtherPublic), from(JWKMyPrivate)).
box_encrypt(PlainText, JWE=#jose_jwe{}, OtherPublicJWK=#jose_jwk{}, MyPrivateJWK=#jose_jwk{}) ->
jose_jwe:block_encrypt({OtherPublicJWK, MyPrivateJWK}, PlainText, JWE);
box_encrypt(PlainText, {JWEModules, JWEMap=#{ <<"apu">> := _, <<"apv">> := _, <<"epk">> := _ }}, OtherPublicJWK=#jose_jwk{}, MyPrivateJWK=#jose_jwk{}) ->
box_encrypt(PlainText, jose_jwe:from({JWEModules, JWEMap}), OtherPublicJWK, MyPrivateJWK);
box_encrypt(PlainText, {JWEModules, JWEMap0}, OtherPublicJWK=#jose_jwk{}, MyPrivateJWK=#jose_jwk{}) ->
Keys = [<<"apu">>, <<"apv">>, <<"epk">>] -- maps:keys(JWEMap0),
JWEMap1 = normalize_box(Keys, JWEMap0, OtherPublicJWK, MyPrivateJWK),
box_encrypt(PlainText, {JWEModules, JWEMap1}, OtherPublicJWK, MyPrivateJWK);
box_encrypt(PlainText, JWEMap, OtherPublicJWK, MyPrivateJWK) when is_map(JWEMap) ->
box_encrypt(PlainText, {#{}, JWEMap}, OtherPublicJWK, MyPrivateJWK);
box_encrypt(PlainText, JWE, JWKOtherPublic, JWKMyPrivate) ->
box_encrypt(PlainText, JWE, from(JWKOtherPublic), from(JWKMyPrivate)).
generate_key(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
{NewKTY, NewFields} = Module:generate_key(KTY, Fields),
#jose_jwk{kty={Module, NewKTY}, fields=NewFields};
generate_key({#{ kty := Module }, Parameters}) ->
{KTY, Fields} = Module:generate_key(Parameters),
#jose_jwk{kty={Module, KTY}, fields=Fields};
generate_key(Parameters) ->
jose_jwk_kty:generate_key(Parameters).
merge(LeftJWK=#jose_jwk{}, RightMap) when is_map(RightMap) ->
{Modules, LeftMap} = to_map(LeftJWK),
from_map({Modules, maps:merge(LeftMap, RightMap)});
merge(LeftOther, RightJWK=#jose_jwk{}) ->
merge(LeftOther, element(2, to_map(RightJWK)));
merge(LeftOther, RightMap) when is_map(RightMap) ->
merge(from(LeftOther), RightMap).
shared_secret(#jose_jwk{kty={Module, YourKTY}}, #jose_jwk{kty={Module, MyKTY}}) ->
Module:derive_key(YourKTY, MyKTY);
shared_secret(YourJWK, MyJWK) ->
shared_secret(from(YourJWK), from(MyJWK)).
sign(PlainText, JWK=#jose_jwk{}) ->
sign(PlainText, signer(JWK), JWK);
sign(PlainText, KeyList)
when is_list(KeyList) ->
Folder = fun
Folder(Key=#jose_jwk{}, {Signers, Keys}) ->
Signer = signer(Key),
{[Signer | Signers], [Key | Keys]};
Folder(Other, Acc) ->
Folder(from(Other), Acc)
end,
{Signers, Keys} = lists:foldr(Folder, {[], []}, KeyList),
sign(PlainText, Signers, Keys);
sign(PlainText, Other) ->
sign(PlainText, from(Other)).
sign(PlainText, JWS=#jose_jws{}, JWK=#jose_jwk{}) ->
jose_jws:sign(JWK, PlainText, JWS);
sign(PlainText, SignerList, KeyList)
when is_list(SignerList)
andalso is_list(KeyList)
andalso length(SignerList) =:= length(KeyList) ->
Signers = jose_jws:from(SignerList),
Keys = from(KeyList),
jose_jws:sign(Keys, PlainText, Signers);
sign(PlainText, JWS=#jose_jws{}, KeyList)
when is_list(KeyList) ->
jose_jws:sign(from(KeyList), PlainText, JWS);
sign(PlainText, SignerList, KeyList)
when is_list(SignerList)
andalso is_list(KeyList)
andalso length(SignerList) =/= length(KeyList) ->
erlang:error({badarg, [PlainText, SignerList, KeyList]});
sign(PlainText, JWSOther, KeyList)
when is_list(KeyList) ->
sign(PlainText, jose_jws:from(JWSOther), KeyList);
sign(PlainText, JWSOther, JWKOther) ->
sign(PlainText, jose_jws:from(JWSOther), from(JWKOther)).
signer(List) when is_list(List) ->
[signer(Element) || Element <- List];
signer(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
Module:signer(KTY, Fields);
signer(Other) ->
signer(from(Other)).
%% See https://tools.ietf.org/html/rfc7638
thumbprint(List) when is_list(List) ->
[thumbprint(Element) || Element <- List];
thumbprint(JWK=#jose_jwk{}) ->
thumbprint(sha256, JWK);
thumbprint(Other) ->
thumbprint(from(Other)).
thumbprint(DigestType, List) when is_list(List) ->
[thumbprint(DigestType, Element) || Element <- List];
thumbprint(DigestType, JWK=#jose_jwk{}) ->
{_, ThumbprintMap} = to_thumbprint_map(JWK),
ThumbprintBinary = jose:encode(ThumbprintMap),
base64url:encode(crypto:hash(DigestType, ThumbprintBinary));
thumbprint(DigestType, Other) ->
thumbprint(DigestType, from(Other)).
verifier(List) when is_list(List) ->
[verifier(Element) || Element <- List];
verifier(#jose_jwk{kty={Module, KTY}, fields=Fields}) ->
Module:verifier(KTY, Fields);
verifier(Other) ->
verifier(from(Other)).
verify(Signed, JWK=#jose_jwk{}) ->
jose_jws:verify(JWK, Signed);
verify(Signed, Other) ->
verify(Signed, from(Other)).
verify_strict(Signed, Allow, JWK=#jose_jwk{}) ->
jose_jws:verify_strict(JWK, Allow, Signed);
verify_strict(Signed, Allow, Other) ->
verify_strict(Signed, Allow, from(Other)).
%%%-------------------------------------------------------------------
%%% Internal functions
%%%-------------------------------------------------------------------
%% @private
normalize_box([<<"apu">> | Keys], Map, OtherPublicJWK, MyPrivateJWK=#jose_jwk{fields=#{ <<"kid">> := KID }}) ->
normalize_box(Keys, Map#{ <<"apu">> => KID }, OtherPublicJWK, MyPrivateJWK);
normalize_box([<<"apu">> | Keys], Map, OtherPublicJWK, MyPrivateJWK) ->
normalize_box(Keys, Map#{ <<"apu">> => thumbprint(MyPrivateJWK) }, OtherPublicJWK, MyPrivateJWK);
normalize_box([<<"apv">> | Keys], Map, OtherPublicJWK=#jose_jwk{fields=#{ <<"kid">> := KID }}, MyPrivateJWK) ->
normalize_box(Keys, Map#{ <<"apv">> => KID }, OtherPublicJWK, MyPrivateJWK);
normalize_box([<<"apv">> | Keys], Map, OtherPublicJWK, MyPrivateJWK) ->
normalize_box(Keys, Map#{ <<"apv">> => thumbprint(OtherPublicJWK) }, OtherPublicJWK, MyPrivateJWK);
normalize_box([<<"epk">> | Keys], Map, OtherPublicJWK, MyPrivateJWK) ->
{_, MyPublicMap} = to_public_map(MyPrivateJWK),
normalize_box(Keys, Map#{ <<"epk">> => MyPublicMap }, OtherPublicJWK, MyPrivateJWK);
normalize_box([], Map, _, _) ->
Map.
%% @private
record_to_map(JWK=#jose_jwk{keys={Module, KEYS}}, Modules, Fields0) ->
Fields1 = Module:to_map(KEYS, Fields0),
record_to_map(JWK#jose_jwk{keys=undefined}, Modules#{ keys => Module }, Fields1);
record_to_map(JWK=#jose_jwk{kty={Module, KTY}}, Modules, Fields0) ->
Fields1 = Module:to_map(KTY, Fields0),
record_to_map(JWK#jose_jwk{kty=undefined}, Modules#{ kty => Module }, Fields1);
record_to_map(_JWK, Modules, Fields) ->
{Modules, Fields}.