|
38 | 38 | decrypt_with_key/5, |
39 | 39 | read_key/3, |
40 | 40 | defaults/0, |
41 | | - key_path/2]). |
| 41 | + key_path/2, |
| 42 | + rotate_integrity_tokens/2, |
| 43 | + remove_old_integrity_tokens/2, |
| 44 | + get_key_id_in_use/1]). |
42 | 45 |
|
43 | 46 | -record(state, {config :: file:filename(), |
44 | 47 | loop :: pid() | undefined, |
@@ -135,6 +138,15 @@ decrypt_with_key(Name, Data, AD, KeyKind, KeyName) -> |
135 | 138 | gen_server:call(Name, {decrypt_with_key, Data, AD, KeyKind, KeyName}, |
136 | 139 | infinity). |
137 | 140 |
|
| 141 | +rotate_integrity_tokens(Name, KeyName) -> |
| 142 | + gen_server:call(Name, {rotate_integrity_tokens, KeyName}, infinity). |
| 143 | + |
| 144 | +remove_old_integrity_tokens(Name, Paths) -> |
| 145 | + gen_server:call(Name, {remove_old_integrity_tokens, Paths}, infinity). |
| 146 | + |
| 147 | +get_key_id_in_use(Name) -> |
| 148 | + gen_server:call(Name, get_key_id_in_use, infinity). |
| 149 | + |
138 | 150 | start_link(Logger, PasswordPromptAllowed) -> |
139 | 151 | start_link(Logger, PasswordPromptAllowed, gosecrets_cfg_path()). |
140 | 152 |
|
@@ -352,6 +364,12 @@ handle_call({encrypt_with_key, _Data, _AD, _KeyKind, _Name} = Cmd, _From, |
352 | 364 | handle_call({decrypt_with_key, _Data, _AD, _KeyKind, _Name} = Cmd, _From, |
353 | 365 | State) -> |
354 | 366 | {reply, convert_empty_data(call_gosecrets(Cmd, State)), State}; |
| 367 | +handle_call({rotate_integrity_tokens, _KeyName} = Cmd, _From, State) -> |
| 368 | + {reply, call_gosecrets(Cmd, State), State}; |
| 369 | +handle_call({remove_old_integrity_tokens, _Paths} = Cmd, _From, State) -> |
| 370 | + {reply, call_gosecrets(Cmd, State), State}; |
| 371 | +handle_call(get_key_id_in_use, _From, State) -> |
| 372 | + {reply, convert_empty_data(call_gosecrets(get_key_id_in_use, State)), State}; |
355 | 373 | handle_call(stop, _From, State) -> |
356 | 374 | {stop, normal, call_gosecrets(stop, State), State}; |
357 | 375 | handle_call(Call, _From, State) -> |
@@ -493,7 +511,14 @@ encode({decrypt_with_key, Data, AD, KeyKind, Name}) -> |
493 | 511 | (encode_param(KeyKind))/binary, |
494 | 512 | (encode_param(Name))/binary>>; |
495 | 513 | encode({read_key, Kind, Name}) -> |
496 | | - <<15, (encode_param(Kind))/binary, (encode_param(Name))/binary>>. |
| 514 | + <<15, (encode_param(Kind))/binary, (encode_param(Name))/binary>>; |
| 515 | +encode({rotate_integrity_tokens, KeyName}) -> |
| 516 | + <<17, (encode_param(KeyName))/binary>>; |
| 517 | +encode({remove_old_integrity_tokens, Paths}) -> |
| 518 | + PathsBin = list_to_binary([encode_param(P) || P <- Paths]), |
| 519 | + <<18, PathsBin/binary>>; |
| 520 | +encode(get_key_id_in_use) -> |
| 521 | + <<19>>. |
497 | 522 |
|
498 | 523 | encode_param(B) when is_atom(B) -> |
499 | 524 | encode_param(atom_to_binary(B)); |
@@ -853,6 +878,86 @@ config_reload_test() -> |
853 | 878 | ?assertEqual({ok, <<"default">>}, get_state(Pid)) |
854 | 879 | end). |
855 | 880 |
|
| 881 | +-define(key1, <<"key10000-0000-0000-0000-000000000000">>). |
| 882 | +-define(key2, <<"key20000-0000-0000-0000-000000000000">>). |
| 883 | +-define(key3, <<"key30000-0000-0000-0000-000000000000">>). |
| 884 | +-define(key4, <<"key40000-0000-0000-0000-000000000000">>). |
| 885 | +-define(key5, <<"key50000-0000-0000-0000-000000000000">>). |
| 886 | +-define(key6, <<"key60000-0000-0000-0000-000000000000">>). |
| 887 | +-define(key7, <<"key70000-0000-0000-0000-000000000000">>). |
| 888 | + |
| 889 | +integrity_check_for_stored_keys_test() -> |
| 890 | + DKFile = path_config:tempfile("bucket_dek_test", ".tmp"), |
| 891 | + ok = filelib:ensure_dir(DKFile), |
| 892 | + Cfg = [{bucket_dek_path, iolist_to_binary(DKFile)}], |
| 893 | + BucketPath = fun (Bucket) -> |
| 894 | + iolist_to_binary(filename:join([DKFile, Bucket, "deks"])) |
| 895 | + end, |
| 896 | + KeyDirs = [key_path(configDek, Cfg), |
| 897 | + key_path(logDek, Cfg), |
| 898 | + key_path(auditDek, Cfg), |
| 899 | + key_path(kek, Cfg), |
| 900 | + BucketPath("bucket1"), |
| 901 | + BucketPath("bucket2")], |
| 902 | + with_all_stored_keys_cleaned( |
| 903 | + Cfg, |
| 904 | + fun () -> |
| 905 | + with_gosecrets( |
| 906 | + Cfg, |
| 907 | + fun (CfgPath, Pid) -> |
| 908 | + TokensPath = filename:join(filename:dirname(CfgPath), |
| 909 | + "stored_keys_tokens"), |
| 910 | + StoreKey = fun (Kind, KeyId, EncryptWithKey) -> |
| 911 | + store_key(Pid, Kind, KeyId, 'raw-aes-gcm', |
| 912 | + rand:bytes(32), |
| 913 | + EncryptWithKey, |
| 914 | + <<"2024-07-26T19:32:19Z">>, false) |
| 915 | + end, |
| 916 | + |
| 917 | + ok = StoreKey(configDek, ?key1, <<"encryptionService">>), |
| 918 | + |
| 919 | + {ok, [undefined]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 920 | + |
| 921 | + %% First rotation with key1 |
| 922 | + ok = rotate_integrity_tokens(Pid, ?key1), |
| 923 | + {ok, [?key1]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 924 | + ok = remove_old_integrity_tokens(Pid, KeyDirs), |
| 925 | + {ok, [?key1]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 926 | + |
| 927 | + %% Store more keys of different kinds |
| 928 | + ok = StoreKey(kek, ?key2, <<"encryptionService">>), |
| 929 | + ok = StoreKey(configDek, ?key3, ?key2), |
| 930 | + ok = StoreKey(logDek, ?key4, ?key2), |
| 931 | + ok = StoreKey(auditDek, ?key5, ?key2), |
| 932 | + Bucket1Key = <<"bucket1/deks/", ?key6/binary>>, |
| 933 | + ok = StoreKey(bucketDek, Bucket1Key, ?key2), |
| 934 | + Bucket2Key = <<"bucket2/deks/", ?key7/binary>>, |
| 935 | + ok = StoreKey(bucketDek, Bucket2Key, ?key2), |
| 936 | + |
| 937 | + %% Second rotation with key1 |
| 938 | + ok = rotate_integrity_tokens(Pid, ?key3), |
| 939 | + {ok, [?key3]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 940 | + |
| 941 | + ok = remove_old_integrity_tokens(Pid, KeyDirs), |
| 942 | + {ok, [?key3]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 943 | + |
| 944 | + %% Final rotation with empty key |
| 945 | + ok = rotate_integrity_tokens(Pid, <<>>), |
| 946 | + {ok, [undefined]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 947 | + ok = remove_old_integrity_tokens(Pid, KeyDirs), |
| 948 | + {ok, [undefined]} = cb_crypto:get_file_dek_ids(TokensPath), |
| 949 | + |
| 950 | + %% Verify all keys are still readable |
| 951 | + {ok, _} = read_key(Pid, configDek, ?key1), |
| 952 | + {ok, _} = read_key(Pid, kek, ?key2), |
| 953 | + {ok, _} = read_key(Pid, configDek, ?key3), |
| 954 | + {ok, _} = read_key(Pid, logDek, ?key4), |
| 955 | + {ok, _} = read_key(Pid, auditDek, ?key5), |
| 956 | + {ok, _} = read_key(Pid, bucketDek, Bucket1Key), |
| 957 | + {ok, _} = read_key(Pid, bucketDek, Bucket2Key) |
| 958 | + end) |
| 959 | + end). |
| 960 | + |
856 | 961 | store_and_read_key_test() -> |
857 | 962 | Cfg = [], |
858 | 963 | DecodeKey = fun (EncodedData) -> |
@@ -1216,15 +1321,15 @@ with_tmp_datakey_cfg(Fun) -> |
1216 | 1321 | end. |
1217 | 1322 |
|
1218 | 1323 | with_all_stored_keys_cleaned(Cfg, Fun) -> |
1219 | | - KeksPath = binary_to_list(key_path(kek, Cfg)), |
1220 | | - DeksPath = binary_to_list(key_path(configDek, Cfg)), |
1221 | | - ok = misc:rm_rf(KeksPath), %% In case if there are leftovers |
1222 | | - ok = misc:rm_rf(DeksPath), |
| 1324 | + Keys = [configDek, logDek, auditDek, kek, bucketDek], |
| 1325 | + Paths = [binary_to_list(P) || Key <- Keys, |
| 1326 | + P <- [key_path(Key, Cfg)], |
| 1327 | + P =/= undefined], |
| 1328 | + [ok = misc:rm_rf(Path) || Path <- Paths], |
1223 | 1329 | try |
1224 | 1330 | Fun() |
1225 | 1331 | after |
1226 | | - ok = misc:rm_rf(KeksPath), |
1227 | | - ok = misc:rm_rf(DeksPath) |
| 1332 | + [ok = misc:rm_rf(Path) || Path <- Paths] |
1228 | 1333 | end. |
1229 | 1334 |
|
1230 | 1335 | with_password_sent(WrongPassword, CorrectPassword, Fun) -> |
@@ -1313,6 +1418,8 @@ with_tmp_cfg(Cfg, Fun) -> |
1313 | 1418 | Fun(CfgPath) |
1314 | 1419 | after |
1315 | 1420 | delete_all_default_files(), |
| 1421 | + file:delete(filename:join(filename:dirname(CfgPath), |
| 1422 | + "stored_keys_tokens")), |
1316 | 1423 | file:delete(CfgPath) |
1317 | 1424 | end. |
1318 | 1425 |
|
|
0 commit comments