blob: 2a2b396fb4c154e13b2146847537f847fa0e145a [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couch_passwords).
-export([simple/2, pbkdf2/3, pbkdf2/4, verify/2]).
-export([hash_admin_password/1, get_unhashed_admins/0]).
-include_lib("couch/include/couch_db.hrl").
-define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)).
-define(SHA1_OUTPUT_LENGTH, 20).
%% legacy scheme, not used for new passwords.
-spec simple(binary(), binary()) -> binary().
simple(Password, Salt) when is_binary(Password), is_binary(Salt) ->
?l2b(couch_util:to_hex(crypto:hash(sha, <<Password/binary, Salt/binary>>)));
simple(Password, Salt) when is_binary(Salt) ->
Msg = io_lib:format("Password value of '~p' is invalid.", [Password]),
throw({forbidden, Msg});
simple(Password, Salt) when is_binary(Password) ->
Msg = io_lib:format("Salt value of '~p' is invalid.", [Salt]),
throw({forbidden, Msg}).
%% CouchDB utility functions
-spec hash_admin_password(binary() | list()) -> binary().
hash_admin_password(ClearPassword) when is_list(ClearPassword) ->
hash_admin_password(?l2b(ClearPassword));
hash_admin_password(ClearPassword) when is_binary(ClearPassword) ->
%% Support both schemes to smooth migration from legacy scheme
Scheme = chttpd_util:get_chttpd_auth_config("password_scheme", "pbkdf2"),
hash_admin_password(Scheme, ClearPassword).
% deprecated
hash_admin_password("simple", ClearPassword) ->
Salt = couch_uuids:random(),
Hash = crypto:hash(sha, <<ClearPassword/binary, Salt/binary>>),
?l2b("-hashed-" ++ couch_util:to_hex(Hash) ++ "," ++ ?b2l(Salt));
hash_admin_password("pbkdf2", ClearPassword) ->
Iterations = chttpd_util:get_chttpd_auth_config("iterations", "10"),
Salt = couch_uuids:random(),
DerivedKey = couch_passwords:pbkdf2(
couch_util:to_binary(ClearPassword),
Salt,
list_to_integer(Iterations)
),
?l2b(
"-pbkdf2-" ++ ?b2l(DerivedKey) ++ "," ++
?b2l(Salt) ++ "," ++
Iterations
).
-spec get_unhashed_admins() -> list().
get_unhashed_admins() ->
lists:filter(
fun
({_User, "-hashed-" ++ _}) ->
% already hashed
false;
({_User, "-pbkdf2-" ++ _}) ->
% already hashed
false;
({_User, _ClearPassword}) ->
true
end,
config:get("admins")
).
pbkdf2(Password, Salt, Iterations) ->
case couch_pbkdf2:pbkdf2(sha, Password, Salt, Iterations, ?SHA1_OUTPUT_LENGTH) of
{ok, Hash} ->
couch_pbkdf2:to_hex(Hash);
Else ->
Else
end.
pbkdf2(Password, Salt, Iterations, DerivedLength) ->
case couch_pbkdf2:pbkdf2(sha, Password, Salt, Iterations, DerivedLength) of
{ok, Hash} ->
couch_pbkdf2:to_hex(Hash);
Else ->
Else
end.
verify(X, Y) ->
couch_pbkdf2:compare_secure(X, Y).