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}"] )
