summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Avdey <eiri@eiri.ca>2020-03-19 20:27:39 -0300
committerEric Avdey <eiri@eiri.ca>2020-04-07 02:35:56 -0300
commit061d624871f05212a32dc4c9e3257b51c10f133b (patch)
tree6e3d110921dea660e26152d2125d74dc495375e0
parentf6c21be95e00fc2bbac25e4adf01cba1a5fddac2 (diff)
downloadcouchdb-061d624871f05212a32dc4c9e3257b51c10f133b.tar.gz
Implement key management as an epi plugin
-rw-r--r--rel/overlay/etc/default.ini15
-rw-r--r--src/fabric/src/fabric2_encryption.erl31
-rw-r--r--src/fabric/src/fabric2_encryption_plugin.erl48
-rw-r--r--src/fabric/src/fabric2_encryption_provider.erl60
-rw-r--r--src/fabric/src/fabric2_epi.erl7
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() ->