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