xref: /aosp_15_r20/external/tink/testing/cross_language/hybrid_encryption_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 tests for Hybrid Encryption."""
15
16from typing import Iterable, Tuple
17
18from absl.testing import absltest
19from absl.testing import parameterized
20
21import tink
22from tink import aead
23from tink import daead
24from tink import hybrid
25
26from tink.proto import common_pb2
27from tink.proto import tink_pb2
28from tink.testing import keyset_builder
29from util import testing_servers
30from util import utilities
31
32SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['hybrid']
33
34
35def setUpModule():
36  hybrid.register()
37  testing_servers.start('hybrid')
38
39
40def tearDownModule():
41  testing_servers.stop()
42
43
44# maps from key_template_name to (key_template, supported_langs)
45_ADDITIONAL_KEY_TEMPLATES = {
46    'ECIES_P256_HKDF_HMAC_SHA256_AES256_SIV':
47        (hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
48            curve_type=common_pb2.NIST_P256,
49            ec_point_format=common_pb2.COMPRESSED,
50            hash_type=common_pb2.SHA256,
51            dem_key_template=daead.deterministic_aead_key_templates.AES256_SIV),
52         ['cc', 'java', 'go', 'python']),
53    'ECIES_P256_HKDF_HMAC_SHA256_XCHACHA20_POLY1305':
54        (hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
55            curve_type=common_pb2.NIST_P256,
56            ec_point_format=common_pb2.UNCOMPRESSED,
57            hash_type=common_pb2.SHA256,
58            dem_key_template=aead.aead_key_templates.XCHACHA20_POLY1305),
59         # Java and Go do not support XCHACHA20_POLY1305 for hybrid encryption.
60         ['cc', 'python']),
61    # equal to HybridKeyTemplates::EciesX25519HkdfHmacSha256Aes128Gcm
62    'ECIES_X25519_HKDF_HMAC_SHA256_AES128_GCM':
63        (hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
64            curve_type=common_pb2.CURVE25519,
65            ec_point_format=common_pb2.COMPRESSED,
66            hash_type=common_pb2.SHA256,
67            dem_key_template=aead.aead_key_templates.AES128_GCM),
68         # Java and Go do not support CURVE25519 for hybrid encryption.
69         ['cc', 'python']),
70    # equal to HybridKeyTemplates::EciesX25519HkdfHmacSha256Aes128CtrHmacSha256
71    'ECIES_X25519_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256':
72        (hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
73            curve_type=common_pb2.CURVE25519,
74            ec_point_format=common_pb2.COMPRESSED,
75            hash_type=common_pb2.SHA256,
76            dem_key_template=aead.aead_key_templates.AES128_CTR_HMAC_SHA256),
77         # Java and Go do not support CURVE25519 for hybrid encryption.
78         ['cc', 'python']),
79    # equal to HybridKeyTemplates::EciesX25519HkdfHmacSha256XChaCha20Poly1305
80    'ECIES_X25519_HKDF_HMAC_SHA256_XCHACHA20_POLY1305':
81        (hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
82            curve_type=common_pb2.CURVE25519,
83            ec_point_format=common_pb2.COMPRESSED,
84            hash_type=common_pb2.SHA256,
85            dem_key_template=aead.aead_key_templates.XCHACHA20_POLY1305),
86         # Java and Go neither support CURVE25519 nor XCHACHA20_POLY130 for
87         # Hybrid Encryption.
88         ['cc', 'python']),
89    # equal to HybridKeyTemplates::EciesX25519HkdfHmacSha256DeterministicAesSiv
90    'ECIES_X25519_HKDF_HMAC_SHA256_AES256_SIV':
91        (hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
92            curve_type=common_pb2.CURVE25519,
93            ec_point_format=common_pb2.COMPRESSED,
94            hash_type=common_pb2.SHA256,
95            dem_key_template=daead.deterministic_aead_key_templates.AES256_SIV),
96         # Java and Go do not support CURVE25519 for Hybrid Encryption.
97         ['cc', 'python']),
98}
99
100
101class HybridEncryptionTest(parameterized.TestCase):
102
103  @parameterized.parameters([
104      *utilities.tinkey_template_names_for(hybrid.HybridDecrypt),
105      *_ADDITIONAL_KEY_TEMPLATES.keys()
106  ])
107  def test_encrypt_decrypt(self, key_template_name):
108    if key_template_name in _ADDITIONAL_KEY_TEMPLATES:
109      key_template, supported_langs = _ADDITIONAL_KEY_TEMPLATES[
110          key_template_name]
111    else:
112      key_template = utilities.KEY_TEMPLATE[key_template_name]
113      supported_langs = (
114          utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[key_template_name])
115    self.assertNotEmpty(supported_langs)
116    # Take the first supported language to generate the private keyset.
117    private_keyset = testing_servers.new_keyset(supported_langs[0],
118                                                key_template)
119    supported_decs = [
120        testing_servers.remote_primitive(lang, private_keyset,
121                                         hybrid.HybridDecrypt)
122        for lang in supported_langs
123    ]
124    public_keyset = testing_servers.public_keyset(supported_langs[0],
125                                                  private_keyset)
126    supported_encs = {
127        lang: testing_servers.remote_primitive(lang, public_keyset,
128                                               hybrid.HybridEncrypt)
129        for lang in supported_langs
130    }
131    for lang, hybrid_encrypt in supported_encs.items():
132      plaintext = (
133          b'This is some plaintext message to be encrypted using key_template '
134          b'%s in %s.' %
135          (key_template_name.encode('utf8'), lang.encode('utf8')))
136      context_info = (b'Some context info for %s using %s for encryption.' %
137                      (key_template_name.encode('utf8'), lang.encode('utf8')))
138      ciphertext = hybrid_encrypt.encrypt(plaintext, context_info)
139      for dec in supported_decs:
140        output = dec.decrypt(ciphertext, context_info)
141        self.assertEqual(output, plaintext)
142
143
144# If the implementations work fine for keysets with single keys, then key
145# rotation should work if the primitive wrapper is implemented correctly.
146# These wrappers do not depend on the key type, so it should be fine to always
147# test with the same key type. But since the wrapper needs to treat keys
148# with output prefix RAW differently, we also include such a template for that.
149KEY_ROTATION_TEMPLATES = [
150    hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM,
151    keyset_builder.raw_template(
152        hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
153]
154
155
156def key_rotation_test_cases(
157) -> Iterable[Tuple[str, str, tink_pb2.KeyTemplate, tink_pb2.KeyTemplate]]:
158  for enc_lang in SUPPORTED_LANGUAGES:
159    for dec_lang in SUPPORTED_LANGUAGES:
160      for old_key_tmpl in KEY_ROTATION_TEMPLATES:
161        for new_key_tmpl in KEY_ROTATION_TEMPLATES:
162          yield (enc_lang, dec_lang, old_key_tmpl, new_key_tmpl)
163
164
165class HybridEncryptionKeyRotationTest(parameterized.TestCase):
166
167  @parameterized.parameters(key_rotation_test_cases())
168  def test_key_rotation(self, enc_lang, dec_lang, old_key_tmpl, new_key_tmpl):
169    # Do a key rotation from an old key generated from old_key_tmpl to a new
170    # key generated from new_key_tmpl. Encryption and decryption are done
171    # in languages enc_lang and dec_lang.
172    builder = keyset_builder.new_keyset_builder()
173    older_key_id = builder.add_new_key(old_key_tmpl)
174    builder.set_primary_key(older_key_id)
175    dec1 = testing_servers.remote_primitive(enc_lang, builder.keyset(),
176                                            hybrid.HybridDecrypt)
177    enc1 = testing_servers.remote_primitive(dec_lang, builder.public_keyset(),
178                                            hybrid.HybridEncrypt)
179    newer_key_id = builder.add_new_key(new_key_tmpl)
180    dec2 = testing_servers.remote_primitive(enc_lang, builder.keyset(),
181                                            hybrid.HybridDecrypt)
182    enc2 = testing_servers.remote_primitive(dec_lang, builder.public_keyset(),
183                                            hybrid.HybridEncrypt)
184
185    builder.set_primary_key(newer_key_id)
186    dec3 = testing_servers.remote_primitive(enc_lang, builder.keyset(),
187                                            hybrid.HybridDecrypt)
188    enc3 = testing_servers.remote_primitive(dec_lang, builder.public_keyset(),
189                                            hybrid.HybridEncrypt)
190
191    builder.disable_key(older_key_id)
192    dec4 = testing_servers.remote_primitive(enc_lang, builder.keyset(),
193                                            hybrid.HybridDecrypt)
194    enc4 = testing_servers.remote_primitive(dec_lang, builder.public_keyset(),
195                                            hybrid.HybridEncrypt)
196    self.assertNotEqual(older_key_id, newer_key_id)
197
198    # p1 encrypts with the older key. So p1, p2 and p3 can decrypt it,
199    # but not p4.
200    ciphertext1 = enc1.encrypt(b'plaintext', b'context')
201    self.assertEqual(dec1.decrypt(ciphertext1, b'context'), b'plaintext')
202    self.assertEqual(dec2.decrypt(ciphertext1, b'context'), b'plaintext')
203    self.assertEqual(dec3.decrypt(ciphertext1, b'context'), b'plaintext')
204    with self.assertRaises(tink.TinkError):
205      _ = dec4.decrypt(ciphertext1, b'context')
206
207    # p2 encrypts with the older key. So p1, p2 and p3 can decrypt it,
208    # but not p4.
209    ciphertext2 = enc2.encrypt(b'plaintext', b'context')
210    self.assertEqual(dec1.decrypt(ciphertext2, b'context'), b'plaintext')
211    self.assertEqual(dec2.decrypt(ciphertext2, b'context'), b'plaintext')
212    self.assertEqual(dec3.decrypt(ciphertext2, b'context'), b'plaintext')
213    with self.assertRaises(tink.TinkError):
214      _ = dec4.decrypt(ciphertext2, b'context')
215
216    # p3 encrypts with the newer key. So p2, p3 and p4 can decrypt it,
217    # but not p1.
218    ciphertext3 = enc3.encrypt(b'plaintext', b'context')
219    with self.assertRaises(tink.TinkError):
220      _ = dec1.decrypt(ciphertext3, b'context')
221    self.assertEqual(dec2.decrypt(ciphertext3, b'context'), b'plaintext')
222    self.assertEqual(dec3.decrypt(ciphertext3, b'context'), b'plaintext')
223    self.assertEqual(dec4.decrypt(ciphertext3, b'context'), b'plaintext')
224
225    # p4 encrypts with the newer key. So p2, p3 and p4 can decrypt it,
226    # but not p1.
227    ciphertext4 = enc4.encrypt(b'plaintext', b'context')
228    with self.assertRaises(tink.TinkError):
229      _ = dec1.decrypt(ciphertext4, b'context')
230    self.assertEqual(dec2.decrypt(ciphertext4, b'context'), b'plaintext')
231    self.assertEqual(dec3.decrypt(ciphertext4, b'context'), b'plaintext')
232    self.assertEqual(dec4.decrypt(ciphertext4, b'context'), b'plaintext')
233
234
235if __name__ == '__main__':
236  absltest.main()
237