1 // Copyright 2022 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 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.mac; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static java.nio.charset.StandardCharsets.UTF_8; 21 import static org.junit.Assert.assertThrows; 22 23 import com.google.crypto.tink.DeterministicAead; 24 import com.google.crypto.tink.InsecureSecretKeyAccess; 25 import com.google.crypto.tink.KeyTemplates; 26 import com.google.crypto.tink.KeysetHandle; 27 import com.google.crypto.tink.Mac; 28 import com.google.crypto.tink.RegistryConfiguration; 29 import com.google.crypto.tink.TinkJsonProtoKeysetFormat; 30 import com.google.crypto.tink.daead.DeterministicAeadConfig; 31 import com.google.crypto.tink.util.SecretBytes; 32 import java.security.GeneralSecurityException; 33 import org.junit.BeforeClass; 34 import org.junit.experimental.theories.DataPoints; 35 import org.junit.experimental.theories.FromDataPoints; 36 import org.junit.experimental.theories.Theories; 37 import org.junit.experimental.theories.Theory; 38 import org.junit.runner.RunWith; 39 40 /** Unit tests for the Mac package. Uses only the public API. */ 41 @RunWith(Theories.class) 42 public final class MacTest { 43 44 @BeforeClass setUp()45 public static void setUp() throws Exception { 46 MacConfig.register(); 47 DeterministicAeadConfig.register(); // Needed for getPrimitiveFromNonMacKeyset_throws. 48 } 49 50 @DataPoints("templates") 51 public static final String[] TEMPLATES = 52 new String[] { 53 "AES256_CMAC", 54 "AES256_CMAC_RAW", 55 "HMAC_SHA256_128BITTAG", 56 "HMAC_SHA256_128BITTAG_RAW", 57 "HMAC_SHA256_256BITTAG", 58 "HMAC_SHA256_256BITTAG_RAW", 59 "HMAC_SHA512_128BITTAG", 60 "HMAC_SHA512_128BITTAG_RAW", 61 "HMAC_SHA512_256BITTAG", 62 "HMAC_SHA512_256BITTAG_RAW", 63 "HMAC_SHA512_512BITTAG", 64 "HMAC_SHA512_512BITTAG_RAW", 65 }; 66 67 @Theory create_computeVerify(@romDataPoints"templates") String templateName)68 public void create_computeVerify(@FromDataPoints("templates") String templateName) 69 throws Exception { 70 KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateName)); 71 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 72 73 byte[] data = "data".getBytes(UTF_8); 74 byte[] tag = mac.computeMac(data); 75 mac.verifyMac(tag, data); 76 77 KeysetHandle otherHandle = KeysetHandle.generateNew(KeyTemplates.get(templateName)); 78 Mac otherMac = otherHandle.getPrimitive(RegistryConfiguration.get(), Mac.class); 79 assertThrows(GeneralSecurityException.class, () -> otherMac.verifyMac(tag, data)); 80 81 byte[] invalid = "invalid".getBytes(UTF_8); 82 byte[] empty = "".getBytes(UTF_8); 83 assertThrows(GeneralSecurityException.class, () -> mac.verifyMac(invalid, data)); 84 assertThrows(GeneralSecurityException.class, () -> mac.verifyMac(tag, invalid)); 85 assertThrows(GeneralSecurityException.class, () -> mac.verifyMac(empty, data)); 86 assertThrows(GeneralSecurityException.class, () -> mac.verifyMac(tag, empty)); 87 mac.verifyMac(mac.computeMac(empty), empty); 88 } 89 90 @Theory useAesCmacParametersAndAesCmacKey()91 public void useAesCmacParametersAndAesCmacKey() throws Exception { 92 AesCmacParameters parameters = 93 AesCmacParameters.builder() 94 .setKeySizeBytes(32) 95 .setTagSizeBytes(13) 96 .setVariant(AesCmacParameters.Variant.LEGACY) 97 .build(); 98 KeysetHandle handle = 99 KeysetHandle.newBuilder() 100 .addEntry( 101 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary()) 102 .build(); 103 104 AesCmacKey aesCmacKey = (AesCmacKey) handle.getAt(0).getKey(); 105 assertThat(aesCmacKey.getParameters()).isEqualTo(parameters); 106 assertThat(aesCmacKey.getIdRequirementOrNull()).isEqualTo(123); 107 SecretBytes secretBytes = aesCmacKey.getAesKey(); 108 assertThat(secretBytes.size()).isEqualTo(32); 109 110 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 111 byte[] data = "data".getBytes(UTF_8); 112 byte[] tag = mac.computeMac(data); 113 mac.verifyMac(tag, data); 114 } 115 116 @Theory useHmacParametersAndHmacKey()117 public void useHmacParametersAndHmacKey() throws Exception { 118 HmacParameters parameters = 119 HmacParameters.builder() 120 .setKeySizeBytes(42) 121 .setTagSizeBytes(13) 122 .setHashType(HmacParameters.HashType.SHA1) 123 .setVariant(HmacParameters.Variant.CRUNCHY) 124 .build(); 125 KeysetHandle handle = 126 KeysetHandle.newBuilder() 127 .addEntry( 128 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary()) 129 .build(); 130 131 HmacKey hmacKey = (HmacKey) handle.getAt(0).getKey(); 132 assertThat(hmacKey.getParameters()).isEqualTo(parameters); 133 assertThat(hmacKey.getIdRequirementOrNull()).isEqualTo(123); 134 SecretBytes secretBytes = hmacKey.getKeyBytes(); 135 assertThat(secretBytes.size()).isEqualTo(42); 136 137 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 138 byte[] data = "data".getBytes(UTF_8); 139 byte[] tag = mac.computeMac(data); 140 mac.verifyMac(tag, data); 141 } 142 143 // A keyset with one MAC key, serialized in Tink's JSON format. 144 private static final String JSON_MAC_KEYSET = 145 "" 146 + "{" 147 + " \"primaryKeyId\": 207420876," 148 + " \"key\": [" 149 + " {" 150 + " \"keyData\": {" 151 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HmacKey\"," 152 + " \"value\": \"GiAPii+kxtLpvCARQpftFLt4R+O6ARsyhTR7SkCCGt0bHRIEEBAIAw==\"," 153 + " \"keyMaterialType\": \"SYMMETRIC\"" 154 + " }," 155 + " \"status\": \"ENABLED\"," 156 + " \"keyId\": 207420876," 157 + " \"outputPrefixType\": \"TINK\"" 158 + " }" 159 + " ]" 160 + "}"; 161 162 @Theory readKeysetEncryptDecrypt()163 public void readKeysetEncryptDecrypt() 164 throws Exception { 165 KeysetHandle handle = 166 TinkJsonProtoKeysetFormat.parseKeyset(JSON_MAC_KEYSET, InsecureSecretKeyAccess.get()); 167 168 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 169 170 byte[] data = "data".getBytes(UTF_8); 171 byte[] tag = mac.computeMac(data); 172 mac.verifyMac(tag, data); 173 } 174 175 // A keyset with multiple keys. The first key is the same as in JSON_AEAD_KEYSET. 176 private static final String JSON_MAC_KEYSET_WITH_MULTIPLE_KEYS = 177 "" 178 + "{" 179 + " \"primaryKeyId\": 2054715504," 180 + " \"key\": [" 181 + " {" 182 + " \"keyData\": {" 183 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HmacKey\"," 184 + " \"value\": \"GiAPii+kxtLpvCARQpftFLt4R+O6ARsyhTR7SkCCGt0bHRIEEBAIAw==\"," 185 + " \"keyMaterialType\": \"SYMMETRIC\"" 186 + " }," 187 + " \"status\": \"ENABLED\"," 188 + " \"keyId\": 207420876," 189 + " \"outputPrefixType\": \"TINK\"" 190 + " }, {" 191 + " \"keyData\": {" 192 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesCmacKey\"," 193 + " \"value\": \"GgIIEBIgLaZ/6QXYeqZB8F4zHTRJU5k6TF5xvlSX9ZVLVA09UY0=\"," 194 + " \"keyMaterialType\": \"SYMMETRIC\"" 195 + " }," 196 + " \"status\": \"ENABLED\"," 197 + " \"keyId\": 2054715504," 198 + " \"outputPrefixType\": \"RAW\"" 199 + " }, {" 200 + " \"keyData\": {" 201 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HmacKey\"," 202 + " \"value\": \"GkCCIGYpFz3mj8wnTH3Ca81F1sQ7JEMxoE8B2nKiND7LrKfbaUx+/qqDXUP" 203 + "VjkzC9XdbjsaEqc9yI+RKyITef+eUEgQQQAgE\"," 204 + " \"keyMaterialType\": \"SYMMETRIC\"" 205 + " }," 206 + " \"status\": \"ENABLED\"," 207 + " \"keyId\": 1540103625," 208 + " \"outputPrefixType\": \"LEGACY\"" 209 + " }, {" 210 + " \"keyData\": {" 211 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HmacKey\"," 212 + " \"value\": \"GkA8u6JKtInsySJDZO4j6TLoIvLuGAeAZHDZoTlST0aZZ8gZZViHogzWTqt" 213 + "i2Vlp3ccy+OdN6lhMxSiphcPaR5OiEgQQIAgE\"," 214 + " \"keyMaterialType\": \"SYMMETRIC\"" 215 + " }," 216 + " \"status\": \"ENABLED\"," 217 + " \"keyId\": 570162478," 218 + " \"outputPrefixType\": \"CRUNCHY\"" 219 + " }" 220 + " ]" 221 + "}"; 222 223 @Theory multipleKeysReadKeysetWithEncryptDecrypt()224 public void multipleKeysReadKeysetWithEncryptDecrypt() 225 throws Exception { 226 KeysetHandle handle = 227 TinkJsonProtoKeysetFormat.parseKeyset( 228 JSON_MAC_KEYSET_WITH_MULTIPLE_KEYS, InsecureSecretKeyAccess.get()); 229 230 Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class); 231 232 byte[] data = "data".getBytes(UTF_8); 233 byte[] tag = mac.computeMac(data); 234 mac.verifyMac(tag, data); 235 236 // Also test that mac can verify tags computed with a non-primary key. We use 237 // JSON_MAC_KEYSET to compute a tag with the first key. 238 KeysetHandle handle1 = 239 TinkJsonProtoKeysetFormat.parseKeyset(JSON_MAC_KEYSET, InsecureSecretKeyAccess.get()); 240 Mac mac1 = handle1.getPrimitive(RegistryConfiguration.get(), Mac.class); 241 byte[] tag1 = mac1.computeMac(data); 242 mac.verifyMac(tag1, data); 243 } 244 245 // A keyset with a valid DeterministicAead key. This keyset can't be used with the Mac primitive. 246 private static final String JSON_DAEAD_KEYSET = 247 "" 248 + "{" 249 + " \"primaryKeyId\": 961932622," 250 + " \"key\": [" 251 + " {" 252 + " \"keyData\": {" 253 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesSivKey\"," 254 + " \"keyMaterialType\": \"SYMMETRIC\"," 255 + " \"value\": \"EkCJ9r5iwc5uxq5ugFyrHXh5dijTa7qalWUgZ8Gf08RxNd545FjtLMYL7ObcaFtCS" 256 + "kvV2+7u6F2DN+kqUjAfkf2W\"" 257 + " }," 258 + " \"outputPrefixType\": \"TINK\"," 259 + " \"keyId\": 961932622," 260 + " \"status\": \"ENABLED\"" 261 + " }" 262 + " ]" 263 + "}"; 264 265 @Theory getPrimitiveFromNonMacKeyset_throws()266 public void getPrimitiveFromNonMacKeyset_throws() throws Exception { 267 KeysetHandle handle = 268 TinkJsonProtoKeysetFormat.parseKeyset(JSON_DAEAD_KEYSET, InsecureSecretKeyAccess.get()); 269 // Test that the keyset can create a DeterministicAead primitive, but not a Mac. 270 Object unused = handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class); 271 assertThrows( 272 GeneralSecurityException.class, 273 () -> handle.getPrimitive(RegistryConfiguration.get(), Mac.class)); 274 } 275 } 276