You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2022/05/06 19:29:08 UTC

[couchdb] branch aegis_3.x updated (20de0c858 -> 45ab534c9)

This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a change to branch aegis_3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git


    from 20de0c858 canary value to detect encryption
     new 9ad380ed0 import https://github.com/whitelynx/erlang-pbkdf2/blob/master/src/pbkdf2.erl
     new 45ab534c9 encryption password from config

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 rel/overlay/etc/default.ini       |   2 +
 src/couch/src/couch_file.erl      |  27 +++++-
 src/couch/src/couch_passwords.erl | 135 +++-----------------------
 src/couch/src/couch_pbkdf2.erl    | 199 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 240 insertions(+), 123 deletions(-)
 create mode 100644 src/couch/src/couch_pbkdf2.erl


[couchdb] 02/02: encryption password from config

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch aegis_3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 45ab534c9f4f4227872dc7a53e0e6a9207465158
Author: Robert Newson <rn...@apache.org>
AuthorDate: Fri May 6 19:47:10 2022 +0100

    encryption password from config
---
 rel/overlay/etc/default.ini  |  2 ++
 src/couch/src/couch_file.erl | 27 +++++++++++++++++++++++----
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 5fb45b5b5..98349f5eb 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -6,6 +6,8 @@ name = {{package_author_name}}
 uuid = {{uuid}}
 database_dir = {{data_dir}}
 view_index_dir = {{view_index_dir}}
+encryption_password = super_secret_password
+encryption_salt = no_saltier_than_this
 ; util_driver_dir =
 ; plugin_dir =
 ;os_process_timeout = 5000 ; 5 seconds. for view servers.
diff --git a/src/couch/src/couch_file.erl b/src/couch/src/couch_file.erl
index e4673c394..f52a12f9e 100644
--- a/src/couch/src/couch_file.erl
+++ b/src/couch/src/couch_file.erl
@@ -64,8 +64,6 @@
 %%  or {error, Reason} if the file could not be opened.
 %%----------------------------------------------------------------------
 
--define(AES_MASTER_KEY, <<0:256>>).
-
 open(Filepath) ->
     open(Filepath, []).
 
