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 75e37bc1c5d0e34322879399289bd56a3f13f316
Author: Eric Avdey <[email protected]>
AuthorDate: Thu Mar 5 15:18:25 2020 -0400

    Store wrapped KEK in db config
---
 src/fabric/src/fabric2_encryption.erl | 63 +++++++++++++++++++++++++----------
 src/fabric/src/fabric2_fdb.erl        | 19 ++++++++---
 2 files changed, 60 insertions(+), 22 deletions(-)

diff --git a/src/fabric/src/fabric2_encryption.erl 
b/src/fabric/src/fabric2_encryption.erl
index 38dec1c..d9791f8 100644
--- a/src/fabric/src/fabric2_encryption.erl
+++ b/src/fabric/src/fabric2_encryption.erl
@@ -17,8 +17,9 @@
 
 -export([
     start_link/0,
-    encode/4,
-    decode/4
+    get_wrapped_kek/1,
+    encode/5,
+    decode/5
 ]).
 
 
@@ -41,25 +42,35 @@
 -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, [], []).
 
 
-encode(DbName, DocId, DocRev, DocBody)
-    when is_binary(DbName),
+get_wrapped_kek(DbName) when is_binary(DbName) ->
+    gen_server:call(?MODULE, {get_wrapped_kek, DbName}).
+
+
+encode(WrappedKEK, DbName, DocId, DocRev, DocBody)
+    when is_binary(WrappedKEK),
+         is_binary(DbName),
          is_binary(DocId),
          is_binary(DocRev),
          is_binary(DocBody) ->
-    gen_server:call(?MODULE, {encode, DbName, DocId, DocRev, DocBody}).
+    gen_server:call(?MODULE, {encode, WrappedKEK, DbName, DocId, DocRev, 
DocBody}).
 
 
-decode(DbName, DocId, DocRev, DocBody)
-    when is_binary(DbName),
+decode(WrappedKEK, DbName, DocId, DocRev, DocBody)
+    when is_binary(WrappedKEK),
+         is_binary(DbName),
          is_binary(DocId),
          is_binary(DocRev),
          is_binary(DocBody) ->
-    gen_server:call(?MODULE, {decode, DbName, DocId, DocRev, DocBody}).
+    gen_server:call(?MODULE, {decode, WrappedKEK, DbName, DocId, DocRev, 
DocBody}).
 
 
 
@@ -80,14 +91,19 @@ terminate(_, _St) ->
     ok.
 
 
-handle_call({encode, DbName, DocId, DocRev, DocBody}, From, St) ->
+handle_call({get_wrapped_kek, _DbName}, _From, #{cache := Cache} = St) ->
+    {ok, KEK, WrappedKEK} = get_kek(),
+    true = ets:insert(Cache, {WrappedKEK, KEK}),
+    {reply, {ok, WrappedKEK}, St};
+
+handle_call({encode, WrappedKEK, DbName, DocId, DocRev, DocBody}, From, St) ->
     #{
         iid := InstanceId,
         cache := Cache,
         waiters := Waiters
     } = St,
 
-    {ok, KEK} = get_kek(Cache, DbName),
+    {ok, KEK} = unwrap_kek(Cache, WrappedKEK),
     {Pid, _Ref} = erlang:spawn_monitor(?MODULE,
         do_encode, [KEK, InstanceId, DbName, DocId, DocRev, DocBody]),
 
@@ -96,14 +112,14 @@ handle_call({encode, DbName, DocId, DocRev, DocBody}, 
From, St) ->
     },
     {noreply, NewSt};
 
-handle_call({decode, DbName, DocId, DocRev, Encoded}, From, St) ->
+handle_call({decode, WrappedKEK, DbName, DocId, DocRev, Encoded}, From, St) ->
     #{
         iid := InstanceId,
         cache := Cache,
         waiters := Waiters
     } = St,
 
-    {ok, KEK} = get_kek(Cache, DbName),
+    {ok, KEK} = unwrap_kek(Cache, WrappedKEK),
     {Pid, _Ref} = erlang:spawn_monitor(?MODULE,
         do_decode, [KEK, InstanceId, DbName, DocId, DocRev, Encoded]),
 
@@ -191,12 +207,25 @@ get_dek(KEK, DocId, DocRev) when bit_size(KEK) == 256 ->
     {ok, DEK}.
 
 
-get_kek(Cache, DbName) ->
-    case ets:lookup(Cache, DbName) of
-        [{DbName, KEK}] ->
+unwrap_kek(Cache, WrappedKEK) ->
+    case ets:lookup(Cache, WrappedKEK) of
+        [{WrappedKEK, KEK}] ->
             {ok, KEK};
         [] ->
