1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(couch_passwords).
14
15-export([simple/2, pbkdf2/3, pbkdf2/4, verify/2]).
16-export([hash_admin_password/1, get_unhashed_admins/0]).
17
18-include_lib("couch/include/couch_db.hrl").
19
20-define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)).
21-define(SHA1_OUTPUT_LENGTH, 20).
22
23%% legacy scheme, not used for new passwords.
24-spec simple(binary(), binary()) -> binary().
25simple(Password, Salt) when is_binary(Password), is_binary(Salt) ->
26    ?l2b(couch_util:to_hex(crypto:hash(sha, <<Password/binary, Salt/binary>>)));
27simple(Password, Salt) when is_binary(Salt) ->
28    Msg = io_lib:format("Password value of '~p' is invalid.", [Password]),
29    throw({forbidden, Msg});
30simple(Password, Salt) when is_binary(Password) ->
31    Msg = io_lib:format("Salt value of '~p' is invalid.", [Salt]),
32    throw({forbidden, Msg}).
33
34%% CouchDB utility functions
35-spec hash_admin_password(binary() | list()) -> binary().
36hash_admin_password(ClearPassword) when is_list(ClearPassword) ->
37    hash_admin_password(?l2b(ClearPassword));
38hash_admin_password(ClearPassword) when is_binary(ClearPassword) ->
39    %% Support both schemes to smooth migration from legacy scheme
40    Scheme = chttpd_util:get_chttpd_auth_config("password_scheme", "pbkdf2"),
41    hash_admin_password(Scheme, ClearPassword).
42
43hash_admin_password("simple", ClearPassword) -> % deprecated
44    Salt = couch_uuids:random(),
45    Hash = crypto:hash(sha, <<ClearPassword/binary, Salt/binary>>),
46    ?l2b("-hashed-" ++ couch_util:to_hex(Hash) ++ "," ++ ?b2l(Salt));
47hash_admin_password("pbkdf2", ClearPassword) ->
48    Iterations = chttpd_util:get_chttpd_auth_config("iterations", "10"),
49    Salt = couch_uuids:random(),
50    DerivedKey = couch_passwords:pbkdf2(couch_util:to_binary(ClearPassword),
51                                        Salt, list_to_integer(Iterations)),
52    ?l2b("-pbkdf2-" ++ ?b2l(DerivedKey) ++ ","
53        ++ ?b2l(Salt) ++ ","
54        ++ Iterations).
55
56-spec get_unhashed_admins() -> list().
57get_unhashed_admins() ->
58    lists:filter(
59        fun({_User, "-hashed-" ++ _}) ->
60            false; % already hashed
61        ({_User, "-pbkdf2-" ++ _}) ->
62            false; % already hashed
63        ({_User, _ClearPassword}) ->
64            true
65        end,
66    config:get("admins")).
67
68%% Current scheme, much stronger.
69-spec pbkdf2(binary(), binary(), integer()) -> binary().
70pbkdf2(Password, Salt, Iterations) when is_binary(Password),
71                                        is_binary(Salt),
72                                        is_integer(Iterations),
73                                        Iterations > 0 ->
74    {ok, Result} = pbkdf2(Password, Salt, Iterations, ?SHA1_OUTPUT_LENGTH),
75    Result;
76pbkdf2(Password, Salt, Iterations) when is_binary(Salt),
77                                        is_integer(Iterations),
78                                        Iterations > 0 ->
79    Msg = io_lib:format("Password value of '~p' is invalid.", [Password]),
80    throw({forbidden, Msg});
81pbkdf2(Password, Salt, Iterations) when is_binary(Password),
82                                        is_integer(Iterations),
83                                        Iterations > 0 ->
84    Msg = io_lib:format("Salt value of '~p' is invalid.", [Salt]),
85    throw({forbidden, Msg}).
86
87-spec pbkdf2(binary(), binary(), integer(), integer())
88    -> {ok, binary()} | {error, derived_key_too_long}.
89pbkdf2(_Password, _Salt, _Iterations, DerivedLength)
90    when DerivedLength > ?MAX_DERIVED_KEY_LENGTH ->
91    {error, derived_key_too_long};
92pbkdf2(Password, Salt, Iterations, DerivedLength) when is_binary(Password),
93                                                       is_binary(Salt),
94                                                       is_integer(Iterations),
95                                                       Iterations > 0,
96                                                       is_integer(DerivedLength) ->
97    L = ceiling(DerivedLength / ?SHA1_OUTPUT_LENGTH),
98    <<Bin:DerivedLength/binary,_/binary>> =
99        iolist_to_binary(pbkdf2(Password, Salt, Iterations, L, 1, [])),
100    {ok, ?l2b(couch_util:to_hex(Bin))}.
101
102-spec pbkdf2(binary(), binary(), integer(), integer(), integer(), iolist())
103    -> iolist().
104pbkdf2(_Password, _Salt, _Iterations, BlockCount, BlockIndex, Acc)
105    when BlockIndex > BlockCount ->
106    lists:reverse(Acc);
107pbkdf2(Password, Salt, Iterations, BlockCount, BlockIndex, Acc) ->
108    Block = pbkdf2(Password, Salt, Iterations, BlockIndex, 1, <<>>, <<>>),
109    pbkdf2(Password, Salt, Iterations, BlockCount, BlockIndex + 1, [Block|Acc]).
110
111-spec pbkdf2(binary(), binary(), integer(), integer(), integer(),
112    binary(), binary()) -> binary().
113pbkdf2(_Password, _Salt, Iterations, _BlockIndex, Iteration, _Prev, Acc)
114    when Iteration > Iterations ->
115    Acc;
116pbkdf2(Password, Salt, Iterations, BlockIndex, 1, _Prev, _Acc) ->
117    InitialBlock = couch_util:hmac(sha, Password,
118        <<Salt/binary,BlockIndex:32/integer>>),
119    pbkdf2(Password, Salt, Iterations, BlockIndex, 2,
120        InitialBlock, InitialBlock);
121pbkdf2(Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) ->
122    Next = couch_util:hmac(sha, Password, Prev),
123    pbkdf2(Password, Salt, Iterations, BlockIndex, Iteration + 1,
124                   Next, crypto:exor(Next, Acc)).
125
126%% verify two lists for equality without short-circuits to avoid timing attacks.
127-spec verify(string(), string(), integer()) -> boolean().
128verify([X|RestX], [Y|RestY], Result) ->
129    verify(RestX, RestY, (X bxor Y) bor Result);
130verify([], [], Result) ->
131    Result == 0.
132
133-spec verify(binary(), binary()) -> boolean();
134            (list(), list()) -> boolean().
135verify(<<X/binary>>, <<Y/binary>>) ->
136    verify(?b2l(X), ?b2l(Y));
137verify(X, Y) when is_list(X) and is_list(Y) ->
138    case length(X) == length(Y) of
139        true ->
140            verify(X, Y, 0);
141        false ->
142            false
143    end;
144verify(_X, _Y) -> false.
145
146-spec ceiling(number()) -> integer().
147ceiling(X) ->
148    T = erlang:trunc(X),
149    case (X - T) of
150        Neg when Neg < 0 -> T;
151        Pos when Pos > 0 -> T + 1;
152        _ -> T
153    end.
154