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 DeterministicAead primitive.""" 15 16from absl.testing import absltest 17from absl.testing import parameterized 18 19import tink 20from tink import daead 21from tink.testing import keyset_builder 22from util import testing_servers 23from util import utilities 24 25SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['daead'] 26 27 28def setUpModule(): 29 daead.register() 30 testing_servers.start('deterministic_aead') 31 32 33def tearDownModule(): 34 testing_servers.stop() 35 36 37class DeterministicAeadTest(parameterized.TestCase): 38 39 @parameterized.parameters( 40 utilities.tinkey_template_names_for(daead.DeterministicAead)) 41 def test_encrypt_decrypt(self, key_template_name): 42 supported_langs = utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 43 key_template_name] 44 self.assertNotEmpty(supported_langs) 45 key_template = utilities.KEY_TEMPLATE[key_template_name] 46 # Take the first supported language to generate the keyset. 47 keyset = testing_servers.new_keyset(supported_langs[0], key_template) 48 supported_daeads = [ 49 testing_servers.remote_primitive(lang, keyset, daead.DeterministicAead) 50 for lang in supported_langs 51 ] 52 self.assertNotEmpty(supported_daeads) 53 plaintext = ( 54 b'This is some plaintext message to be encrypted using ' 55 b'key_template %s.' % key_template_name.encode('utf8')) 56 associated_data = ( 57 b'Some associated data for %s.' % key_template_name.encode('utf8')) 58 ciphertext = None 59 for p in supported_daeads: 60 if ciphertext: 61 self.assertEqual( 62 ciphertext, 63 p.encrypt_deterministically(plaintext, associated_data)) 64 else: 65 ciphertext = p.encrypt_deterministically(plaintext, associated_data) 66 for p2 in supported_daeads: 67 output = p2.decrypt_deterministically(ciphertext, associated_data) 68 self.assertEqual(output, plaintext) 69 70 @parameterized.parameters( 71 utilities.tinkey_template_names_for(daead.DeterministicAead)) 72 def test_encrypt_decrypt_without_associated_data(self, key_template_name): 73 supported_langs = utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[ 74 key_template_name] 75 self.assertNotEmpty(supported_langs) 76 key_template = utilities.KEY_TEMPLATE[key_template_name] 77 # Take the first supported language to generate the keyset. 78 keyset = testing_servers.new_keyset(supported_langs[0], key_template) 79 supported_daeads = [ 80 testing_servers.remote_primitive(lang, keyset, daead.DeterministicAead) 81 for lang in supported_langs 82 ] 83 self.assertNotEmpty(supported_daeads) 84 plaintext = b'plaintext' 85 associated_data = b'' 86 ciphertext = None 87 for p in supported_daeads: 88 if ciphertext: 89 self.assertEqual( 90 ciphertext, 91 p.encrypt_deterministically(plaintext, associated_data)) 92 else: 93 ciphertext = p.encrypt_deterministically(plaintext, associated_data) 94 for p2 in supported_daeads: 95 output = p2.decrypt_deterministically(ciphertext, associated_data) 96 self.assertEqual(output, plaintext) 97 98 99# If the implementations work fine for keysets with single keys, then key 100# rotation should work if the primitive wrapper is implemented correctly. 101# These wrappers do not depend on the key type, so it should be fine to always 102# test with the same key type. But since the wrapper needs to treat keys 103# with output prefix RAW differently, we also include such a template for that. 104KEY_ROTATION_TEMPLATES = [ 105 daead.deterministic_aead_key_templates.AES256_SIV, 106 keyset_builder.raw_template( 107 daead.deterministic_aead_key_templates.AES256_SIV) 108] 109 110 111def key_rotation_test_cases(): 112 for enc_lang in SUPPORTED_LANGUAGES: 113 for dec_lang in SUPPORTED_LANGUAGES: 114 for old_key_tmpl in KEY_ROTATION_TEMPLATES: 115 for new_key_tmpl in KEY_ROTATION_TEMPLATES: 116 yield (enc_lang, dec_lang, old_key_tmpl, new_key_tmpl) 117 118 119class DaeadKeyRotationTest(parameterized.TestCase): 120 121 @parameterized.parameters(key_rotation_test_cases()) 122 def test_key_rotation(self, enc_lang, dec_lang, old_key_tmpl, new_key_tmpl): 123 # Do a key rotation from an old key generated from old_key_tmpl to a new 124 # key generated from new_key_tmpl. Encryption and decryption are done 125 # in languages enc_lang and dec_lang. 126 builder = keyset_builder.new_keyset_builder() 127 older_key_id = builder.add_new_key(old_key_tmpl) 128 builder.set_primary_key(older_key_id) 129 enc_daead1 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 130 daead.DeterministicAead) 131 dec_daead1 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 132 daead.DeterministicAead) 133 newer_key_id = builder.add_new_key(new_key_tmpl) 134 enc_daead2 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 135 daead.DeterministicAead) 136 dec_daead2 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 137 daead.DeterministicAead) 138 139 builder.set_primary_key(newer_key_id) 140 enc_daead3 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 141 daead.DeterministicAead) 142 dec_daead3 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 143 daead.DeterministicAead) 144 145 builder.disable_key(older_key_id) 146 enc_daead4 = testing_servers.remote_primitive(enc_lang, builder.keyset(), 147 daead.DeterministicAead) 148 dec_daead4 = testing_servers.remote_primitive(dec_lang, builder.keyset(), 149 daead.DeterministicAead) 150 151 self.assertNotEqual(older_key_id, newer_key_id) 152 # 1 encrypts with the older key. So 1, 2 and 3 can decrypt it, but not 4. 153 ciphertext1 = enc_daead1.encrypt_deterministically(b'plaintext', b'ad') 154 self.assertEqual(dec_daead1.decrypt_deterministically(ciphertext1, b'ad'), 155 b'plaintext') 156 self.assertEqual(dec_daead2.decrypt_deterministically(ciphertext1, b'ad'), 157 b'plaintext') 158 self.assertEqual(dec_daead3.decrypt_deterministically(ciphertext1, b'ad'), 159 b'plaintext') 160 with self.assertRaises(tink.TinkError): 161 _ = dec_daead4.decrypt_deterministically(ciphertext1, b'ad') 162 163 # 2 encrypts with the older key. So 1, 2 and 3 can decrypt it, but not 4. 164 ciphertext2 = enc_daead2.encrypt_deterministically(b'plaintext', b'ad') 165 self.assertEqual(dec_daead1.decrypt_deterministically(ciphertext2, b'ad'), 166 b'plaintext') 167 self.assertEqual(dec_daead2.decrypt_deterministically(ciphertext2, b'ad'), 168 b'plaintext') 169 self.assertEqual(dec_daead3.decrypt_deterministically(ciphertext2, b'ad'), 170 b'plaintext') 171 with self.assertRaises(tink.TinkError): 172 _ = dec_daead4.decrypt_deterministically(ciphertext2, b'ad') 173 174 # 3 encrypts with the newer key. So 2, 3 and 4 can decrypt it, but not 1. 175 ciphertext3 = enc_daead3.encrypt_deterministically(b'plaintext', b'ad') 176 with self.assertRaises(tink.TinkError): 177 _ = dec_daead1.decrypt_deterministically(ciphertext3, b'ad') 178 self.assertEqual(dec_daead2.decrypt_deterministically(ciphertext3, b'ad'), 179 b'plaintext') 180 self.assertEqual(dec_daead3.decrypt_deterministically(ciphertext3, b'ad'), 181 b'plaintext') 182 self.assertEqual(dec_daead4.decrypt_deterministically(ciphertext3, b'ad'), 183 b'plaintext') 184 185 # 4 encrypts with the newer key. So 2, 3 and 4 can decrypt it, but not 1. 186 ciphertext4 = enc_daead4.encrypt_deterministically(b'plaintext', b'ad') 187 with self.assertRaises(tink.TinkError): 188 _ = dec_daead1.decrypt_deterministically(ciphertext4, b'ad') 189 self.assertEqual(dec_daead2.decrypt_deterministically(ciphertext4, b'ad'), 190 b'plaintext') 191 self.assertEqual(dec_daead3.decrypt_deterministically(ciphertext4, b'ad'), 192 b'plaintext') 193 self.assertEqual(dec_daead4.decrypt_deterministically(ciphertext4, b'ad'), 194 b'plaintext') 195 196if __name__ == '__main__': 197 absltest.main() 198