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

eiri pushed a commit to branch prototype/fdb-encryption
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 8f9a988c875973a530806be492cb731b55bf7d12
Author: Eric Avdey <[email protected]>
AuthorDate: Thu Mar 19 21:30:12 2020 -0300

    Switch from rev to update counter for key derivation
---
 src/fabric/src/fabric2_encryption.erl | 46 +++++++++++++++++++----------------
 src/fabric/src/fabric2_fdb.erl        | 18 ++++++++------
 2 files changed, 35 insertions(+), 29 deletions(-)

diff --git a/src/fabric/src/fabric2_encryption.erl 
b/src/fabric/src/fabric2_encryption.erl
index 6a9e82e..b589f6e 100644
--- a/src/fabric/src/fabric2_encryption.erl
+++ b/src/fabric/src/fabric2_encryption.erl
@@ -55,22 +55,24 @@ get_wrapped_kek(DbName) when is_binary(DbName) ->
     gen_server:call(?MODULE, {get_wrapped_kek, DbName}).
 
 
-encode(WrappedKEK, DbName, DocId, DocRev, DocBody)
+encode(WrappedKEK, DbName, DocId, UpdateCounter, DocBody)
     when is_binary(WrappedKEK),
          is_binary(DbName),
          is_binary(DocId),
-         is_binary(DocRev),
+         is_integer(UpdateCounter), UpdateCounter > 0,
          is_binary(DocBody) ->
-    gen_server:call(?MODULE, {encode, WrappedKEK, DbName, DocId, DocRev, 
DocBody}).
+    gen_server:call(?MODULE,
+        {encode, WrappedKEK, DbName, DocId, UpdateCounter, DocBody}).
 
 
-decode(WrappedKEK, DbName, DocId, DocRev, DocBody)
+decode(WrappedKEK, DbName, DocId, UpdateCounter, DocBody)
     when is_binary(WrappedKEK),
          is_binary(DbName),
          is_binary(DocId),
-         is_binary(DocRev),
+         is_integer(UpdateCounter), UpdateCounter > 0,
          is_binary(DocBody) ->
-    gen_server:call(?MODULE, {decode, WrappedKEK, DbName, DocId, DocRev, 
DocBody}).
+    gen_server:call(?MODULE,
+        {decode, WrappedKEK, DbName, DocId, UpdateCounter, DocBody}).
 
 
 
@@ -96,7 +98,8 @@ handle_call({get_wrapped_kek, _DbName}, _From, #{cache := 
Cache} = St) ->
     true = ets:insert(Cache, {WrappedKEK, KEK}),
     {reply, {ok, WrappedKEK}, St};
 
-handle_call({encode, WrappedKEK, DbName, DocId, DocRev, DocBody}, From, St) ->
+handle_call({encode, WrappedKEK, DbName, DocId, UpdateCounter, DocBody},
+        From, St) ->
     #{
         iid := InstanceId,
         cache := Cache,
@@ -105,14 +108,15 @@ handle_call({encode, WrappedKEK, DbName, DocId, DocRev, 
DocBody}, From, St) ->
 
     {ok, KEK} = unwrap_kek(Cache, WrappedKEK),
     {Pid, _Ref} = erlang:spawn_monitor(?MODULE,
-        do_encode, [KEK, InstanceId, DbName, DocId, DocRev, DocBody]),
+        do_encode, [KEK, InstanceId, DbName, DocId, UpdateCounter, DocBody]),
 
     NewSt = St#{
         waiters := dict:store(Pid, From, Waiters)
     },
     {noreply, NewSt};
 
-handle_call({decode, WrappedKEK, DbName, DocId, DocRev, Encoded}, From, St) ->
+handle_call({decode, WrappedKEK, DbName, DocId, UpdateCounter, Encoded},
+        From, St) ->
     #{
         iid := InstanceId,
         cache := Cache,
