diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-05 01:07:49 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-05 01:07:49 +0000 |
commit | b1b2c372a44f7e7ae18aa6f2042f6bee93ba9603 (patch) | |
tree | 171da275a686c68bd5a5347e43ffd8d12156b42f /keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java | |
parent | bec05a44379f642a7359df10eb20c57a53bc95e6 (diff) | |
parent | cc03cf106ba6f2f0e586e4bc1f1ad3e8352c3d0e (diff) | |
download | wycheproof-android13-s1-release.tar.gz |
Snap for 8404621 from cc03cf106ba6f2f0e586e4bc1f1ad3e8352c3d0e to tm-releaseandroid-vts-13.0_r8android-vts-13.0_r7android-vts-13.0_r6android-vts-13.0_r5android-vts-13.0_r4android-vts-13.0_r3android-vts-13.0_r2android-vts-13.0_r1android-security-13.0.0_r9android-security-13.0.0_r8android-security-13.0.0_r7android-security-13.0.0_r6android-security-13.0.0_r5android-security-13.0.0_r4android-security-13.0.0_r3android-security-13.0.0_r2android-security-13.0.0_r17android-security-13.0.0_r16android-security-13.0.0_r15android-security-13.0.0_r14android-security-13.0.0_r13android-security-13.0.0_r12android-security-13.0.0_r11android-security-13.0.0_r10android-security-13.0.0_r1android-platform-13.0.0_r2android-platform-13.0.0_r1android-cts-13.0_r8android-cts-13.0_r7android-cts-13.0_r6android-cts-13.0_r5android-cts-13.0_r4android-cts-13.0_r3android-cts-13.0_r2android-cts-13.0_r1android-13.0.0_r8android-13.0.0_r7android-13.0.0_r6android-13.0.0_r5android-13.0.0_r4android-13.0.0_r31android-13.0.0_r3android-13.0.0_r2android-13.0.0_r12android-13.0.0_r1android13-tests-releaseandroid13-security-releaseandroid13-s3-releaseandroid13-s2-releaseandroid13-s1-releaseandroid13-release
Change-Id: I99dbfefcaf1f502c8fecb4bafd578a3bf9c415f6
Diffstat (limited to 'keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java')
-rw-r--r-- | keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java new file mode 100644 index 0000000..eeb48ec --- /dev/null +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java @@ -0,0 +1,332 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * <p>http://www.apache.org/licenses/LICENSE-2.0 + * + * <p>Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.security.wycheproof; + +import static org.junit.Assert.assertEquals; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Locale; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** This test uses test vectors in JSON format to test MAC primitives. */ +@RunWith(JUnit4.class) +public class JsonMacTest { + + /** Convenience method to get a byte array from an JsonObject */ + protected static byte[] getBytes(JsonObject obj, String name) throws Exception { + return JsonUtil.asByteArray(obj.get(name)); + } + + protected static boolean arrayEquals(byte[] a, byte[] b) { + if (a.length != b.length) { + return false; + } + byte res = 0; + for (int i = 0; i < a.length; i++) { + res |= (byte) (a[i] ^ b[i]); + } + return res == 0; + } + + /** + * Computes a MAC. + * + * @param algorithm the algorithm. + * @param key the key bytes + * @param msg the message to MAC. + * @param tagSize the expected size of the tag in bits. + * @return the tag + * @throws GeneralSecurityException if the algorithm or the parameter sizes are not supported or + * if the initialization failed. For example one case are GMACs with a tag size othe than 128 + * bits, since the JCE interface does not seem to support such a specification. + */ + protected static byte[] computeMac(String algorithm, byte[] key, byte[] msg, int tagSize) + throws GeneralSecurityException { + Mac mac = Mac.getInstance(algorithm); + algorithm = algorithm.toUpperCase(Locale.ENGLISH); + if (algorithm.startsWith("HMAC")) { + SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); + // TODO(bleichen): Is there a provider independent truncation? + // The class javax.xml.crypto.dsig.spec.HMACParameterSpec would allow to + // truncate HMAC tags as follows: + // <pre> + // HMACParameterSpec params = new HMACParameterSpec(tagSize); + // mac.init(keySpec, params); + // mac.update(msg); + // return mac.doFinal(); + // </pre> + // But this class is often not supported. Hence the computation here, just computes a + // full length tag and truncates it. The drawback of having to truncate tags is that + // the caller has to compare truncated tags during verification. + mac.init(keySpec); + mac.update(msg); + byte[] tag = mac.doFinal(); + return Arrays.copyOf(tag, tagSize / 8); + } else { + throw new NoSuchAlgorithmException(algorithm); + } + } + + /** + * Tests a randomized MAC (i.e. a message authetication that takes an additional IV as parameter) + * against test vectors. + * + * @param filename the JSON file with the test vectors. + */ + public void testMac(String filename) throws Exception { + // Checking preconditions. + JsonObject test = JsonUtil.getTestVectors(filename); + String algorithm = test.get("algorithm").getAsString(); + try { + Mac.getInstance(algorithm); + } catch (NoSuchAlgorithmException ex) { + System.out.println("Algorithm is not supported. Skipping test for " + algorithm); + return; + } + + int numTests = test.get("numberOfTests").getAsInt(); + int cntTests = 0; + int passedTests = 0; + int errors = 0; + for (JsonElement g : test.getAsJsonArray("testGroups")) { + JsonObject group = g.getAsJsonObject(); + int tagSize = group.get("tagSize").getAsInt(); + for (JsonElement t : group.getAsJsonArray("tests")) { + cntTests++; + JsonObject testcase = t.getAsJsonObject(); + int tcid = testcase.get("tcId").getAsInt(); + String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString(); + byte[] key = getBytes(testcase, "key"); + byte[] msg = getBytes(testcase, "msg"); + byte[] expectedTag = getBytes(testcase, "tag"); + // Result is one of "valid", "invalid", "acceptable". + // "valid" are test vectors with matching plaintext, ciphertext and tag. + // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag. + // "acceptable" are test vectors with weak parameters or legacy formats. + String result = testcase.get("result").getAsString(); + + byte[] computedTag = null; + try { + computedTag = computeMac(algorithm, key, msg, tagSize); + } catch (GeneralSecurityException ex) { + // Some libraries restrict key size or tag size. Hence valid MACs might be + // rejected. + continue; + } catch (IllegalArgumentException ex) { + // Thrown by javax.crypto.spec.SecretKeySpec (e.g. when the key is empty). + continue; + } + + boolean eq = arrayEquals(expectedTag, computedTag); + if (result.equals("invalid")) { + if (eq) { + // Some test vectors use invalid parameters that should be rejected. + // E.g. an implementation must not allow AES-GMAC with an IV of length 0, + // since this leaks the authentication key. + System.out.println("Computed mac for test case " + tc); + errors++; + } + } else { + if (eq) { + passedTests++; + } else { + System.out.println( + "Incorrect tag for " + + tc + + " expected:" + + TestUtil.bytesToHex(expectedTag) + + " computed:" + + TestUtil.bytesToHex(computedTag)); + errors++; + } + } + } + } + System.out.println("passed Tests for " + algorithm + ":" + passedTests); + assertEquals(0, errors); + assertEquals(numTests, cntTests); + } + + /** + * Returns an initialized instance of a randomized MAC. + * + * @param algorithm the algorithm. + * @param key the key bytes + * @param iv the bytes of the initialization vector + * @param tagSize the expected size of the tag in bits. + * @return an initialized instance of a MAC. + * @throws GeneralSecurityException if the algorithm or the parameter sizes are not supported or + * if the initialization failed. For example one case are GMACs with a tag size othe than 128 + * bits, since the JCE interface does not seem to support such a specification. + */ + protected static Mac getInitializedMacWithIv(String algorithm, byte[] key, byte[] iv, int tagSize) + throws GeneralSecurityException { + Mac mac = Mac.getInstance(algorithm); + algorithm = algorithm.toUpperCase(Locale.ENGLISH); + if (algorithm.equals("AES-GMAC")) { + SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + if (tagSize != 128) { + throw new InvalidAlgorithmParameterException("only 128-bit tag is supported"); + } + IvParameterSpec params = new IvParameterSpec(iv); + // TODO(bleichen): I'm unaware of a method that allows to specify the tag size in JCE. + // E.g. the following parameter specification does not work (at least not in BC): + // GCMParameterSpec params = new GCMParameterSpec(tagSize, iv); + mac.init(keySpec, params); + return mac; + } else { + throw new NoSuchAlgorithmException(algorithm); + } + } + + /** + * Tests a randomized MAC (i.e. a message authetication that takes an additional IV as + * parameter) against test vectors. + * + * @param filename the JSON file with the test vectors. + * @param algorithm the JCE name of the algorithm to test. + */ + public void testMacWithIv(String filename, String algorithm) throws Exception { + // Checking preconditions. + try { + Mac.getInstance(algorithm); + } catch (NoSuchAlgorithmException ex) { + System.out.println("Algorithm is not supported. Skipping test for " + algorithm); + return; + } + + JsonObject test = JsonUtil.getTestVectors(filename); + int numTests = test.get("numberOfTests").getAsInt(); + int cntTests = 0; + int passedTests = 0; + int errors = 0; + for (JsonElement g : test.getAsJsonArray("testGroups")) { + JsonObject group = g.getAsJsonObject(); + int tagSize = group.get("tagSize").getAsInt(); + for (JsonElement t : group.getAsJsonArray("tests")) { + cntTests++; + JsonObject testcase = t.getAsJsonObject(); + int tcid = testcase.get("tcId").getAsInt(); + String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString(); + byte[] key = getBytes(testcase, "key"); + byte[] iv = getBytes(testcase, "iv"); + byte[] msg = getBytes(testcase, "msg"); + byte[] expectedTag = getBytes(testcase, "tag"); + // Result is one of "valid", "invalid", "acceptable". + // "valid" are test vectors with matching plaintext, ciphertext and tag. + // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag. + // "acceptable" are test vectors with weak parameters or legacy formats. + String result = testcase.get("result").getAsString(); + + Mac mac; + try { + mac = getInitializedMacWithIv(algorithm, key, iv, tagSize); + } catch (GeneralSecurityException ex) { + // Some libraries restrict key size, iv size and tag size. + // Because of the initialization of the Mac might fail. + continue; + } catch (IllegalArgumentException ex) { + // Thrown by javax.crypto.spec.SecretKeySpec (e.g. when the key is empty). + continue; + } + + byte[] computedTag = mac.doFinal(msg); + boolean eq = arrayEquals(expectedTag, computedTag); + if (result.equals("invalid")) { + if (eq) { + // Some test vectors use invalid parameters that should be rejected. + // E.g. an implementation must not allow AES-GMAC with an IV of length 0, + // since this leaks the authentication key. + System.out.println("Computed mac for test case " + tc); + errors++; + } + } else { + if (eq) { + passedTests++; + } else { + System.out.println( + "Incorrect tag for " + + tc + + " expected:" + + TestUtil.bytesToHex(expectedTag) + + " computed:" + + TestUtil.bytesToHex(computedTag)); + errors++; + } + } + } + } + System.out.println("passed Tests for " + algorithm + ":" + passedTests); + assertEquals(0, errors); + assertEquals(numTests, cntTests); + } + + @Test + public void testHmacSha1() throws Exception { + testMac("hmac_sha1_test.json"); + } + + @Test + public void testHmacSha224() throws Exception { + testMac("hmac_sha224_test.json"); + } + + @Test + public void testHmacSha256() throws Exception { + testMac("hmac_sha256_test.json"); + } + + @Test + public void testHmacSha384() throws Exception { + testMac("hmac_sha384_test.json"); + } + + @Test + public void testHmacSha512() throws Exception { + testMac("hmac_sha512_test.json"); + } + + @Test + public void testHmacSha3_224() throws Exception { + testMac("hmac_sha3_224_test.json"); + } + + @Test + public void testHmacSha3_256() throws Exception { + testMac("hmac_sha3_256_test.json"); + } + + @Test + public void testHmacSha3_384() throws Exception { + testMac("hmac_sha3_384_test.json"); + } + + @Test + public void testHmacSha3_512() throws Exception { + testMac("hmac_sha3_512_test.json"); + } + + @Test + public void testAesGmac() throws Exception { + testMacWithIv("gmac_test.json", "AES-GMAC"); + } +} |