summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Avdey <eiri@eiri.ca>2020-03-05 15:18:25 -0400
committerEric Avdey <eiri@eiri.ca>2020-04-07 02:33:18 -0300
commite50f63eb7d337eb6eda6d54f45883976240fbcfe (patch)
tree226c0ea4b2f9a6694315b13bf30e11102cb21d8e
parente2236e9047536626f16ce9a35246d56e5f40c1c3 (diff)
downloadcouchdb-e50f63eb7d337eb6eda6d54f45883976240fbcfe.tar.gz
Store wrapped KEK in db config
-rw-r--r--src/fabric/src/fabric2_encryption.erl63
-rw-r--r--src/fabric/src/fabric2_fdb.erl19
2 files changed, 60 insertions, 22 deletions
diff --git a/src/fabric/src/fabric2_encryption.erl b/src/fabric/src/fabric2_encryption.erl
index 38dec1cf0..d9791f898 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 ca5b5c83c..356ea94a8 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -196,11 +196,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)},
@@ -227,6 +229,7 @@ create(#{} = Db0, Options) ->
revs_limit => 1000,
security_doc => {[]},
+ wrapped_kek => WrappedKEK,
user_ctx => UserCtx,
validate_doc_update_funs => [],
@@ -267,6 +270,7 @@ open(#{} = Db0, Options) ->
uuid => <<>>,
revs_limit => 1000,
security_doc => {[]},
+ wrapped_kek => <<>>,
user_ctx => UserCtx,
@@ -467,7 +471,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)).
@@ -1354,7 +1359,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{
@@ -1369,7 +1375,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),
@@ -1387,14 +1394,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) ->