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/09 13:29:39 UTC

[couchdb] branch aegis_3.x updated: use AES_SIV (RFC 5297) instead of AES Key Wrap

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


The following commit(s) were added to refs/heads/aegis_3.x by this push:
     new 3281159af use AES_SIV (RFC 5297) instead of AES Key Wrap
3281159af is described below

commit 3281159af84a62dd78d4514ee40be2423b08e7aa
Author: Robert Newson <rn...@apache.org>
AuthorDate: Mon May 9 14:29:05 2022 +0100

    use AES_SIV (RFC 5297) instead of AES Key Wrap
---
 rebar.config.script             |   1 +
 rel/reltool.config              |   2 +
 src/aegis/src/aegis.app.src     |  28 ++++++++++
 src/aegis/src/aegis.erl         |  51 ++++++++++++++++++
 src/aegis/src/aegis_cmac.erl    |  67 +++++++++++++++++++++++
 src/aegis/src/aegis_s2v.erl     |  51 ++++++++++++++++++
 src/aegis/src/aegis_siv.erl     | 106 ++++++++++++++++++++++++++++++++++++
 src/aegis/src/aegis_util.erl    |  90 +++++++++++++++++++++++++++++++
 src/couch/src/couch_file.erl    |  10 ++--
 src/couch/src/couch_keywrap.erl | 115 ----------------------------------------
 10 files changed, 401 insertions(+), 120 deletions(-)

