blob: f146fcf761b141abd07a99701db2ccf2ab1e5ac8 [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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.crypto.tink.subtle;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.google.crypto.tink.KeyWrap;
import com.google.crypto.tink.testing.TestUtil;
import com.google.crypto.tink.testing.WycheproofTestUtil;
import java.security.GeneralSecurityException;
import java.util.Set;
import java.util.TreeSet;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link Kwp}. */
@RunWith(JUnit4.class)
public class KwpTest {
@Test
public void testWrapUnwrapMsgSizes() throws Exception {
byte[] wrapKey = Random.randBytes(16);
KeyWrap wrapper = new Kwp(wrapKey);
for (int wrappedSize = 16; wrappedSize < 128; wrappedSize++) {
byte[] keyMaterialToWrap = Random.randBytes(wrappedSize);
byte[] wrapped = wrapper.wrap(keyMaterialToWrap);
byte[] unwrapped = wrapper.unwrap(wrapped);
assertArrayEquals(keyMaterialToWrap, unwrapped);
}
}
@Test
public void testInvalidKeySizes() throws Exception {
// Tests the wrapping key. Its key size is either 16 or 32.
for (int i = 0; i < 255; i++) {
if (i == 16 || i == 32) {
continue;
}
try {
KeyWrap unused = new Kwp(new byte[i]);
fail("Constructed wrapper with invalid key size");
} catch (GeneralSecurityException ex) {
// expected
}
}
}
@Test
public void testInvalidWrappingSizes() throws Exception {
byte[] wrapKey = Random.randBytes(16);
KeyWrap wrapper = new Kwp(wrapKey);
for (int wrappedSize = 0; wrappedSize < 16; wrappedSize++) {
try {
wrapper.wrap(new byte[wrappedSize]);
fail("Should not wrap short keys");
} catch (GeneralSecurityException ex) {
// expected
}
}
}
@Test
public void testWycheproof() throws Exception {
final String expectedVersion = "0.6";
JSONObject json =
WycheproofTestUtil.readJson("../wycheproof/testvectors/kwp_test.json");
Set<String> exceptions = new TreeSet<String>();
String generatorVersion = json.getString("generatorVersion");
if (!generatorVersion.equals(expectedVersion)) {
System.out.printf("Expecting test vectors with version %s found version %s.\n",
expectedVersion, generatorVersion);
}
int errors = 0;
JSONArray testGroups = json.getJSONArray("testGroups");
for (int i = 0; i < testGroups.length(); i++) {
JSONObject group = testGroups.getJSONObject(i);
JSONArray tests = group.getJSONArray("tests");
for (int j = 0; j < tests.length(); j++) {
JSONObject testcase = tests.getJSONObject(j);
int tcid = testcase.getInt("tcId");
String tc = "tcId: " + tcid + " " + testcase.getString("comment");
byte[] key = Hex.decode(testcase.getString("key"));
byte[] data = Hex.decode(testcase.getString("msg"));
byte[] expected = Hex.decode(testcase.getString("ct"));
// 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.getString("result");
// Test wrapping
KeyWrap wrapper;
try {
wrapper = new Kwp(key);
} catch (GeneralSecurityException ex) {
// tink restrict the key sizes to 128 or 256 bits.
if (key.length == 16 || key.length == 32) {
System.out.printf("Rejected valid key:%s\n", tc);
System.out.println(ex.toString());
errors++;
}
continue;
}
try {
byte[] wrapped = wrapper.wrap(data);
boolean eq = TestUtil.arrayEquals(expected, wrapped);
if (result.equals("invalid")) {
if (eq) {
// Some test vectors use invalid parameters that should be rejected.
System.out.printf("Wrapped test case:%s\n", tc);
errors++;
}
} else {
if (!eq) {
System.out.printf("Incorrect wrapping for test case:%s wrapped bytes:%s\n",
tc, Hex.encode(wrapped));
errors++;
}
}
} catch (GeneralSecurityException ex) {
if (result.equals("valid")) {
System.out.printf("Failed to wrap test case:%s\n", tc);
errors++;
}
} catch (Exception ex) {
// Other exceptions are violating the interface.
System.out.printf("Test case %s throws %s.\n", tc, ex);
errors++;
}
// Test unwrapping
// The algorithms tested in this class are typically malleable. Hence, it is in possible
// that modifying ciphertext randomly results in some other valid ciphertext.
// However, all the test vectors in Wycheproof are constructed such that they have
// invalid padding. If this changes then the test below is too strict.
try {
byte[] unwrapped = wrapper.unwrap(expected);
boolean eq = TestUtil.arrayEquals(data, unwrapped);
if (result.equals("invalid")) {
System.out.printf("Unwrapped invalid test case:%s unwrapped:%s\n", tc,
Hex.encode(unwrapped));
errors++;
} else {
if (!eq) {
System.out.printf("Incorrect unwrap. Excepted:%s actual:%s\n",
Hex.encode(data), Hex.encode(unwrapped));
errors++;
}
}
} catch (GeneralSecurityException ex) {
// Trying to unwrap an invalid key should always result in a GeneralSecurityException
// or a subclass of it.
exceptions.add(ex.toString());
if (result.equals("valid")) {
System.out.printf("Failed to unwrap:%s\n", tc);
errors++;
}
} catch (Exception ex) {
// Other exceptions indicate a programming error.
System.out.printf("Test case:%s throws %s\n", tc, ex);
exceptions.add(ex.toString());
errors++;
}
}
}
// Even though strong pseudorandomness implies that information about incorrectly formatted
// ciphertexts is not helpful to an attacker, we still don't want to do this and expect
// exceptions that do not carry information about the unwrapped data.
System.out.printf("Number of distinct exceptions:%d\n", exceptions.size());
for (String ex : exceptions) {
System.out.println(ex);
}
assertEquals(0, errors);
}
}