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"""Tests keyset validation. 15 16Currently only tests if keysets without a primary key are properly handled. 17""" 18 19import datetime 20from typing import Any 21from typing import Iterable 22from typing import Tuple 23 24from absl.testing import absltest 25from absl.testing import parameterized 26import tink 27from tink import aead 28from tink import daead 29from tink import hybrid 30from tink import jwt 31from tink import mac 32from tink import prf 33from tink import signature 34 35from tink.proto import tink_pb2 36import tink_config 37from util import testing_servers 38from util import utilities 39 40 41def unset_primary(keyset: bytes) -> bytes: 42 """Parses keyset and generates modified keyset without primary key.""" 43 keyset_proto = tink_pb2.Keyset.FromString(keyset) 44 keyset_proto.ClearField('primary_key_id') 45 return keyset_proto.SerializeToString() 46 47 48def test_cases(primitive: Any) -> Iterable[Tuple[str, str]]: 49 for key_type in tink_config.key_types_for_primitive(primitive): 50 for key_template_name in utilities.KEY_TEMPLATE_NAMES[key_type]: 51 for lang in tink_config.supported_languages_for_key_type(key_type): 52 yield (key_template_name, lang) 53 54 55def setUpModule(): 56 testing_servers.start('key_version') 57 58 59def tearDownModule(): 60 testing_servers.stop() 61 62 63class KeysetValidationTest(parameterized.TestCase): 64 """These tests verify keysets are properly validated.""" 65 66 @parameterized.parameters(test_cases(aead.Aead)) 67 def test_aead_without_primary(self, key_template_name, lang): 68 """Unsets the primary key and tries to use an AEAD primitive.""" 69 template = utilities.KEY_TEMPLATE[key_template_name] 70 keyset = testing_servers.new_keyset(lang, template) 71 72 with self.assertRaises(tink.TinkError): 73 _ = testing_servers.remote_primitive(lang, unset_primary(keyset), 74 aead.Aead) 75 76 @parameterized.parameters(test_cases(daead.DeterministicAead)) 77 def test_daead_without_primary(self, key_template_name, lang): 78 """Unsets the primary key and tries to use a DAEAD primitive.""" 79 template = utilities.KEY_TEMPLATE[key_template_name] 80 keyset = testing_servers.new_keyset(lang, template) 81 with self.assertRaises(tink.TinkError): 82 _ = testing_servers.remote_primitive(lang, unset_primary(keyset), 83 daead.DeterministicAead) 84 85 @parameterized.parameters(test_cases(mac.Mac)) 86 def test_mac_without_primary(self, key_template_name, lang): 87 """Unsets the primary key and tries to use a MAC primitive.""" 88 template = utilities.KEY_TEMPLATE[key_template_name] 89 keyset = testing_servers.new_keyset(lang, template) 90 with self.assertRaises(tink.TinkError): 91 testing_servers.remote_primitive(lang, unset_primary(keyset), mac.Mac) 92 93 @parameterized.parameters(test_cases(prf.PrfSet)) 94 def test_prf_without_primary(self, key_template_name, lang): 95 """Unsets the primary key and tries to use a PRF set primitive.""" 96 template = utilities.KEY_TEMPLATE[key_template_name] 97 keyset = testing_servers.new_keyset(lang, template) 98 with self.assertRaises(tink.TinkError): 99 _ = testing_servers.remote_primitive(lang, unset_primary(keyset), 100 prf.PrfSet) 101 102 @parameterized.parameters(test_cases(signature.PublicKeySign)) 103 def test_signature_without_primary(self, key_template_name, lang): 104 """Unsets the primary key and tries to sign and verify signatures.""" 105 template = utilities.KEY_TEMPLATE[key_template_name] 106 private_keyset = testing_servers.new_keyset(lang, template) 107 public_keyset = testing_servers.public_keyset(lang, private_keyset) 108 signer = testing_servers.remote_primitive(lang, private_keyset, 109 signature.PublicKeySign) 110 sig = signer.sign(b'foo') 111 verifier = testing_servers.remote_primitive(lang, public_keyset, 112 signature.PublicKeyVerify) 113 verifier.verify(sig, b'foo') 114 private_keyset_without_primary = unset_primary(private_keyset) 115 public_keyset_without_primary = unset_primary(public_keyset) 116 with self.assertRaises(tink.TinkError): 117 _ = testing_servers.remote_primitive( 118 lang, private_keyset_without_primary, signature.PublicKeySign) 119 if lang not in ['python']: 120 with self.assertRaises(tink.TinkError): 121 _ = testing_servers.remote_primitive( 122 lang, public_keyset_without_primary, signature.PublicKeyVerify) 123 if lang in ['python']: 124 # TODO(b/252792776) This should fail. 125 verifier_without_primary = testing_servers.remote_primitive( 126 lang, public_keyset_without_primary, signature.PublicKeyVerify) 127 verifier_without_primary.verify(sig, b'foo') 128 129 @parameterized.parameters(test_cases(hybrid.HybridDecrypt)) 130 def test_hybrid_without_primary(self, key_template_name, lang): 131 """Unsets the primary key and tries to use hybrid encryption.""" 132 template = utilities.KEY_TEMPLATE[key_template_name] 133 private_keyset = testing_servers.new_keyset(lang, template) 134 public_keyset = testing_servers.public_keyset(lang, private_keyset) 135 136 private_keyset_without_primary = unset_primary(private_keyset) 137 with self.assertRaises(tink.TinkError): 138 testing_servers.remote_primitive( 139 lang, unset_primary(private_keyset_without_primary), 140 hybrid.HybridDecrypt) 141 142 public_keyset_without_primary = unset_primary(public_keyset) 143 with self.assertRaises(tink.TinkError): 144 enc_without_primary = testing_servers.remote_primitive( 145 lang, public_keyset_without_primary, hybrid.HybridEncrypt) 146 # TODO(b/228140127) This should fail above already. 147 enc_without_primary.encrypt(b'foo', b'context_info') 148 149 @parameterized.parameters(test_cases(jwt.JwtPublicKeySign)) 150 def test_jwt_signature_without_primary(self, key_template_name, lang): 151 """Unsets the primary key and tries to sign and verify JWT signatures.""" 152 template = utilities.KEY_TEMPLATE[key_template_name] 153 private_keyset = testing_servers.new_keyset(lang, template) 154 public_keyset = testing_servers.public_keyset(lang, private_keyset) 155 signer = testing_servers.remote_primitive(lang, private_keyset, 156 jwt.JwtPublicKeySign) 157 158 now = datetime.datetime.now(tz=datetime.timezone.utc) 159 raw_jwt = jwt.new_raw_jwt( 160 issuer='issuer', 161 expiration=now + datetime.timedelta(seconds=100)) 162 token = signer.sign_and_encode(raw_jwt) 163 164 private_keyset_without_primary = unset_primary(private_keyset) 165 public_keyset_without_primary = unset_primary(public_keyset) 166 167 with self.assertRaises(tink.TinkError): 168 _ = testing_servers.remote_primitive( 169 lang, private_keyset_without_primary, jwt.JwtPublicKeySign) 170 171 if lang not in ['cc', 'python']: 172 with self.assertRaises(tink.TinkError): 173 _ = testing_servers.remote_primitive(lang, 174 public_keyset_without_primary, 175 jwt.JwtPublicKeyVerify) 176 if lang in ['cc', 'python']: 177 # TODO(b/252792776) This should fail. 178 verifier_without_primary = testing_servers.remote_primitive( 179 lang, public_keyset_without_primary, jwt.JwtPublicKeyVerify) 180 validator = jwt.new_validator(expected_issuer='issuer', fixed_now=now) 181 verifier_without_primary.verify_and_decode(token, validator) 182 183 @parameterized.parameters(test_cases(jwt.JwtMac)) 184 def test_jwt_mac_without_primary(self, key_template_name, lang): 185 """Unsets the primary key and tries to create and verify JWT MACs.""" 186 template = utilities.KEY_TEMPLATE[key_template_name] 187 keyset = testing_servers.new_keyset(lang, template) 188 189 with self.assertRaises(tink.TinkError): 190 _ = testing_servers.remote_primitive(lang, unset_primary(keyset), 191 jwt.JwtMac) 192 193 194if __name__ == '__main__': 195 absltest.main() 196