summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Avdey <eiri@eiri.ca>2020-04-02 11:40:11 -0300
committerEric Avdey <eiri@eiri.ca>2020-04-07 02:37:13 -0300
commit34399cddc9460106b9c6c0984b7151ae20d40e1f (patch)
tree611c56b25337fdca9220401413f03e0c88f805c2
parent6f293918e5691244d4b021f04718bff4892739d4 (diff)
downloadcouchdb-34399cddc9460106b9c6c0984b7151ae20d40e1f.tar.gz
Switch to couch_keywrap in encryption provider
-rw-r--r--rel/overlay/etc/default.ini21
-rw-r--r--src/fabric/src/fabric2_encryption_provider.erl147
2 files changed, 125 insertions, 43 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index c9cce5459..1aa451e0f 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -708,17 +708,10 @@ 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
+; A key provider is an external script, that on call prints master key
+; in its binary form to standartd output. The master key must be 128, 192
+; or 256 (preferred) bites long. The master key cached in memory
+; for the length of CouchDB run, i.e. it key provider script executed
+; only once.
+;
+;key_provider = /usr/local/bin/decrypt_master_key
diff --git a/src/fabric/src/fabric2_encryption_provider.erl b/src/fabric/src/fabric2_encryption_provider.erl
index a96cadfb6..5f474c8ab 100644
--- a/src/fabric/src/fabric2_encryption_provider.erl
+++ b/src/fabric/src/fabric2_encryption_provider.erl
@@ -19,6 +19,12 @@
]).
+-include_lib("syntax_tools/include/merl.hrl").
+
+
+-define(MEK_CACHE_MODULE, fabric2_encryption_master_key).
+
+
get_aad(DbName) ->
FdbDirs = fabric2_server:fdb_directory(),
FdbDir = iolist_to_binary(FdbDirs),
@@ -26,11 +32,10 @@ get_aad(DbName) ->
get_kek(_DbName) ->
- case get_mek_iv() of
- {ok, MEK, IV} ->
+ case get_mek() of
+ {ok, MEK} ->
KEK = crypto:strong_rand_bytes(32),
- Enc = crypto:stream_init(aes_ctr, MEK, IV),
- {_, WrappedKEK} = crypto:stream_encrypt(Enc, KEK),
+ WrappedKEK = couch_keywrap:key_wrap(MEK, KEK),
{ok, KEK, WrappedKEK};
{error, Error} ->
{error, Error}
@@ -38,39 +43,123 @@ get_kek(_DbName) ->
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),
- {ok, KEK, WrappedKEK};
+ case get_mek() of
+ {ok, MEK} ->
+ case couch_keywrap:key_unwrap(MEK, WrappedKEK) of
+ fail ->
+ {error, unable_to_unwrap_kek};
+ KEK ->
+ {ok, KEK, WrappedKEK}
+ end;
{error, Error} ->
{error, Error}
end.
-get_mek_iv() ->
- case config:get_boolean("encryption", "enabled", false) of
- false ->
- {error, encryption_disabled};
+get_mek() ->
+ case erlang:function_exported(?MEK_CACHE_MODULE, get, 0) of
true ->
- KeyFile = config:get("encryption", "key_file"),
- IVFile = config:get("encryption", "iv_file"),
- get_mek_iv(KeyFile, IVFile)
+ ?MEK_CACHE_MODULE:get();
+ false ->
+ maybe_cache_mek()
end.
-get_mek_iv(KeyFile, IVFile) ->
- case {file:read_file(KeyFile), file:read_file(IVFile)} 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}}
+maybe_cache_mek() ->
+ KeyProvider = config:get("encryption", "key_provider"),
+ case iolist_to_binary(os:cmd(KeyProvider)) of
+ MEK when bit_size(MEK) rem 64 == 0, bit_size(MEK) =< 256 ->
+ ModuleName = ?MEK_CACHE_MODULE,
+ Module = ?Q("-module('@ModuleName@')."),
+ Export = ?Q("-export([get/0])."),
+ Function = erl_syntax:function(merl:term(get), [
+ ?Q("() -> {ok, _@MEK@}")
+ ]),
+ merl:compile_and_load([Module, Export, Function],
+ [no_spawn_compiler_process, no_line_info]),
+ {ok, MEK};
+ _ ->
+ {error, invalid_key_length}
end.
+
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+get_mek_failure_test_() ->
+ {
+ setup,
+ fun() ->
+ ok = meck:new([config], [passthrough]),
+ ok = meck:expect(config, get, fun("encryption", "key_provider") ->
+ "echo 0"
+ end)
+ end,
+ fun(ok) ->
+ meck:unload()
+ end,
+ fun(ok) ->
+ [
+ {"should return error when failed to acquire mek",
+ ?_assertEqual({error, invalid_key_length}, get_mek())}
+ ]
+ end
+ }.
+
+
+get_unwrap_kek_test_() ->
+ {
+ setup,
+ fun() ->
+ ok = meck:new([config], [passthrough]),
+ ok = meck:expect(config, get, fun("encryption", "key_provider") ->
+ "echo 0000000000000000000000000000000"
+ end)
+ end,
+ fun(ok) ->
+ meck:unload()
+ end,
+ fun(ok) ->
+ [
+ {"should cache acquired mek",
+ fun test_get_mek/0},
+ {"should gernerate and wrap kek",
+ fun test_get_kek/0},
+ {"should unwrap valid wrapped kek",
+ fun test_unwrap_kek/0},
+ {"should return error on invalid wrapped key",
+ ?_assertMatch({error, _}, unwrap_kek(<<0:320>>))}
+ ]
+ end
+ }.
+
+
+test_get_mek() ->
+ {ok, MEK1} = get_mek(),
+ {ok, MEK2} = get_mek(),
+ ?assertEqual(MEK2, MEK1),
+ N1 = meck:num_calls(config, get, ["encryption", "key_provider"]),
+ ?assertEqual(1, N1).
+
+
+test_get_kek() ->
+ Resp = get_kek(<<"db">>),
+ ?assertMatch({ok, _, _}, Resp),
+ {ok, KEK, WrappedKEK} = Resp,
+ ?assertEqual(256, bit_size(KEK)),
+ ?assertNotEqual(WrappedKEK, KEK).
+
+
+test_unwrap_kek() ->
+ WrappedKEK = <<16#0c714838ba4b937fdde5a2ca8a318ead3c2c49ddfc77eef90e1a954f18962848f601d18f7cf32bb9:320>>,
+ Resp = unwrap_kek(WrappedKEK),
+ ?assertMatch({ok, _, _}, Resp),
+ {ok, KEK, WrappedKEK2} = Resp,
+ ?assertEqual(256, bit_size(KEK)),
+ ?assertEqual(WrappedKEK, WrappedKEK2),
+ ?assertNotEqual(WrappedKEK2, KEK).
+
+-endif.