diff options
author | Eric Avdey <eiri@eiri.ca> | 2020-03-19 20:27:39 -0300 |
---|---|---|
committer | Eric Avdey <eiri@eiri.ca> | 2020-04-07 02:35:56 -0300 |
commit | 061d624871f05212a32dc4c9e3257b51c10f133b (patch) | |
tree | 6e3d110921dea660e26152d2125d74dc495375e0 | |
parent | f6c21be95e00fc2bbac25e4adf01cba1a5fddac2 (diff) | |
download | couchdb-061d624871f05212a32dc4c9e3257b51c10f133b.tar.gz |
Implement key management as an epi plugin
-rw-r--r-- | rel/overlay/etc/default.ini | 15 | ||||
-rw-r--r-- | src/fabric/src/fabric2_encryption.erl | 31 | ||||
-rw-r--r-- | src/fabric/src/fabric2_encryption_plugin.erl | 48 | ||||
-rw-r--r-- | src/fabric/src/fabric2_encryption_provider.erl | 60 | ||||
-rw-r--r-- | src/fabric/src/fabric2_epi.erl | 7 |
5 files changed, 132 insertions, 29 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index a9b4dbbd4..c9cce5459 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -707,3 +707,18 @@ opts = #{budget => 100, target => 2500, window => 60000, sensitivity => 1000} [encryption] enabled = false +; +; To generate master key and initialization vector run the following command +; ("secret" is an example passphrase here, use something else) +; +; `openssl enc -aes-256-ctr -k secret -P -md sha1` +; output: +; salt=FC0D0243C5126FB5 +; key=9B43A7711CDDE41FE065FC03A14BBD6A177CBD6A4B474A05DEC9798C79B98045 +; iv =5B3C6478BBC698AAA8CA5BA51DB8FF95 +; Put key in "key.dat" file excluding "key" characters and a carriage return +; Put iv in "iv.dat" file excluding "iv" characters and a carriage return +; +; Keep both files as read-only and owned by couch process. +; key_file = /var/secured/mount/location/key.dat +; iv_file = /var/secured/mount/location/iv.dat diff --git a/src/fabric/src/fabric2_encryption.erl b/src/fabric/src/fabric2_encryption.erl index a4e40bd4b..c440c3d59 100644 --- a/src/fabric/src/fabric2_encryption.erl +++ b/src/fabric/src/fabric2_encryption.erl @@ -42,10 +42,6 @@ -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, [], []). @@ -96,8 +92,8 @@ terminate(_, _St) -> ok. -handle_call({get_wrapped_kek, _DbName}, _From, #{cache := Cache} = St) -> - {ok, KEK, WrappedKEK} = get_kek(), +handle_call({get_wrapped_kek, DbName}, _From, #{cache := Cache} = St) -> + {ok, KEK, WrappedKEK} = fabric2_encryption_plugin:get_wrapped_kek(DbName), true = ets:insert(Cache, {WrappedKEK, KEK}), {reply, {ok, WrappedKEK}, St}; @@ -219,36 +215,17 @@ unwrap_kek(Cache, WrappedKEK) -> [{WrappedKEK, KEK}] -> {ok, KEK}; [] -> - {ok, KEK, WrappedKEK} = unwrap_kek(WrappedKEK), + {ok, KEK, WrappedKEK} = fabric2_encryption_plugin: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), - Enc = crypto:stream_init(aes_ctr, ?MEK, ?IV), - {_, WrappedKEK} = crypto:stream_encrypt(Enc, KEK), - {ok, KEK, WrappedKEK}. - - -%% this mocks a call to an expernal system to unwrap KEK -unwrap_kek(WrappedKEK) -> - Enc = crypto:stream_init(aes_ctr, ?MEK, ?IV), - {_, KEK} = crypto:stream_decrypt(Enc, WrappedKEK), - {ok, KEK, WrappedKEK}. - - -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -get_unwrap_kek_test() -> - {ok, KEK, WrappedKEK} = get_kek(), - ?assertNotEqual(KEK, WrappedKEK), - ?assertEqual({ok, KEK, WrappedKEK}, unwrap_kek(WrappedKEK)). - get_dek_test() -> KEK = crypto:strong_rand_bytes(32), {ok, DEK} = get_dek(KEK, <<"0001">>, 1), diff --git a/src/fabric/src/fabric2_encryption_plugin.erl b/src/fabric/src/fabric2_encryption_plugin.erl new file mode 100644 index 000000000..1c8802264 --- /dev/null +++ b/src/fabric/src/fabric2_encryption_plugin.erl @@ -0,0 +1,48 @@ +% 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_plugin). + +-export([ + get_wrapped_kek/1, + unwrap_kek/1 +]). + + +-define(SERVICE_ID, fabric2_encryption). + + +-spec get_wrapped_kek(DbName :: binary()) -> + {ok, KEK :: binary(), WrappedKEK :: binary()} | {error, Error :: term()}. +get_wrapped_kek(DbName) -> + Default = boom, + maybe_handle(get_kek, [DbName], Default). + + +-spec unwrap_kek(WrappedKEK :: binary()) -> + {ok, KEK :: binary(), WrappedKEK :: binary()} | {error, Error :: term()}. +unwrap_kek(WrappedKEK) -> + Default = boom, + maybe_handle(unwrap_kek, [WrappedKEK], Default). + + + +maybe_handle(Func, Args, Default) -> + Handle = couch_epi:get_handle(?SERVICE_ID), + case couch_epi:decide(Handle, ?SERVICE_ID, Func, Args, []) of + no_decision when is_function(Default) -> + apply(Default, Args); + no_decision -> + Default; + {decided, Result} -> + Result + end. diff --git a/src/fabric/src/fabric2_encryption_provider.erl b/src/fabric/src/fabric2_encryption_provider.erl new file mode 100644 index 000000000..505de9dbe --- /dev/null +++ b/src/fabric/src/fabric2_encryption_provider.erl @@ -0,0 +1,60 @@ +% 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_provider). + +-export([ + get_kek/1, + unwrap_kek/1 +]). + + +get_kek(_DbName) -> + case get_mek_iv() of + {ok, MEK, IV} -> + KEK = crypto:strong_rand_bytes(32), + Enc = crypto:stream_init(aes_ctr, MEK, IV), + {_, WrappedKEK} = crypto:stream_encrypt(Enc, KEK), + {decided, {ok, KEK, WrappedKEK}}; + {error, Error} -> + {decided, {error, Error}} + end. + + +unwrap_kek(WrappedKEK) -> + case get_mek_iv() of + {ok, MEK, IV} -> + Enc = crypto:stream_init(aes_ctr, MEK, IV), + {_, KEK} = crypto:stream_decrypt(Enc, WrappedKEK), + {decided, {ok, KEK, WrappedKEK}}; + {error, Error} -> + {decided, {error, Error}} + end. + + + +get_mek_iv() -> + KeyFileReturn = file:read_file(config:get("encryption", "key_file")), + IVFileReturn = file:read_file(config:get("encryption", "iv_file")), + case {KeyFileReturn, IVFileReturn} of + {{ok, MEK}, {ok, IV}} + when bit_size(MEK) == 512, bit_size(IV) == 256 -> + {ok, <<<<I:4>> || <<I>> <= MEK>>, <<<<I:4>> || <<I>> <= IV>>}; + {{ok, _}, _} -> + {error, invalid_key_length}; + {{error, Error}, _} -> + {error, {invalid_key_file, Error}}; + {_, {ok, _}} -> + {error, invalid_iv_length}; + {_, {error, Error}} -> + {error, {invalid_iv_file, Error}} + end. diff --git a/src/fabric/src/fabric2_epi.erl b/src/fabric/src/fabric2_epi.erl index f73eeb0d2..9caf2da6a 100644 --- a/src/fabric/src/fabric2_epi.erl +++ b/src/fabric/src/fabric2_epi.erl @@ -28,11 +28,14 @@ app() -> fabric. providers() -> - []. + [ + {fabric2_encryption, fabric2_encryption_provider} + ]. services() -> [ - {fabric2_db, fabric2_db_plugin} + {fabric2_db, fabric2_db_plugin}, + {fabric2_encryption, fabric2_encryption_plugin} ]. data_subscriptions() -> |