1 // Copyright 2017 Google Inc. 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.subtle; 18 19 import static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertThrows; 22 23 import com.google.crypto.tink.KeyWrap; 24 import com.google.crypto.tink.testing.TestUtil; 25 import com.google.crypto.tink.testing.WycheproofTestUtil; 26 import com.google.gson.JsonArray; 27 import com.google.gson.JsonObject; 28 import java.security.GeneralSecurityException; 29 import java.util.Set; 30 import java.util.TreeSet; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.junit.runners.JUnit4; 34 35 /** Unit tests for {@link Kwp}. */ 36 @RunWith(JUnit4.class) 37 public class KwpTest { 38 39 @Test testWrapUnwrapMsgSizes()40 public void testWrapUnwrapMsgSizes() throws Exception { 41 byte[] wrapKey = Random.randBytes(16); 42 KeyWrap wrapper = new Kwp(wrapKey); 43 for (int wrappedSize = 16; wrappedSize < 128; wrappedSize++) { 44 byte[] keyMaterialToWrap = Random.randBytes(wrappedSize); 45 byte[] wrapped = wrapper.wrap(keyMaterialToWrap); 46 byte[] unwrapped = wrapper.unwrap(wrapped); 47 assertArrayEquals(keyMaterialToWrap, unwrapped); 48 } 49 } 50 51 @Test testInvalidKeySizes()52 public void testInvalidKeySizes() throws Exception { 53 // Tests the wrapping key. Its key size is either 16 or 32. 54 for (int j = 0; j < 255; j++) { 55 final int i = j; 56 if (i == 16 || i == 32) { 57 continue; 58 } 59 assertThrows( 60 GeneralSecurityException.class, 61 () -> { 62 KeyWrap unused = new Kwp(new byte[i]); 63 }); 64 } 65 } 66 67 @Test testInvalidWrappingSizes()68 public void testInvalidWrappingSizes() throws Exception { 69 byte[] wrapKey = Random.randBytes(16); 70 KeyWrap wrapper = new Kwp(wrapKey); 71 for (int i = 0; i < 16; i++) { 72 final int wrappedSize = i; 73 assertThrows(GeneralSecurityException.class, () -> wrapper.wrap(new byte[wrappedSize])); 74 } 75 } 76 77 @Test testWycheproof()78 public void testWycheproof() throws Exception { 79 final String expectedVersion = "0.6"; 80 JsonObject json = 81 WycheproofTestUtil.readJson("../wycheproof/testvectors/kwp_test.json"); 82 Set<String> exceptions = new TreeSet<String>(); 83 String generatorVersion = json.get("generatorVersion").getAsString(); 84 if (!generatorVersion.equals(expectedVersion)) { 85 System.out.printf("Expecting test vectors with version %s found version %s.\n", 86 expectedVersion, generatorVersion); 87 } 88 int errors = 0; 89 JsonArray testGroups = json.getAsJsonArray("testGroups"); 90 for (int i = 0; i < testGroups.size(); i++) { 91 JsonObject group = testGroups.get(i).getAsJsonObject(); 92 JsonArray tests = group.getAsJsonArray("tests"); 93 for (int j = 0; j < tests.size(); j++) { 94 JsonObject testcase = tests.get(j).getAsJsonObject(); 95 int tcid = testcase.get("tcId").getAsInt(); 96 String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString(); 97 byte[] key = Hex.decode(testcase.get("key").getAsString()); 98 byte[] data = Hex.decode(testcase.get("msg").getAsString()); 99 byte[] expected = Hex.decode(testcase.get("ct").getAsString()); 100 // Result is one of "valid", "invalid", "acceptable". 101 // "valid" are test vectors with matching plaintext, ciphertext and tag. 102 // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag. 103 // "acceptable" are test vectors with weak parameters or legacy formats. 104 String result = testcase.get("result").getAsString(); 105 106 // Test wrapping 107 KeyWrap wrapper; 108 try { 109 wrapper = new Kwp(key); 110 } catch (GeneralSecurityException ex) { 111 // tink restrict the key sizes to 128 or 256 bits. 112 if (key.length == 16 || key.length == 32) { 113 System.out.printf("Rejected valid key:%s\n", tc); 114 System.out.println(ex.toString()); 115 errors++; 116 } 117 continue; 118 } 119 try { 120 byte[] wrapped = wrapper.wrap(data); 121 boolean eq = TestUtil.arrayEquals(expected, wrapped); 122 if (result.equals("invalid")) { 123 if (eq) { 124 // Some test vectors use invalid parameters that should be rejected. 125 System.out.printf("Wrapped test case:%s\n", tc); 126 errors++; 127 } 128 } else { 129 if (!eq) { 130 System.out.printf("Incorrect wrapping for test case:%s wrapped bytes:%s\n", 131 tc, Hex.encode(wrapped)); 132 errors++; 133 } 134 } 135 } catch (GeneralSecurityException ex) { 136 if (result.equals("valid")) { 137 System.out.printf("Failed to wrap test case:%s\n", tc); 138 errors++; 139 } 140 } catch (Exception ex) { 141 // Other exceptions are violating the interface. 142 System.out.printf("Test case %s throws %s.\n", tc, ex); 143 errors++; 144 } 145 146 // Test unwrapping 147 // The algorithms tested in this class are typically malleable. Hence, it is in possible 148 // that modifying ciphertext randomly results in some other valid ciphertext. 149 // However, all the test vectors in Wycheproof are constructed such that they have 150 // invalid padding. If this changes then the test below is too strict. 151 try { 152 byte[] unwrapped = wrapper.unwrap(expected); 153 boolean eq = TestUtil.arrayEquals(data, unwrapped); 154 if (result.equals("invalid")) { 155 System.out.printf("Unwrapped invalid test case:%s unwrapped:%s\n", tc, 156 Hex.encode(unwrapped)); 157 errors++; 158 } else { 159 if (!eq) { 160 System.out.printf("Incorrect unwrap. Excepted:%s actual:%s\n", 161 Hex.encode(data), Hex.encode(unwrapped)); 162 errors++; 163 } 164 } 165 } catch (GeneralSecurityException ex) { 166 // Trying to unwrap an invalid key should always result in a GeneralSecurityException 167 // or a subclass of it. 168 exceptions.add(ex.toString()); 169 if (result.equals("valid")) { 170 System.out.printf("Failed to unwrap:%s\n", tc); 171 errors++; 172 } 173 } catch (Exception ex) { 174 // Other exceptions indicate a programming error. 175 System.out.printf("Test case:%s throws %s\n", tc, ex); 176 exceptions.add(ex.toString()); 177 errors++; 178 } 179 } 180 } 181 // Even though strong pseudorandomness implies that information about incorrectly formatted 182 // ciphertexts is not helpful to an attacker, we still don't want to do this and expect 183 // exceptions that do not carry information about the unwrapped data. 184 System.out.printf("Number of distinct exceptions:%d\n", exceptions.size()); 185 for (String ex : exceptions) { 186 System.out.println(ex); 187 } 188 assertEquals(0, errors); 189 } 190 } 191