Skip to content

Commit

Permalink
Upgrade hash algorithm for proxy auth (#4438)
Browse files Browse the repository at this point in the history
Proxy auth can now use one of the configured hash algorithms
from chttpd_auth/hash_algorithms to decode authentication tokens.
  • Loading branch information
big-r81 committed Feb 23, 2023
1 parent 187d933 commit ae3d297
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 17 deletions.
87 changes: 87 additions & 0 deletions src/chttpd/test/eunit/chttpd_auth_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

-module(chttpd_auth_tests).

-define(WORKING_HASHES, "sha256, sha512, sha, blake2s").
-define(FAILING_HASHES, "md4, md5, ripemd160").

-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").

Expand All @@ -24,6 +27,27 @@ setup() ->
teardown(_Url) ->
ok.

setup_proxy_auth() ->
{StartCtx, ProxyCfgFile} = start_couch_with_cfg("{chttpd_auth, proxy_authentication_handler}"),
config:set("chttpd", "require_valid_user", "false", false),
config:set("chttpd_auth", "hash_algorithms", ?WORKING_HASHES, false),
config:set("chttpd_auth", "proxy_use_secret", "true", false),
config:set("chttpd_auth", "secret", "the_secret", false),
HashesShouldWork = re:split(?WORKING_HASHES, "\\s*,\\s*", [
trim, {return, binary}
]),
HashesShouldFail = re:split(?FAILING_HASHES, "\\s*,\\s*", [trim, {return, binary}]),
SupportedHashAlgorithms = crypto:supports(hashs),
{{StartCtx, ProxyCfgFile}, HashesShouldWork, HashesShouldFail, SupportedHashAlgorithms}.

teardown_proxy_auth({{Ctx, ProxyCfgFile}, _, _, _}) ->
ok = file:delete(ProxyCfgFile),
config:delete("chttpd_auth", "hash_algorithms", false),
config:delete("chttpd_auth", "secret", false),
config:delete("chttpd_auth", "proxy_use_secret", false),
config:delete("chttpd", "require_valid_user", false),
test_util:stop_couch(Ctx).

require_valid_user_exception_test_() ->
{
"_up",
Expand All @@ -43,6 +67,20 @@ require_valid_user_exception_test_() ->
}
}.

proxy_auth_test_() ->
{
"Testing hash algorithms for proxy auth",
{
setup,
fun setup_proxy_auth/0,
fun teardown_proxy_auth/1,
with([
?TDEF(test_hash_algorithms_with_proxy_auth_should_work),
?TDEF(test_hash_algorithms_with_proxy_auth_should_fail)
])
}
}.

set_require_user_false() ->
ok = config:set("chttpd", "require_valid_user", "false", _Persist = false).

Expand Down Expand Up @@ -125,3 +163,52 @@ should_handle_require_valid_user_except_up_on_non_up_routes(_Url) ->
set_require_user_except_for_up_true(),
?assertThrow(ExpectAuth, chttpd_auth:party_mode_handler(NonUpRequest))
end).

% Helper functions
base_url() ->
Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
Port = integer_to_list(mochiweb_socket_server:get(chttpd, port)),
"http://" ++ Addr ++ ":" ++ Port.

append_to_cfg_chain(Cfg) ->
CfgDir = filename:dirname(lists:last(?CONFIG_CHAIN)),
CfgFile = filename:join([CfgDir, "chttpd_proxy_auth_cfg.ini"]),
CfgSect = io_lib:format("[chttpd]~nauthentication_handlers = ~s~n", [Cfg]),
ok = file:write_file(CfgFile, CfgSect),
?CONFIG_CHAIN ++ [CfgFile].

start_couch_with_cfg(Cfg) ->
CfgChain = append_to_cfg_chain(Cfg),
StartCtx = test_util:start_couch(CfgChain, [chttpd]),
ProxyCfgFile = lists:last(CfgChain),
{StartCtx, ProxyCfgFile}.

% Test functions
test_hash_algorithm([]) ->
ok;
test_hash_algorithm([DefaultHashAlgorithm | DecodingHashAlgorithmsList] = _) ->
Secret = chttpd_util:get_chttpd_auth_config("secret"),
Token = couch_util:to_hex(couch_util:hmac(DefaultHashAlgorithm, Secret, "PROXY-USER")),
Headers = [
{"X-Auth-CouchDB-UserName", "PROXY-USER"},
{"X-Auth-CouchDB-Roles", "PROXY-USER-ROLE1, PROXY-USER-ROLE2"},
{"X-Auth-CouchDB-Token", Token}
],
{ok, _, _, ReqBody} = test_request:get(base_url() ++ "/_session", Headers),
IsAuthenticatedViaProxy = couch_util:get_nested_json_value(
jiffy:decode(ReqBody), [<<"info">>, <<"authenticated">>]
),
?assertEqual(IsAuthenticatedViaProxy, <<"proxy">>),
test_hash_algorithm(DecodingHashAlgorithmsList).

test_hash_algorithms_with_proxy_auth_should_work(
{_Ctx, WorkingHashes, _FailingHashes, SupportedHashAlgorithms} = _
) ->
Hashes = couch_util:verify_hash_names(WorkingHashes, SupportedHashAlgorithms),
test_hash_algorithm(Hashes).

