You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2022/05/10 13:34:01 UTC

[couchdb] branch aegis_3.x updated (382774726 -> 4973c0494)

This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a change to branch aegis_3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git


 discard 382774726 Add key rotation facility
     new 4973c0494 Add key rotation facility

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (382774726)
            \
             N -- N -- N   refs/heads/aegis_3.x (4973c0494)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 rel/overlay/etc/default.ini  |  2 +-
 src/couch/src/couch_file.erl | 90 ++++++++++++++++++++++++++++----------------
 2 files changed, 59 insertions(+), 33 deletions(-)


[couchdb] 01/01: Add key rotation facility

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch aegis_3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 4973c0494e8d83fea5ab8bca8b3e23e0daa6c6fb
Author: Robert Newson <rn...@apache.org>
AuthorDate: Tue May 10 08:29:45 2022 +0100

    Add key rotation facility
---
 rel/overlay/etc/default.ini  |   9 +++-
 src/couch/src/couch_file.erl | 107 ++++++++++++++++++++++++++-----------------
 2 files changed, 72 insertions(+), 44 deletions(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 98349f5eb..bfda46f44 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -6,8 +6,6 @@ name = {{package_author_name}}
 uuid = {{uuid}}
 database_dir = {{data_dir}}
 view_index_dir = {{view_index_dir}}
-encryption_password = super_secret_password
-encryption_salt = no_saltier_than_this
 ; util_driver_dir =
 ; plugin_dir =
 ;os_process_timeout = 5000 ; 5 seconds. for view servers.
@@ -749,3 +747,10 @@ port = {{prometheus_port}}
 ; to disable this setting could be if the views need an upgrade but located on
 ; read-only file system.
 ;commit_on_header_upgrade = true
+
+[encryption]
+wrapping_key_id = bec46c7438e685d6
+
+[encryption_keys]
+254abda45788029b = 0585d781ef090ee26f329a36f66935ce
+bec46c7438e685d6 = e6f598615eb7502b473b101d64f56410
\ No newline at end of file
diff --git a/src/couch/src/couch_file.erl b/src/couch/src/couch_file.erl
index 6198d8f9a..d63ebde7b 100644
--- a/src/couch/src/couch_file.erl
+++ b/src/couch/src/couch_file.erl
@@ -23,7 +23,6 @@
 -define(IS_OLD_STATE(S), is_pid(S#file.db_monitor)).
 -define(PREFIX_SIZE, 5).
 -define(DEFAULT_READ_COUNT, 1024).
--define(ENCRYPTED_HEADER, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15).
 
 -type block_id() :: non_neg_integer().
 -type location() :: non_neg_integer().
@@ -39,6 +38,12 @@
     dec
 }).
 
+-record(encryption_header, {
+    version = 1,
+    wrapping_key_id,
+    wrapped_key
+}).
+
 % public API
 -export([open/1, open/2, close/1, bytes/1, sync/1, truncate/2, set_db_pid/2]).
 -export([pread_term/2, pread_iolist/2, pread_binary/2]).
@@ -619,18 +624,10 @@ handle_call({append_bins, Bins}, _From, #file{eof = Pos} = File) ->
         Error ->
             {reply, Error, reset_eof(File)}
     end;
-handle_call({write_header, Bin}, _From, #file{eof = Pos} = File) ->
-    BinSize = byte_size(Bin),
-    case Pos rem ?SIZE_BLOCK of
-        0 ->
-            Padding = <<>>;
-        BlockOffset ->
-            Padding = <<0:(8 * (?SIZE_BLOCK - BlockOffset))>>
-    end,
-    FinalBin = [Padding, <<1, BinSize:32/integer>> | make_blocks(5, [Bin])],
-    case encrypted_write(File, FinalBin) of
-        ok ->
-            {reply, ok, File#file{eof = Pos + iolist_size(FinalBin)}};
+handle_call({write_header, Bin}, _From, #file{} = File) ->
+    case write_header_int(File, Bin) of
+        {ok, File1} ->
+            {reply, ok, File1};
         Error ->
             {reply, Error, reset_eof(File)}
     end;
@@ -700,6 +697,22 @@ load_header(#file{} = File, Pos, HeaderLen, RestBlock) ->
     Md5Sig = couch_hash:md5_hash(HeaderBin),
     {ok, HeaderBin}.
 
+write_header_int(#file{eof = Pos} = File, Bin) ->
+    BinSize = byte_size(Bin),
+    case Pos rem ?SIZE_BLOCK of
+        0 ->
+            Padding = <<>>;
+        BlockOffset ->
+            Padding = <<0:(8 * (?SIZE_BLOCK - BlockOffset))>>
+    end,
+    FinalBin = [Padding, <<1, BinSize:32/integer>> | make_blocks(5, [Bin])],
+    case encrypted_write(File, FinalBin) of
+        ok ->
+            {ok, File#file{eof = Pos + iolist_size(FinalBin)}};
+        Else ->
+            Else
+    end.
+
 %% Read multiple block locations using a single file:pread/2.
 -spec find_header(file:fd(), block_id(), non_neg_integer()) ->
     {ok, binary()} | no_valid_header.
@@ -928,29 +941,43 @@ reset_eof(#file{} = File) ->
     File#file{eof = Eof}.
 
 
-%% we've wiped all the data, including the wrapped key, so we need a new one.
-init_crypto(#file{eof = 0} = File) ->
+%% new file or we've wiped all the data, including the wrapped key, so we need a new one.
+init_crypto(#file{eof = 0} = File0) ->
     Key = crypto:strong_rand_bytes(32),
-    WrappedKey = aegis:wrap_key(master_key(), [], Key),
-    Header = <<?ENCRYPTED_HEADER, WrappedKey/binary>>,
-    ok = file:write(File#file.fd, Header),
-    ok = file:sync(File#file.fd),
-    {ok, init_crypto(File#file{eof = iolist_size(Header)}, Key)};
+    {WrappingKeyId, WrappingKey} = current_wrapping_key(),
+    WrappedKey = aegis:wrap_key(WrappingKey, [WrappingKeyId], Key),
+    Header = #encryption_header{
+        wrapping_key_id = WrappingKeyId,
+        wrapped_key = WrappedKey
+    },
+    HeaderBin = term_to_binary(Header),
+    Md5 = couch_hash:md5_hash(HeaderBin),
+    FinalBin = <<Md5/binary, HeaderBin/binary>>,
+    case write_header_int(File0#file{enc = unencrypted}, FinalBin) of
+        {ok, File1} ->
+            ok = file:sync(File1#file.fd),
+            {ok, init_crypto(File1, Key)};
+        Else ->
+            Else
+    end;
 
 %% we're opening an existing file and need to unwrap the key.
 init_crypto(#file{enc = undefined, dec = undefined} = File) ->
-    case file:pread(File#file.fd, 0, 64) of
-        {ok, <<?ENCRYPTED_HEADER, WrappedKey:48/binary>>} ->
-            case aegis:unwrap_key(master_key(), [], WrappedKey) of
+    {ok, HeaderBin} = load_header(File#file{dec = unencrypted}, 0),
+    case binary_to_term(HeaderBin, [safe]) of
+        #encryption_header{
+           version = 1,
+           wrapping_key_id = WrappingKeyId,
+           wrapped_key = WrappedKey
+          } ->
+            case aegis:unwrap_key(wrapping_key(WrappingKeyId), [WrappingKeyId], WrappedKey) of
                 fail ->
                     {error, unwrap_failed};
                 Key when is_binary(Key) ->
                     {ok, init_crypto(File, Key)}
             end;
-        {ok, _} ->
-            {ok, File#file{enc = unencrypted, dec = unencrypted}};
-        Else ->
-            Else
+        _ ->
+            {ok, File#file{enc = unencrypted, dec = unencrypted}}
     end;
 
 %% we're opening an existing file that contains a wrapped key
@@ -1027,27 +1054,23 @@ unpad(Pos, Bin) when is_binary(Bin) ->
     Result.
 
 
-master_key() ->
-    couch_pbkdf2:pbkdf2(sha256, master_password(), master_salt(), 100000).
+current_wrapping_key() ->
+    KeyId = config:get("encryption", "wrapping_key_id"),
+    {?l2b(KeyId), wrapping_key(KeyId)}.
 
 
-master_password() ->
-    case config:get("couchdb", "encryption_password") of
-        undefined ->
-            undefined;
-        Password ->
-            ?l2b(Password)
-    end.
+wrapping_key(KeyId) ->
+    get_config_binary("encryption_keys", couch_util:to_list(KeyId), undefined).
 
-master_salt() ->
-    case config:get("couchdb", "encryption_salt") of
+
+get_config_binary(Section, Key, Default) ->
+    case config:get(Section, Key) of
         undefined ->
-            undefined;
-        Salt ->
-            ?l2b(Salt)
+            Default;
+        Value ->
+            ?l2b(Value)
     end.
 
-
 -ifdef(TEST).
 -include_lib("couch/include/couch_eunit.hrl").