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:11 UTC

[couchdb] branch jwtf-es256-fail-erlang-20 created (now ba6d78d4a)

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a change to branch jwtf-es256-fail-erlang-20
in repository https://gitbox.apache.org/repos/asf/couchdb.git


      at ba6d78d4a Exclude jwtf EC algorithms on Erlang 20

This branch includes the following new commits:

     new 793967191 Fix ES{256,384,512} support
     new ba6d78d4a Exclude jwtf EC algorithms on Erlang 20

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[couchdb] 01/02: Fix ES{256,384,512} support

Posted by va...@apache.org.
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)).
 


[couchdb] 02/02: Exclude jwtf EC algorithms on Erlang 20

Posted by va...@apache.org.
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 ba6d78d4a675988f96a27981f889e9108eefc1d6
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue May 24 16:06:34 2022 -0400

    Exclude jwtf EC algorithms on Erlang 20
---
 src/jwtf/src/jwtf.erl        | 17 +++++++++++++++++
 src/jwtf/test/jwtf_tests.erl |  4 ++++
 2 files changed, 21 insertions(+)

diff --git a/src/jwtf/src/jwtf.erl b/src/jwtf/src/jwtf.erl
index f0a5bd6ca..e45a1ade1 100644
--- a/src/jwtf/src/jwtf.erl
+++ b/src/jwtf/src/jwtf.erl
@@ -29,6 +29,9 @@
 
 -include_lib("public_key/include/public_key.hrl").
 
+
+-ifdef(OTP_RELEASE).
+
 -define(ALGS, [
     % RSA PKCS#1 signature with SHA-256
     {<<"RS256">>, {public_key, sha256}},
@@ -42,6 +45,20 @@
     {<<"HS512">>, {hmac, sha512}}
 ]).
 
+-else.
+
+-define(ALGS, [
+    % RSA PKCS#1 signature with SHA-256
+    {<<"RS256">>, {public_key, sha256}},
+    {<<"RS384">>, {public_key, sha384}},
+    {<<"RS512">>, {public_key, sha512}},
+    {<<"HS256">>, {hmac, sha256}},
+    {<<"HS384">>, {hmac, sha384}},
+    {<<"HS512">>, {hmac, sha512}}
+]).
+
+-endif.
+
 -define(CHECKS, [
     alg,
     exp,
diff --git a/src/jwtf/test/jwtf_tests.erl b/src/jwtf/test/jwtf_tests.erl
index b0b9f1fab..57fb9ac8d 100644
--- a/src/jwtf/test/jwtf_tests.erl
+++ b/src/jwtf/test/jwtf_tests.erl
@@ -285,6 +285,8 @@ rs256_test() ->
 
     ?assertMatch({ok, ExpectedPayload}, jwtf:decode(EncodedToken, Checks, KS)).
 
+
+-ifdef(OTP_RELEASE).
 %% jwt.io generated
 es256_test() ->
     EncodedToken = <<
@@ -306,6 +308,8 @@ es256_test() ->
 
     ?assertMatch({ok, ExpectedPayload}, jwtf:decode(EncodedToken, Checks, KS)).
 
+-endif.
+
 encode_missing_alg_test() ->
     ?assertEqual(
         {error, {bad_request, <<"Missing alg header parameter">>}},