diff options
author | Eric Avdey <eiri@eiri.ca> | 2020-03-05 15:18:25 -0400 |
---|---|---|
committer | Eric Avdey <eiri@eiri.ca> | 2020-04-07 02:33:18 -0300 |
commit | e50f63eb7d337eb6eda6d54f45883976240fbcfe (patch) | |
tree | 226c0ea4b2f9a6694315b13bf30e11102cb21d8e | |
parent | e2236e9047536626f16ce9a35246d56e5f40c1c3 (diff) | |
download | couchdb-e50f63eb7d337eb6eda6d54f45883976240fbcfe.tar.gz |
Store wrapped KEK in db config
-rw-r--r-- | src/fabric/src/fabric2_encryption.erl | 63 | ||||
-rw-r--r-- | 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 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) -> |