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

eiri pushed a commit to branch prototype/fdb-encryption
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit db79fdf98ac68f3584078cd2010a607298e253c4
Author: Eric Avdey <[email protected]>
AuthorDate: Thu Mar 19 20:27:39 2020 -0300

    Implement key management as an epi plugin
---
 rel/overlay/etc/default.ini                    | 15 +++++++
 src/fabric/src/fabric2_encryption.erl          | 31 ++-----------
 src/fabric/src/fabric2_encryption_plugin.erl   | 48 +++++++++++++++++++++
 src/fabric/src/fabric2_encryption_provider.erl | 60 ++++++++++++++++++++++++++
 src/fabric/src/fabric2_epi.erl                 |  7 ++-
 5 files changed, 132 insertions(+), 29 deletions(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index fef0867..9215b8f 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -702,3 +702,18 @@ compaction = false
 
 [encryption]
 enabled = false
+;
+; To generate master key and initialization vector run the following command
+; ("secret" is an example passphrase here, use something else)
+;
+; `openssl enc -aes-256-ctr -k secret -P -md sha1`
+; output:
+;   salt=FC0D0243C5126FB5
+;   key=9B43A7711CDDE41FE065FC03A14BBD6A177CBD6A4B474A05DEC9798C79B98045
+;   iv =5B3C6478BBC698AAA8CA5BA51DB8FF95
+; Put key in "key.dat" file excluding "key" characters and a carriage return
+; Put iv in "iv.dat" file excluding "iv" characters and a carriage return
+;
+; Keep both files as read-only and owned by couch process.
+; key_file = /var/secured/mount/location/key.dat
+; iv_file = /var/secured/mount/location/iv.dat
diff --git a/src/fabric/src/fabric2_encryption.erl 
b/src/fabric/src/fabric2_encryption.erl
index 1d6ad3e..a1ce7bd 100644
--- a/src/fabric/src/fabric2_encryption.erl
+++ b/src/fabric/src/fabric2_encryption.erl
@@ -42,10 +42,6 @@
 -define(INIT_TIMEOUT, 60000).
 -define(LABEL, "couchdb-aes256-gcm-encryption-key").
 
-%% Master encryption key. Obviously never known to this module in real life
--define(MEK, 
<<246,83,186,200,242,183,138,51,2,193,181,37,156,130,190,209,181,69,206,157,69,154,112,158,141,158,196,132,81,253,187,67>>).
--define(IV, <<0:128>>).
-
 
 start_link() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@@ -96,8 +92,8 @@ terminate(_, _St) ->
     ok.
 
 
-handle_call({get_wrapped_kek, _DbName}, _From, #{cache := Cache} = St) ->
-    {ok, KEK, WrappedKEK} = get_kek(),
+handle_call({get_wrapped_kek, DbName}, _From, #{cache := Cache} = St) ->
+    {ok, KEK, WrappedKEK} = fabric2_encryption_plugin:get_wrapped_kek(DbName),
     true = ets:insert(Cache, {WrappedKEK, KEK}),
     {reply, {ok, WrappedKEK}, St};
 
@@ -219,35 +215,16 @@ unwrap_kek(Cache, WrappedKEK) ->
         [{WrappedKEK, KEK}] ->
             {ok, KEK};
         [] ->
-            {ok, KEK, WrappedKEK} = unwrap_kek(WrappedKEK),
+            {ok, KEK, WrappedKEK} = fabric2_encryption_plugin:unwrap_kek(
+                WrappedKEK),
             true = ets:insert(Cache, {WrappedKEK, KEK}),
             {ok, KEK}
     end.
 
 
-%% this mocks a call to an expernal system to aquire KEK
-get_kek() ->
-    KEK = crypto:strong_rand_bytes(32),
-    Enc = crypto:stream_init(aes_ctr, ?MEK, ?IV),
-    {_, WrappedKEK} = crypto:stream_encrypt(Enc, KEK),
-    {ok, KEK, WrappedKEK}.
-
-
-%% this mocks a call to an expernal system to unwrap KEK
-unwrap_kek(WrappedKEK) ->
-    Enc = crypto:stream_init(aes_ctr, ?MEK, ?IV),
-    {_, KEK} = crypto:stream_decrypt(Enc, WrappedKEK),
-    {ok, KEK, WrappedKEK}.
-
-
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
 
-get_unwrap_kek_test() ->
-    {ok, KEK, WrappedKEK} = get_kek(),
-    ?assertNotEqual(KEK, WrappedKEK),
-    ?assertEqual({ok, KEK, WrappedKEK}, unwrap_kek(WrappedKEK)).
-
 get_dek_test() ->
     KEK = crypto:strong_rand_bytes(32),
     {ok, DEK} = get_dek(KEK, <<"0001">>, 1),
diff --git a/src/fabric/src/fabric2_encryption_plugin.erl 
b/src/fabric/src/fabric2_encryption_plugin.erl
new file mode 100644
index 0000000..1c88022
--- /dev/null
+++ b/src/fabric/src/fabric2_encryption_plugin.erl
@@ -0,0 +1,48 @@
+% 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(fabric2_encryption_plugin).
+
+-export([
+    get_wrapped_kek/1,
+    unwrap_kek/1
+]).
+
+
+-define(SERVICE_ID, fabric2_encryption).
+
+
+-spec get_wrapped_kek(DbName :: binary()) ->
+    {ok, KEK :: binary(), WrappedKEK :: binary()} | {error, Error :: term()}.
+get_wrapped_kek(DbName) ->
+    Default = boom,
+    maybe_handle(get_kek, [DbName], Default).
+
+
+-spec unwrap_kek(WrappedKEK :: binary()) ->
+    {ok, KEK :: binary(), WrappedKEK :: binary()} | {error, Error :: term()}.
+unwrap_kek(WrappedKEK) ->
+    Default = boom,
+    maybe_handle(unwrap_kek, [WrappedKEK], Default).
+
+
+
+maybe_handle(Func, Args, Default) ->
+    Handle = couch_epi:get_handle(?SERVICE_ID),
+    case couch_epi:decide(Handle, ?SERVICE_ID, Func, Args, []) of
+        no_decision when is_function(Default) ->
+            apply(Default, Args);
+        no_decision ->
+            Default;
+        {decided, Result} ->
+            Result
+    end.
diff --git a/src/fabric/src/fabric2_encryption_provider.erl 
b/src/fabric/src/fabric2_encryption_provider.erl
new file mode 100644
index 0000000..505de9d
--- /dev/null
+++ b/src/fabric/src/fabric2_encryption_provider.erl
@@ -0,0 +1,60 @@
+% 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(fabric2_encryption_provider).
+
+-export([
+    get_kek/1,
+    unwrap_kek/1
+]).
+
+
+get_kek(_DbName) ->
+    case get_mek_iv() of
+        {ok, MEK, IV} ->
+            KEK = crypto:strong_rand_bytes(32),
+            Enc = crypto:stream_init(aes_ctr, MEK, IV),
+            {_, WrappedKEK} = crypto:stream_encrypt(Enc, KEK),
+            {decided, {ok, KEK, WrappedKEK}};
+        {error, Error} ->
+            {decided, {error, Error}}
+    end.
+
+
+unwrap_kek(WrappedKEK) ->
+    case get_mek_iv() of
+        {ok, MEK, IV} ->
+            Enc = crypto:stream_init(aes_ctr, MEK, IV),
+            {_, KEK} = crypto:stream_decrypt(Enc, WrappedKEK),
+            {decided, {ok, KEK, WrappedKEK}};
+        {error, Error} ->
+            {decided, {error, Error}}
+    end.
+
+
+
+get_mek_iv() ->
+    KeyFileReturn = file:read_file(config:get("encryption", "key_file")),
+    IVFileReturn = file:read_file(config:get("encryption", "iv_file")),
+    case {KeyFileReturn, IVFileReturn} of
+        {{ok, MEK}, {ok, IV}}
+                when bit_size(MEK) == 512, bit_size(IV) == 256 ->
+            {ok, <<<<I:4>> || <<I>> <= MEK>>, <<<<I:4>> || <<I>> <= IV>>};
+        {{ok, _}, _} ->
+            {error, invalid_key_length};
+        {{error, Error}, _} ->
+            {error, {invalid_key_file, Error}};
+        {_, {ok, _}} ->
+            {error, invalid_iv_length};
+        {_, {error, Error}} ->
+            {error, {invalid_iv_file, Error}}
+    end.
diff --git a/src/fabric/src/fabric2_epi.erl b/src/fabric/src/fabric2_epi.erl
index f73eeb0..9caf2da 100644
--- a/src/fabric/src/fabric2_epi.erl
+++ b/src/fabric/src/fabric2_epi.erl
@@ -28,11 +28,14 @@ app() ->
     fabric.
 
 providers() ->
-    [].
+    [
+        {fabric2_encryption, fabric2_encryption_provider}
+    ].
 
 services() ->
     [
-        {fabric2_db, fabric2_db_plugin}
+        {fabric2_db, fabric2_db_plugin},
+        {fabric2_encryption, fabric2_encryption_plugin}
     ].
 
 data_subscriptions() ->

Reply via email to