diff options
author | Eric Avdey <eiri@eiri.ca> | 2020-04-02 11:40:11 -0300 |
---|---|---|
committer | Eric Avdey <eiri@eiri.ca> | 2020-04-07 02:37:13 -0300 |
commit | 34399cddc9460106b9c6c0984b7151ae20d40e1f (patch) | |
tree | 611c56b25337fdca9220401413f03e0c88f805c2 | |
parent | 6f293918e5691244d4b021f04718bff4892739d4 (diff) | |
download | couchdb-34399cddc9460106b9c6c0984b7151ae20d40e1f.tar.gz |
Switch to couch_keywrap in encryption provider
-rw-r--r-- | rel/overlay/etc/default.ini | 21 | ||||
-rw-r--r-- | src/fabric/src/fabric2_encryption_provider.erl | 147 |
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. |