Skip to content

Commit

Permalink
Add options parameter for sign/verify func
Browse files Browse the repository at this point in the history
Available options:
  - `raw`: verify/sign raw signature instead of DER format
     (see: https://elixirforum.com/t/verifying-web-crypto-signatures-in-erlang-elixir/20727/2
  • Loading branch information
HJianBo committed Apr 24, 2020
1 parent ebbc2ae commit 7cce3a4
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ ebin
.erlang.mk
*.d
test/eunit/
rebar3.crashdump
.rebar3/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ jwerl:verify(Jwt, rs512, PublcPem).
Jwt = jwerl:sign([{name, <<"bob">>}], es256, PrivtPem).
jwerl:verify(Jwt, es256, PublcPem).

% Compatibility
% - sign/verify the signature with raw format instead of DER
% - it is necessary to compatible with nodejs or other platforms
Jwt = jwerl:sign([{name, <<"bob">>}], es256, PrivtPem, #{raw => true}).
jwerl:verify(Jwt, es256, PublcPem, Claims, #{raw => true}).
```


Expand Down
85 changes: 62 additions & 23 deletions src/jwerl.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(jwerl).

-export([sign/1, sign/2, sign/3,
-export([sign/1, sign/2, sign/3, sign/4,
verify/1, verify/2, verify/3, verify/4, verify/5,
header/1]).

Expand All @@ -15,14 +15,19 @@
es256 | es384 | es512 |
none.

% @equiv sign(Data, hs256, <<"">>)
% @equiv sign(Data, hs256, <<"">>, #{})
-spec sign(Data :: map()) -> binary().
sign(Data) ->
sign(Data, hs256, <<"">>).
% @equiv sign(Data, Algorithm, <<"">>)
sign(Data, hs256, <<"">>, #{}).
% @equiv sign(Data, Algorithm, <<"">>, #{})
-spec sign(Data :: map(), Algorithm :: algorithm()) -> binary().
sign(Data, Algorithm) ->
sign(Data, Algorithm, <<"">>).
sign(Data, Algorithm, <<"">>, #{}).
% @equiv sign(Data, Algorithm, KeyOrPem, #{})
-spec sign(Data :: map(), Algorithm :: algorithm(), KeyOrPem :: binary()) -> binary().
sign(Data, Algorithm, KeyOrPem) ->
sign(Data, Algorithm, KeyOrPem, #{}).

% @doc
% Sign <tt>Data</tt> with the given <tt>Algorithm</tt> and <tt>KeyOrPem</tt>.
%
Expand All @@ -42,9 +47,9 @@ sign(Data, Algorithm) ->
% Token = jwerl:sign(#{key =&gt; &lt;&lt;"Hello World"&gt;&gt;}, hs256, &lt;&lt;"s3cr3t k3y"&gt;&gt;).
% </pre>
% @end
-spec sign(Data :: map() | list(), Algorithm :: algorithm(), KeyOrPem :: binary()) -> binary().
sign(Data, Algorithm, KeyOrPem) when (is_map(Data) orelse is_list(Data)), is_atom(Algorithm), is_binary(KeyOrPem) ->
encode(jsx:encode(Data), config_headers(#{alg => algorithm_to_binary(Algorithm)}), KeyOrPem).
-spec sign(Data :: map() | list(), Algorithm :: algorithm(), KeyOrPem :: binary(), Opts :: map()) -> binary().
sign(Data, Algorithm, KeyOrPem, Opts) when (is_map(Data) orelse is_list(Data)), is_atom(Algorithm), is_binary(KeyOrPem), is_map(Opts) ->
encode(jsx:encode(Data), config_headers(#{alg => algorithm_to_binary(Algorithm)}), KeyOrPem, Opts).

% @equiv verify(Data, <<"">>, hs256, #{}, #{})
verify(Data) ->
Expand All @@ -55,9 +60,10 @@ verify(Data, Algorithm) ->
% @equiv verify(Data, Algorithm, KeyOrPem, #{}, #{})
verify(Data, Algorithm, KeyOrPem) ->
verify(Data, Algorithm, KeyOrPem, #{}, #{}).
% @equiv verify(Data, Algorithm, KeyOrPem, #{}, #{})
% @equiv verify(Data, Algorithm, KeyOrPem, Claims, #{})
verify(Data, Algorithm, KeyOrPem, Claims) ->
verify(Data, Algorithm, KeyOrPem, Claims, #{}).

% @doc
% Verify a JWToken according to the given <tt>Algorithm</tt>, <tt>KeyOrPem</tt> and <tt>Claims</tt>.
% This verifycation can ignore (<tt>CheckClaims =:= false</tt>) claims.
Expand All @@ -74,7 +80,7 @@ verify(Data, Algorithm, KeyOrPem, Claims) ->
-spec verify(Data :: binary(), Algorithm :: algorithm(), KeyOrPem :: binary(), CheckClaims :: map() | list() | false, Opts :: map()) ->
{ok, map()} | {error, term()}.
verify(Data, Algorithm, KeyOrPem, Claims, Opts) ->
case decode(Data, KeyOrPem, Algorithm) of
case decode(Data, KeyOrPem, Algorithm, Opts) of
{ok, TokenData} when is_map(Claims) orelse is_list(Claims) ->
case check_claims(TokenData, Claims, Opts) of
ok ->
Expand Down Expand Up @@ -175,16 +181,16 @@ get_claims(Map) when is_map(Map) ->
get_claims(List) when is_list(List) ->
List.

encode(Data, #{alg := <<"none">>} = Options, _) ->
encode_input(Data, Options);
encode(Data, Options, Key) ->
Input = encode_input(Data, Options),
<<Input/binary, ".", (signature(maps:get(alg, Options), Key, Input))/binary>>.
encode(Data, #{alg := <<"none">>} = Header, _, _) ->
encode_input(Data, Header);
encode(Data, Header, Key, Opts) ->
Input = encode_input(Data, Header),
<<Input/binary, ".", (signature(maps:get(alg, Header), Key, Input, Opts))/binary>>.

decode(Data, KeyOrPem, Algorithm) ->
decode(Data, KeyOrPem, Algorithm, Opts) ->
Header = decode_header(Data),
case algorithm_to_atom(maps:get(alg, Header)) of
Algorithm -> payload(Data, Algorithm, KeyOrPem);
Algorithm -> payload(Data, Algorithm, KeyOrPem, Opts);
Algorithm1 -> {error, {invalid_algorithm, Algorithm1, Algorithm}}
end.

Expand Down Expand Up @@ -220,16 +226,21 @@ decode_header(Data) ->
[Header|_] = binary:split(Data, <<".">>, [global]),
jsx:decode(base64_decode(Header), [return_maps, {labels, attempt_atom}]).

payload(Data, none, _) ->
payload(Data, none, _, _) ->
[_, Data1|_] = binary:split(Data, <<".">>, [global]),
{ok, jsx:decode(base64_decode(Data1), [return_maps, {labels, attempt_atom}])};
payload(Data, Algorithm, Key) ->
[Header, Data1, Signature] = binary:split(Data, <<".">>, [global]),
payload(Data, Algorithm, Key, Opts) ->
[Header, Data1, Signature0] = binary:split(Data, <<".">>, [global]),
{AlgMod, ShaBits} = algorithm_to_infos(Algorithm),

Signature = case maps:get(raw, Opts, false) of
true -> raw_to_der(base64_decode(Signature0));
_ -> base64_decode(Signature0)
end,
case erlang:apply(AlgMod, verify, [ShaBits,
Key,
<<Header/binary, ".", Data1/binary>>,
base64_decode(Signature)]) of
Signature]) of
true ->
{ok, jsx:decode(base64_decode(Data1), [return_maps, {labels, attempt_atom}])};
_ ->
Expand All @@ -239,11 +250,39 @@ payload(Data, Algorithm, Key) ->
encode_input(Data, Options) ->
<<(base64_encode(jsx:encode(Options)))/binary, ".", (base64_encode(Data))/binary>>.

signature(Algorithm, Key, Data) ->
signature(Algorithm, Key, Data, Opts) ->
{AlgMod, ShaBits} = algorithm_to_infos(Algorithm),
Signature = erlang:apply(AlgMod, sign, [ShaBits, Key, Data]),
Signature0 = erlang:apply(AlgMod, sign, [ShaBits, Key, Data]),
Signature = case maps:get(raw, Opts, false) of
true ->
der_to_raw(Signature0);
_ ->
Signature0
end,
base64_encode(Signature).

der_to_raw(<<48,_,_, L1, R:L1/binary, _, L2, S:L2/binary>>) ->
<<(trim_zero_padding(R))/binary, (trim_zero_padding(S))/binary>>.

raw_to_der(Bin) when is_binary(Bin) ->
Size = byte_size(Bin) div 2,
<<R:Size/binary, S:Size/binary>> = Bin,
R1 = add_zero_padding(R),
S1 = add_zero_padding(S),
DerBody = <<2, (byte_size(R1)):8, R1/binary, 2, (byte_size(S1)):8, S1/binary>>,
<<48, (byte_size(DerBody)):8, DerBody/binary>>.

trim_zero_padding(B) ->
case byte_size(B) rem 2 of
0 -> B;
_ -> <<_, B1/binary>> = B, B1
end.

add_zero_padding(B = <<1:1, _:7, _/binary>>) ->
<<0, B/binary>>;
add_zero_padding(B) ->
B.

algorithm_to_atom(<<"HS256">>) -> hs256;
algorithm_to_atom(<<"RS256">>) -> rs256;
algorithm_to_atom(<<"ES256">>) -> es256;
Expand Down
8 changes: 6 additions & 2 deletions test/jwerl_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@ t_jwerl_ecdsa() ->
?assertMatch({ok, Data}, jwerl:verify(
jwerl:sign(Data, es512, ec_private_key()),
es512,
ec_public_key())).
ec_public_key())),

?assertMatch({ok, Data}, jwerl:verify(
jwerl:sign(Data, es512, ec_private_key(), #{raw => true}),
es512,
ec_public_key(), [], #{raw => true})).

t_jwerl_no_claims() ->
Now = os:system_time(seconds),
Expand Down Expand Up @@ -210,7 +215,6 @@ t_jwerl_not_registered_claim_name() ->
Data = #{<<"planet">> => <<"zorg">>},
?assertMatch({ok, Data}, jwerl:verify(jwerl:sign(Data))).


rsa_private_key() ->
% openssl genrsa -out private_key.pem 4096
<<"-----BEGIN RSA PRIVATE KEY-----\n",
Expand Down

0 comments on commit 7cce3a4

Please sign in to comment.