diff --git a/rebar.config.script b/rebar.config.script
index da3fc58a1..fdee29bc1 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -113,6 +113,7 @@ os:putenv("COUCHDB_APPS_CONFIG_DIR", filename:join([COUCHDB_ROOT, "rel/apps"])).
 SubDirs = [
     %% must be compiled first as it has a custom behavior
     "src/couch_epi",
+    "src/aegis",
     "src/couch_log",
     "src/chttpd",
     "src/couch",
diff --git a/rel/reltool.config b/rel/reltool.config
index ab26fb2ed..a7ab87c5f 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -26,6 +26,7 @@
         syntax_tools,
         xmerl,
         %% couchdb
+        aegis,
         b64url,
         bear,
         chttpd,
@@ -90,6 +91,7 @@
     {app, xmerl, [{incl_cond, include}]},
 
     %% couchdb
+    {app, aegis, [{incl_cond, include}]},
     {app, b64url, [{incl_cond, include}]},
     {app, bear, [{incl_cond, include}]},
     {app, chttpd, [{incl_cond, include}]},
diff --git a/src/aegis/src/aegis.app.src b/src/aegis/src/aegis.app.src
new file mode 100644
index 000000000..9088ec46c
--- /dev/null
+++ b/src/aegis/src/aegis.app.src
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+{application, aegis,
+ [{description, "An OTP application"},
+  {vsn, git},
+  {registered, []},
+  {applications,
+   [kernel,
+    stdlib,
+    crypto
+   ]},
+  {env,[]},
+  {modules, []},
+
+  {maintainers, []},
+  {licenses, []},
+  {links, []}
+ ]}.
diff --git a/src/aegis/src/aegis.erl b/src/aegis/src/aegis.erl
new file mode 100644
index 000000000..90e3f330a
--- /dev/null
+++ b/src/aegis/src/aegis.erl
@@ -0,0 +1,51 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis).
+
+-export([wrap_key/3, unwrap_key/3]).
+
+wrap_key(KEK, AAD, DEK) when is_binary(KEK), is_list(AAD), is_binary(DEK) ->
+    ExpandedKey = aegis_util:expand(KEK),
+    {CipherText, CipherTag} =
+        aegis_siv:block_encrypt(
+            ExpandedKey,
+            AAD,
+            DEK),
+    <<CipherTag/binary, CipherText/binary>>.
+
+
+unwrap_key(KEK, AAD, <<CipherTag:16/binary, CipherText/binary>>) when is_binary(KEK), is_list(AAD) ->
+    ExpandedKey = aegis_util:expand(KEK),
+    aegis_siv:block_decrypt(
+        ExpandedKey,
+        AAD,
+        {CipherText, CipherTag}).
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+aegis_test_() ->
+    [
+     ?_assertEqual(<<91,78,2,43,95,157,34,252,93,35,150,141,155,139,247,136,
+                     154,203,16,143,196,78,93,9,189,119,22,27,60,47,186,114,
+                     70,231,113,189,36,236,139,153,85,58,207,165,169,70,67,61>>,
+                   wrap_key(<<0:256>>, [], <<1:256>>)),
+     ?_assertEqual(<<1:256>>,
+                   unwrap_key(<<0:256>>, [],
+                              <<91,78,2,43,95,157,34,252,93,35,150,141,155,139,247,136,
+                                154,203,16,143,196,78,93,9,189,119,22,27,60,47,186,114,
+                                70,231,113,189,36,236,139,153,85,58,207,165,169,70,67,61>>))
+    ].
+
+-endif.
diff --git a/src/aegis/src/aegis_cmac.erl b/src/aegis/src/aegis_cmac.erl
new file mode 100644
index 000000000..074a77346
--- /dev/null
+++ b/src/aegis/src/aegis_cmac.erl
@@ -0,0 +1,67 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_cmac).
+
+-export([cmac/2]).
+
+cmac(Key, Message) ->
+    cmac(Key, <<0:128>>, Message).
+
+cmac(Key, X, <<Last:16/binary>>) ->
+    {K1, _K2} = generate_subkeys(Key),
+    crypto:crypto_one_time(cmac_cipher(Key), Key, crypto:exor(X, crypto:exor(Last, K1)), true);
+
+cmac(Key, X, <<Block:16/binary, Rest/binary>>) ->
+    cmac(Key, crypto:crypto_one_time(cmac_cipher(Key), Key, crypto:exor(X, Block), true), Rest);
+
+cmac(Key, X, Last) ->
+    {_K1, K2} = generate_subkeys(Key),
+    crypto:crypto_one_time(cmac_cipher(Key), Key,
+        crypto:exor(X, crypto:exor(aegis_util:pad(Last), K2)), true).
+
+
+generate_subkeys(Key) ->
+    L = crypto:crypto_one_time(cmac_cipher(Key), Key, <<0:128>>, true),
+    K1 = aegis_util:double(L),
+    K2 = aegis_util:double(K1),
+    {K1, K2}.
+
+cmac_cipher(Key) when bit_size(Key) == 128 ->
+    aes_128_ecb;
+cmac_cipher(Key) when bit_size(Key) == 256 ->
+    aes_256_ecb.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+cmac_test_() ->
+    [
+     ?_assertEqual(<<16#bb1d6929e95937287fa37d129b756746:128>>,
+         cmac(<<16#2b7e151628aed2a6abf7158809cf4f3c:128>>,
+         <<>>)),
+
+     ?_assertEqual(<<16#070a16b46b4d4144f79bdd9dd04a287c:128>>,
+         cmac(<<16#2b7e151628aed2a6abf7158809cf4f3c:128>>,
+         <<16#6bc1bee22e409f96e93d7e117393172a:128>>)),
+
+     ?_assertEqual(<<16#028962f61b7bf89efc6b551f4667d983:128>>,
+         cmac(<<16#603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4:256>>,
+         <<>>)),
+
+     ?_assertEqual(<<16#28a7023f452e8f82bd4bf28d8c37c35c:128>>,
+         cmac(<<16#603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4:256>>,
+         <<16#6bc1bee22e409f96e93d7e117393172a:128>>))
+    ].
+
+-endif.
diff --git a/src/aegis/src/aegis_s2v.erl b/src/aegis/src/aegis_s2v.erl
new file mode 100644
index 000000000..0bae2c403
--- /dev/null
+++ b/src/aegis/src/aegis_s2v.erl
@@ -0,0 +1,51 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_s2v).
+
+-export([s2v/3]).
+
+
+s2v(Key, [], <<>>) ->
+    aegis_cmac:cmac(Key, <<1:128>>);
+
+s2v(Key, AAD, PlainText) when length(AAD) < 127 ->
+    s2v(Key, AAD, PlainText, aegis_cmac:cmac(Key, <<0:128>>)).
+
+s2v(Key, [], PlainText, Acc) when bit_size(PlainText) >= 128 ->
+    aegis_cmac:cmac(Key, aegis_util:xorend(PlainText, Acc));
+
+s2v(Key, [], PlainText, Acc) ->
+    aegis_cmac:cmac(Key,
+        crypto:exor(aegis_util:double(Acc), aegis_util:pad(PlainText)));
+
+s2v(Key, [H | T], PlainText, Acc0) ->
+    Acc1 = crypto:exor(aegis_util:double(Acc0), aegis_cmac:cmac(Key, H)),
+    s2v(Key, T, PlainText, Acc1).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+s2v_0_test() ->
+    ?assertEqual(<<16#85632d07c6e8f37f950acd320a2ecc93:128>>,
+        s2v(
+            <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0:128>>,
+            [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+            <<16#112233445566778899aabbccddee:112>>)).
+
+%% for test coverage only. this value does not come from a test vector.
+s2v_1_test() ->
+    ?assertEqual(<<106,56,130,35,180,192,121,7,97,30,181,248,111,114,85,151>>,
+        s2v(<<0:128>>, [], <<>>)).
+
+
+-endif.
diff --git a/src/aegis/src/aegis_siv.erl b/src/aegis/src/aegis_siv.erl
new file mode 100644
index 000000000..4c0542956
--- /dev/null
+++ b/src/aegis/src/aegis_siv.erl
@@ -0,0 +1,106 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_siv).
+
+-export([block_encrypt/3, block_decrypt/3]).
+
+-spec block_encrypt(binary(), list(), binary()) -> {binary(), binary()}.
+block_encrypt(Key, AAD, PlainText)
+  when bit_size(Key) == 256; bit_size(Key) == 512 ->
+    {K1, K2} = split(Key),
+    <<V:128>> = aegis_s2v:s2v(K1, AAD, PlainText),
+    Q = V band 16#ffffffffffffffff7fffffff7fffffff,
+    CipherText = aes_ctr(K2, <<Q:128>>, PlainText),
+    {CipherText, <<V:128>>}.
+
+
+block_decrypt(Key, AAD, {CipherText, <<V:128>>})
+  when bit_size(Key) == 256; bit_size(Key) == 512 ->
+    {K1, K2} = split(Key),
+    Q = V band 16#ffffffffffffffff7fffffff7fffffff,
+    PlainText = aes_ctr(K2, <<Q:128>>, CipherText),
+    <<T:128>> = aegis_s2v:s2v(K1, AAD, PlainText),
+    case V == T of
+        true ->
+            PlainText;
+        false ->
+            fail
+    end.
+
+
+split(Key) ->
+    Half = byte_size(Key) div 2,
+    <<K1:Half/binary, K2:Half/binary>> = Key,
+    {K1, K2}.
+
+
+aes_ctr(Key, IV, Data) ->
+    Cipher = ctr_cipher(Key),
+    crypto:crypto_one_time(Cipher, Key, IV, Data, true).
+
+
+ctr_cipher(Key) when bit_size(Key) == 128 ->
+    aes_128_ctr;
+
+ctr_cipher(Key) when bit_size(Key) == 256 ->
+    aes_256_ctr.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+encrypt_test_() ->
+    [
+        ?_assertEqual({<<16#40c02b9690c4dc04daef7f6afe5c:112>>,
+                       <<16#85632d07c6e8f37f950acd320a2ecc93:128>>},
+            block_encrypt(
+                <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff:256>>,
+                [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+                <<16#112233445566778899aabbccddee:112>>)),
+
+        ?_assertEqual({<<16#cb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d:376>>,
+                       <<16#7bdb6e3b432667eb06f4d14bff2fbd0f:128>>},
+            block_encrypt(
+                <<16#7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f:256>>,
+                [<<16#00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100:320>>,
+                 <<16#102030405060708090a0:80>>,
+                 <<16#09f911029d74e35bd84156c5635688c0:128>>],
+              <<16#7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553:376>>))
+    ].
+
+decrypt_test_() ->
+    [
+        ?_assertEqual(<<16#112233445566778899aabbccddee:112>>,
+            block_decrypt(
+                <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff:256>>,
+                [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+                {<<16#40c02b9690c4dc04daef7f6afe5c:112>>, <<16#85632d07c6e8f37f950acd320a2ecc93:128>>})),
+
+        ?_assertEqual(fail,
+            block_decrypt(
+                <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff:256>>,
+                [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+                {<<16#40c02b9690c4dc04daef7f6afe5c:112>>, <<16#85632d07c6e8f37f950acd320a2ecc94:128>>})),
+
+        ?_assertEqual(<<16#7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553:376>>,
+
+            block_decrypt(
+                <<16#7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f:256>>,
+                [<<16#00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100:320>>,
+                 <<16#102030405060708090a0:80>>,
+                 <<16#09f911029d74e35bd84156c5635688c0:128>>],
+             {<<16#cb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d:376>>,
+              <<16#7bdb6e3b432667eb06f4d14bff2fbd0f:128>>}))
+    ].
+
+-endif.
diff --git a/src/aegis/src/aegis_util.erl b/src/aegis/src/aegis_util.erl
new file mode 100644
index 000000000..1705ac222
--- /dev/null
+++ b/src/aegis/src/aegis_util.erl
@@ -0,0 +1,90 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_util).
+
+-export([
+    double/1,
+    expand/1,
+    pad/1,
+    xorend/2
+]).
+
+%% @doc double
+%% is the multiplication of S and 0...010 in the finite field
+%% represented using the primitive polynomial
+%% x<sup>128</sup> + x<sup>7</sup> + x<sup>2</sup> + x + 1.
+%% @end
+-spec double(Val :: binary()) -> binary().
+double(<<0:1, Lo:127>>) ->
+    <<(Lo bsl 1):128>>;
+
+double(<<1:1, Lo:127>>) ->
+    crypto:exor(<<(Lo bsl 1):128>>, <<16#87:128>>).
+
+
+%% because SIV only uses half the bits of the input key
+%% to encrypt and the other half for the authentication/IV
+%% we expand our keys to 512 to ensure an overall security
+%% threshold of 256.
+expand(Key) when bit_size(Key) == 256 ->
+    %% expansion technique from Bjoern Tackmann - IBM Zurich
+    K0 = crypto:crypto_one_time(aes_256_ecb, Key, <<0:128>>, true),
+    K1 = crypto:crypto_one_time(aes_256_ecb, Key, <<1:128>>, true),
+    K2 = crypto:crypto_one_time(aes_256_ecb, Key, <<2:128>>, true),
+    K3 = crypto:crypto_one_time(aes_256_ecb, Key, <<3:128>>, true),
+    <<K0/binary, K1/binary, K2/binary, K3/binary>>.
+
+
+%% @doc pad
+%% indicates padding of string X, len(X) &lt; 128, out to 128 bits by
+%% the concatenation of a single bit of 1 followed by as many 0 bits
+%% as are necessary.
+%% @end
+-spec pad(binary()) -> binary().
+pad(Val) when bit_size(Val) =< 128 ->
+    Pad = 128 - bit_size(Val) - 1,
+    <<Val/binary, 1:1, 0:Pad>>.
+
+
+%% @doc xorend
+%% where len(A) &gt;= len(B), means xoring a string B onto the end of
+%% string A -- i.e., leftmost(A, len(A)-len(B)) || (rightmost(A,
+%% len(B)) xor B).
+%% @end
+-spec xorend(binary(), binary()) -> binary().
+xorend(A, B) when byte_size(A) >= byte_size(B) ->
+    Diff = byte_size(A) - byte_size(B),
+    <<Left:Diff/binary, Right/binary>> = A,
+    Xor = crypto:exor(Right, B),
+    <<Left/binary, Xor/binary>>.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+double_0_test() ->
+    ?assertEqual(
+        <<16#1c09bf5f83df7e080280b050b37e0e74:128>>,
+        double(<<16#0e04dfafc1efbf040140582859bf073a:128>>)).
+
+double_1_test() ->
+    ?assertEqual(
+        <<16#dbe13bd0ed8c85dc9af179c99ddbf819:128>>,
+        double(<<16#edf09de876c642ee4d78bce4ceedfc4f:128>>)).
+
+pad_test() ->
+    ?assertEqual(
+        <<16#112233445566778899aabbccddee8000:128>>,
+        pad(<<16#112233445566778899aabbccddee:112>>)).
+
+-endif.
diff --git a/src/couch/src/couch_file.erl b/src/couch/src/couch_file.erl
index f64c36ef2..892d0d2b6 100644
--- a/src/couch/src/couch_file.erl
+++ b/src/couch/src/couch_file.erl
@@ -23,7 +23,7 @@
 -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).
+-define(ENCRYPTED_HEADER, 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15).
 
 -type block_id() :: non_neg_integer().
 -type location() :: non_neg_integer().
@@ -931,7 +931,7 @@ reset_eof(#file{} = File) ->
 %% we've wiped all the data, including the wrapped key, so we need a new one.
 init_crypto(#file{eof = 0} = File) ->
     Key = crypto:strong_rand_bytes(32),
-    WrappedKey = couch_keywrap:key_wrap(master_key(), Key),
+    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),
@@ -939,9 +939,9 @@ init_crypto(#file{eof = 0} = File) ->
 
 %% 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, 48) of
-        {ok, <<?ENCRYPTED_HEADER, WrappedKey/binary>>} ->
-            case couch_keywrap:key_unwrap(master_key(), WrappedKey) of
+    case file:pread(File#file.fd, 0, 64) of
+        {ok, <<?ENCRYPTED_HEADER, WrappedKey:48/binary>>} ->
+            case aegis:unwrap_key(master_key(), [], WrappedKey) of
                 fail ->
                     {error, unwrap_failed};
                 Key when is_binary(Key) ->
diff --git a/src/couch/src/couch_keywrap.erl b/src/couch/src/couch_keywrap.erl
deleted file mode 100644
index 2cfa2b104..000000000
--- a/src/couch/src/couch_keywrap.erl
+++ /dev/null
@@ -1,115 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_keywrap).
-
-%% Implementation of NIST Special Publication 800-38F
-%% For wrapping and unwrapping keys with AES.
-
--export([key_wrap/2, key_unwrap/2]).
-
--define(ICV1, 16#A6A6A6A6A6A6A6A6).
-
--spec key_wrap(WrappingKey :: binary(), KeyToWrap :: binary()) -> binary().
-key_wrap(WrappingKey, KeyToWrap) when
-    is_binary(WrappingKey), bit_size(KeyToWrap) rem 64 == 0
-->
-    N = bit_size(KeyToWrap) div 64,
-    wrap(WrappingKey, <<?ICV1:64>>, KeyToWrap, 1, 6 * N).
-
-wrap(_WrappingKey, A, R, T, End) when T > End ->
-    <<A/binary, R/binary>>;
-wrap(WrappingKey, A, R, T, End) ->
-    <<R1:64, Rest/binary>> = R,
-    <<MSB_B:64, LSB_B:64>> = crypto:crypto_one_time(aes_256_ecb, WrappingKey, <<A/binary, R1:64>>, true),
-    wrap(WrappingKey, <<(MSB_B bxor T):64>>, <<Rest/binary, LSB_B:64>>, T + 1, End).
-
--spec key_unwrap(WrappingKey :: binary(), KeyToUnwrap :: binary()) -> binary() | fail.
-key_unwrap(WrappingKey, KeyToUnwrap) when
-    is_binary(WrappingKey), bit_size(KeyToUnwrap) rem 64 == 0
-->
-    N = (bit_size(KeyToUnwrap) div 64),
-    <<A:64, R/binary>> = KeyToUnwrap,
-    case unwrap(WrappingKey, <<A:64>>, R, 6 * (N - 1)) of
-        <<?ICV1:64, UnwrappedKey/binary>> ->
-            UnwrappedKey;
-        _ ->
-            fail
-    end.
-
-unwrap(_WrappingKey, A, R, 0) ->
-    <<A/binary, R/binary>>;
-unwrap(WrappingKey, <<A:64>>, R, T) ->
-    RestSize = bit_size(R) - 64,
-    <<Rest:RestSize, R2:64>> = R,
-    <<MSB_B:64, LSB_B:64>> = crypto:crypto_one_time(aes_256_ecb, WrappingKey, <<(A bxor T):64, R2:64>>, false),
-    unwrap(WrappingKey, <<MSB_B:64>>, <<LSB_B:64, Rest:RestSize>>, T - 1).
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-wrap_test_() ->
-    [
-        %% 128 KEK / 128 DATA
-        test_wrap_unwrap(
-            <<16#000102030405060708090A0B0C0D0E0F:128>>,
-            <<16#00112233445566778899AABBCCDDEEFF:128>>,
-            <<16#1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5:192>>
-        ),
-        %% 192 KEK / 128 DATA
-        test_wrap_unwrap(
-            <<16#000102030405060708090A0B0C0D0E0F1011121314151617:192>>,
-            <<16#00112233445566778899AABBCCDDEEFF:128>>,
-            <<16#96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D:192>>
-        ),
-        %% 256 KEK / 128 DATA
-        test_wrap_unwrap(
-            <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
-            <<16#00112233445566778899AABBCCDDEEFF:128>>,
-            <<16#64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7:192>>
-        ),
-        %% 192 KEK / 192 DATA
-        test_wrap_unwrap(
-            <<16#000102030405060708090A0B0C0D0E0F1011121314151617:192>>,
-            <<16#00112233445566778899AABBCCDDEEFF0001020304050607:192>>,
-            <<16#031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2:256>>
-        ),
-        %% 256 KEK / 192 DATA
-        test_wrap_unwrap(
-            <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
-            <<16#00112233445566778899AABBCCDDEEFF0001020304050607:192>>,
-            <<16#A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1:256>>
-        ),
-        %% 256 KEK / 256 DATA
-        test_wrap_unwrap(
-            <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
-            <<16#00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F:256>>,
-            <<
-                16#28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21:320
-            >>
-        )
-    ].
-
-test_wrap_unwrap(WrappingKey, KeyToWrap, ExpectedWrappedKey) ->
-    [
-        ?_assertEqual(ExpectedWrappedKey, key_wrap(WrappingKey, KeyToWrap)),
-        ?_assertEqual(KeyToWrap, key_unwrap(WrappingKey, key_wrap(WrappingKey, KeyToWrap)))
-    ].
-
-fail_test() ->
-    KEK = <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
-    CipherText = <<
-        16#28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD20:320
-    >>,
-    ?assertEqual(fail, key_unwrap(KEK, CipherText)).
-
--endif.