-            KEK = crypto:hash(sha256, DbName),
-            true = ets:insert(Cache, {DbName, KEK}),
+            {ok, KEK, WrappedKEK} = 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),
+    WrappedKEK = crypto:crypto_one_time(aes_256_ctr, ?MEK, ?IV, KEK, true),
+    {ok, KEK, WrappedKEK}.
+
+
+%% this mocks a call to an expernal system to unwrap KEK
+unwrap_kek(WrappedKEK) ->
+    KEK = crypto:crypto_one_time(aes_256_ctr, ?MEK, ?IV, WrappedKEK, true),
+    {ok, KEK, WrappedKEK}.
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index 5172b90..14108ad 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -186,11 +186,13 @@ create(#{} = Db0, Options) ->
     erlfdb:set(Tx, DbVersionKey, DbVersion),
 
     UUID = fabric2_util:uuid(),
+    {ok, WrappedKEK} = fabric2_encryption:get_wrapped_kek(DbName),
 
     Defaults = [
         {?DB_CONFIG, <<"uuid">>, UUID},
         {?DB_CONFIG, <<"revs_limit">>, ?uint2bin(1000)},
         {?DB_CONFIG, <<"security_doc">>, <<"{}">>},
+        {?DB_CONFIG, <<"wrapped_kek">>, WrappedKEK},
         {?DB_STATS, <<"doc_count">>, ?uint2bin(0)},
         {?DB_STATS, <<"doc_del_count">>, ?uint2bin(0)},
         {?DB_STATS, <<"doc_design_count">>, ?uint2bin(0)},
@@ -217,6 +219,7 @@ create(#{} = Db0, Options) ->
 
         revs_limit => 1000,
         security_doc => {[]},
+        wrapped_kek => WrappedKEK,
         user_ctx => UserCtx,
 
         validate_doc_update_funs => [],
@@ -254,6 +257,7 @@ open(#{} = Db0, Options) ->
         uuid => <<>>,
         revs_limit => 1000,
         security_doc => {[]},
+        wrapped_kek => <<>>,
 
         user_ctx => UserCtx,
 
@@ -424,7 +428,8 @@ load_config(#{} = Db) ->
         case Key of
             <<"uuid">> ->  DbAcc#{uuid := V};
             <<"revs_limit">> -> DbAcc#{revs_limit := ?bin2uint(V)};
-            <<"security_doc">> -> DbAcc#{security_doc := ?JSON_DECODE(V)}
+            <<"security_doc">> -> DbAcc#{security_doc := ?JSON_DECODE(V)};
+            <<"wrapped_kek">> -> DbAcc#{wrapped_kek := V}
         end
     end, Db, erlfdb:wait(Future)).
 
@@ -1290,7 +1295,8 @@ fdb_to_revinfo(Key, {1, RPath, AttHash}) ->
 doc_to_fdb(Db, #doc{} = Doc) ->
     #{
         name := DbName,
-        db_prefix := DbPrefix
+        db_prefix := DbPrefix,
+        wrapped_kek := WrappedKEK
     } = Db,
 
     #doc{
@@ -1305,7 +1311,8 @@ doc_to_fdb(Db, #doc{} = Doc) ->
 
     BinRev = couch_doc:rev_to_str({Start, Rev}),
     BinBody = term_to_binary(Body, [{compressed, 0}, {minor_version, 1}]),
-    {ok, Encoded} = fabric2_encryption:encode(DbName, Id, BinRev, BinBody),
+    {ok, Encoded} = fabric2_encryption:encode(
+        WrappedKEK, DbName, Id, BinRev, BinBody),
 
     Value = term_to_binary({Encoded, DiskAtts, Deleted}, [{minor_version, 1}]),
     Chunks = chunkify_binary(Value),
@@ -1323,14 +1330,16 @@ fdb_to_doc(_Db, _DocId, _Pos, _Path, []) ->
 
 fdb_to_doc(Db, DocId, Pos, [Rev | _] = Path, BinRows) when is_list(BinRows) ->
     #{
-        name := DbName
+        name := DbName,
+        wrapped_kek := WrappedKEK
     } = Db,
 
     Bin = iolist_to_binary(BinRows),
     {Encoded, DiskAtts, Deleted} = binary_to_term(Bin, [safe]),
 
     BinRev = couch_doc:rev_to_str({Pos, Rev}),
-    {ok, BinBody} = fabric2_encryption:decode(DbName, DocId, BinRev, Encoded),
+    {ok, BinBody} = fabric2_encryption:decode(
+        WrappedKEK, DbName, DocId, BinRev, Encoded),
     Body = binary_to_term(BinBody, [safe]),
 
     Atts = lists:map(fun(Att) ->

Reply via email to