1 /* 2 * Copyright (c) 2018, 2020, 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 * @run main ChaCha20NoReuse 29 * @summary ChaCha20 Cipher Implementation (key/nonce reuse protection) 30 */ 31 32 import java.util.*; 33 import javax.crypto.Cipher; 34 import java.security.spec.AlgorithmParameterSpec; 35 import javax.crypto.spec.ChaCha20ParameterSpec; 36 import javax.crypto.spec.IvParameterSpec; 37 import javax.crypto.spec.SecretKeySpec; 38 import javax.crypto.AEADBadTagException; 39 import javax.crypto.SecretKey; 40 import java.security.InvalidKeyException; 41 42 public class ChaCha20NoReuse { 43 44 private static final String ALG_CC20 = "ChaCha20"; 45 private static final String ALG_CC20_P1305 = "ChaCha20-Poly1305"; 46 47 /** 48 * Basic TestMethod interface definition. 49 */ 50 public interface TestMethod { 51 /** 52 * Runs the actual test case 53 * 54 * @param algorithm the algorithm to use (e.g. ChaCha20, etc.) 55 * 56 * @return true if the test passes, false otherwise. 57 */ 58 boolean run(String algorithm); 59 60 /** 61 * Check if this TestMethod can be run for this algorithm. Some tests 62 * are specific to ChaCha20 or ChaCha20-Poly1305, so this method 63 * can be used to determine if a given Cipher type is appropriate. 64 * 65 * @param algorithm the algorithm to use. 66 * 67 * @return true if this test can be run on this algorithm, 68 * false otherwise. 69 */ 70 boolean isValid(String algorithm); 71 } 72 73 public static class TestData { 74 public TestData(String name, String keyStr, String nonceStr, int ctr, 75 int dir, String inputStr, String aadStr, String outStr) { 76 testName = Objects.requireNonNull(name); 77 Hex.Decoder decoder = Hex.decoder(); 78 key = decoder.decode(keyStr); 79 nonce = decoder.decode(nonceStr); 80 if ((counter = ctr) < 0) { 81 throw new IllegalArgumentException( 82 "counter must be 0 or greater"); 83 } 84 direction = dir; 85 if ((direction != Cipher.ENCRYPT_MODE) && 86 (direction != Cipher.DECRYPT_MODE)) { 87 throw new IllegalArgumentException( 88 "Direction must be ENCRYPT_MODE or DECRYPT_MODE"); 89 } 90 input = decoder.decode(inputStr); 91 aad = (aadStr != null) ? decoder.decode(aadStr) : null; 92 expOutput = decoder.decode(outStr); 93 } 94 95 public final String testName; 96 public final byte[] key; 97 public final byte[] nonce; 98 public final int counter; 99 public final int direction; 100 public final byte[] input; 101 public final byte[] aad; 102 public final byte[] expOutput; 103 } 104 105 public static final List<TestData> testList = new LinkedList<TestData>() {{ 106 add(new TestData("RFC 7539 Sample Test Vector [ENCRYPT]", 107 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 108 "000000000000004a00000000", 109 1, Cipher.ENCRYPT_MODE, 110 "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + 111 "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + 112 "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + 113 "637265656e20776f756c642062652069742e", 114 null, 115 "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" + 116 "f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" + 117 "07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" + 118 "5af90bbf74a35be6b40b8eedf2785e42874d")); 119 add(new TestData("RFC 7539 Sample Test Vector [DECRYPT]", 120 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 121 "000000000000004a00000000", 122 1, Cipher.DECRYPT_MODE, 123 "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" + 124 "f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" + 125 "07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" + 126 "5af90bbf74a35be6b40b8eedf2785e42874d", 127 null, 128 "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + 129 "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + 130 "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + 131 "637265656e20776f756c642062652069742e")); 132 }}; 133 134 public static final List<TestData> aeadTestList = 135 new LinkedList<TestData>() {{ 136 add(new TestData("RFC 7539 Sample AEAD Test Vector", 137 "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 138 "070000004041424344454647", 139 1, Cipher.ENCRYPT_MODE, 140 "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + 141 "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + 142 "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + 143 "637265656e20776f756c642062652069742e", 144 "50515253c0c1c2c3c4c5c6c7", 145 "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + 146 "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + 147 "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + 148 "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + 149 "0691")); 150 add(new TestData("RFC 7539 A.5 Sample Decryption", 151 "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", 152 "000000000102030405060708", 153 1, Cipher.DECRYPT_MODE, 154 "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + 155 "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + 156 "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + 157 "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + 158 "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + 159 "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + 160 "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + 161 "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + 162 "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38", 163 "f33388860000000000004e91", 164 "496e7465726e65742d4472616674732061726520647261667420646f63756d65" + 165 "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + 166 "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + 167 "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + 168 "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + 169 "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + 170 "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + 171 "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + 172 "726573732e2fe2809d")); 173 }}; 174 175 /** 176 * Make sure we do not use this Cipher object without initializing it 177 * at all 178 */ 179 public static final TestMethod noInitTest = new TestMethod() { 180 @Override 181 public boolean isValid(String algorithm) { 182 return true; // Valid for all algs 183 } 184 185 @Override 186 public boolean run(String algorithm) { 187 System.out.println("----- No Init Test [" + algorithm + 188 "] -----"); 189 try { 190 Cipher cipher = Cipher.getInstance(algorithm); 191 TestData testData; 192 switch (algorithm) { 193 case ALG_CC20: 194 testData = testList.get(0); 195 break; 196 case ALG_CC20_P1305: 197 testData = aeadTestList.get(0); 198 break; 199 default: 200 throw new IllegalArgumentException( 201 "Unsupported cipher type: " + algorithm); 202 } 203 204 // Attempting to use the cipher without initializing it 205 // should throw an IllegalStateException 206 try { 207 if (algorithm.equals(ALG_CC20_P1305)) { 208 cipher.updateAAD(testData.aad); 209 } 210 cipher.doFinal(testData.input); 211 throw new RuntimeException( 212 "Expected IllegalStateException not thrown"); 213 } catch (IllegalStateException ise) { 214 // Do nothing, this is what we expected to happen 215 } 216 } catch (Exception exc) { 217 System.out.println("Unexpected exception: " + exc); 218 exc.printStackTrace(); 219 return false; 220 } 221 222 return true; 223 } 224 }; 225 226 /** 227 * Make sure we don't allow a double init using the same parameters 228 */ 229 public static final TestMethod doubleInitTest = new TestMethod() { 230 @Override 231 public boolean isValid(String algorithm) { 232 return true; // Valid for all algs 233 } 234 235 @Override 236 public boolean run(String algorithm) { 237 System.out.println("----- Double Init Test [" + algorithm + 238 "] -----"); 239 try { 240 AlgorithmParameterSpec spec; 241 Cipher cipher = Cipher.getInstance(algorithm); 242 TestData testData; 243 switch (algorithm) { 244 case ALG_CC20: 245 testData = testList.get(0); 246 spec = new ChaCha20ParameterSpec(testData.nonce, 247 testData.counter); 248 break; 249 case ALG_CC20_P1305: 250 testData = aeadTestList.get(0); 251 spec = new IvParameterSpec(testData.nonce); 252 break; 253 default: 254 throw new IllegalArgumentException( 255 "Unsupported cipher type: " + algorithm); 256 } 257 SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); 258 259 // Initialize the first time, this should work. 260 cipher.init(testData.direction, key, spec); 261 262 // Immediately initializing a second time with the same 263 // parameters should fail 264 try { 265 cipher.init(testData.direction, key, spec); 266 throw new RuntimeException( 267 "Expected InvalidKeyException not thrown"); 268 } catch (InvalidKeyException ike) { 269 // Do nothing, this is what we expected to happen 270 } 271 } catch (Exception exc) { 272 System.out.println("Unexpected exception: " + exc); 273 exc.printStackTrace(); 274 return false; 275 } 276 277 return true; 278 } 279 }; 280 281 /** 282 * Attempt to run two full encryption operations without an init in 283 * between. 284 */ 285 public static final TestMethod encTwiceNoInit = new TestMethod() { 286 @Override 287 public boolean isValid(String algorithm) { 288 return true; // Valid for all algs 289 } 290 291 @Override 292 public boolean run(String algorithm) { 293 System.out.println("----- Encrypt second time without init [" + 294 algorithm + "] -----"); 295 try { 296 AlgorithmParameterSpec spec; 297 Cipher cipher = Cipher.getInstance(algorithm); 298 TestData testData; 299 switch (algorithm) { 300 case ALG_CC20: 301 testData = testList.get(0); 302 spec = new ChaCha20ParameterSpec(testData.nonce, 303 testData.counter); 304 break; 305 case ALG_CC20_P1305: 306 testData = aeadTestList.get(0); 307 spec = new IvParameterSpec(testData.nonce); 308 break; 309 default: 310 throw new IllegalArgumentException( 311 "Unsupported cipher type: " + algorithm); 312 } 313 SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); 314 315 // Initialize and encrypt 316 cipher.init(testData.direction, key, spec); 317 if (algorithm.equals(ALG_CC20_P1305)) { 318 cipher.updateAAD(testData.aad); 319 } 320 cipher.doFinal(testData.input); 321 System.out.println("First encryption complete"); 322 323 // Now attempt to encrypt again without changing the key/IV 324 // This should fail. 325 try { 326 if (algorithm.equals(ALG_CC20_P1305)) { 327 cipher.updateAAD(testData.aad); 328 } 329 cipher.doFinal(testData.input); 330 throw new RuntimeException( 331 "Expected IllegalStateException not thrown"); 332 } catch (IllegalStateException ise) { 333 // Do nothing, this is what we expected to happen 334 } 335 } catch (Exception exc) { 336 System.out.println("Unexpected exception: " + exc); 337 exc.printStackTrace(); 338 return false; 339 } 340 341 return true; 342 } 343 }; 344 345 /** 346 * Attempt to run two full decryption operations without an init in 347 * between. 348 */ 349 public static final TestMethod decTwiceNoInit = new TestMethod() { 350 @Override 351 public boolean isValid(String algorithm) { 352 return true; // Valid for all algs 353 } 354 355 @Override 356 public boolean run(String algorithm) { 357 System.out.println("----- Decrypt second time without init [" + 358 algorithm + "] -----"); 359 try { 360 AlgorithmParameterSpec spec; 361 Cipher cipher = Cipher.getInstance(algorithm); 362 TestData testData; 363 switch (algorithm) { 364 case ALG_CC20: 365 testData = testList.get(1); 366 spec = new ChaCha20ParameterSpec(testData.nonce, 367 testData.counter); 368 break; 369 case ALG_CC20_P1305: 370 testData = aeadTestList.get(1); 371 spec = new IvParameterSpec(testData.nonce); 372 break; 373 default: 374 throw new IllegalArgumentException( 375 "Unsupported cipher type: " + algorithm); 376 } 377 SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); 378 379 // Initialize and encrypt 380 cipher.init(testData.direction, key, spec); 381 if (algorithm.equals(ALG_CC20_P1305)) { 382 cipher.updateAAD(testData.aad); 383 } 384 cipher.doFinal(testData.input); 385 System.out.println("First decryption complete"); 386 387 // Now attempt to encrypt again without changing the key/IV 388 // This should fail. 389 try { 390 if (algorithm.equals(ALG_CC20_P1305)) { 391 cipher.updateAAD(testData.aad); 392 } 393 cipher.doFinal(testData.input); 394 throw new RuntimeException( 395 "Expected IllegalStateException not thrown"); 396 } catch (IllegalStateException ise) { 397 // Do nothing, this is what we expected to happen 398 } 399 } catch (Exception exc) { 400 System.out.println("Unexpected exception: " + exc); 401 exc.printStackTrace(); 402 return false; 403 } 404 405 return true; 406 } 407 }; 408 409 /** 410 * Perform an AEAD decryption with corrupted data so the tag does not 411 * match. Then attempt to reuse the cipher without initialization. 412 */ 413 public static final TestMethod decFailNoInit = new TestMethod() { 414 @Override 415 public boolean isValid(String algorithm) { 416 return algorithm.equals(ALG_CC20_P1305); 417 } 418 419 @Override 420 public boolean run(String algorithm) { 421 System.out.println( 422 "----- Fail decryption, try again with no init [" + 423 algorithm + "] -----"); 424 try { 425 TestData testData = aeadTestList.get(1); 426 AlgorithmParameterSpec spec = 427 new IvParameterSpec(testData.nonce); 428 byte[] corruptInput = testData.input.clone(); 429 corruptInput[0]++; // Corrupt the ciphertext 430 SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); 431 Cipher cipher = Cipher.getInstance(algorithm); 432 433 try { 434 // Initialize and encrypt 435 cipher.init(testData.direction, key, spec); 436 cipher.updateAAD(testData.aad); 437 cipher.doFinal(corruptInput); 438 throw new RuntimeException( 439 "Expected AEADBadTagException not thrown"); 440 } catch (AEADBadTagException abte) { 441 System.out.println("Expected decryption failure occurred"); 442 } 443 444 // Make sure that despite the exception, the Cipher object is 445 // not in a state that would leave it initialized and able 446 // to process future decryption operations without init. 447 try { 448 cipher.updateAAD(testData.aad); 449 cipher.doFinal(testData.input); 450 throw new RuntimeException( 451 "Expected IllegalStateException not thrown"); 452 } catch (IllegalStateException ise) { 453 // Do nothing, this is what we expected to happen 454 } 455 } catch (Exception exc) { 456 System.out.println("Unexpected exception: " + exc); 457 exc.printStackTrace(); 458 return false; 459 } 460 461 return true; 462 } 463 }; 464 465 /** 466 * Encrypt once successfully, then attempt to init with the same 467 * key and nonce. 468 */ 469 public static final TestMethod encTwiceInitSameParams = new TestMethod() { 470 @Override 471 public boolean isValid(String algorithm) { 472 return true; // Valid for all algs 473 } 474 475 @Override 476 public boolean run(String algorithm) { 477 System.out.println("----- Encrypt, then init with same params [" + 478 algorithm + "] -----"); 479 try { 480 AlgorithmParameterSpec spec; 481 Cipher cipher = Cipher.getInstance(algorithm); 482 TestData testData; 483 switch (algorithm) { 484 case ALG_CC20: 485 testData = testList.get(0); 486 spec = new ChaCha20ParameterSpec(testData.nonce, 487 testData.counter); 488 break; 489 case ALG_CC20_P1305: 490 testData = aeadTestList.get(0); 491 spec = new IvParameterSpec(testData.nonce); 492 break; 493 default: 494 throw new IllegalArgumentException( 495 "Unsupported cipher type: " + algorithm); 496 } 497 SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); 498 499 // Initialize then encrypt 500 cipher.init(testData.direction, key, spec); 501 if (algorithm.equals(ALG_CC20_P1305)) { 502 cipher.updateAAD(testData.aad); 503 } 504 cipher.doFinal(testData.input); 505 System.out.println("First encryption complete"); 506 507 // Initializing after the completed encryption with 508 // the same key and nonce should fail. 509 try { 510 cipher.init(testData.direction, key, spec); 511 throw new RuntimeException( 512 "Expected InvalidKeyException not thrown"); 513 } catch (InvalidKeyException ike) { 514 // Do nothing, this is what we expected to happen 515 } 516 } catch (Exception exc) { 517 System.out.println("Unexpected exception: " + exc); 518 exc.printStackTrace(); 519 return false; 520 } 521 522 return true; 523 } 524 }; 525 526 /** 527 * Decrypt once successfully, then attempt to init with the same 528 * key and nonce. 529 */ 530 public static final TestMethod decTwiceInitSameParams = new TestMethod() { 531 @Override 532 public boolean isValid(String algorithm) { 533 return true; // Valid for all algs 534 } 535 536 @Override 537 public boolean run(String algorithm) { 538 System.out.println("----- Decrypt, then init with same params [" + 539 algorithm + "] -----"); 540 try { 541 AlgorithmParameterSpec spec; 542 Cipher cipher = Cipher.getInstance(algorithm); 543 TestData testData; 544 switch (algorithm) { 545 case ALG_CC20: 546 testData = testList.get(1); 547 spec = new ChaCha20ParameterSpec(testData.nonce, 548 testData.counter); 549 break; 550 case ALG_CC20_P1305: 551 testData = aeadTestList.get(1); 552 spec = new IvParameterSpec(testData.nonce); 553 break; 554 default: 555 throw new IllegalArgumentException( 556 "Unsupported cipher type: " + algorithm); 557 } 558 SecretKey key = new SecretKeySpec(testData.key, ALG_CC20); 559 560 // Initialize then decrypt 561 cipher.init(testData.direction, key, spec); 562 if (algorithm.equals(ALG_CC20_P1305)) { 563 cipher.updateAAD(testData.aad); 564 } 565 cipher.doFinal(testData.input); 566 System.out.println("First decryption complete"); 567 568 // Initializing after the completed decryption with 569 // the same key and nonce should fail. 570 try { 571 cipher.init(testData.direction, key, spec); 572 throw new RuntimeException( 573 "Expected InvalidKeyException not thrown"); 574 } catch (InvalidKeyException ike) { 575 // Do nothing, this is what we expected to happen 576 } 577 } catch (Exception exc) { 578 System.out.println("Unexpected exception: " + exc); 579 exc.printStackTrace(); 580 return false; 581 } 582 583 return true; 584 } 585 }; 586 587 public static final List<String> algList = 588 Arrays.asList(ALG_CC20, ALG_CC20_P1305); 589 590 public static final List<TestMethod> testMethodList = 591 Arrays.asList(noInitTest, doubleInitTest, encTwiceNoInit, 592 decTwiceNoInit, decFailNoInit, encTwiceInitSameParams, 593 decTwiceInitSameParams); 594 595 public static void main(String args[]) throws Exception { 596 int testsPassed = 0; 597 int testNumber = 0; 598 599 for (TestMethod tm : testMethodList) { 600 for (String alg : algList) { 601 if (tm.isValid(alg)) { 602 testNumber++; 603 boolean result = tm.run(alg); 604 System.out.println("Result: " + (result ? "PASS" : "FAIL")); 605 if (result) { 606 testsPassed++; 607 } 608 } 609 } 610 } 611 612 System.out.println("Total Tests: " + testNumber + 613 ", Tests passed: " + testsPassed); 614 if (testsPassed < testNumber) { 615 throw new RuntimeException( 616 "Not all tests passed. See output for failure info"); 617 } 618 } 619 } 620