test_hash_algorithms_with_proxy_auth_should_fail(
{_Ctx, _WorkingHashes, FailingHashes, SupportedHashAlgorithms} = _
) ->
Hashes = couch_util:verify_hash_names(FailingHashes, SupportedHashAlgorithms),
?assertThrow({not_found, _}, test_hash_algorithm(Hashes)).
15 changes: 9 additions & 6 deletions src/couch/src/couch_httpd_auth.erl
Original file line number Diff line number Diff line change
Expand Up @@ -201,18 +201,21 @@ proxy_auth_user(Req) ->
undefined ->
Req#httpd{user_ctx = #user_ctx{name = ?l2b(UserName), roles = Roles}};
Secret ->
ExpectedToken = couch_util:to_hex(
couch_util:hmac(sha, Secret, UserName)
),
case header_value(Req, XHeaderToken) of
Token when Token == ExpectedToken ->
HashAlgorithms = couch_util:get_config_hash_algorithms(),
Token = header_value(Req, XHeaderToken),
VerifyTokens = fun(HashAlg) ->
Hmac = couch_util:hmac(HashAlg, Secret, UserName),
couch_passwords:verify(couch_util:to_hex(Hmac), Token)
end,
case lists:any(VerifyTokens, HashAlgorithms) of
true ->
Req#httpd{
user_ctx = #user_ctx{
name = ?l2b(UserName),
roles = Roles
}
};
_ ->
false ->
nil
end
end;
Expand Down
16 changes: 9 additions & 7 deletions src/docs/src/api/server/authn.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,22 +291,24 @@ remotely authenticated user. By default, the client just needs to pass specific
headers to CouchDB with related requests:

- :config:option:`X-Auth-CouchDB-UserName <chttpd_auth/x_auth_username>`:
username;
username
- :config:option:`X-Auth-CouchDB-Roles <chttpd_auth/x_auth_roles>`:
comma-separated (``,``) list of user roles;
comma-separated (``,``) list of user roles
- :config:option:`X-Auth-CouchDB-Token <chttpd_auth/x_auth_token>`:
authentication token. When
:config:option:`proxy_use_secret <chttpd_auth/proxy_use_secret>`
is set (which is strongly recommended!), this header provides an HMAC of the
username to authenticate and the secret token to prevent requests from
untrusted sources. (Use the SHA1 of the username and sign with the secret)
untrusted sources. (Use one of the configured hash algorithms in
:config:option:`chttpd_auth/hash_algorithms <chttpd_auth/hash_algorithms>`
and sign the username with the secret)

**Creating the token (example with openssl)**:

.. code-block:: sh
echo -n "foo" | openssl dgst -sha1 -hmac "the_secret"
# (stdin)= 22047ebd7c4ec67dfbcbad7213a693249dbfbf86
echo -n "foo" | openssl dgst -sha256 -hmac "the_secret"
# (stdin)= 3f0786e96b20b0102b77f1a49c041be6977cfb3bf78c41a12adc121cd9b4e68a
**Request**:

Expand All @@ -318,7 +320,7 @@ headers to CouchDB with related requests:
Content-Type: application/json; charset=utf-8
X-Auth-CouchDB-Roles: users,blogger
X-Auth-CouchDB-UserName: foo
X-Auth-CouchDB-Token: 22047ebd7c4ec67dfbcbad7213a693249dbfbf86
X-Auth-CouchDB-Token: 3f0786e96b20b0102b77f1a49c041be6977cfb3bf78c41a12adc121cd9b4e68a
**Response**:

Expand Down Expand Up @@ -351,7 +353,7 @@ headers to CouchDB with related requests:
}
}
Note that you don't need to request :ref:`session <api/auth/session>`
Note that you don't need to request a :ref:`session <api/auth/session>`
to be authenticated by this method if all required HTTP headers are provided.

.. _api/auth/jwt:
Expand Down
16 changes: 12 additions & 4 deletions src/docs/src/config/auth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,22 @@ Authentication Configuration
[chttpd_auth]
authentication_redirect = /_utils/session.html

.. config:option:: hash_algorithms :: Supported hash algorithms for cookie auth
.. config:option:: hash_algorithms :: Supported hash algorithms for cookie and \
proxy auth
.. versionadded:: 3.3

Sets the HMAC hash algorithm used for cookie authentication. You can provide a
comma-separated list of hash algorithms. New cookie sessions or
.. note::
Until CouchDB version 3.3.1, :ref:`api/auth/proxy` used only the hash
algorithm ``sha1`` as validation of
:config:option:`X-Auth-CouchDB-Token <chttpd_auth/x_auth_token>`.

Sets the HMAC hash algorithm used for cookie and proxy authentication. You can
provide a comma-separated list of hash algorithms. New cookie sessions or
session updates are calculated with the first hash algorithm. All values in the
list can be used to decode the cookie session. ::
list can be used to decode the cookie session and the token
:config:option:`X-Auth-CouchDB-Token <chttpd_auth/x_auth_token>` for
:ref:`api/auth/proxy`. ::

[chttpd_auth]
hash_algorithms = sha256, sha
Expand Down

0 comments on commit ae3d297

Please sign in to comment.