Path: blob/master/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20Poly1305ParamTest.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 8153029 825776926* @library /test/lib27* @run main ChaCha20Poly1305ParamTest28* @summary ChaCha20 Cipher Implementation (parameters)29*/3031import java.util.*;32import java.io.IOException;33import java.security.GeneralSecurityException;34import javax.crypto.Cipher;35import javax.crypto.SecretKey;36import javax.crypto.spec.ChaCha20ParameterSpec;37import javax.crypto.spec.SecretKeySpec;38import javax.crypto.AEADBadTagException;39import java.security.spec.AlgorithmParameterSpec;40import java.security.AlgorithmParameters;41import java.security.NoSuchAlgorithmException;42import java.nio.ByteBuffer;43import java.security.InvalidKeyException;44import java.security.MessageDigest;45import java.security.spec.InvalidParameterSpecException;46import javax.crypto.spec.IvParameterSpec;47import jdk.test.lib.Convert;4849public class ChaCha20Poly1305ParamTest {50public static class TestData {51public TestData(String name, String keyStr, String nonceStr, int ctr,52int dir, String inputStr, String aadStr, String outStr) {53testName = Objects.requireNonNull(name);54HexFormat hex = HexFormat.of();55key = hex.parseHex(keyStr);56nonce = hex.parseHex(nonceStr);57if ((counter = ctr) < 0) {58throw new IllegalArgumentException(59"counter must be 0 or greater");60}61direction = dir;62if ((direction != Cipher.ENCRYPT_MODE) &&63(direction != Cipher.DECRYPT_MODE)) {64throw new IllegalArgumentException(65"Direction must be ENCRYPT_MODE or DECRYPT_MODE");66}67input = hex.parseHex(inputStr);68aad = (aadStr != null) ? hex.parseHex(aadStr) : null;69expOutput = hex.parseHex(outStr);70}7172public final String testName;73public final byte[] key;74public final byte[] nonce;75public final int counter;76public final int direction;77public final byte[] input;78public final byte[] aad;79public final byte[] expOutput;80}8182public static final List<TestData> aeadTestList =83new LinkedList<TestData>() {{84add(new TestData("RFC 7539 Sample AEAD Test Vector",85"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",86"070000004041424344454647",871, Cipher.ENCRYPT_MODE,88"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +89"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +90"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +91"637265656e20776f756c642062652069742e",92"50515253c0c1c2c3c4c5c6c7",93"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" +94"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" +95"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" +96"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" +97"0691"));98add(new TestData("RFC 7539 A.5 Sample Decryption",99"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",100"000000000102030405060708",1011, Cipher.DECRYPT_MODE,102"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" +103"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" +104"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" +105"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" +106"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" +107"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" +108"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" +109"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" +110"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38",111"f33388860000000000004e91",112"496e7465726e65742d4472616674732061726520647261667420646f63756d65" +113"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" +114"6f6e74687320616e64206d617920626520757064617465642c207265706c6163" +115"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" +116"6e747320617420616e792074696d652e20497420697320696e617070726f7072" +117"6961746520746f2075736520496e7465726e65742d4472616674732061732072" +118"65666572656e6365206d6174657269616c206f7220746f206369746520746865" +119"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" +120"726573732e2fe2809d"));121}};122123// 12-byte nonce DER-encoded as an OCTET_STRING124public static final byte[] NONCE_OCTET_STR_12 = {1254, 12, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8126};127128// Invalid 16-byte nonce DER-encoded as an OCTET_STRING129public static final byte[] NONCE_OCTET_STR_16 = {1304, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15131};132133// Throwaway key for default init tests134public static final SecretKey DEF_KEY = new SecretKeySpec(new byte[]135{1360, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,13716, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31138}, "ChaCha20");139140public static void main(String args[]) throws Exception {141int testsPassed = 0;142int testNumber = 0;143144// Try some default initializations145testDefaultAlgParams("ChaCha20", Cipher.ENCRYPT_MODE, true);146testDefaultAlgParams("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true);147testDefaultAlgParamSpec("ChaCha20", Cipher.ENCRYPT_MODE, true);148testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true);149testDefaultAlgParams("ChaCha20", Cipher.DECRYPT_MODE, false);150testDefaultAlgParams("ChaCha20-Poly1305", Cipher.DECRYPT_MODE, false);151testDefaultAlgParamSpec("ChaCha20", Cipher.DECRYPT_MODE, false);152testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.DECRYPT_MODE,153false);154155// Try (and hopefully fail) to create a ChaCha20 AlgorithmParameterSpec156System.out.println(157"*** Test: Try to make ChaCha20 AlgorithmParameterSpec");158try {159ChaCha20ParameterSpec badChaCha20Spec =160new ChaCha20ParameterSpec(NONCE_OCTET_STR_16, 1);161throw new RuntimeException("ChaCha20 AlgorithmParameterSpec " +162"with 16 byte nonce should fail");163} catch (IllegalArgumentException iae) {164System.out.println("Caught expected exception: " + iae);165}166167// Try (and hopefully fail) to create a ChaCha20 AlgorithmParameters168System.out.println(169"*** Test: Try to make ChaCha20 AlgorithmParameters");170try {171AlgorithmParameters apsNoChaCha20 =172AlgorithmParameters.getInstance("ChaCha20");173throw new RuntimeException(174"ChaCha20 AlgorithmParameters should fail");175} catch (NoSuchAlgorithmException nsae) {176System.out.println("Caught expected exception: " + nsae);177}178179// Create the AlgorithmParameters object from a valid encoding180System.out.println("*** Test: Create and init ChaCha20-Poly1305 APS");181AlgorithmParameters apsGood =182AlgorithmParameters.getInstance("ChaCha20-Poly1305");183apsGood.init(NONCE_OCTET_STR_12);184System.out.println("Test Passed");185186// Pull an AlgorithmParameters object out of the initialized cipher187// and compare its value against the original.188System.out.println("*** Test: Init ChaCha20-Poly1305 Cipher using " +189"AP, retrieve AP and compare");190Cipher cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");191cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY, apsGood);192AlgorithmParameters pulledParams = cc20p1305.getParameters();193byte[] apsGoodData = apsGood.getEncoded();194byte[] pulledParamsData = pulledParams.getEncoded();195if (!Arrays.equals(apsGoodData, pulledParamsData)) {196throw new RuntimeException(197"Retrieved parameters do not match those used to init cipher");198}199System.out.println("Test Passed");200201// Try the same test with ChaCha20. It should always be null.202System.out.println("*** Test: Init ChaCha20 Cipher using " +203"AP, retrieve AP and compare");204Cipher cc20 = Cipher.getInstance("ChaCha20");205cc20.init(Cipher.ENCRYPT_MODE, DEF_KEY);206pulledParams = cc20.getParameters();207if (pulledParams != null) {208throw new RuntimeException("Unexpected non-null " +209"AlgorithmParameters from ChaCha20 cipiher");210}211System.out.println("Test Passed");212213// Create and try to init using invalid encoding214AlgorithmParameters apsBad =215AlgorithmParameters.getInstance("ChaCha20-Poly1305");216System.out.println("*** Test: Use invalid encoding scheme");217try {218apsBad.init(NONCE_OCTET_STR_12, "OraclePrivate");219throw new RuntimeException("Allowed unsupported encoding scheme: " +220apsBad.getAlgorithm());221} catch (IOException iae) {222System.out.println("Caught expected exception: " + iae);223}224225// Try to init using supported scheme but invalid length226System.out.println("*** Test: Use supported scheme, nonce too large");227try {228apsBad.init(NONCE_OCTET_STR_16, "ASN.1");229throw new RuntimeException("Allowed invalid encoded length");230} catch (IOException ioe) {231System.out.println("Caught expected exception: " + ioe);232}233234// The next set of tests cover cases where ChaCha20-Poly1305 cipher235// objects have the getParameters() call executed after instantiation236// but before initialization.237System.out.println("*** Test: getParameters before init");238cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");239AlgorithmParameters algParams = cc20p1305.getParameters();240byte[] preInitNonce = getNonceFromParams(algParams);241// A second pre-init getParameters() call should return a new set of242// random parameters.243AlgorithmParameters algParamsTwo = cc20p1305.getParameters();244byte[] secondNonce = getNonceFromParams(algParamsTwo);245if (MessageDigest.isEqual(preInitNonce, secondNonce)) {246throw new RuntimeException("Unexpected nonce match between " +247"two pre-init getParameters() calls");248}249250// Next we will initialize the Cipher object using a form of init251// that doesn't take AlgorithmParameters or AlgorithmParameterSpec.252// The nonce created using the pre-init getParameters() call should253// be overwritten by a freshly generated set of random parameters.254cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);255AlgorithmParameters postInitAps = cc20p1305.getParameters();256byte[] postInitNonce = getNonceFromParams(postInitAps);257if (MessageDigest.isEqual(preInitNonce, postInitNonce)) {258throw new RuntimeException("Unexpected nonce match between " +259"pre and post-init getParameters() calls");260}261System.out.println("Test Passed");262263// After an initialization, subsequent calls to getParameters() should264// return the same parameter value until the next initialization takes265// place.266System.out.println("*** Test: getParameters after init");267AlgorithmParameters postInitApsTwo = cc20p1305.getParameters();268byte[] postInitNonceTwo = getNonceFromParams(postInitApsTwo);269if (!MessageDigest.isEqual(postInitNonce, postInitNonceTwo)) {270throw new RuntimeException("Unexpected nonce mismatch between " +271"two post-init getParameters() calls");272}273System.out.println("Test Passed");274275// Test reinitialization use cases.276// First test: instantiate, init(no param), encrypt. Get params277// and attempt to reinit with same parameters. Should fail.278System.out.println("*** Test: Init w/ random nonce, init 2nd time");279cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");280cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);281algParams = cc20p1305.getParameters();282preInitNonce = getNonceFromParams(algParams);283// Perform a simple encryption operation284cc20p1305.doFinal(aeadTestList.get(0).input);285try {286// Now try to reinitialize using the same parameters287cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY, algParams);288throw new RuntimeException("Illegal key/nonce reuse");289} catch (InvalidKeyException ike) {290System.out.println("Caught expected exception: " + ike);291}292293// Test the reinit guard using an AlgorithmParameterSpec with the294// Same nonce value. This should also be a failure.295try {296cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY,297new IvParameterSpec(preInitNonce));298throw new RuntimeException("Illegal key/nonce reuse");299} catch (InvalidKeyException ike) {300System.out.println("Caught expected exception: " + ike);301}302303// Try one more time, this time providing a new 12-byte nonce, which304// should be allowed even if the key is the same.305cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY,306new IvParameterSpec(NONCE_OCTET_STR_12, 2, 12));307System.out.println("Test Passed");308309// Reinit test: instantiate, init(no param), getParam, encrypt,310// then init(no param). Should work and the parameters should be311// different after each init.312cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");313cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);314byte[] paramInitOne = getNonceFromParams(cc20p1305.getParameters());315// Perform a simple encryption operation316cc20p1305.doFinal(aeadTestList.get(0).input);317// reinit (no params)318cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);319byte[] paramInitTwo = getNonceFromParams(cc20p1305.getParameters());320if (MessageDigest.isEqual(paramInitOne, paramInitTwo)) {321throw new RuntimeException("Unexpected nonce match between " +322"pre and post-init getParameters() calls");323}324System.out.println("Test Passed");325326// Reinit test: instantiate, init(no param), doFinal, then doFinal327// again without intervening init. Should fail due to no-reuse328// protections.329try {330cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");331cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);332cc20p1305.doFinal(aeadTestList.get(0).input);333cc20p1305.doFinal(aeadTestList.get(0).input);334throw new RuntimeException("Illegal key/nonce reuse");335} catch (IllegalStateException ise) {336System.out.println("Caught expected exception: " + ise);337}338339System.out.println("----- AEAD Tests -----");340for (TestData test : aeadTestList) {341System.out.println("*** Test " + ++testNumber + ": " +342test.testName);343if (runAEADTest(test)) {344testsPassed++;345}346}347System.out.println();348349System.out.println("Total tests: " + testNumber +350", Passed: " + testsPassed + ", Failed: " +351(testNumber - testsPassed));352if (testsPassed != testNumber) {353throw new RuntimeException("One or more tests failed. " +354"Check output for details");355}356}357358/**359* Attempt default inits with null AlgorithmParameters360*361* @param alg the algorithm (ChaCha20, ChaCha20-Poly1305)362* @param mode the Cipher mode (ENCRYPT_MODE, etc.)363*/364private static void testDefaultAlgParams(String alg, int mode,365boolean shouldPass) {366byte[] ivOne = null, ivTwo = null;367System.out.println("Test default AlgorithmParameters: Cipher = " +368alg + ", mode = " + mode);369try {370AlgorithmParameters params = null;371Cipher cipher = Cipher.getInstance(alg);372cipher.init(mode, DEF_KEY, params, null);373ivOne = cipher.getIV();374cipher.init(mode, DEF_KEY, params, null);375ivTwo = cipher.getIV();376if (!shouldPass) {377throw new RuntimeException(378"Did not receive expected exception");379}380} catch (GeneralSecurityException gse) {381if (shouldPass) {382throw new RuntimeException(gse);383}384System.out.println("Caught expected exception: " + gse);385return;386}387if (Arrays.equals(ivOne, ivTwo)) {388throw new RuntimeException(389"FAIL! Two inits generated same nonces");390} else {391System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " "));392System.out.println("IV 1:\n" + dumpHexBytes(ivTwo, 16, "\n", " "));393}394}395396/**397* Attempt default inits with null AlgorithmParameters398*399* @param alg the algorithm (ChaCha20, ChaCha20-Poly1305)400* @param mode the Cipher mode (ENCRYPT_MODE, etc.)401*/402private static void testDefaultAlgParamSpec(String alg, int mode,403boolean shouldPass) {404byte[] ivOne = null, ivTwo = null;405System.out.println("Test default AlgorithmParameterSpec: Cipher = " +406alg + ", mode = " + mode);407try {408AlgorithmParameterSpec params = null;409Cipher cipher = Cipher.getInstance(alg);410cipher.init(mode, DEF_KEY, params, null);411ivOne = cipher.getIV();412cipher.init(mode, DEF_KEY, params, null);413ivTwo = cipher.getIV();414if (!shouldPass) {415throw new RuntimeException(416"Did not receive expected exception");417}418} catch (GeneralSecurityException gse) {419if (shouldPass) {420throw new RuntimeException(gse);421}422System.out.println("Caught expected exception: " + gse);423return;424}425if (Arrays.equals(ivOne, ivTwo)) {426throw new RuntimeException(427"FAIL! Two inits generated same nonces");428} else {429System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " "));430System.out.println("IV 2:\n" + dumpHexBytes(ivTwo, 16, "\n", " "));431}432}433434private static boolean runAEADTest(TestData testData)435throws GeneralSecurityException, IOException {436boolean result = false;437438Cipher mambo = Cipher.getInstance("ChaCha20-Poly1305");439SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");440AlgorithmParameters mamboParams =441AlgorithmParameters.getInstance("ChaCha20-Poly1305");442443// Put the nonce into ASN.1 ChaCha20-Poly1305 parameter format444byte[] derNonce = new byte[testData.nonce.length + 2];445derNonce[0] = 0x04;446derNonce[1] = (byte)testData.nonce.length;447System.arraycopy(testData.nonce, 0, derNonce, 2,448testData.nonce.length);449mamboParams.init(derNonce);450451mambo.init(testData.direction, mamboKey, mamboParams);452453byte[] out = new byte[mambo.getOutputSize(testData.input.length)];454int outOff = 0;455try {456mambo.updateAAD(testData.aad);457outOff += mambo.update(testData.input, 0, testData.input.length,458out, outOff);459outOff += mambo.doFinal(out, outOff);460} catch (AEADBadTagException abte) {461// If we get a bad tag or derive a tag mismatch, log it462// and register it as a failure463System.out.println("FAIL: " + abte);464return false;465}466467if (!Arrays.equals(out, testData.expOutput)) {468System.out.println("ERROR - Output Mismatch!");469System.out.println("Expected:\n" +470dumpHexBytes(testData.expOutput, 16, "\n", " "));471System.out.println("Actual:\n" +472dumpHexBytes(out, 16, "\n", " "));473System.out.println();474} else {475result = true;476}477478return result;479}480481private static byte[] getNonceFromParams(AlgorithmParameters params)482throws InvalidParameterSpecException {483return params.getParameterSpec(IvParameterSpec.class).getIV();484}485486/**487* Dump the hex bytes of a buffer into string form.488*489* @param data The array of bytes to dump to stdout.490* @param itemsPerLine The number of bytes to display per line491* if the {@code lineDelim} character is blank then all bytes492* will be printed on a single line.493* @param lineDelim The delimiter between lines494* @param itemDelim The delimiter between bytes495*496* @return The hexdump of the byte array497*/498private static String dumpHexBytes(byte[] data, int itemsPerLine,499String lineDelim, String itemDelim) {500return dumpHexBytes(ByteBuffer.wrap(data), itemsPerLine, lineDelim,501itemDelim);502}503504private static String dumpHexBytes(ByteBuffer data, int itemsPerLine,505String lineDelim, String itemDelim) {506StringBuilder sb = new StringBuilder();507if (data != null) {508data.mark();509int i = 0;510while (data.remaining() > 0) {511if (i % itemsPerLine == 0 && i != 0) {512sb.append(lineDelim);513}514sb.append(String.format("%02X", data.get())).append(itemDelim);515i++;516}517data.reset();518}519520return sb.toString();521}522}523524525526