1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 8153029 27 * @library /test/lib 28 * @build jdk.test.lib.Convert 29 * @run main ChaCha20Poly1305ParamTest 30 * @summary ChaCha20 Cipher Implementation (parameters) 31 */ 32 33 import java.util.*; 34 import java.io.IOException; 35 import java.security.GeneralSecurityException; 36 import javax.crypto.Cipher; 37 import javax.crypto.SecretKey; 38 import javax.crypto.spec.ChaCha20ParameterSpec; 39 import javax.crypto.spec.SecretKeySpec; 40 import javax.crypto.AEADBadTagException; 41 import java.security.spec.AlgorithmParameterSpec; 42 import java.security.AlgorithmParameters; 43 import java.security.NoSuchAlgorithmException; 44 import java.nio.ByteBuffer; 45 import jdk.test.lib.Convert; 46 47 public class ChaCha20Poly1305ParamTest { 48 public static class TestData { 49 public TestData(String name, String keyStr, String nonceStr, int ctr, 50 int dir, String inputStr, String aadStr, String outStr) { 51 testName = Objects.requireNonNull(name); 52 key = Convert.hexStringToByteArray(Objects.requireNonNull(keyStr)); 53 nonce = Convert.hexStringToByteArray( 54 Objects.requireNonNull(nonceStr)); 55 if ((counter = ctr) < 0) { 56 throw new IllegalArgumentException( 57 "counter must be 0 or greater"); 58 } 59 direction = dir; 60 if ((direction != Cipher.ENCRYPT_MODE) && 61 (direction != Cipher.DECRYPT_MODE)) { 62 throw new IllegalArgumentException( 63 "Direction must be ENCRYPT_MODE or DECRYPT_MODE"); 64 } 65 input = Convert.hexStringToByteArray( 66 Objects.requireNonNull(inputStr)); 67 aad = (aadStr != null) ? 68 Convert.hexStringToByteArray(aadStr) : null; 69 expOutput = Convert.hexStringToByteArray( 70 Objects.requireNonNull(outStr)); 71 } 72 73 public final String testName; 74 public final byte[] key; 75 public final byte[] nonce; 76 public final int counter; 77 public final int direction; 78 public final byte[] input; 79 public final byte[] aad; 80 public final byte[] expOutput; 81 } 82 83 public static final List<TestData> aeadTestList = 84 new LinkedList<TestData>() {{ 85 add(new TestData("RFC 7539 Sample AEAD Test Vector", 86 "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 87 "070000004041424344454647", 88 1, Cipher.ENCRYPT_MODE, 89 "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + 90 "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + 91 "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + 92 "637265656e20776f756c642062652069742e", 93 "50515253c0c1c2c3c4c5c6c7", 94 "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + 95 "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + 96 "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + 97 "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + 98 "0691")); 99 add(new TestData("RFC 7539 A.5 Sample Decryption", 100 "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", 101 "000000000102030405060708", 102 1, Cipher.DECRYPT_MODE, 103 "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + 104 "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + 105 "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + 106 "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + 107 "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + 108 "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + 109 "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + 110 "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + 111 "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38", 112 "f33388860000000000004e91", 113 "496e7465726e65742d4472616674732061726520647261667420646f63756d65" + 114 "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + 115 "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + 116 "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + 117 "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + 118 "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + 119 "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + 120 "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + 121 "726573732e2fe2809d")); 122 }}; 123 124 // 12-byte nonce DER-encoded as an OCTET_STRING 125 public static final byte[] NONCE_OCTET_STR_12 = { 126 4, 12, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8 127 }; 128 129 // Invalid 16-byte nonce DER-encoded as an OCTET_STRING 130 public static final byte[] NONCE_OCTET_STR_16 = { 131 4, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 132 }; 133 134 // Throwaway key for default init tests 135 public static final SecretKey DEF_KEY = new SecretKeySpec(new byte[] 136 { 137 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 138 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 139 }, "ChaCha20"); 140 141 public static void main(String args[]) throws Exception { 142 int testsPassed = 0; 143 int testNumber = 0; 144 145 // Try some default initializations 146 testDefaultAlgParams("ChaCha20", Cipher.ENCRYPT_MODE, true); 147 testDefaultAlgParams("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true); 148 testDefaultAlgParamSpec("ChaCha20", Cipher.ENCRYPT_MODE, true); 149 testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true); 150 testDefaultAlgParams("ChaCha20", Cipher.DECRYPT_MODE, false); 151 testDefaultAlgParams("ChaCha20-Poly1305", Cipher.DECRYPT_MODE, false); 152 testDefaultAlgParamSpec("ChaCha20", Cipher.DECRYPT_MODE, false); 153 testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.DECRYPT_MODE, 154 false); 155 156 // Try (and hopefully fail) to create a ChaCha20 AlgorithmParameterSpec 157 System.out.println( 158 "*** Test: Try to make ChaCha20 AlgorithmParameterSpec"); 159 try { 160 ChaCha20ParameterSpec badChaCha20Spec = 161 new ChaCha20ParameterSpec(NONCE_OCTET_STR_16, 1); 162 throw new RuntimeException("ChaCha20 AlgorithmParameterSpec " + 163 "with 16 byte nonce should fail"); 164 } catch (IllegalArgumentException iae) { 165 System.out.println("Caught expected exception: " + iae); 166 } 167 168 // Try (and hopefully fail) to create a ChaCha20 AlgorithmParameters 169 System.out.println( 170 "*** Test: Try to make ChaCha20 AlgorithmParameters"); 171 try { 172 AlgorithmParameters apsNoChaCha20 = 173 AlgorithmParameters.getInstance("ChaCha20"); 174 throw new RuntimeException( 175 "ChaCha20 AlgorithmParameters should fail"); 176 } catch (NoSuchAlgorithmException nsae) { 177 System.out.println("Caught expected exception: " + nsae); 178 } 179 180 // Create the AlgorithmParameters object from a valid encoding 181 System.out.println("*** Test: Create and init ChaCha20-Poly1305 APS"); 182 AlgorithmParameters apsGood = 183 AlgorithmParameters.getInstance("ChaCha20-Poly1305"); 184 apsGood.init(NONCE_OCTET_STR_12); 185 System.out.println("Test Passed"); 186 187 // Pull an AlgorithmParameters object out of the initialized cipher 188 // and compare its value against the original. 189 System.out.println("*** Test: Init ChaCha20-Poly1305 Cipher using " + 190 "AP, retrieve AP and compare"); 191 Cipher cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305"); 192 cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY, apsGood); 193 AlgorithmParameters pulledParams = cc20p1305.getParameters(); 194 byte[] apsGoodData = apsGood.getEncoded(); 195 byte[] pulledParamsData = pulledParams.getEncoded(); 196 if (!Arrays.equals(apsGoodData, pulledParamsData)) { 197 throw new RuntimeException( 198 "Retrieved parameters do not match those used to init cipher"); 199 } 200 System.out.println("Test Passed"); 201 202 // Try the same test with ChaCha20. It should always be null. 203 System.out.println("*** Test: Init ChaCha20 Cipher using " + 204 "AP, retrieve AP and compare"); 205 Cipher cc20 = Cipher.getInstance("ChaCha20"); 206 cc20.init(Cipher.ENCRYPT_MODE, DEF_KEY); 207 pulledParams = cc20.getParameters(); 208 if (pulledParams != null) { 209 throw new RuntimeException("Unexpected non-null " + 210 "AlgorithmParameters from ChaCha20 cipiher"); 211 } 212 System.out.println("Test Passed"); 213 214 // Create and try to init using invalid encoding 215 AlgorithmParameters apsBad = 216 AlgorithmParameters.getInstance("ChaCha20-Poly1305"); 217 System.out.println("*** Test: Use invalid encoding scheme"); 218 try { 219 apsBad.init(NONCE_OCTET_STR_12, "OraclePrivate"); 220 throw new RuntimeException("Allowed unsupported encoding scheme: " + 221 apsBad.getAlgorithm()); 222 } catch (IOException iae) { 223 System.out.println("Caught expected exception: " + iae); 224 } 225 226 // Try to init using supported scheme but invalid length 227 System.out.println("*** Test: Use supported scheme, nonce too large"); 228 try { 229 apsBad.init(NONCE_OCTET_STR_16, "ASN.1"); 230 throw new RuntimeException("Allowed invalid encoded length"); 231 } catch (IOException ioe) { 232 System.out.println("Caught expected exception: " + ioe); 233 } 234 235 System.out.println("----- AEAD Tests -----"); 236 for (TestData test : aeadTestList) { 237 System.out.println("*** Test " + ++testNumber + ": " + 238 test.testName); 239 if (runAEADTest(test)) { 240 testsPassed++; 241 } 242 } 243 System.out.println(); 244 245 System.out.println("Total tests: " + testNumber + 246 ", Passed: " + testsPassed + ", Failed: " + 247 (testNumber - testsPassed)); 248 if (testsPassed != testNumber) { 249 throw new RuntimeException("One or more tests failed. " + 250 "Check output for details"); 251 } 252 } 253 254 /** 255 * Attempt default inits with null AlgorithmParameters 256 * 257 * @param alg the algorithm (ChaCha20, ChaCha20-Poly1305) 258 * @param mode the Cipher mode (ENCRYPT_MODE, etc.) 259 */ 260 private static void testDefaultAlgParams(String alg, int mode, 261 boolean shouldPass) { 262 byte[] ivOne = null, ivTwo = null; 263 System.out.println("Test default AlgorithmParameters: Cipher = " + 264 alg + ", mode = " + mode); 265 try { 266 AlgorithmParameters params = null; 267 Cipher cipher = Cipher.getInstance(alg); 268 cipher.init(mode, DEF_KEY, params, null); 269 ivOne = cipher.getIV(); 270 cipher.init(mode, DEF_KEY, params, null); 271 ivTwo = cipher.getIV(); 272 if (!shouldPass) { 273 throw new RuntimeException( 274 "Did not receive expected exception"); 275 } 276 } catch (GeneralSecurityException gse) { 277 if (shouldPass) { 278 throw new RuntimeException(gse); 279 } 280 System.out.println("Caught expected exception: " + gse); 281 return; 282 } 283 if (Arrays.equals(ivOne, ivTwo)) { 284 throw new RuntimeException( 285 "FAIL! Two inits generated same nonces"); 286 } else { 287 System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " ")); 288 System.out.println("IV 1:\n" + dumpHexBytes(ivTwo, 16, "\n", " ")); 289 } 290 } 291 292 /** 293 * Attempt default inits with null AlgorithmParameters 294 * 295 * @param alg the algorithm (ChaCha20, ChaCha20-Poly1305) 296 * @param mode the Cipher mode (ENCRYPT_MODE, etc.) 297 */ 298 private static void testDefaultAlgParamSpec(String alg, int mode, 299 boolean shouldPass) { 300 byte[] ivOne = null, ivTwo = null; 301 System.out.println("Test default AlgorithmParameterSpec: Cipher = " + 302 alg + ", mode = " + mode); 303 try { 304 AlgorithmParameterSpec params = null; 305 Cipher cipher = Cipher.getInstance(alg); 306 cipher.init(mode, DEF_KEY, params, null); 307 ivOne = cipher.getIV(); 308 cipher.init(mode, DEF_KEY, params, null); 309 ivTwo = cipher.getIV(); 310 if (!shouldPass) { 311 throw new RuntimeException( 312 "Did not receive expected exception"); 313 } 314 } catch (GeneralSecurityException gse) { 315 if (shouldPass) { 316 throw new RuntimeException(gse); 317 } 318 System.out.println("Caught expected exception: " + gse); 319 return; 320 } 321 if (Arrays.equals(ivOne, ivTwo)) { 322 throw new RuntimeException( 323 "FAIL! Two inits generated same nonces"); 324 } else { 325 System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " ")); 326 System.out.println("IV 2:\n" + dumpHexBytes(ivTwo, 16, "\n", " ")); 327 } 328 } 329 330 private static boolean runAEADTest(TestData testData) 331 throws GeneralSecurityException, IOException { 332 boolean result = false; 333 334 Cipher mambo = Cipher.getInstance("ChaCha20-Poly1305"); 335 SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20"); 336 AlgorithmParameters mamboParams = 337 AlgorithmParameters.getInstance("ChaCha20-Poly1305"); 338 339 // Put the nonce into ASN.1 ChaCha20-Poly1305 parameter format 340 byte[] derNonce = new byte[testData.nonce.length + 2]; 341 derNonce[0] = 0x04; 342 derNonce[1] = (byte)testData.nonce.length; 343 System.arraycopy(testData.nonce, 0, derNonce, 2, 344 testData.nonce.length); 345 mamboParams.init(derNonce); 346 347 mambo.init(testData.direction, mamboKey, mamboParams); 348 349 byte[] out = new byte[mambo.getOutputSize(testData.input.length)]; 350 int outOff = 0; 351 try { 352 mambo.updateAAD(testData.aad); 353 outOff += mambo.update(testData.input, 0, testData.input.length, 354 out, outOff); 355 outOff += mambo.doFinal(out, outOff); 356 } catch (AEADBadTagException abte) { 357 // If we get a bad tag or derive a tag mismatch, log it 358 // and register it as a failure 359 System.out.println("FAIL: " + abte); 360 return false; 361 } 362 363 if (!Arrays.equals(out, testData.expOutput)) { 364 System.out.println("ERROR - Output Mismatch!"); 365 System.out.println("Expected:\n" + 366 dumpHexBytes(testData.expOutput, 16, "\n", " ")); 367 System.out.println("Actual:\n" + 368 dumpHexBytes(out, 16, "\n", " ")); 369 System.out.println(); 370 } else { 371 result = true; 372 } 373 374 return result; 375 } 376 377 /** 378 * Dump the hex bytes of a buffer into string form. 379 * 380 * @param data The array of bytes to dump to stdout. 381 * @param itemsPerLine The number of bytes to display per line 382 * if the {@code lineDelim} character is blank then all bytes 383 * will be printed on a single line. 384 * @param lineDelim The delimiter between lines 385 * @param itemDelim The delimiter between bytes 386 * 387 * @return The hexdump of the byte array 388 */ 389 private static String dumpHexBytes(byte[] data, int itemsPerLine, 390 String lineDelim, String itemDelim) { 391 return dumpHexBytes(ByteBuffer.wrap(data), itemsPerLine, lineDelim, 392 itemDelim); 393 } 394 395 private static String dumpHexBytes(ByteBuffer data, int itemsPerLine, 396 String lineDelim, String itemDelim) { 397 StringBuilder sb = new StringBuilder(); 398 if (data != null) { 399 data.mark(); 400 int i = 0; 401 while (data.remaining() > 0) { 402 if (i % itemsPerLine == 0 && i != 0) { 403 sb.append(lineDelim); 404 } 405 sb.append(String.format("%02X", data.get())).append(itemDelim); 406 i++; 407 } 408 data.reset(); 409 } 410 411 return sb.toString(); 412 } 413 } 414