@@ -932,7 +930,7 @@ reset_eof(#file{} = File) ->
 %% we've wiped all the data, including the wrapped key, so we need a new one.
 init_key(#file{eof = 0} = File) ->
     Key = crypto:strong_rand_bytes(32),
-    WrappedKey = couch_keywrap:key_wrap(?AES_MASTER_KEY, Key),
+    WrappedKey = couch_keywrap:key_wrap(master_key(), Key),
     Header = <<?ENCRYPTED_HEADER, WrappedKey/binary>>,
     ok = file:write(File#file.fd, Header),
     ok = file:sync(File#file.fd),
@@ -942,7 +940,7 @@ init_key(#file{eof = 0} = File) ->
 init_key(#file{key = undefined} = File) ->
     case file:pread(File#file.fd, 0, 48) of
         {ok, <<?ENCRYPTED_HEADER, WrappedKey/binary>>} ->
-            case couch_keywrap:key_unwrap(?AES_MASTER_KEY, WrappedKey) of
+            case couch_keywrap:key_unwrap(master_key(), WrappedKey) of
                 fail ->
                     {error, unwrap_failed};
                 Key when is_binary(Key) ->
@@ -1023,6 +1021,27 @@ unpad(Pos, Bin) when is_binary(Bin) ->
     Result.
 
 
+master_key() ->
+    couch_pbkdf2:pbkdf2(sha256, master_password(), master_salt(), 100000).
+
+
+master_password() ->
+    case config:get("couchdb", "encryption_password") of
+        undefined ->
+            undefined;
+        Password ->
+            ?l2b(Password)
+    end.
+
+master_salt() ->
+    case config:get("couchdb", "encryption_salt") of
+        undefined ->
+            undefined;
+        Salt ->
+            ?l2b(Salt)
+    end.
+
+
 -ifdef(TEST).
 -include_lib("couch/include/couch_eunit.hrl").
 


[couchdb] 01/02: import https://github.com/whitelynx/erlang-pbkdf2/blob/master/src/pbkdf2.erl

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch aegis_3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 9ad380ed08c87f1f88e703408fb6aeb88ca69e42
Author: Robert Newson <rn...@apache.org>
AuthorDate: Fri May 6 19:23:45 2022 +0100

    import https://github.com/whitelynx/erlang-pbkdf2/blob/master/src/pbkdf2.erl
---
 src/couch/src/couch_passwords.erl | 135 +++-----------------------
 src/couch/src/couch_pbkdf2.erl    | 199 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 215 insertions(+), 119 deletions(-)

diff --git a/src/couch/src/couch_passwords.erl b/src/couch/src/couch_passwords.erl
index 828d2f68b..2a2b396fb 100644
--- a/src/couch/src/couch_passwords.erl
+++ b/src/couch/src/couch_passwords.erl
@@ -75,126 +75,23 @@ get_unhashed_admins() ->
         config:get("admins")
     ).
 
-%% Current scheme, much stronger.
--spec pbkdf2(binary(), binary(), integer()) -> binary().
-pbkdf2(Password, Salt, Iterations) when
-    is_binary(Password),
-    is_binary(Salt),
-    is_integer(Iterations),
-    Iterations > 0
-->
-    {ok, Result} = pbkdf2(Password, Salt, Iterations, ?SHA1_OUTPUT_LENGTH),
-    Result;
-pbkdf2(Password, Salt, Iterations) when
-    is_binary(Salt),
-    is_integer(Iterations),
-    Iterations > 0
-->
-    Msg = io_lib:format("Password value of '~p' is invalid.", [Password]),
-    throw({forbidden, Msg});
-pbkdf2(Password, Salt, Iterations) when
-    is_binary(Password),
-    is_integer(Iterations),
-    Iterations > 0
-->
-    Msg = io_lib:format("Salt value of '~p' is invalid.", [Salt]),
-    throw({forbidden, Msg}).
 
--spec pbkdf2(binary(), binary(), integer(), integer()) ->
-    {ok, binary()} | {error, derived_key_too_long}.
-pbkdf2(_Password, _Salt, _Iterations, DerivedLength) when
-    DerivedLength > ?MAX_DERIVED_KEY_LENGTH
-->
-    {error, derived_key_too_long};
-pbkdf2(Password, Salt, Iterations, DerivedLength) when
-    is_binary(Password),
-    is_binary(Salt),
-    is_integer(Iterations),
-    Iterations > 0,
-    is_integer(DerivedLength)
-->
-    L = ceiling(DerivedLength / ?SHA1_OUTPUT_LENGTH),
-    <<Bin:DerivedLength/binary, _/binary>> =
-        iolist_to_binary(pbkdf2(Password, Salt, Iterations, L, 1, [])),
-    {ok, ?l2b(couch_util:to_hex(Bin))}.
-
--spec pbkdf2(binary(), binary(), integer(), integer(), integer(), iolist()) ->
-    iolist().
-pbkdf2(_Password, _Salt, _Iterations, BlockCount, BlockIndex, Acc) when
-    BlockIndex > BlockCount
-->
-    lists:reverse(Acc);
-pbkdf2(Password, Salt, Iterations, BlockCount, BlockIndex, Acc) ->
-    Block = pbkdf2(Password, Salt, Iterations, BlockIndex, 1, <<>>, <<>>),
-    pbkdf2(Password, Salt, Iterations, BlockCount, BlockIndex + 1, [Block | Acc]).
-
--spec pbkdf2(
-    binary(),
-    binary(),
-    integer(),
-    integer(),
-    integer(),
-    binary(),
-    binary()
-) -> binary().
-pbkdf2(_Password, _Salt, Iterations, _BlockIndex, Iteration, _Prev, Acc) when
-    Iteration > Iterations
-->
-    Acc;
-pbkdf2(Password, Salt, Iterations, BlockIndex, 1, _Prev, _Acc) ->
-    InitialBlock = couch_util:hmac(
-        sha,
-        Password,
-        <<Salt/binary, BlockIndex:32/integer>>
-    ),
-    pbkdf2(
-        Password,
-        Salt,
-        Iterations,
-        BlockIndex,
-        2,
-        InitialBlock,
-        InitialBlock
-    );
-pbkdf2(Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) ->
-    Next = couch_util:hmac(sha, Password, Prev),
-    pbkdf2(
-        Password,
-        Salt,
-        Iterations,
-        BlockIndex,
-        Iteration + 1,
-        Next,
-        crypto:exor(Next, Acc)
-    ).
-
-%% verify two lists for equality without short-circuits to avoid timing attacks.
--spec verify(string(), string(), integer()) -> boolean().
-verify([X | RestX], [Y | RestY], Result) ->
-    verify(RestX, RestY, (X bxor Y) bor Result);
-verify([], [], Result) ->
-    Result == 0.
+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.
 
--spec verify
-    (binary(), binary()) -> boolean();
-    (list(), list()) -> boolean().
-verify(<<X/binary>>, <<Y/binary>>) ->
-    verify(?b2l(X), ?b2l(Y));
-verify(X, Y) when is_list(X) and is_list(Y) ->
-    case length(X) == length(Y) of
-        true ->
-            verify(X, Y, 0);
-        false ->
-            false
-    end;
-verify(_X, _Y) ->
-    false.
 
--spec ceiling(number()) -> integer().
-ceiling(X) ->
-    T = erlang:trunc(X),
-    case (X - T) of
-        Neg when Neg < 0 -> T;
-        Pos when Pos > 0 -> T + 1;
-        _ -> T
+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).
diff --git a/src/couch/src/couch_pbkdf2.erl b/src/couch/src/couch_pbkdf2.erl
new file mode 100644
index 000000000..69668c97a
--- /dev/null
+++ b/src/couch/src/couch_pbkdf2.erl
@@ -0,0 +1,199 @@
+% 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.
+
+% from https://github.com/whitelynx/erlang-pbkdf2/blob/master/src/pbkdf2.erl
+
+-module(couch_pbkdf2).
+
+-export([pbkdf2/4, pbkdf2/5, compare_secure/2, to_hex/1]).
+
+
+%-type(hex_char() :: $0 .. $9 | $a .. $f).
+-type(hex_char() :: 48 .. 57 | 97 .. 102).
+-type(hex_list() :: [hex_char()]).
+
+
+-type digest_func_info() :: md4 | md5 | ripemd160 | sha | sha224 | sha256 | sha384 | sha512.
+
+-type mac_func_info() :: {hmac, digest_func_info()} | digest_func_info().
+
+
+-define(MAX_DERIVED_KEY_LENGTH, (1 bsl 32 - 1)).
+
+%======================================================================================================================
+% Public API
+
+-spec pbkdf2(MacFunc, Password, Salt, Iterations) -> {ok, Key} | {error, derived_key_too_long} when
+	MacFunc :: mac_func_info(),
+	Password :: binary(),
+	Salt :: binary(),
+	Iterations :: integer(),
+	Key :: binary().
+
+pbkdf2(MacFunc, Password, Salt, Iterations) ->
+	MacFunc1 = resolve_mac_func(MacFunc),
+	DerivedLength = byte_size(MacFunc1(<<"test key">>, <<"test data">>)),
+	pbkdf2(MacFunc1, Password, Salt, Iterations, DerivedLength, 1, []).
+
+%----------------------------------------------------------------------------------------------------------------------
+
+-spec pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength) -> {ok, Key} | {error, derived_key_too_long} when
+	MacFunc :: mac_func_info(),
+	Password :: binary(),
+	Salt :: binary(),
+	Iterations :: integer(),
+	DerivedLength :: integer(),
+	Key :: binary().
+
+pbkdf2(_MacFunc, _Password, _Salt, _Iterations, DerivedLength) when DerivedLength > ?MAX_DERIVED_KEY_LENGTH ->
+	{error, derived_key_too_long};
+
+pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength) ->
+	MacFunc1 = resolve_mac_func(MacFunc),
+	Bin = pbkdf2(MacFunc1, Password, Salt, Iterations, DerivedLength, 1, []),
+	{ok, Bin}.
+
+%======================================================================================================================
+% Internal Functions
+
+-spec pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength, BlockIndex, Acc) -> Key when
+	MacFunc :: mac_func_info(),
+	Password :: binary(),
+	Salt :: binary(),
+	Iterations :: integer(),
+	DerivedLength :: integer(),
+	BlockIndex :: integer(),
+	Acc :: iolist(),
+	Key :: binary().
+
+pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength, BlockIndex, Acc) ->
+	case iolist_size(Acc) > DerivedLength of
+		true ->
+			<<Bin:DerivedLength/binary, _/binary>> = iolist_to_binary(lists:reverse(Acc)),
+			Bin;
+		false ->
+			Block = pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, 1, <<>>, <<>>),
+			pbkdf2(MacFunc, Password, Salt, Iterations, DerivedLength, BlockIndex + 1, [Block | Acc])
+	end.
+
+%----------------------------------------------------------------------------------------------------------------------
+
+-spec pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) -> Key when
+	MacFunc :: mac_func_info(),
+	Password :: binary(),
+	Salt :: binary(),
+	Iterations :: integer(),
+	BlockIndex :: integer(),
+	Iteration :: integer(),
+	Prev :: binary(),
+	Acc :: binary(),
+	Key :: binary().
+
+pbkdf2(_MacFunc, _Password, _Salt, Iterations, _BlockIndex, Iteration, _Prev, Acc) when Iteration > Iterations ->
+	Acc;
+
+pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, 1, _Prev, _Acc) ->
+	InitialBlock = MacFunc(Password, <<Salt/binary, BlockIndex:32/integer>>),
+	pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, 2, InitialBlock, InitialBlock);
+
+pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, Iteration, Prev, Acc) ->
+	Next = MacFunc(Password, Prev),
+	pbkdf2(MacFunc, Password, Salt, Iterations, BlockIndex, Iteration + 1, Next, crypto:exor(Next, Acc)).
+
+%----------------------------------------------------------------------------------------------------------------------
+
+%-type mac_func_info() :: mac_func() | {hmac, digest_func_info()}
+%	| md4 | md5 | ripemd160 | sha | sha224 | sha256 | sha384 | sha512.
+
+resolve_mac_func({hmac, DigestFunc}) ->
+	fun(Key, Data) ->
+		%crypto:hmac(DigestFunc, Key, Data)
+		HMAC = crypto:hmac_init(DigestFunc, Key),
+		HMAC1 = crypto:hmac_update(HMAC, Data),
+		crypto:hmac_final(HMAC1)
+	end;
+
+resolve_mac_func(MacFunc) when is_function(MacFunc) ->
+	MacFunc;
+
+resolve_mac_func(md4) -> resolve_mac_func({hmac, md4});
+resolve_mac_func(md5) -> resolve_mac_func({hmac, md5});
+resolve_mac_func(ripemd160) -> resolve_mac_func({hmac, ripemd160});
+resolve_mac_func(sha) -> resolve_mac_func({hmac, sha});
+resolve_mac_func(sha224) -> resolve_mac_func({hmac, sha224});
+resolve_mac_func(sha256) -> resolve_mac_func({hmac, sha256});
+resolve_mac_func(sha384) -> resolve_mac_func({hmac, sha384});
+resolve_mac_func(sha512) -> resolve_mac_func({hmac, sha512}).
+
+%----------------------------------------------------------------------------------------------------------------------
+
+%% Compare two strings or binaries for equality without short-circuits to avoid timing attacks.
+
+-spec compare_secure(First, Second) -> boolean() when
+	First :: binary() | string(),
+	Second :: binary() | string().
+
+compare_secure(<<X/binary>>, <<Y/binary>>) ->
+	compare_secure(binary_to_list(X), binary_to_list(Y));
+
+compare_secure(X, Y) when is_list(X) and is_list(Y) ->
+	case length(X) == length(Y) of
+		true ->
+			compare_secure(X, Y, 0);
+		false ->
+			false
+	end;
+
+compare_secure(_X, _Y) -> false.
+
+-spec compare_secure(First, Second, Accum) -> boolean() when
+	First :: string(),
+	Second :: string(),
+	Accum :: integer().
+
+compare_secure([X|RestX], [Y|RestY], Result) ->
+	compare_secure(RestX, RestY, (X bxor Y) bor Result);
+
+compare_secure([], [], Result) ->
+	Result == 0.
+
+%----------------------------------------------------------------------------------------------------------------------
+
+-spec to_hex(Data) -> HexData when
+	Data :: binary() | list(),
+	HexData :: binary() | hex_list().
+
+to_hex(<<>>) ->
+	<<>>;
+
+to_hex(<<Char:8/integer, Rest/binary>>) ->
+	CharHex1 = to_hex_digit(Char div 16),
+	CharHex2 = to_hex_digit(Char rem 16),
+	RestHex = to_hex(Rest),
+	<<CharHex1, CharHex2, RestHex/binary>>;
+
+to_hex([]) ->
+	[];
+
+to_hex([Char | Rest]) ->
+	[to_hex_digit(Char div 16), to_hex_digit(Char rem 16) | to_hex(Rest)].
+
+%----------------------------------------------------------------------------------------------------------------------
+
+-spec to_hex_digit(Nyble) -> hex_char() when
+	Nyble :: 0 .. 15.
+
+to_hex_digit(N) when N < 10 ->
+	$0 + N;
+
+to_hex_digit(N) ->
+	$a + N - 10.