Path: blob/master/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20NoReuse.java
41161 views
/*1* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/**24* @test25* @bug 815302926* @library /test/lib27* @run main ChaCha20NoReuse28* @summary ChaCha20 Cipher Implementation (key/nonce reuse protection)29*/3031import java.util.*;32import javax.crypto.Cipher;33import java.security.spec.AlgorithmParameterSpec;34import javax.crypto.spec.ChaCha20ParameterSpec;35import javax.crypto.spec.IvParameterSpec;36import javax.crypto.spec.SecretKeySpec;37import javax.crypto.AEADBadTagException;38import javax.crypto.SecretKey;39import java.security.InvalidKeyException;4041public class ChaCha20NoReuse {4243private static final String ALG_CC20 = "ChaCha20";44private static final String ALG_CC20_P1305 = "ChaCha20-Poly1305";4546/**47* Basic TestMethod interface definition.48*/49public interface TestMethod {50/**51* Runs the actual test case52*53* @param algorithm the algorithm to use (e.g. ChaCha20, etc.)54*55* @return true if the test passes, false otherwise.56*/57boolean run(String algorithm);5859/**60* Check if this TestMethod can be run for this algorithm. Some tests61* are specific to ChaCha20 or ChaCha20-Poly1305, so this method62* can be used to determine if a given Cipher type is appropriate.63*64* @param algorithm the algorithm to use.65*66* @return true if this test can be run on this algorithm,67* false otherwise.68*/69boolean isValid(String algorithm);70}7172public static class TestData {73public TestData(String name, String keyStr, String nonceStr, int ctr,74int dir, String inputStr, String aadStr, String outStr) {75testName = Objects.requireNonNull(name);76HexFormat hex = HexFormat.of();77key = hex.parseHex(keyStr);78nonce = hex.parseHex(nonceStr);79if ((counter = ctr) < 0) {80throw new IllegalArgumentException(81"counter must be 0 or greater");82}83direction = dir;84if ((direction != Cipher.ENCRYPT_MODE) &&85(direction != Cipher.DECRYPT_MODE)) {86throw new IllegalArgumentException(87"Direction must be ENCRYPT_MODE or DECRYPT_MODE");88}89input = hex.parseHex(inputStr);90aad = (aadStr != null) ? hex.parseHex(aadStr) : null;91expOutput = hex.parseHex(outStr);92}9394public final String testName;95public final byte[] key;96public final byte[] nonce;97public final int counter;98public final int direction;99public final byte[] input;100public final byte[] aad;101public final byte[] expOutput;102}103104public static final List<TestData> testList = new LinkedList<TestData>() {{105add(new TestData("RFC 7539 Sample Test Vector [ENCRYPT]",106"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",107"000000000000004a00000000",1081, Cipher.ENCRYPT_MODE,109"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +110"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +111"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +112"637265656e20776f756c642062652069742e",113null,114"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" +115"f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" +116"07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" +117"5af90bbf74a35be6b40b8eedf2785e42874d"));118add(new TestData("RFC 7539 Sample Test Vector [DECRYPT]",119"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",120"000000000000004a00000000",1211, Cipher.DECRYPT_MODE,122"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" +123"f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" +124"07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" +125"5af90bbf74a35be6b40b8eedf2785e42874d",126null,127"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +128"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +129"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +130"637265656e20776f756c642062652069742e"));131}};132133public static final List<TestData> aeadTestList =134new LinkedList<TestData>() {{135add(new TestData("RFC 7539 Sample AEAD Test Vector",136"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",137"070000004041424344454647",1381, Cipher.ENCRYPT_MODE,139"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +140"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +141"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +142"637265656e20776f756c642062652069742e",143"50515253c0c1c2c3c4c5c6c7",144"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" +145"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" +146"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" +147"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" +148"0691"));149add(new TestData("RFC 7539 A.5 Sample Decryption",150"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",151"000000000102030405060708",1521, Cipher.DECRYPT_MODE,153"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" +154"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" +155"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" +156"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" +157"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" +158"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" +159"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" +160"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" +161"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38",162"f33388860000000000004e91",163"496e7465726e65742d4472616674732061726520647261667420646f63756d65" +164"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" +165"6f6e74687320616e64206d617920626520757064617465642c207265706c6163" +166"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" +167"6e747320617420616e792074696d652e20497420697320696e617070726f7072" +168"6961746520746f2075736520496e7465726e65742d4472616674732061732072" +169"65666572656e6365206d6174657269616c206f7220746f206369746520746865" +170"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" +171"726573732e2fe2809d"));172}};173174/**175* Make sure we do not use this Cipher object without initializing it176* at all177*/178public static final TestMethod noInitTest = new TestMethod() {179@Override180public boolean isValid(String algorithm) {181return true; // Valid for all algs182}183184@Override185public boolean run(String algorithm) {186System.out.println("----- No Init Test [" + algorithm +187"] -----");188try {189Cipher cipher = Cipher.getInstance(algorithm);190TestData testData;191switch (algorithm) {192case ALG_CC20:193testData = testList.get(0);194break;195case ALG_CC20_P1305:196testData = aeadTestList.get(0);197break;198default:199throw new IllegalArgumentException(200"Unsupported cipher type: " + algorithm);201}202203// Attempting to use the cipher without initializing it204// should throw an IllegalStateException205try {206if (algorithm.equals(ALG_CC20_P1305)) {207cipher.updateAAD(testData.aad);208}209cipher.doFinal(testData.input);210throw new RuntimeException(211"Expected IllegalStateException not thrown");212} catch (IllegalStateException ise) {213// Do nothing, this is what we expected to happen214}215} catch (Exception exc) {216System.out.println("Unexpected exception: " + exc);217exc.printStackTrace();218return false;219}220221return true;222}223};224225/**226* Make sure we don't allow a double init using the same parameters227*/228public static final TestMethod doubleInitTest = new TestMethod() {229@Override230public boolean isValid(String algorithm) {231return true; // Valid for all algs232}233234@Override235public boolean run(String algorithm) {236System.out.println("----- Double Init Test [" + algorithm +237"] -----");238try {239AlgorithmParameterSpec spec;240Cipher cipher = Cipher.getInstance(algorithm);241TestData testData;242switch (algorithm) {243case ALG_CC20:244testData = testList.get(0);245spec = new ChaCha20ParameterSpec(testData.nonce,246testData.counter);247break;248case ALG_CC20_P1305:249testData = aeadTestList.get(0);250spec = new IvParameterSpec(testData.nonce);251break;252default:253throw new IllegalArgumentException(254"Unsupported cipher type: " + algorithm);255}256SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);257258// Initialize the first time, this should work.259cipher.init(testData.direction, key, spec);260261// Immediately initializing a second time with the same262// parameters should fail263try {264cipher.init(testData.direction, key, spec);265throw new RuntimeException(266"Expected InvalidKeyException not thrown");267} catch (InvalidKeyException ike) {268// Do nothing, this is what we expected to happen269}270} catch (Exception exc) {271System.out.println("Unexpected exception: " + exc);272exc.printStackTrace();273return false;274}275276return true;277}278};279280/**281* Attempt to run two full encryption operations without an init in282* between.283*/284public static final TestMethod encTwiceNoInit = new TestMethod() {285@Override286public boolean isValid(String algorithm) {287return true; // Valid for all algs288}289290@Override291public boolean run(String algorithm) {292System.out.println("----- Encrypt second time without init [" +293algorithm + "] -----");294try {295AlgorithmParameterSpec spec;296Cipher cipher = Cipher.getInstance(algorithm);297TestData testData;298switch (algorithm) {299case ALG_CC20:300testData = testList.get(0);301spec = new ChaCha20ParameterSpec(testData.nonce,302testData.counter);303break;304case ALG_CC20_P1305:305testData = aeadTestList.get(0);306spec = new IvParameterSpec(testData.nonce);307break;308default:309throw new IllegalArgumentException(310"Unsupported cipher type: " + algorithm);311}312SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);313314// Initialize and encrypt315cipher.init(testData.direction, key, spec);316if (algorithm.equals(ALG_CC20_P1305)) {317cipher.updateAAD(testData.aad);318}319cipher.doFinal(testData.input);320System.out.println("First encryption complete");321322// Now attempt to encrypt again without changing the key/IV323// This should fail.324try {325if (algorithm.equals(ALG_CC20_P1305)) {326cipher.updateAAD(testData.aad);327}328cipher.doFinal(testData.input);329throw new RuntimeException(330"Expected IllegalStateException not thrown");331} catch (IllegalStateException ise) {332// Do nothing, this is what we expected to happen333}334} catch (Exception exc) {335System.out.println("Unexpected exception: " + exc);336exc.printStackTrace();337return false;338}339340return true;341}342};343344/**345* Attempt to run two full decryption operations without an init in346* between.347*/348public static final TestMethod decTwiceNoInit = new TestMethod() {349@Override350public boolean isValid(String algorithm) {351return true; // Valid for all algs352}353354@Override355public boolean run(String algorithm) {356System.out.println("----- Decrypt second time without init [" +357algorithm + "] -----");358try {359AlgorithmParameterSpec spec;360Cipher cipher = Cipher.getInstance(algorithm);361TestData testData;362switch (algorithm) {363case ALG_CC20:364testData = testList.get(1);365spec = new ChaCha20ParameterSpec(testData.nonce,366testData.counter);367break;368case ALG_CC20_P1305:369testData = aeadTestList.get(1);370spec = new IvParameterSpec(testData.nonce);371break;372default:373throw new IllegalArgumentException(374"Unsupported cipher type: " + algorithm);375}376SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);377378// Initialize and encrypt379cipher.init(testData.direction, key, spec);380if (algorithm.equals(ALG_CC20_P1305)) {381cipher.updateAAD(testData.aad);382}383cipher.doFinal(testData.input);384System.out.println("First decryption complete");385386// Now attempt to encrypt again without changing the key/IV387// This should fail.388try {389if (algorithm.equals(ALG_CC20_P1305)) {390cipher.updateAAD(testData.aad);391}392cipher.doFinal(testData.input);393throw new RuntimeException(394"Expected IllegalStateException not thrown");395} catch (IllegalStateException ise) {396// Do nothing, this is what we expected to happen397}398} catch (Exception exc) {399System.out.println("Unexpected exception: " + exc);400exc.printStackTrace();401return false;402}403404return true;405}406};407408/**409* Perform an AEAD decryption with corrupted data so the tag does not410* match. Then attempt to reuse the cipher without initialization.411*/412public static final TestMethod decFailNoInit = new TestMethod() {413@Override414public boolean isValid(String algorithm) {415return algorithm.equals(ALG_CC20_P1305);416}417418@Override419public boolean run(String algorithm) {420System.out.println(421"----- Fail decryption, try again with no init [" +422algorithm + "] -----");423try {424TestData testData = aeadTestList.get(1);425AlgorithmParameterSpec spec =426new IvParameterSpec(testData.nonce);427byte[] corruptInput = testData.input.clone();428corruptInput[0]++; // Corrupt the ciphertext429SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);430Cipher cipher = Cipher.getInstance(algorithm);431432try {433// Initialize and encrypt434cipher.init(testData.direction, key, spec);435cipher.updateAAD(testData.aad);436cipher.doFinal(corruptInput);437throw new RuntimeException(438"Expected AEADBadTagException not thrown");439} catch (AEADBadTagException abte) {440System.out.println("Expected decryption failure occurred");441}442443// Make sure that despite the exception, the Cipher object is444// not in a state that would leave it initialized and able445// to process future decryption operations without init.446try {447cipher.updateAAD(testData.aad);448cipher.doFinal(testData.input);449throw new RuntimeException(450"Expected IllegalStateException not thrown");451} catch (IllegalStateException ise) {452// Do nothing, this is what we expected to happen453}454} catch (Exception exc) {455System.out.println("Unexpected exception: " + exc);456exc.printStackTrace();457return false;458}459460return true;461}462};463464/**465* Encrypt once successfully, then attempt to init with the same466* key and nonce.467*/468public static final TestMethod encTwiceInitSameParams = new TestMethod() {469@Override470public boolean isValid(String algorithm) {471return true; // Valid for all algs472}473474@Override475public boolean run(String algorithm) {476System.out.println("----- Encrypt, then init with same params [" +477algorithm + "] -----");478try {479AlgorithmParameterSpec spec;480Cipher cipher = Cipher.getInstance(algorithm);481TestData testData;482switch (algorithm) {483case ALG_CC20:484testData = testList.get(0);485spec = new ChaCha20ParameterSpec(testData.nonce,486testData.counter);487break;488case ALG_CC20_P1305:489testData = aeadTestList.get(0);490spec = new IvParameterSpec(testData.nonce);491break;492default:493throw new IllegalArgumentException(494"Unsupported cipher type: " + algorithm);495}496SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);497498// Initialize then encrypt499cipher.init(testData.direction, key, spec);500if (algorithm.equals(ALG_CC20_P1305)) {501cipher.updateAAD(testData.aad);502}503cipher.doFinal(testData.input);504System.out.println("First encryption complete");505506// Initializing after the completed encryption with507// the same key and nonce should fail.508try {509cipher.init(testData.direction, key, spec);510throw new RuntimeException(511"Expected InvalidKeyException not thrown");512} catch (InvalidKeyException ike) {513// Do nothing, this is what we expected to happen514}515} catch (Exception exc) {516System.out.println("Unexpected exception: " + exc);517exc.printStackTrace();518return false;519}520521return true;522}523};524525/**526* Decrypt once successfully, then attempt to init with the same527* key and nonce.528*/529public static final TestMethod decTwiceInitSameParams = new TestMethod() {530@Override531public boolean isValid(String algorithm) {532return true; // Valid for all algs533}534535@Override536public boolean run(String algorithm) {537System.out.println("----- Decrypt, then init with same params [" +538algorithm + "] -----");539try {540AlgorithmParameterSpec spec;541Cipher cipher = Cipher.getInstance(algorithm);542TestData testData;543switch (algorithm) {544case ALG_CC20:545testData = testList.get(1);546spec = new ChaCha20ParameterSpec(testData.nonce,547testData.counter);548break;549case ALG_CC20_P1305:550testData = aeadTestList.get(1);551spec = new IvParameterSpec(testData.nonce);552break;553default:554throw new IllegalArgumentException(555"Unsupported cipher type: " + algorithm);556}557SecretKey key = new SecretKeySpec(testData.key, ALG_CC20);558559// Initialize then decrypt560cipher.init(testData.direction, key, spec);561if (algorithm.equals(ALG_CC20_P1305)) {562cipher.updateAAD(testData.aad);563}564cipher.doFinal(testData.input);565System.out.println("First decryption complete");566567// Initializing after the completed decryption with568// the same key and nonce should fail.569try {570cipher.init(testData.direction, key, spec);571throw new RuntimeException(572"Expected InvalidKeyException not thrown");573} catch (InvalidKeyException ike) {574// Do nothing, this is what we expected to happen575}576} catch (Exception exc) {577System.out.println("Unexpected exception: " + exc);578exc.printStackTrace();579return false;580}581582return true;583}584};585586public static final List<String> algList =587Arrays.asList(ALG_CC20, ALG_CC20_P1305);588589public static final List<TestMethod> testMethodList =590Arrays.asList(noInitTest, doubleInitTest, encTwiceNoInit,591decTwiceNoInit, decFailNoInit, encTwiceInitSameParams,592decTwiceInitSameParams);593594public static void main(String args[]) throws Exception {595int testsPassed = 0;596int testNumber = 0;597598for (TestMethod tm : testMethodList) {599for (String alg : algList) {600if (tm.isValid(alg)) {601testNumber++;602boolean result = tm.run(alg);603System.out.println("Result: " + (result ? "PASS" : "FAIL"));604if (result) {605testsPassed++;606}607}608}609}610611System.out.println("Total Tests: " + testNumber +612", Tests passed: " + testsPassed);613if (testsPassed < testNumber) {614throw new RuntimeException(615"Not all tests passed. See output for failure info");616}617}618}619620621622