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),
