xref: /aosp_15_r20/external/tink/testing/cross_language/aead_consistency_test.py (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1# Copyright 2020 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Cross-language consistency tests for AEAD.
15
16These tests generate different kind of AEAD keysets, some are valid, some are
17invalid. The test succeeds if all implementation treat the keyset consistently,
18so either encryption/decryption works as expected, or the keyset is rejected.
19"""
20
21import itertools
22from typing import List
23from typing import Tuple
24
25from absl.testing import absltest
26from absl.testing import parameterized
27import tink
28
29from tink.proto import aes_ctr_hmac_aead_pb2
30from tink.proto import aes_eax_pb2
31from tink.proto import aes_gcm_pb2
32from tink.proto import common_pb2
33from tink.proto import tink_pb2
34import tink_config
35from util import testing_servers
36from util import utilities
37
38HASH_TYPES = [
39    common_pb2.UNKNOWN_HASH, common_pb2.SHA1, common_pb2.SHA224,
40    common_pb2.SHA256, common_pb2.SHA384, common_pb2.SHA512
41]
42
43
44def setUpModule():
45  tink.aead.register()
46  testing_servers.start('aead_consistency')
47
48
49def tearDownModule():
50  testing_servers.stop()
51
52
53def _gen_keyset(
54    type_url: str, value: bytes,
55    key_material_type: tink_pb2.KeyData.KeyMaterialType) -> tink_pb2.Keyset:
56  """Generates a new Keyset."""
57  keyset = tink_pb2.Keyset()
58  key = keyset.key.add()
59  key.key_data.type_url = type_url
60  key.key_data.value = value
61  key.key_data.key_material_type = key_material_type
62  key.status = tink_pb2.ENABLED
63  key.key_id = 42
64  key.output_prefix_type = tink_pb2.TINK
65  keyset.primary_key_id = 42
66  return keyset
67
68
69def _gen_key_value(size: int) -> bytes:
70  """Returns a fixed key_value of a given size."""
71  return bytes(i for i in range(size))
72
73
74def aes_eax_key_test_cases():
75  def _test_case(key_size=16, iv_size=16, key_version=0):
76    key = aes_eax_pb2.AesEaxKey()
77    key.version = key_version
78    key.key_value = _gen_key_value(key_size)
79    key.params.iv_size = iv_size
80    keyset = _gen_keyset(
81        'type.googleapis.com/google.crypto.tink.AesEaxKey',
82        key.SerializeToString(),
83        tink_pb2.KeyData.SYMMETRIC)
84    return ('AesEaxKey(%d,%d,%d)' % (key_size, iv_size, key_version), keyset)
85  for key_size in [15, 16, 24, 32, 64, 96]:
86    for iv_size in [11, 12, 16, 17, 24, 32]:
87      yield _test_case(key_size=key_size, iv_size=iv_size)
88  yield _test_case(key_version=1)
89
90
91def aes_gcm_key_test_cases():
92  def _test_case(key_size=16, key_version=0):
93    key = aes_gcm_pb2.AesGcmKey()
94    key.version = key_version
95    key.key_value = _gen_key_value(key_size)
96    keyset = _gen_keyset(
97        'type.googleapis.com/google.crypto.tink.AesGcmKey',
98        key.SerializeToString(),
99        tink_pb2.KeyData.SYMMETRIC)
100    return ('AesGcmKey(%d,%d)' % (key_size, key_version), keyset)
101  for key_size in [15, 16, 24, 32, 64, 96]:
102    yield _test_case(key_size=key_size)
103  yield _test_case(key_version=1)
104
105
106def aes_ctr_hmac_aead_key_test_cases():
107  def _test_case(aes_key_size=16,
108                 iv_size=16,
109                 hmac_key_size=16,
110                 hmac_tag_size=16,
111                 hash_type=common_pb2.SHA256,
112                 key_version=0,
113                 aes_ctr_version=0,
114                 hmac_version=0):
115    key = aes_ctr_hmac_aead_pb2.AesCtrHmacAeadKey()
116    key.version = key_version
117    key.aes_ctr_key.version = aes_ctr_version
118    key.aes_ctr_key.params.iv_size = iv_size
119    key.aes_ctr_key.key_value = _gen_key_value(aes_key_size)
120    key.hmac_key.version = hmac_version
121    key.hmac_key.params.tag_size = hmac_tag_size
122    key.hmac_key.params.hash = hash_type
123    key.hmac_key.key_value = _gen_key_value(hmac_key_size)
124    keyset = _gen_keyset(
125        'type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey',
126        key.SerializeToString(), tink_pb2.KeyData.SYMMETRIC)
127    return ('AesCtrHmacAeadKey(%d,%d,%d,%d,%s,%d,%d,%d)' %
128            (aes_key_size, iv_size, hmac_key_size, hmac_tag_size,
129             common_pb2.HashType.Name(hash_type), key_version, aes_ctr_version,
130             hmac_version), keyset)
131
132  yield _test_case()
133  for aes_key_size in [15, 16, 24, 32, 64, 96]:
134    for iv_size in [11, 12, 16, 17, 24, 32]:
135      yield _test_case(aes_key_size=aes_key_size, iv_size=iv_size)
136  for hmac_key_size in [15, 16, 24, 32, 64, 96]:
137    for hmac_tag_size in [9, 10, 16, 20, 21, 24, 32, 33, 64, 65]:
138      yield _test_case(hmac_key_size=hmac_key_size, hmac_tag_size=hmac_tag_size)
139  for hash_type in HASH_TYPES:
140    yield _test_case(hash_type=hash_type)
141  yield _test_case(key_version=1)
142  yield _test_case(aes_ctr_version=1)
143  yield _test_case(hmac_version=1)
144
145
146class AeadKeyConsistencyTest(parameterized.TestCase):
147  """Tests that all implementation treat all generated keyset in the same way.
148
149  We only consider keyset with single keys. This should be fine, since most
150  inconsistencies between languages will occur in the key validation, and
151  that is done for each key independently.
152  """
153
154  def _create_aeads_ignore_errors(
155      self, keyset: tink_pb2.Keyset) -> List[Tuple[tink.aead.Aead, str]]:
156    """Creates AEADs for the given keyset in each language.
157
158    Args:
159      keyset: A keyset as 'keyset' proto.
160
161    Returns:
162      A list of pairs (aead, language)
163    """
164
165    result = []
166    for lang in utilities.ALL_LANGUAGES:
167      try:
168        aead = testing_servers.remote_primitive(lang,
169                                                keyset.SerializeToString(),
170                                                tink.aead.Aead)
171        result.append((aead, lang))
172      except tink.TinkError:
173        pass
174    return result
175
176  @parameterized.parameters(
177      itertools.chain(aes_eax_key_test_cases(), aes_gcm_key_test_cases(),
178                      aes_ctr_hmac_aead_key_test_cases()))
179  def test_aead_creation_supported_languages_consistent(self, name, keyset):
180    """Tests that AEAD creation is consistent in all supporeted languages."""
181    supported_langs = tink_config.supported_languages_for_key_type(
182        tink_config.key_type_from_type_url(keyset.key[0].key_data.type_url))
183
184    langs = [lang for _, lang in self._create_aeads_ignore_errors(keyset)]
185
186    if langs:
187      with self.subTest('When creating AEAD objects for %s' % name):
188        self.assertEqual(langs, supported_langs)
189
190  @parameterized.parameters(
191      itertools.chain(aes_eax_key_test_cases(), aes_gcm_key_test_cases(),
192                      aes_ctr_hmac_aead_key_test_cases()))
193  def test_aead_creation_non_supported_languages_fail(self, name, keyset):
194    """Tests that AEAD creation fails in all unsupported languages."""
195    supported_langs = tink_config.supported_languages_for_key_type(
196        tink_config.key_type_from_type_url(keyset.key[0].key_data.type_url))
197
198    langs = [lang for _, lang in self._create_aeads_ignore_errors(keyset)]
199
200    for lang in utilities.ALL_LANGUAGES:
201      if lang not in supported_langs:
202        self.assertNotIn(
203            lang, langs,
204            'AEAD-Creation should fail in language %s for %s' % (lang, name))
205
206  @parameterized.parameters(
207      itertools.chain(aes_eax_key_test_cases(), aes_gcm_key_test_cases(),
208                      aes_ctr_hmac_aead_key_test_cases()))
209  def test_aead_pairwise_consistency(self, name, keyset):
210    """Tests that created AEADS behave consistently."""
211
212    aead_and_lang = self._create_aeads_ignore_errors(keyset)
213    plaintext = b'plaintext'
214    associated_data = b'associated_data'
215
216    for aead, lang in aead_and_lang:
217      aead0 = aead_and_lang[0][0]
218      lang0 = aead_and_lang[0][1]
219
220      with self.subTest('Comparing %s-aead to %s-aead for %s ' %
221                        (lang0, lang, name)):
222        ciphertext = aead.encrypt(plaintext, associated_data)
223        self.assertEqual(aead0.decrypt(ciphertext, associated_data), plaintext)
224
225        ciphertext = aead0.encrypt(plaintext, associated_data)
226        self.assertEqual(aead.decrypt(ciphertext, associated_data), plaintext)
227
228
229if __name__ == '__main__':
230  absltest.main()
231