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 cd40630a9a676f5e11d9f9e9265a72b30086fe44
Author: Eric Avdey <[email protected]>
AuthorDate: Wed Mar 4 16:04:46 2020 -0400

    Add basic data encryption
---
 src/fabric/src/fabric2_encryption.erl | 128 ++++++++++++++++++++++++++++++++++
 src/fabric/src/fabric2_fdb.erl        |  20 +++++-
 src/fabric/src/fabric2_sup.erl        |   8 +++
 3 files changed, 153 insertions(+), 3 deletions(-)

diff --git a/src/fabric/src/fabric2_encryption.erl 
b/src/fabric/src/fabric2_encryption.erl
new file mode 100644
index 0000000..5d8d389
--- /dev/null
+++ b/src/fabric/src/fabric2_encryption.erl
@@ -0,0 +1,128 @@
+% 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).
+-behaviour(gen_server).
+-vsn(1).
+
+
+-export([
+    start_link/0,
+    encode/4,
+    decode/4
+]).
+
+
+-export([
+    init/1,
+    terminate/2,
+    handle_call/3,
+    handle_cast/2,
+    handle_info/2,
+    code_change/3
+]).
+
+
+-define(INIT_TIMEOUT, 60000).
+-define(LABEL, "couchdb-aes256-gcm-encryption-key").
+
+
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+
+encode(DbName, DocId, DocRev, DocBody)
+    when is_binary(DbName),
+         is_binary(DocId),
+         is_binary(DocRev),
+         is_binary(DocBody) ->
+    gen_server:call(?MODULE, {encode, DbName, DocId, DocRev, DocBody}).
+
+
+decode(DbName, DocId, DocRev, DocBody)
+    when is_binary(DbName),
+         is_binary(DocId),
+         is_binary(DocRev),
+         is_binary(DocBody) ->
+    gen_server:call(?MODULE, {decode, DbName, DocId, DocRev, DocBody}).
+
+
+
+init(_) ->
+    process_flag(sensitive, true),
+    process_flag(trap_exit, true),
+
+    case init_st() of
+        {ok, St} ->
+            proc_lib:init_ack({ok, self()}),
+            gen_server:enter_loop(?MODULE, [], St, ?INIT_TIMEOUT);
+        Error ->
+            proc_lib:init_ack(Error)
+    end.
+
+
+terminate(_, _St) ->
+    ok.
+
+
+handle_call({encode, DbName, DocId, DocRev, DocBody}, _From, St) ->
+    #{iid := InstanceId} = St,
+    {ok, AAD} = get_aad(InstanceId, DbName),
+    {ok, DEK} = get_dek(DbName, DocId, DocRev),
+    {CipherText, CipherTag} = crypto:crypto_one_time_aead(
+        aes_256_gcm, DEK, <<0:96>>, DocBody, AAD, 16, true),
+    Encoded = <<CipherTag/binary, CipherText/binary>>,
+    {reply, {ok, Encoded}, St};
+
+handle_call({decode, DbName, DocId, DocRev, Encoded}, _From, St) ->
+    #{iid := InstanceId} = St,
+    {ok, AAD} = get_aad(InstanceId, DbName),
+    {ok, DEK} = get_dek(DbName, DocId, DocRev),
+    <<CipherTag:16/binary, CipherText/binary>> = Encoded,
+    DocBody = crypto:crypto_one_time_aead(
+        aes_256_gcm, DEK, <<0:96>>, CipherText, AAD, CipherTag, false),
+    {reply, {ok, DocBody}, St}.
+
+
+handle_cast(Msg, St) ->
+    {stop, {bad_cast, Msg}, St}.
+
+
+handle_info(timeout, St) ->
+    {stop, normal, St}.
+
+
+code_change(_OldVsn, St, _Extra) ->
+    {ok, St}.
+
+
+
+init_st() ->
+    FdbDirs = fabric2_server:fdb_directory(),
+    {ok, #{iid => iolist_to_binary(FdbDirs)}}.
+
+
+get_aad(InstanceId, DbName) when is_binary(InstanceId), is_binary(DbName) ->
+    {ok, <<InstanceId/binary, 0:8, DbName/binary>>}.
+
+
+get_dek(DbName, DocId, DocRev) ->
+    {ok, KEK} = get_kek(DbName),
+    Context = <<DocId/binary, 0:8, DocRev/binary>>,
+    PlainText = <<1:16, ?LABEL, 0:8, Context/binary, 256:16>>,
+    <<_:256>> = DEK = crypto:mac(hmac, sha256, KEK, PlainText),
+    {ok, DEK}.
+
+
+get_kek(DbName) ->
+    KEK = crypto:hash(sha256, DbName),
+    {ok, KEK}.
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index c34b33c..5172b90 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -1289,6 +1289,7 @@ fdb_to_revinfo(Key, {1, RPath, AttHash}) ->
 
 doc_to_fdb(Db, #doc{} = Doc) ->
     #{
+        name := DbName,
         db_prefix := DbPrefix
     } = Db,
 
@@ -1302,7 +1303,11 @@ doc_to_fdb(Db, #doc{} = Doc) ->
 
     DiskAtts = lists:map(fun couch_att:to_disk_term/1, Atts),
 
-    Value = term_to_binary({Body, DiskAtts, Deleted}, [{minor_version, 1}]),
+    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),
+
+    Value = term_to_binary({Encoded, DiskAtts, Deleted}, [{minor_version, 1}]),
     Chunks = chunkify_binary(Value),
 
     {Rows, _} = lists:mapfoldl(fun(Chunk, ChunkId) ->
@@ -1316,9 +1321,18 @@ doc_to_fdb(Db, #doc{} = Doc) ->
 fdb_to_doc(_Db, _DocId, _Pos, _Path, []) ->
     {not_found, missing};
 
-fdb_to_doc(Db, DocId, Pos, Path, BinRows) when is_list(BinRows) ->
+fdb_to_doc(Db, DocId, Pos, [Rev | _] = Path, BinRows) when is_list(BinRows) ->
+    #{
+        name := DbName
+    } = Db,
+
     Bin = iolist_to_binary(BinRows),
-    {Body, DiskAtts, Deleted} = binary_to_term(Bin, [safe]),
+    {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),
+    Body = binary_to_term(BinBody, [safe]),
+
     Atts = lists:map(fun(Att) ->
         couch_att:from_disk_term(Db, DocId, Att)
     end, DiskAtts),
diff --git a/src/fabric/src/fabric2_sup.erl b/src/fabric/src/fabric2_sup.erl
index 2510b13..f57443b 100644
--- a/src/fabric/src/fabric2_sup.erl
+++ b/src/fabric/src/fabric2_sup.erl
@@ -47,6 +47,14 @@ init([]) ->
             5000,
             worker,
             [fabric2_server]
+        },
+        {
+            fabric2_encryption,
+            {fabric2_encryption, start_link, []},
+            permanent,
+            5000,
+            worker,
+            [fabric2_encryption]
         }
     ],
     ChildrenWithEpi = couch_epi:register_service(fabric2_epi, Children),

Reply via email to