You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2022/05/24 20:08:12 UTC
[couchdb] 01/02: Fix ES{256,384,512} support
This is an automated email from the ASF dual-hosted git repository.
vatamane pushed a commit to branch jwtf-es256-fail-erlang-20
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 793967191dab6056b4a2d3d831f4e76ec8870f3d
Author: Robert Newson <rn...@apache.org>
AuthorDate: Tue May 24 16:16:57 2022 +0100
Fix ES{256,384,512} support
DERp
---
src/jwtf/src/jwtf.erl | 39 +++++++++++++++++++++++--
src/jwtf/test/jwtf_tests.erl | 69 +++++++++++++++++++++++++++++++++++++++-----
2 files changed, 97 insertions(+), 11 deletions(-)
diff --git a/src/jwtf/src/jwtf.erl b/src/jwtf/src/jwtf.erl
index 2bb005bcb..f0a5bd6ca 100644
--- a/src/jwtf/src/jwtf.erl
+++ b/src/jwtf/src/jwtf.erl
@@ -27,6 +27,8 @@
verification_algorithm/1
]).
+-include_lib("public_key/include/public_key.hrl").
+
-define(ALGS, [
% RSA PKCS#1 signature with SHA-256
{<<"RS256">>, {public_key, sha256}},
@@ -70,7 +72,13 @@ encode(Header = {HeaderProps}, Claims, Key) ->
SignatureOrMac =
case verification_algorithm(Alg) of
{public_key, Algorithm} ->
- public_key:sign(Message, Algorithm, Key);
+ Signature = public_key:sign(Message, Algorithm, Key),
+ case Alg of
+ <<"ES", _/binary>> ->
+ der_to_jose(Alg, Signature);
+ _ ->
+ Signature
+ end;
{hmac, Algorithm} ->
hmac(Algorithm, Key, Message)
end,
@@ -268,12 +276,19 @@ key(Props, Checks, KS) ->
verify(Alg, Header, Payload, SignatureOrMac0, Key) ->
Message = <<Header/binary, $., Payload/binary>>,
SignatureOrMac1 = b64url:decode(SignatureOrMac0),
+ SignatureOrMac2 =
+ case Alg of
+ <<"ES", _/binary>> ->
+ jose_to_der(SignatureOrMac1);
+ _ ->
+ SignatureOrMac1
+ end,
{VerificationMethod, Algorithm} = verification_algorithm(Alg),
case VerificationMethod of
public_key ->
- public_key_verify(Algorithm, Message, SignatureOrMac1, Key);
+ public_key_verify(Algorithm, Message, SignatureOrMac2, Key);
hmac ->
- hmac_verify(Algorithm, Message, SignatureOrMac1, Key)
+ hmac_verify(Algorithm, Message, SignatureOrMac2, Key)
end.
public_key_verify(Algorithm, Message, Signature, PublicKey) ->
@@ -292,6 +307,24 @@ hmac_verify(Algorithm, Message, HMAC, SecretKey) ->
throw({bad_request, <<"Bad HMAC">>})
end.
+jose_to_der(Signature) ->
+ NumLen = 8 * byte_size(Signature) div 2,
+ <<R:NumLen, S:NumLen>> = Signature,
+ SigValue = #'ECDSA-Sig-Value'{r = R, s = S},
+ public_key:der_encode('ECDSA-Sig-Value', SigValue).
+
+der_to_jose(Alg, Signature) ->
+ #'ECDSA-Sig-Value'{r = R, s = S} = public_key:der_decode('ECDSA-Sig-Value', Signature),
+ Len = rs_len(Alg),
+ <<R:Len, S:Len>>.
+
+rs_len(<<"ES256">>) ->
+ 256;
+rs_len(<<"ES384">>) ->
+ 384;
+rs_len(<<"ES512">>) ->
+ 512.
+
split(EncodedToken) ->
case binary:split(EncodedToken, <<$.>>, [global]) of
[_, _, _] = Split -> Split;
diff --git a/src/jwtf/test/jwtf_tests.erl b/src/jwtf/test/jwtf_tests.erl
index e36ecbd23..b0b9f1fab 100644
--- a/src/jwtf/test/jwtf_tests.erl
+++ b/src/jwtf/test/jwtf_tests.erl
@@ -24,7 +24,7 @@ encode(Header0, Payload0) ->
valid_header() ->
{[{<<"typ">>, <<"JWT">>}, {<<"alg">>, <<"RS256">>}]}.
-jwt_io_pubkey() ->
+jwt_io_rsa_pubkey() ->
PublicKeyPEM = <<
"-----BEGIN PUBLIC KEY-----\n"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGH"
@@ -36,6 +36,16 @@ jwt_io_pubkey() ->
[PEMEntry] = public_key:pem_decode(PublicKeyPEM),
public_key:pem_entry_decode(PEMEntry).
+jwt_io_ec_pubkey() ->
+ PublicKeyPEM = <<
+ "-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9"
+ "q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n"
+ "-----END PUBLIC KEY-----\n"
+ >>,
+ [PEMEntry] = public_key:pem_decode(PublicKeyPEM),
+ public_key:pem_entry_decode(PEMEntry).
+
b64_badarg_test() ->
Encoded = <<"0.0.0">>,
?assertEqual(
@@ -169,7 +179,7 @@ bad_rs256_sig_test() ->
{[{<<"typ">>, <<"JWT">>}, {<<"alg">>, <<"RS256">>}]},
{[]}
),
- KS = fun(<<"RS256">>, undefined) -> jwt_io_pubkey() end,
+ KS = fun(<<"RS256">>, undefined) -> jwt_io_rsa_pubkey() end,
?assertEqual(
{error, {bad_request, <<"Bad signature">>}},
jwtf:decode(Encoded, [], KS)
@@ -264,7 +274,28 @@ rs256_test() ->
>>,
Checks = [sig, alg],
- KS = fun(<<"RS256">>, undefined) -> jwt_io_pubkey() end,
+ KS = fun(<<"RS256">>, undefined) -> jwt_io_rsa_pubkey() end,
+
+ ExpectedPayload =
+ {[
+ {<<"sub">>, <<"1234567890">>},
+ {<<"name">>, <<"John Doe">>},
+ {<<"admin">>, true}
+ ]},
+
+ ?assertMatch({ok, ExpectedPayload}, jwtf:decode(EncodedToken, Checks, KS)).
+
+%% jwt.io generated
+es256_test() ->
+ EncodedToken = <<
+ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0N"
+ "TY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.1g"
+ "LptYop2guxSZHmf0ga292suPxwBdkijA1ZopCSSYLBdEl8Bg2fsxoU"
+ "cZuSGztMU9qAKV2p80NQn8czeGhHXA"
+ >>,
+
+ Checks = [sig, alg],
+ KS = fun(<<"ES256">>, undefined) -> jwt_io_ec_pubkey() end,
ExpectedPayload =
{[
@@ -292,10 +323,12 @@ encode_decode_test_() ->
encode_decode(Alg) ->
{EncodeKey, DecodeKey} =
- case jwtf:verification_algorithm(Alg) of
- {public_key, _Algorithm} ->
- create_keypair();
- {hmac, _Algorithm} ->
+ case Alg of
+ <<"RS", _/binary>> ->
+ create_rsa_keypair();
+ <<"ES", _/binary>> ->
+ create_ec_keypair();
+ <<"HS", _/binary>> ->
Key = <<"a-super-secret-key">>,
{Key, Key}
end,
@@ -319,7 +352,7 @@ claims() ->
{<<"exp">>, EpochSeconds + 3600}
]}.
-create_keypair() ->
+create_rsa_keypair() ->
%% https://tools.ietf.org/html/rfc7517#appendix-C
N = decode(<<
"t6Q8PWSi1dkJj9hTP8hNYFlvadM7DflW9mWepOJhJ66w7nyoK1gPNqFMSQRy"
@@ -349,6 +382,26 @@ create_keypair() ->
},
{RSAPrivateKey, RSAPublicKey}.
+create_ec_keypair() ->
+ PublicPEM = <<"-----BEGIN PUBLIC KEY-----\n"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9"
+ "q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n"
+ "-----END PUBLIC KEY-----">>,
+ PrivatePEM = <<"-----BEGIN PRIVATE KEY-----\n"
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2"
+ "OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r"
+ "1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n"
+ "-----END PRIVATE KEY-----">>,
+
+ [PublicEntry] = public_key:pem_decode(PublicPEM),
+ ECPublicKey = public_key:pem_entry_decode(PublicEntry),
+
+ [PrivateEntry] = public_key:pem_decode(PrivatePEM),
+ ECPrivateKey = public_key:pem_entry_decode(PrivateEntry),
+
+ {ECPrivateKey, ECPublicKey}.
+
+
decode(Goop) ->
crypto:bytes_to_integer(b64url:decode(Goop)).