@@ -121,7 +125,7 @@ handle_call({decode, WrappedKEK, DbName, DocId, DocRev, 
Encoded}, From, St) ->
 
     {ok, KEK} = unwrap_kek(Cache, WrappedKEK),
     {Pid, _Ref} = erlang:spawn_monitor(?MODULE,
-        do_decode, [KEK, InstanceId, DbName, DocId, DocRev, Encoded]),
+        do_decode, [KEK, InstanceId, DbName, DocId, UpdateCounter, Encoded]),
 
     NewSt = St#{
         waiters := dict:store(Pid, From, Waiters)
@@ -164,10 +168,10 @@ init_st() ->
     }}.
 
 
-do_encode(KEK, InstanceId, DbName, DocId, DocRev, DocBody) ->
+do_encode(KEK, InstanceId, DbName, DocId, UpdateCounter, DocBody) ->
     try
         {ok, AAD} = get_aad(InstanceId, DbName),
-        {ok, DEK} = get_dek(KEK, DocId, DocRev),
+        {ok, DEK} = get_dek(KEK, DocId, UpdateCounter),
         {CipherText, CipherTag} = crypto:block_encrypt(
             aes_gcm, DEK, <<0:96>>, {AAD, DocBody, 16}),
         <<CipherTag/binary, CipherText/binary>>
@@ -180,11 +184,11 @@ do_encode(KEK, InstanceId, DbName, DocId, DocRev, 
DocBody) ->
     end.
 
 
-do_decode(KEK, InstanceId, DbName, DocId, DocRev, Encoded) ->
+do_decode(KEK, InstanceId, DbName, DocId, UpdateCounter, Encoded) ->
     try
         <<CipherTag:16/binary, CipherText/binary>> = Encoded,
         {ok, AAD} = get_aad(InstanceId, DbName),
-        {ok, DEK} = get_dek(KEK, DocId, DocRev),
+        {ok, DEK} = get_dek(KEK, DocId, UpdateCounter),
         crypto:block_decrypt(
             aes_gcm, DEK, <<0:96>>, {AAD, CipherText, CipherTag})
     of
@@ -200,8 +204,8 @@ get_aad(InstanceId, DbName) when is_binary(InstanceId), 
is_binary(DbName) ->
     {ok, <<InstanceId/binary, 0:8, DbName/binary>>}.
 
 
-get_dek(KEK, DocId, DocRev) when bit_size(KEK) == 256 ->
-    Context = <<DocId/binary, 0:8, DocRev/binary>>,
+get_dek(KEK, DocId, UpdateCounter) when bit_size(KEK) == 256 ->
+    Context = <<DocId/binary, 0:8, (integer_to_binary(UpdateCounter))/binary>>,
     PlainText = <<1:16, ?LABEL, 0:8, Context/binary, 256:16>>,
     <<_:256>> = DEK = crypto:hmac(sha256, KEK, PlainText),
     {ok, DEK}.
@@ -243,24 +247,24 @@ get_unwrap_kek_test() ->
 
 get_dek_test() ->
     KEK = crypto:strong_rand_bytes(32),
-    {ok, DEK} = get_dek(KEK, <<"0001">>, <<"1-abcdefgh">>),
+    {ok, DEK} = get_dek(KEK, <<"0001">>, 1),
     ?assertNotEqual(KEK, DEK),
     ?assertEqual(32, byte_size(DEK)).
 
 encode_decode_test() ->
     KEK = crypto:strong_rand_bytes(32),
-    {IId, DbName, DocId, DocRev, DocBody}
-        = {<<"dev">>, <<"db">>, <<"0001">>, <<"1-abcdefgh">>, <<"[ohai]">>},
+    {IId, DbName, DocId, UpdateCounter, DocBody}
+        = {<<"dev">>, <<"db">>, <<"0001">>, 1, <<"[ohai]">>},
 
     {ok, EncResult} = try
