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

rnewson pushed a commit to branch jwt-enhancements
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 51fb706022f40aa7fed3c7a4db97b1f16c4ded7f
Author: Robert Newson <[email protected]>
AuthorDate: Thu Mar 19 19:06:23 2020 +0000

    support RSA for JWT auth
---
 rel/overlay/etc/default.ini        |  8 +++++--
 src/couch/src/couch_httpd_auth.erl | 21 ++++++++++++++----
 test/elixir/test/jwtauth_test.exs  | 44 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 82a5659..fbb5f5c 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -141,12 +141,16 @@ max_db_number_for_dbs_info_req = 100
 ; admin_only_all_dbs = true
 
 ;[jwt_auth]
-; Symmetric secret to be used when checking JWT token signatures
-; secret =
 ; List of claims to validate
 ; required_claims = exp
 ; List of algorithms to accept during checks
 ; allowed_algorithms = HS256
+;
+; [jwt_keys]
+; Default secret to be used when checking JWT token signatures
+; _default =
+; for key with KID of "foo";
+; foo =
 
 [couch_peruser]
 ; If enabled, couch_peruser ensures that a private per-user database
diff --git a/src/couch/src/couch_httpd_auth.erl 
b/src/couch/src/couch_httpd_auth.erl
index 6b85a02..2925920 100644
--- a/src/couch/src/couch_httpd_auth.erl
+++ b/src/couch/src/couch_httpd_auth.erl
@@ -189,11 +189,11 @@ proxy_auth_user(Req) ->
     end.
 
 jwt_authentication_handler(Req) ->
-    case {config:get("jwt_auth", "secret"), header_value(Req, 
"Authorization")} of
-        {Secret, "Bearer " ++ Jwt} when Secret /= undefined ->
+    case header_value(Req, "Authorization") of
+        "Bearer " ++ Jwt ->
             RequiredClaims = get_configured_claims(),
             AllowedAlgorithms = get_configured_algorithms(),
-            case jwtf:decode(?l2b(Jwt), [{alg, AllowedAlgorithms} | 
RequiredClaims], fun(_,_) -> Secret end) of
+            case jwtf:decode(?l2b(Jwt), [{alg, AllowedAlgorithms} | 
RequiredClaims], fun jwt_keystore/2) of
                 {ok, {Claims}} ->
                     case lists:keyfind(<<"sub">>, 1, Claims) of
                         false -> throw({unauthorized, <<"Token missing sub 
claim.">>});
@@ -204,7 +204,7 @@ jwt_authentication_handler(Req) ->
                 {error, Reason} ->
                     throw({unauthorized, Reason})
             end;
-        {_, _} -> Req
+        _ -> Req
     end.
 
 get_configured_algorithms() ->
@@ -213,6 +213,19 @@ get_configured_algorithms() ->
 get_configured_claims() ->
     re:split(config:get("jwt_auth", "required_claims", ""), "\s*,\s*", 
[{return, binary}]).
 
+jwt_keystore(Alg, undefined) ->
+    jwt_keystore(Alg, "_default");
+jwt_keystore(Alg, KID) ->
+    Key = config:get("jwt_keys", KID),
+    case jwtf:verification_algorithm(Alg) of
+        {hmac, _} ->
+            Key;
+        {public_key, _} ->
+            BinKey = ?l2b(string:replace(Key, ";", "\n", all)),
+            [PEMEntry] = public_key:pem_decode(BinKey),
+            public_key:pem_entry_decode(PEMEntry)
+    end.
+
 cookie_authentication_handler(Req) ->
     cookie_authentication_handler(Req, couch_auth_cache).
 
diff --git a/test/elixir/test/jwtauth_test.exs 
b/test/elixir/test/jwtauth_test.exs
index aee14b3..bb4c42d 100644
--- a/test/elixir/test/jwtauth_test.exs
+++ b/test/elixir/test/jwtauth_test.exs
@@ -9,8 +9,8 @@ defmodule JwtAuthTest do
 
     server_config = [
       %{
-        :section => "jwt_auth",
-        :key => "secret",
+        :section => "jwt_keys",
+        :key => "_default",
         :value => secret
       },
       %{
@@ -25,8 +25,48 @@ defmodule JwtAuthTest do
     run_on_modified_server(server_config, fn -> test_fun("HS512", secret) end)
   end
 
+  defmodule RSA do
+    require Record
+    Record.defrecord :public, :RSAPublicKey,
+      Record.extract(:RSAPublicKey, from_lib: 
"public_key/include/public_key.hrl")
+    Record.defrecord :private, :RSAPrivateKey,
+      Record.extract(:RSAPrivateKey, from_lib: 
"public_key/include/public_key.hrl")
+  end
+
+  test "jwt auth with RSA secret", _context do
+    require JwtAuthTest.RSA
+
+    private_key = :public_key.generate_key({:rsa, 2048, 17})
+    public_key = RSA.public(
+      modulus: RSA.private(private_key, :modulus),
+      publicExponent: RSA.private(private_key, :publicExponent))
+
+    public_pem = :public_key.pem_encode(
+      [:public_key.pem_entry_encode(
+          :SubjectPublicKeyInfo, public_key)])
+    public_pem = String.replace(public_pem, "\n", ";")
+
+    server_config = [
+      %{
+        :section => "jwt_keys",
+        :key => "_default",
+        :value => public_pem
+      },
+      %{
+        :section => "jwt_auth",
+        :key => "allowed_algorithms",
+        :value => "RS256, RS384, RS512"
+      }
+    ]
+
+    run_on_modified_server(server_config, fn -> test_fun("RS256", private_key) 
end)
+    run_on_modified_server(server_config, fn -> test_fun("RS384", private_key) 
end)
+    run_on_modified_server(server_config, fn -> test_fun("RS512", private_key) 
end)
+  end
+
   def test_fun(alg, key) do
     {:ok, token} = :jwtf.encode({[{"alg", alg}, {"typ", "JWT"}]}, {[{"sub", 
"[email protected]"}]}, key)
+
     resp = Couch.get("/_session",
       headers: [authorization: "Bearer #{token}"]
     )

Reply via email to