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 the Aead primitive. 15 16These tests check some basic AEAD properties, and that all implementations can 17interoperate with each other. 18""" 19 20from typing import Iterable, List, Tuple 21 22from absl.testing import absltest 23from absl.testing import parameterized 24import tink 25from tink import aead 26 27from tink.proto import tink_pb2 28from tink.testing import keyset_builder 29import tink_config 30from util import testing_servers 31from util import utilities 32 33SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['aead'] 34 35 36def setUpModule(): 37 aead.register() 38 testing_servers.start('aead') 39 40 41def tearDownModule(): 42 testing_servers.stop() 43 44 45# Fake KMS keys are base64-encoded keysets. Each server must register a 46# fake KmsClient that can handle these keys. 47_FAKE_KMS_KEY_URI = ( 48 'fake-kms://CM2b3_MDElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRv' 49 'LnRpbmsuQWVzR2NtS2V5EhIaEIK75t5L-adlUwVhWvRuWUwYARABGM2b3_MDIAE') 50 51 52# maps from key_template_name to (key_template, supported_langs). Contains all 53# templates we want to test which do not have a name in Tinkey. 54_ADDITIONAL_KEY_TEMPLATES = { 55 '_FAKE_KMS_AEAD': ( 56 aead.aead_key_templates.create_kms_aead_key_template(_FAKE_KMS_KEY_URI), 57 tink_config.supported_languages_for_key_type('KmsAeadKey'), 58 ), 59 # Since the key type KmsEnvelopeAeadKey is supported by all languages, the 60 # following templates should be supported if the DEK Template is supported 61 # by the language. 62 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES128_GCM': ( 63 aead.aead_key_templates.create_kms_envelope_aead_key_template( 64 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES128_GCM 65 ), 66 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['AES128_GCM'], 67 ), 68 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES256_GCM': ( 69 aead.aead_key_templates.create_kms_envelope_aead_key_template( 70 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES256_GCM 71 ), 72 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['AES256_GCM'], 73 ), 74 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES128_EAX': ( 75 aead.aead_key_templates.create_kms_envelope_aead_key_template( 76 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES128_EAX 77 ), 78 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['AES128_EAX'], 79 ), 80 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES256_EAX': ( 81 aead.aead_key_templates.create_kms_envelope_aead_key_template( 82 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES256_EAX 83 ), 84 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['AES256_EAX'], 85 ), 86 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES128_GCM_SIV': ( 87 aead.aead_key_templates.create_kms_envelope_aead_key_template( 88 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES128_GCM_SIV 89 ), 90 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['AES128_GCM_SIV'], 91 ), 92 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES256_GCM_SIV': ( 93 aead.aead_key_templates.create_kms_envelope_aead_key_template( 94 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES256_GCM_SIV 95 ), 96 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['AES256_GCM_SIV'], 97 ), 98 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES128_CTR_HMAC_SHA256': ( 99 aead.aead_key_templates.create_kms_envelope_aead_key_template( 100 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES128_CTR_HMAC_SHA256 101 ), 102 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 103 'AES128_CTR_HMAC_SHA256' 104 ], 105 ), 106 '_FAKE_KMS_ENVELOPE_AEAD_WITH_AES256_CTR_HMAC_SHA256': ( 107 aead.aead_key_templates.create_kms_envelope_aead_key_template( 108 _FAKE_KMS_KEY_URI, aead.aead_key_templates.AES256_CTR_HMAC_SHA256 109 ), 110 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 111 'AES256_CTR_HMAC_SHA256' 112 ], 113 ), 114 '_FAKE_KMS_ENVELOPE_AEAD_WITH_CHACHA20_POLY1305': ( 115 aead.aead_key_templates.create_kms_envelope_aead_key_template( 116 _FAKE_KMS_KEY_URI, utilities.KEY_TEMPLATE['CHACHA20_POLY1305'] 117 ), 118 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['CHACHA20_POLY1305'], 119 ), 120 '_FAKE_KMS_ENVELOPE_AEAD_WITH_XCHACHA20_POLY1305': ( 121 aead.aead_key_templates.create_kms_envelope_aead_key_template( 122 _FAKE_KMS_KEY_URI, aead.aead_key_templates.XCHACHA20_POLY1305 123 ), 124 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME['XCHACHA20_POLY1305'], 125 ), 126} 127 128 129class AeadPythonTest(parameterized.TestCase): 130 131 def _create_aeads_ignore_errors(self, 132 keyset: bytes) -> List[Tuple[aead.Aead, str]]: 133 """Creates AEADs for the given keyset in each language. 134 135 Args: 136 keyset: A keyset as a serialized 'keyset' proto. 137 138 Returns: 139 A list of pairs (aead, language) 140 """ 141 142 result = [] 143 for lang in utilities.ALL_LANGUAGES: 144 try: 145 aead_p = testing_servers.remote_primitive(lang, keyset, aead.Aead) 146 result.append((aead_p, lang)) 147 except tink.TinkError: 148 pass 149 return result 150 151 def _langs_from_key_template_name(self, key_template_name: str) -> List[str]: 152 if key_template_name in _ADDITIONAL_KEY_TEMPLATES: 153 _, supported_langs = _ADDITIONAL_KEY_TEMPLATES[key_template_name] 154 return supported_langs 155 else: 156 return utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[key_template_name] 157 158 def _as_proto_template(self, key_template_name: str) -> tink_pb2.KeyTemplate: 159 if key_template_name in _ADDITIONAL_KEY_TEMPLATES: 160 key_template, _ = _ADDITIONAL_KEY_TEMPLATES[key_template_name] 161 return key_template 162 else: 163 return utilities.KEY_TEMPLATE[key_template_name] 164 165 @parameterized.parameters([ 166 *utilities.tinkey_template_names_for(aead.Aead), 167 *_ADDITIONAL_KEY_TEMPLATES.keys() 168 ]) 169 def test_encrypt_decrypt(self, key_template_name): 170 langs = self._langs_from_key_template_name(key_template_name) 171 self.assertNotEmpty(langs) 172 proto_template = self._as_proto_template(key_template_name) 173 # Take the first supported language to generate the keyset. 174 keyset = testing_servers.new_keyset(langs[0], proto_template) 175 176 supported_aeads = self._create_aeads_ignore_errors(keyset) 177 self.assertEqual(set([lang for (_, lang) in supported_aeads]), set(langs)) 178 for (p, lang) in supported_aeads: 179 plaintext = ( 180 b'This is some plaintext message to be encrypted using key_template ' 181 b'%s using %s for encryption.' % 182 (key_template_name.encode('utf8'), lang.encode('utf8'))) 183 associated_data = ( 184 b'Some associated data for %s using %s for encryption.' % 185 (key_template_name.encode('utf8'), lang.encode('utf8'))) 186 ciphertext = p.encrypt(plaintext, associated_data) 187 for (p2, lang2) in supported_aeads: 188 output = p2.decrypt(ciphertext, associated_data) 189 self.assertEqual( 190 output, plaintext, 191 'While encrypting in %s an decrypting in %s' % (lang, lang2)) 192 193 @parameterized.parameters( 194 tink_config.supported_languages_for_key_type('KmsEnvelopeAeadKey') 195 ) 196 def test_envelope_encryption_rejects_envelope_templates_as_dek(self, lang): 197 dek_template = ( 198 aead.aead_key_templates.create_kms_envelope_aead_key_template( 199 kek_uri=_FAKE_KMS_KEY_URI, 200 dek_template=aead.aead_key_templates.AES128_GCM, 201 ) 202 ) 203 template = aead.aead_key_templates.create_kms_envelope_aead_key_template( 204 kek_uri=_FAKE_KMS_KEY_URI, dek_template=dek_template 205 ) 206 with self.assertRaises(tink.TinkError): 207 _ = testing_servers.new_keyset(lang, template) 208 209 @parameterized.parameters( 210 tink_config.supported_languages_for_key_type('KmsAeadKey') 211 ) 212 def test_envelope_encryption_rejects_kms_templates_as_dek(self, lang): 213 dek_template = aead.aead_key_templates.create_kms_aead_key_template( 214 key_uri=_FAKE_KMS_KEY_URI 215 ) 216 template = aead.aead_key_templates.create_kms_envelope_aead_key_template( 217 kek_uri=_FAKE_KMS_KEY_URI, dek_template=dek_template 218 ) 219 with self.assertRaises(tink.TinkError): 220 _ = testing_servers.new_keyset(lang, template) 221 222 223# If the implementations work fine for keysets with single keys, then key 224# rotation should work if the primitive wrapper is implemented correctly. 225# These wrappers do not depend on the key type, so it should be fine to always 226# test with the same key type. Since the AEAD wrapper needs to treat keys 227# with output prefix RAW differently, we also include such a template for that. 228KEY_ROTATION_TEMPLATES = [ 229 aead.aead_key_templates.AES128_CTR_HMAC_SHA256, 230 keyset_builder.raw_template(aead.aead_key_templates.AES128_CTR_HMAC_SHA256) 231] 232 233 234def key_rotation_test_cases( 235) -> Iterable[Tuple[str, str, tink_pb2.KeyTemplate, tink_pb2.KeyTemplate]]: 236 for enc_lang in SUPPORTED_LANGUAGES: 237 for dec_lang in SUPPORTED_LANGUAGES: 238 for old_key_tmpl in KEY_ROTATION_TEMPLATES: 239 for new_key_tmpl in KEY_ROTATION_TEMPLATES: 240 yield (enc_lang, dec_lang, old_key_tmpl, new_key_tmpl) 241 242 243class AeadKeyRotationTest(parameterized.TestCase): 244 245 @parameterized.parameters(key_rotation_test_cases()) 246 def test_key_rotation(self, enc_lang, dec_lang, old_key_tmpl, new_key_tmpl): 247 # Do a key rotation from an old key generated from old_key_tmpl to a new 248 # key generated from new_key_tmpl. Encryption and decryption are done 249 # in languages enc_lang and dec_lang. 250 builder = keyset_builder.new_keyset_builder() 251 older_key_id = builder.add_new_key(old_key_tmpl) 252 builder.set_primary_key(older_key_id) 253 enc_aead1 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 254 aead.Aead) 255 dec_aead1 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 256 aead.Aead) 257 newer_key_id = builder.add_new_key(new_key_tmpl) 258 enc_aead2 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 259 aead.Aead) 260 dec_aead2 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 261 aead.Aead) 262 263 builder.set_primary_key(newer_key_id) 264 enc_aead3 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 265 aead.Aead) 266 dec_aead3 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 267 aead.Aead) 268 269 builder.disable_key(older_key_id) 270 enc_aead4 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 271 aead.Aead) 272 dec_aead4 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 273 aead.Aead) 274 275 self.assertNotEqual(older_key_id, newer_key_id) 276 # 1 encrypts with the older key. So 1, 2 and 3 can decrypt it, but not 4. 277 ciphertext1 = enc_aead1.encrypt(b'plaintext', b'ad') 278 self.assertEqual(dec_aead1.decrypt(ciphertext1, b'ad'), b'plaintext') 279 self.assertEqual(dec_aead2.decrypt(ciphertext1, b'ad'), b'plaintext') 280 self.assertEqual(dec_aead3.decrypt(ciphertext1, b'ad'), b'plaintext') 281 with self.assertRaises(tink.TinkError): 282 _ = dec_aead4.decrypt(ciphertext1, b'ad') 283 284 # 2 encrypts with the older key. So 1, 2 and 3 can decrypt it, but not 4. 285 ciphertext2 = enc_aead2.encrypt(b'plaintext', b'ad') 286 self.assertEqual(dec_aead1.decrypt(ciphertext2, b'ad'), b'plaintext') 287 self.assertEqual(dec_aead2.decrypt(ciphertext2, b'ad'), b'plaintext') 288 self.assertEqual(dec_aead3.decrypt(ciphertext2, b'ad'), b'plaintext') 289 with self.assertRaises(tink.TinkError): 290 _ = dec_aead4.decrypt(ciphertext2, b'ad') 291 292 # 3 encrypts with the newer key. So 2, 3 and 4 can decrypt it, but not 1. 293 ciphertext3 = enc_aead3.encrypt(b'plaintext', b'ad') 294 with self.assertRaises(tink.TinkError): 295 _ = dec_aead1.decrypt(ciphertext3, b'ad') 296 self.assertEqual(dec_aead2.decrypt(ciphertext3, b'ad'), b'plaintext') 297 self.assertEqual(dec_aead3.decrypt(ciphertext3, b'ad'), b'plaintext') 298 self.assertEqual(dec_aead4.decrypt(ciphertext3, b'ad'), b'plaintext') 299 300 # 4 encrypts with the newer key. So 2, 3 and 4 can decrypt it, but not 1. 301 ciphertext4 = enc_aead4.encrypt(b'plaintext', b'ad') 302 with self.assertRaises(tink.TinkError): 303 _ = dec_aead1.decrypt(ciphertext4, b'ad') 304 self.assertEqual(dec_aead2.decrypt(ciphertext4, b'ad'), b'plaintext') 305 self.assertEqual(dec_aead3.decrypt(ciphertext4, b'ad'), b'plaintext') 306 self.assertEqual(dec_aead4.decrypt(ciphertext4, b'ad'), b'plaintext') 307 308if __name__ == '__main__': 309 absltest.main() 310