-        do_encode(KEK, IId, DbName, DocId, DocRev, DocBody)
+        do_encode(KEK, IId, DbName, DocId, UpdateCounter, DocBody)
     catch
         exit:ER -> ER
     end,
     ?assertNotEqual(DocBody, EncResult),
 
     {ok, DecResult} = try
-        do_decode(KEK, IId, DbName, DocId, DocRev, EncResult)
+        do_decode(KEK, IId, DbName, DocId, UpdateCounter, EncResult)
     catch
         exit:DR -> DR
     end,
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index d68985e..203a5b7 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -205,6 +205,7 @@ create(#{} = Db0, Options) ->
         {?DB_STATS, <<"doc_del_count">>, ?uint2bin(0)},
         {?DB_STATS, <<"doc_design_count">>, ?uint2bin(0)},
         {?DB_STATS, <<"doc_local_count">>, ?uint2bin(0)},
+        {?DB_STATS, <<"update_count">>, ?uint2bin(1)},
         {?DB_STATS, <<"sizes">>, <<"external">>, ?uint2bin(2)},
         {?DB_STATS, <<"sizes">>, <<"views">>, ?uint2bin(0)}
     ],
@@ -813,10 +814,11 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, 
ToUpdate, ToRemove) ->
             ok
     end,
 
-    % Update database size
+    % Update database size and db's update counter
     AddSize = sum_add_rev_sizes([NewWinner | ToUpdate]),
     RemSize = sum_rem_rev_sizes(ToRemove),
     incr_stat(Db, <<"sizes">>, <<"external">>, AddSize - RemSize),
+    incr_stat(Db, <<"update_count">>, 1),
 
     ok.
 
@@ -1335,12 +1337,13 @@ doc_to_fdb(Db, #doc{} = Doc) ->
 
     DiskAtts = lists:map(fun couch_att:to_disk_term/1, Atts),
 
-    BinRev = couch_doc:rev_to_str({Start, Rev}),
+    UpdateCounter = get_stat(Db, <<"update_count">>),
     BinBody = term_to_binary(Body, [{compressed, 0}, {minor_version, 1}]),
     {ok, Encoded} = fabric2_encryption:encode(
-        WrappedKEK, DbName, Id, BinRev, BinBody),
+        WrappedKEK, DbName, Id, UpdateCounter, BinBody),
 
-    Value = term_to_binary({Encoded, DiskAtts, Deleted}, [{minor_version, 1}]),
+    Value = term_to_binary({UpdateCounter, Encoded, DiskAtts, Deleted},
+        [{minor_version, 1}]),
     Chunks = chunkify_binary(Value),
 
     {Rows, _} = lists:mapfoldl(fun(Chunk, ChunkId) ->
@@ -1354,18 +1357,17 @@ doc_to_fdb(Db, #doc{} = Doc) ->
 fdb_to_doc(_Db, _DocId, _Pos, _Path, []) ->
     {not_found, missing};
 
-fdb_to_doc(Db, DocId, Pos, [Rev | _] = Path, BinRows) when is_list(BinRows) ->
+fdb_to_doc(Db, DocId, Pos, Path, BinRows) when is_list(BinRows) ->
     #{
         name := DbName,
         wrapped_kek := WrappedKEK
     } = Db,
 
     Bin = iolist_to_binary(BinRows),
-    {Encoded, DiskAtts, Deleted} = binary_to_term(Bin, [safe]),
+    {UpdateCounter, Encoded, DiskAtts, Deleted} = binary_to_term(Bin, [safe]),
 
-    BinRev = couch_doc:rev_to_str({Pos, Rev}),
     {ok, BinBody} = fabric2_encryption:decode(
-        WrappedKEK, DbName, DocId, BinRev, Encoded),
+        WrappedKEK, DbName, DocId, UpdateCounter, Encoded),
     Body = binary_to_term(BinBody, [safe]),
 
     Atts = lists:map(fun(Att) ->

Reply via email to