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