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 MAC primitive.""" 15 16from typing import Iterable, Tuple 17 18from absl.testing import absltest 19from absl.testing import parameterized 20 21import tink 22from tink import mac 23 24from tink.proto import tink_pb2 25from tink.testing import keyset_builder 26import tink_config 27from util import testing_servers 28from util import utilities 29 30SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['mac'] 31 32 33def setUpModule(): 34 mac.register() 35 testing_servers.start('mac') 36 37 38def tearDownModule(): 39 testing_servers.stop() 40 41 42# maps from key_template_name to (key_template, key_type) 43_ADDITIONAL_KEY_TEMPLATES = { 44 # We additionally test a RAW and a LEGACY template, because LEGACY keys 45 # require tags with a special format, and RAW don't. 46 # We test one key template for each, because this is handled by the 47 # primitive wrapper which is independent of the particular key template 48 # used. 49 'HMAC_SHA256_128BITTAG_RAW': (keyset_builder.raw_template( 50 mac.mac_key_templates.HMAC_SHA256_128BITTAG), 'HmacKey'), 51 'HMAC_SHA256_128BITTAG_LEGACY': (keyset_builder.legacy_template( 52 mac.mac_key_templates.HMAC_SHA256_128BITTAG), 'HmacKey') 53} 54 55 56class MacTest(parameterized.TestCase): 57 58 @parameterized.parameters([ 59 *utilities.tinkey_template_names_for(mac.Mac), 60 *_ADDITIONAL_KEY_TEMPLATES.keys() 61 ]) 62 def test_compute_verify_mac(self, key_template_name): 63 if key_template_name in _ADDITIONAL_KEY_TEMPLATES: 64 key_template, key_type = _ADDITIONAL_KEY_TEMPLATES[ 65 key_template_name] 66 supported_langs = tink_config.supported_languages_for_key_type(key_type) 67 else: 68 key_template = utilities.KEY_TEMPLATE[key_template_name] 69 supported_langs = ( 70 utilities.SUPPORTED_LANGUAGES_BY_TEMPLATE_NAME[key_template_name]) 71 self.assertNotEmpty(supported_langs) 72 # Take the first supported language to generate the keyset. 73 keyset = testing_servers.new_keyset(supported_langs[0], key_template) 74 supported_macs = {} 75 for lang in supported_langs: 76 supported_macs[lang] = testing_servers.remote_primitive( 77 lang, keyset, mac.Mac) 78 for lang, p in supported_macs.items(): 79 data = ( 80 b'This is some data to be authenticated using key_template ' 81 b'%s in %s.' % (key_template_name.encode('utf8'), 82 lang.encode('utf8'))) 83 mac_value = p.compute_mac(data) 84 for _, p2 in supported_macs.items(): 85 self.assertIsNone(p2.verify_mac(mac_value, data)) 86 87 88# If the implementations work fine for keysets with single keys, then key 89# rotation should work if the primitive wrapper is implemented correctly. 90# These wrappers do not depend on the key type, so it should be fine to always 91# test with the same key type. The wrapper needs to treat keys with output 92# prefix RAW and LEGACY differently, so we also test templates with these 93# prefixes. 94KEY_ROTATION_TEMPLATES = [ 95 mac.mac_key_templates.HMAC_SHA512_512BITTAG, 96 keyset_builder.raw_template(mac.mac_key_templates.HMAC_SHA512_512BITTAG), 97 keyset_builder.legacy_template(mac.mac_key_templates.HMAC_SHA512_512BITTAG) 98] 99 100 101def key_rotation_test_cases( 102) -> Iterable[Tuple[str, str, tink_pb2.KeyTemplate, tink_pb2.KeyTemplate]]: 103 for compute_lang in SUPPORTED_LANGUAGES: 104 for verify_lang in SUPPORTED_LANGUAGES: 105 for old_key_tmpl in KEY_ROTATION_TEMPLATES: 106 for new_key_tmpl in KEY_ROTATION_TEMPLATES: 107 yield (compute_lang, verify_lang, old_key_tmpl, new_key_tmpl) 108 109 110class MacKeyRotationTest(parameterized.TestCase): 111 112 @parameterized.parameters(key_rotation_test_cases()) 113 def test_key_rotation( 114 self, compute_lang, verify_lang, old_key_tmpl, new_key_tmpl): 115 # Do a key rotation from an old key generated from old_key_tmpl to a new 116 # key generated from new_key_tmpl. MAC computation and verification are done 117 # in languages compute_lang and verify_lang. 118 builder = keyset_builder.new_keyset_builder() 119 older_key_id = builder.add_new_key(old_key_tmpl) 120 builder.set_primary_key(older_key_id) 121 compute_mac1 = testing_servers.remote_primitive(compute_lang, 122 builder.keyset(), mac.Mac) 123 verify_mac1 = testing_servers.remote_primitive(verify_lang, 124 builder.keyset(), mac.Mac) 125 newer_key_id = builder.add_new_key(new_key_tmpl) 126 compute_mac2 = testing_servers.remote_primitive(compute_lang, 127 builder.keyset(), mac.Mac) 128 verify_mac2 = testing_servers.remote_primitive(verify_lang, 129 builder.keyset(), mac.Mac) 130 131 builder.set_primary_key(newer_key_id) 132 compute_mac3 = testing_servers.remote_primitive(compute_lang, 133 builder.keyset(), mac.Mac) 134 verify_mac3 = testing_servers.remote_primitive(verify_lang, 135 builder.keyset(), mac.Mac) 136 137 builder.disable_key(older_key_id) 138 compute_mac4 = testing_servers.remote_primitive(compute_lang, 139 builder.keyset(), mac.Mac) 140 verify_mac4 = testing_servers.remote_primitive(verify_lang, 141 builder.keyset(), mac.Mac) 142 143 self.assertNotEqual(older_key_id, newer_key_id) 144 # 1 uses the older key. So 1, 2 and 3 can verify the mac, but not 4. 145 mac_value1 = compute_mac1.compute_mac(b'plaintext') 146 verify_mac1.verify_mac(mac_value1, b'plaintext') 147 verify_mac2.verify_mac(mac_value1, b'plaintext') 148 verify_mac3.verify_mac(mac_value1, b'plaintext') 149 with self.assertRaises(tink.TinkError): 150 verify_mac4.verify_mac(mac_value1, b'plaintext') 151 152 # 2 uses the older key. So 1, 2 and 3 can verify the mac, but not 4. 153 mac_value2 = compute_mac2.compute_mac(b'plaintext') 154 verify_mac1.verify_mac(mac_value2, b'plaintext') 155 verify_mac2.verify_mac(mac_value2, b'plaintext') 156 verify_mac3.verify_mac(mac_value2, b'plaintext') 157 with self.assertRaises(tink.TinkError): 158 verify_mac4.verify_mac(mac_value2, b'plaintext') 159 160 # 3 uses the newer key. So 2, 3 and 4 can verify the mac, but not 1. 161 mac_value3 = compute_mac3.compute_mac(b'plaintext') 162 with self.assertRaises(tink.TinkError): 163 verify_mac1.verify_mac(mac_value3, b'plaintext') 164 verify_mac2.verify_mac(mac_value3, b'plaintext') 165 verify_mac3.verify_mac(mac_value3, b'plaintext') 166 verify_mac4.verify_mac(mac_value3, b'plaintext') 167 168 # 4 uses the newer key. So 2, 3 and 4 can verify the mac, but not 1. 169 mac_value4 = compute_mac4.compute_mac(b'plaintext') 170 with self.assertRaises(tink.TinkError): 171 verify_mac1.verify_mac(mac_value4, b'plaintext') 172 verify_mac2.verify_mac(mac_value4, b'plaintext') 173 verify_mac3.verify_mac(mac_value4, b'plaintext') 174 verify_mac4.verify_mac(mac_value4, b'plaintext') 175 176if __name__ == '__main__': 177 absltest.main() 178