From fbe0f0384e513e435092945451e29d4df97e17ec Mon Sep 17 00:00:00 2001 From: Kimura Yukihiro Date: Tue, 22 Feb 2022 17:44:56 +0000 Subject: [PATCH 1/7] 8254085: javax/swing/text/Caret/TestCaretPositionJTextPane.java failed with "RuntimeException: Wrong caret position" Backport-of: 51a865d66a5dddbbaaa4bb656fa02ecd1bee1a0b --- test/jdk/javax/swing/text/Caret/TestCaretPositionJTextPane.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/javax/swing/text/Caret/TestCaretPositionJTextPane.java b/test/jdk/javax/swing/text/Caret/TestCaretPositionJTextPane.java index 84750e5974a..a6928dbb532 100644 --- a/test/jdk/javax/swing/text/Caret/TestCaretPositionJTextPane.java +++ b/test/jdk/javax/swing/text/Caret/TestCaretPositionJTextPane.java @@ -98,7 +98,7 @@ public static void main(String args[]) throws Exception { robot.waitForIdle(); Point p = textPane.getLocationOnScreen(); - robot.mouseMove(p.x+ 480, p.y+6); + robot.mouseMove(p.x+ 380, p.y+6); robot.waitForIdle(); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); robot.waitForIdle(); From 841eeef9753e0ef42c7e7922adc1823fbe003bf3 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 24 Feb 2022 08:22:48 +0000 Subject: [PATCH 2/7] 8277488: Add expiry exception for Digicert (geotrustglobalca) expiring in May 2022 Backport-of: d3749de47832c6de4bcee9cf64a0b698e796b2f2 --- test/jdk/sun/security/lib/cacerts/VerifyCACerts.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java index 91d78e8fecc..122a0190177 100644 --- a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java +++ b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -260,6 +260,8 @@ public class VerifyCACerts { add("luxtrustglobalrootca [jdk]"); // Valid until: Wed Mar 17 11:33:33 PDT 2021 add("quovadisrootca [jdk]"); + // Valid until: Sat May 21 04:00:00 GMT 2022 + add("geotrustglobalca [jdk]"); } }; From c27b321d7b4067d86921d9fbc2d518a06f5bc4af Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Sat, 26 Feb 2022 04:38:49 +0000 Subject: [PATCH 3/7] 8261107: ArrayIndexOutOfBoundsException in the ICC_Profile.getInstance(InputStream) Backport-of: 06b33a0ad78d1577711af22020cf5fdf25112523 --- .../classes/java/awt/color/ICC_Profile.java | 4 +- .../ICC_Profile/GetInstanceBrokenStream.java | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/jdk/java/awt/color/ICC_Profile/GetInstanceBrokenStream.java diff --git a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java index 2f85d51dffc..de99302f1eb 100644 --- a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java +++ b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java @@ -1030,10 +1030,10 @@ public static ICC_Profile getInstance(InputStream s) throws IOException { static byte[] getProfileDataFromStream(InputStream s) throws IOException { BufferedInputStream bis = new BufferedInputStream(s); - bis.mark(128); + bis.mark(128); // 128 is the length of the ICC profile header byte[] header = bis.readNBytes(128); - if (header[36] != 0x61 || header[37] != 0x63 || + if (header.length < 128 || header[36] != 0x61 || header[37] != 0x63 || header[38] != 0x73 || header[39] != 0x70) { return null; /* not a valid profile */ } diff --git a/test/jdk/java/awt/color/ICC_Profile/GetInstanceBrokenStream.java b/test/jdk/java/awt/color/ICC_Profile/GetInstanceBrokenStream.java new file mode 100644 index 00000000000..b98cae0cdca --- /dev/null +++ b/test/jdk/java/awt/color/ICC_Profile/GetInstanceBrokenStream.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.color.ICC_Profile; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * @test + * @bug 8261107 + * @summary Short and broken streams should be reported as unsupported + */ +public final class GetInstanceBrokenStream { + + public static void main(String[] args) throws IOException { + // Empty header + testHeader(new byte[]{}); + // Short header + testHeader(new byte[]{-12, 3, 45}); + // Broken header + testHeader(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 0x61, 0x63, 0x73, 0x70}); + } + + private static void testHeader(byte[] data) throws IOException { + ByteArrayInputStream bais = new ByteArrayInputStream(data); + try { + ICC_Profile.getInstance(bais); + } catch (IllegalArgumentException e) { + // expected + } + } +} From 87b1fe961ac353aab53e8bb56a93f425bba64eb8 Mon Sep 17 00:00:00 2001 From: Zdenek Zambersky Date: Mon, 28 Feb 2022 17:12:07 +0000 Subject: [PATCH 4/7] 8279669: test/jdk/com/sun/jdi/TestScaffold.java uses wrong condition Reviewed-by: sgehwolf Backport-of: 4c52eb39431c2479b0d140907bdcc0311d30f871 --- test/jdk/com/sun/jdi/TestScaffold.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/jdk/com/sun/jdi/TestScaffold.java b/test/jdk/com/sun/jdi/TestScaffold.java index 2561c5b5ba6..1d3843b9224 100644 --- a/test/jdk/com/sun/jdi/TestScaffold.java +++ b/test/jdk/com/sun/jdi/TestScaffold.java @@ -535,9 +535,10 @@ public void eventReceived(Event event) { Location loc = ((Locatable)event).location(); ReferenceType rt = loc.declaringType(); String name = rt.name(); - if (name.startsWith("java.") && - !name.startsWith("sun.") && - !name.startsWith("com.")) { + if (name.startsWith("java.") + || name.startsWith("sun.") + || name.startsWith("com.") + || name.startsWith("jdk.")) { if (mainStartClass != null) { redefine(mainStartClass); } From 21acb89266ce4b56158502511627439fb94c1a2a Mon Sep 17 00:00:00 2001 From: "Keith W. Campbell" Date: Mon, 28 Feb 2022 17:10:51 -0500 Subject: [PATCH 5/7] Use configured git command Signed-off-by: Keith W. Campbell --- closed/OpenJ9.gmk | 12 ++++++------ closed/custom/ReleaseFile.gmk | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/closed/OpenJ9.gmk b/closed/OpenJ9.gmk index 29983ced5c0..bae984a7ed0 100644 --- a/closed/OpenJ9.gmk +++ b/closed/OpenJ9.gmk @@ -1,5 +1,5 @@ # =========================================================================== -# (c) Copyright IBM Corp. 2017, 2021 All Rights Reserved +# (c) Copyright IBM Corp. 2017, 2022 All Rights Reserved # =========================================================================== # This code is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 only, as @@ -32,20 +32,20 @@ ifeq (,$(BUILD_ID)) BUILD_ID := 000000 endif -OPENJDK_SHA := $(shell git -C $(TOPDIR) rev-parse --short HEAD) +OPENJDK_SHA := $(shell $(GIT) -C $(TOPDIR) rev-parse --short HEAD) ifeq (,$(OPENJDK_SHA)) $(error Could not determine OpenJDK SHA) endif -OPENJ9_SHA := $(shell git -C $(OPENJ9_TOPDIR) rev-parse --short HEAD) +OPENJ9_SHA := $(shell $(GIT) -C $(OPENJ9_TOPDIR) rev-parse --short HEAD) ifeq (,$(OPENJ9_SHA)) $(error Could not determine OpenJ9 SHA) endif # Find OpenJ9 tag associated with current commit (suppressing stderr in case there is no such tag). -OPENJ9_TAG := $(shell git -C $(OPENJ9_TOPDIR) describe --exact-match HEAD 2>/dev/null) +OPENJ9_TAG := $(shell $(GIT) -C $(OPENJ9_TOPDIR) describe --exact-match HEAD 2>/dev/null) ifeq (,$(OPENJ9_TAG)) - OPENJ9_BRANCH := $(shell git -C $(OPENJ9_TOPDIR) rev-parse --abbrev-ref HEAD) + OPENJ9_BRANCH := $(shell $(GIT) -C $(OPENJ9_TOPDIR) rev-parse --abbrev-ref HEAD) ifeq (,$(OPENJ9_BRANCH)) $(error Could not determine OpenJ9 branch) endif @@ -54,7 +54,7 @@ else OPENJ9_VERSION_STRING := $(OPENJ9_TAG) endif -OPENJ9OMR_SHA := $(shell git -C $(OPENJ9OMR_TOPDIR) rev-parse --short HEAD) +OPENJ9OMR_SHA := $(shell $(GIT) -C $(OPENJ9OMR_TOPDIR) rev-parse --short HEAD) ifeq (,$(OPENJ9OMR_SHA)) $(error Could not determine OMR SHA) endif diff --git a/closed/custom/ReleaseFile.gmk b/closed/custom/ReleaseFile.gmk index 4a7267056de..8512c7702f1 100644 --- a/closed/custom/ReleaseFile.gmk +++ b/closed/custom/ReleaseFile.gmk @@ -1,5 +1,5 @@ # =========================================================================== -# (c) Copyright IBM Corp. 2017, 2020 All Rights Reserved +# (c) Copyright IBM Corp. 2017, 2022 All Rights Reserved # =========================================================================== # This code is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 only, as @@ -18,6 +18,6 @@ # 2 along with this work; if not, see . # =========================================================================== -SOURCE_REVISION := OpenJDK:$(shell git -C $(TOPDIR) rev-parse --short HEAD) -SOURCE_REVISION += OpenJ9:$(shell git -C $(OPENJ9_TOPDIR) rev-parse --short HEAD) -SOURCE_REVISION += OMR:$(shell git -C $(OPENJ9OMR_TOPDIR) rev-parse --short HEAD) +SOURCE_REVISION := OpenJDK:$(shell $(GIT) -C $(TOPDIR) rev-parse --short HEAD) +SOURCE_REVISION += OpenJ9:$(shell $(GIT) -C $(OPENJ9_TOPDIR) rev-parse --short HEAD) +SOURCE_REVISION += OMR:$(shell $(GIT) -C $(OPENJ9OMR_TOPDIR) rev-parse --short HEAD) From 7f00d6aeb2028b6859841853b5049f2e5f16fbe7 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 1 Mar 2022 16:58:10 +0000 Subject: [PATCH 6/7] 8255410: Add ChaCha20 and Poly1305 support to SunPKCS11 provider Reviewed-by: mdoerr Backport-of: 5d8c1cc8a05e0d9aedd6d54b8147d374c2290024 --- .../sun/security/pkcs11/P11AEADCipher.java | 288 ++++++++++------ .../sun/security/pkcs11/P11KeyGenerator.java | 6 +- .../security/pkcs11/P11SecretKeyFactory.java | 5 + .../sun/security/pkcs11/SunPKCS11.java | 22 +- .../security/pkcs11/wrapper/CK_MECHANISM.java | 7 +- .../CK_SALSA20_CHACHA20_POLY1305_PARAMS.java | 80 +++++ .../share/native/libj2pkcs11/p11_convert.c | 74 +++- .../share/native/libj2pkcs11/p11_util.c | 7 +- .../share/native/libj2pkcs11/pkcs11wrapper.h | 4 +- .../pkcs11/Cipher/TestChaChaPoly.java | 323 ++++++++++++++++++ .../pkcs11/Cipher/TestChaChaPolyKAT.java | 233 +++++++++++++ .../pkcs11/Cipher/TestChaChaPolyNoReuse.java | 267 +++++++++++++++ .../Cipher/TestChaChaPolyOutputSize.java | 164 +++++++++ .../security/pkcs11/Cipher/TestKATForGCM.java | 12 - .../pkcs11/KeyGenerator/TestChaCha20.java | 91 +++++ test/jdk/sun/security/pkcs11/PKCS11Test.java | 34 ++ .../pkcs11/SecretKeyFactory/TestGeneral.java | 119 +++++++ .../pkcs11/tls/TestLeadingZeroesP11.java | 29 -- 18 files changed, 1608 insertions(+), 157 deletions(-) create mode 100644 src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_SALSA20_CHACHA20_POLY1305_PARAMS.java create mode 100644 test/jdk/sun/security/pkcs11/Cipher/TestChaChaPoly.java create mode 100644 test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyKAT.java create mode 100644 test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyNoReuse.java create mode 100644 test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyOutputSize.java create mode 100644 test/jdk/sun/security/pkcs11/KeyGenerator/TestChaCha20.java create mode 100644 test/jdk/sun/security/pkcs11/SecretKeyFactory/TestGeneral.java diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java index 7913d755d4e..fa8fb3f7ed7 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11AEADCipher.java @@ -42,28 +42,38 @@ /** * P11 AEAD Cipher implementation class. This class currently supports - * AES with GCM mode. + * AES cipher in GCM mode and CHACHA20-POLY1305 cipher. * * Note that AEAD modes do not use padding, so this class does not have - * its own padding impl. In addition, NSS CKM_AES_GCM only supports single-part - * encryption/decryption, thus the current impl uses PKCS#11 C_Encrypt/C_Decrypt - * calls and buffers data until doFinal is called. - * - * Note that PKCS#11 standard currently only supports GCM and CCM AEAD modes. - * There are no provisions for other AEAD modes yet. + * its own padding impl. In addition, some vendors such as NSS may not support + * multi-part encryption/decryption for AEAD cipher algorithms, thus the + * current impl uses PKCS#11 C_Encrypt/C_Decrypt calls and buffers data until + * doFinal is called. * * @since 13 */ final class P11AEADCipher extends CipherSpi { - // mode constant for GCM mode - private static final int MODE_GCM = 10; + // supported AEAD algorithms/transformations + private enum Transformation { + AES_GCM("AES", "GCM", "NOPADDING", 16, 16), + CHACHA20_POLY1305("CHACHA20", "NONE", "NOPADDING", 12, 16); - // default constants for GCM - private static final int GCM_DEFAULT_TAG_LEN = 16; - private static final int GCM_DEFAULT_IV_LEN = 16; + final String keyAlgo; + final String mode; + final String padding; + final int defIvLen; // in bytes + final int defTagLen; // in bytes - private static final String ALGO = "AES"; + Transformation(String keyAlgo, String mode, String padding, + int defIvLen, int defTagLen) { + this.keyAlgo = keyAlgo; + this.mode = mode; + this.padding = padding; + this.defIvLen = defIvLen; + this.defTagLen = defTagLen; + } + } // token instance private final Token token; @@ -71,10 +81,10 @@ final class P11AEADCipher extends CipherSpi { // mechanism id private final long mechanism; - // mode, one of MODE_* above - private final int blockMode; + // type of this AEAD cipher, one of Transformation enum above + private final Transformation type; - // acceptable key size, -1 if more than 1 key sizes are accepted + // acceptable key size in bytes, -1 if more than 1 key sizes are accepted private final int fixedKeySize; // associated session, if any @@ -111,99 +121,129 @@ final class P11AEADCipher extends CipherSpi { this.mechanism = mechanism; String[] algoParts = algorithm.split("/"); - if (algoParts.length != 3) { - throw new ProviderException("Unsupported Transformation format: " + - algorithm); - } - if (!algoParts[0].startsWith("AES")) { - throw new ProviderException("Only support AES for AEAD cipher mode"); - } - int index = algoParts[0].indexOf('_'); - if (index != -1) { - // should be well-formed since we specify what we support - fixedKeySize = Integer.parseInt(algoParts[0].substring(index+1)) >> 3; + if (algoParts[0].startsWith("AES")) { + // for AES_GCM, need 3 parts + if (algoParts.length != 3) { + throw new AssertionError("Invalid Transformation format: " + + algorithm); + } + int index = algoParts[0].indexOf('_'); + if (index != -1) { + // should be well-formed since we specify what we support + fixedKeySize = Integer.parseInt(algoParts[0].substring(index+1)) >> 3; + } else { + fixedKeySize = -1; + } + this.type = Transformation.AES_GCM; + engineSetMode(algoParts[1]); + try { + engineSetPadding(algoParts[2]); + } catch (NoSuchPaddingException e) { + throw new NoSuchAlgorithmException(); + } + } else if (algoParts[0].equals("ChaCha20-Poly1305")) { + fixedKeySize = 32; + this.type = Transformation.CHACHA20_POLY1305; + if (algoParts.length > 3) { + throw new AssertionError( + "Invalid Transformation format: " + algorithm); + } else { + if (algoParts.length > 1) { + engineSetMode(algoParts[1]); + } + try { + if (algoParts.length > 2) { + engineSetPadding(algoParts[2]); + } + } catch (NoSuchPaddingException e) { + throw new NoSuchAlgorithmException(); + } + } } else { - fixedKeySize = -1; - } - this.blockMode = parseMode(algoParts[1]); - if (!algoParts[2].equals("NoPadding")) { - throw new ProviderException("Only NoPadding is supported for AEAD cipher mode"); + throw new AssertionError("Unsupported transformation " + algorithm); } } + @Override protected void engineSetMode(String mode) throws NoSuchAlgorithmException { - // Disallow change of mode for now since currently it's explicitly - // defined in transformation strings - throw new NoSuchAlgorithmException("Unsupported mode " + mode); - } - - private int parseMode(String mode) throws NoSuchAlgorithmException { - mode = mode.toUpperCase(Locale.ENGLISH); - int result; - if (mode.equals("GCM")) { - result = MODE_GCM; - } else { + if (!mode.toUpperCase(Locale.ENGLISH).equals(type.mode)) { throw new NoSuchAlgorithmException("Unsupported mode " + mode); } - return result; } // see JCE spec + @Override protected void engineSetPadding(String padding) throws NoSuchPaddingException { - // Disallow change of padding for now since currently it's explicitly - // defined in transformation strings - throw new NoSuchPaddingException("Unsupported padding " + padding); + if (!padding.toUpperCase(Locale.ENGLISH).equals(type.padding)) { + throw new NoSuchPaddingException("Unsupported padding " + padding); + } } // see JCE spec + @Override protected int engineGetBlockSize() { - return 16; // constant; only AES is supported + switch (type) { + case AES_GCM: return 16; + case CHACHA20_POLY1305: return 0; + default: throw new AssertionError("Unsupported type " + type); + } } // see JCE spec + @Override protected int engineGetOutputSize(int inputLen) { return doFinalLength(inputLen); } // see JCE spec + @Override protected byte[] engineGetIV() { return (iv == null) ? null : iv.clone(); } // see JCE spec protected AlgorithmParameters engineGetParameters() { - if (encrypt && iv == null && tagLen == -1) { - switch (blockMode) { - case MODE_GCM: - iv = new byte[GCM_DEFAULT_IV_LEN]; - tagLen = GCM_DEFAULT_TAG_LEN; - break; - default: - throw new ProviderException("Unsupported mode"); - } - random.nextBytes(iv); - } - try { - AlgorithmParameterSpec spec; - String apAlgo; - switch (blockMode) { - case MODE_GCM: - apAlgo = "GCM"; + String apAlgo; + AlgorithmParameterSpec spec = null; + switch (type) { + case AES_GCM: + apAlgo = "GCM"; + if (encrypt && iv == null && tagLen == -1) { + iv = new byte[type.defIvLen]; + tagLen = type.defTagLen; + random.nextBytes(iv); + } + if (iv != null) { spec = new GCMParameterSpec(tagLen << 3, iv); - break; - default: - throw new ProviderException("Unsupported mode"); + } + break; + case CHACHA20_POLY1305: + if (encrypt && iv == null) { + iv = new byte[type.defIvLen]; + random.nextBytes(iv); + } + apAlgo = "ChaCha20-Poly1305"; + if (iv != null) { + spec = new IvParameterSpec(iv); + } + break; + default: + throw new AssertionError("Unsupported type " + type); + } + if (spec != null) { + try { + AlgorithmParameters params = + AlgorithmParameters.getInstance(apAlgo); + params.init(spec); + return params; + } catch (GeneralSecurityException e) { + // NoSuchAlgorithmException, NoSuchProviderException + // InvalidParameterSpecException + throw new ProviderException("Could not encode parameters", e); } - AlgorithmParameters params = - AlgorithmParameters.getInstance(apAlgo); - params.init(spec); - return params; - } catch (GeneralSecurityException e) { - // NoSuchAlgorithmException, NoSuchProviderException - // InvalidParameterSpecException - throw new ProviderException("Could not encode parameters", e); } + return null; } // see JCE spec @@ -231,20 +271,30 @@ protected void engineInit(int opmode, Key key, updateCalled = false; byte[] ivValue = null; int tagLen = -1; - if (params != null) { - switch (blockMode) { - case MODE_GCM: - if (!(params instanceof GCMParameterSpec)) { - throw new InvalidAlgorithmParameterException - ("Only GCMParameterSpec is supported"); + switch (type) { + case AES_GCM: + if (params != null) { + if (!(params instanceof GCMParameterSpec)) { + throw new InvalidAlgorithmParameterException + ("Only GCMParameterSpec is supported"); + } + ivValue = ((GCMParameterSpec) params).getIV(); + tagLen = ((GCMParameterSpec) params).getTLen() >> 3; + } + break; + case CHACHA20_POLY1305: + if (params != null) { + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException + ("Only IvParameterSpec is supported"); + } + ivValue = ((IvParameterSpec) params).getIV(); + tagLen = type.defTagLen; } - ivValue = ((GCMParameterSpec) params).getIV(); - tagLen = ((GCMParameterSpec) params).getTLen() >> 3; break; default: - throw new ProviderException("Unsupported mode"); - } - } + throw new AssertionError("Unsupported type " + type); + }; implInit(opmode, key, ivValue, tagLen, sr); } @@ -260,13 +310,17 @@ protected void engineInit(int opmode, Key key, AlgorithmParameters params, try { AlgorithmParameterSpec paramSpec = null; if (params != null) { - switch (blockMode) { - case MODE_GCM: + switch (type) { + case AES_GCM: paramSpec = params.getParameterSpec(GCMParameterSpec.class); break; + case CHACHA20_POLY1305: + paramSpec = + params.getParameterSpec(IvParameterSpec.class); + break; default: - throw new ProviderException("Unsupported mode"); + throw new AssertionError("Unsupported type " + type); } } engineInit(opmode, key, paramSpec, sr); @@ -285,15 +339,16 @@ private void implInit(int opmode, Key key, byte[] iv, int tagLen, key.getEncoded().length) != fixedKeySize) { throw new InvalidKeyException("Key size is invalid"); } - P11Key newKey = P11SecretKeyFactory.convertKey(token, key, ALGO); + P11Key newKey = P11SecretKeyFactory.convertKey(token, key, + type.keyAlgo); switch (opmode) { case Cipher.ENCRYPT_MODE: encrypt = true; requireReinit = Arrays.equals(iv, lastEncIv) && (newKey == lastEncKey); if (requireReinit) { - throw new InvalidAlgorithmParameterException - ("Cannot reuse iv for GCM encryption"); + throw new InvalidAlgorithmParameterException( + "Cannot reuse the same key and iv pair"); } break; case Cipher.DECRYPT_MODE: @@ -309,16 +364,22 @@ private void implInit(int opmode, Key key, byte[] iv, int tagLen, if (sr != null) { this.random = sr; } + if (iv == null && tagLen == -1) { // generate default values - switch (blockMode) { - case MODE_GCM: - iv = new byte[GCM_DEFAULT_IV_LEN]; + switch (type) { + case AES_GCM: + iv = new byte[type.defIvLen]; + this.random.nextBytes(iv); + tagLen = type.defTagLen; + break; + case CHACHA20_POLY1305: + iv = new byte[type.defIvLen]; this.random.nextBytes(iv); - tagLen = GCM_DEFAULT_TAG_LEN; + tagLen = type.defTagLen; break; default: - throw new ProviderException("Unsupported mode"); + throw new AssertionError("Unsupported type " + type); } } this.iv = iv; @@ -382,7 +443,7 @@ private void initialize() throws PKCS11Exception { } if (requireReinit) { throw new IllegalStateException - ("Must use either different key or iv for GCM encryption"); + ("Must use either different key or iv"); } token.ensureValid(); @@ -392,13 +453,17 @@ private void initialize() throws PKCS11Exception { long p11KeyID = p11Key.getKeyID(); try { CK_MECHANISM mechWithParams; - switch (blockMode) { - case MODE_GCM: + switch (type) { + case AES_GCM: mechWithParams = new CK_MECHANISM(mechanism, new CK_GCM_PARAMS(tagLen << 3, iv, aad)); break; + case CHACHA20_POLY1305: + mechWithParams = new CK_MECHANISM(mechanism, + new CK_SALSA20_CHACHA20_POLY1305_PARAMS(iv, aad)); + break; default: - throw new ProviderException("Unsupported mode: " + blockMode); + throw new AssertionError("Unsupported type: " + type); } if (session == null) { session = token.getOpSession(); @@ -431,10 +496,13 @@ private int doFinalLength(int inLen) { if (encrypt) { result += tagLen; } else { - // PKCS11Exception: CKR_BUFFER_TOO_SMALL - //result -= tagLen; + // In earlier NSS versions, AES_GCM would report + // CKR_BUFFER_TOO_SMALL error if minus tagLen + if (type == Transformation.CHACHA20_POLY1305) { + result -= tagLen; + } } - return result; + return (result > 0? result : 0); } // reset the states to the pre-initialized values @@ -492,7 +560,7 @@ protected synchronized void engineUpdateAAD(byte[] src, int srcOfs, int srcLen) } if (requireReinit) { throw new IllegalStateException - ("Must use either different key or iv for GCM encryption"); + ("Must use either different key or iv for encryption"); } if (p11Key == null) { throw new IllegalStateException("Need to initialize Cipher first"); @@ -595,6 +663,7 @@ private int implDoFinal(byte[] in, int inOfs, int inLen, if (outLen < requiredOutLen) { throw new ShortBufferException(); } + boolean doCancel = true; try { ensureInitialized(); @@ -712,6 +781,7 @@ private int implDoFinal(ByteBuffer inBuffer, ByteBuffer outBuffer) outAddr, outArray, outOfs, outLen); doCancel = false; } + inBuffer.position(inBuffer.limit()); outBuffer.position(outBuffer.position() + k); return k; } catch (PKCS11Exception e) { @@ -748,8 +818,8 @@ private void handleException(PKCS11Exception e) } else if (errorCode == CKR_ENCRYPTED_DATA_INVALID || // Solaris-specific errorCode == CKR_GENERAL_ERROR) { - throw (BadPaddingException) - (new BadPaddingException(e.toString()).initCause(e)); + throw (AEADBadTagException) + (new AEADBadTagException(e.toString()).initCause(e)); } } @@ -772,7 +842,7 @@ protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, @Override protected int engineGetKeySize(Key key) throws InvalidKeyException { int n = P11SecretKeyFactory.convertKey - (token, key, ALGO).length(); + (token, key, type.keyAlgo).length(); return n; } } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyGenerator.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyGenerator.java index 4df62de6db1..926414608cb 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyGenerator.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -194,6 +194,10 @@ private void setDefaultKeySize() { keySize = 128; keyType = CKK_BLOWFISH; break; + case (int)CKM_CHACHA20_KEY_GEN: + keySize = 256; + keyType = CKK_CHACHA20; + break; default: throw new ProviderException("Unknown mechanism " + mechanism); } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java index 68a3a0665c0..c98960f7fcc 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java @@ -68,6 +68,7 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi { addKeyType("DESede", CKK_DES3); addKeyType("AES", CKK_AES); addKeyType("Blowfish", CKK_BLOWFISH); + addKeyType("ChaCha20", CKK_CHACHA20); // we don't implement RC2 or IDEA, but we want to be able to generate // keys for those SSL/TLS ciphersuites. @@ -237,6 +238,10 @@ private static P11Key createKey(Token token, byte[] encoded, P11KeyGenerator.checkKeySize(CKM_BLOWFISH_KEY_GEN, n, token); break; + case (int)CKK_CHACHA20: + keyLength = P11KeyGenerator.checkKeySize( + CKM_CHACHA20_KEY_GEN, n, token); + break; case (int)CKK_GENERIC_SECRET: case (int)PCKK_TLSPREMASTER: case (int)PCKK_TLSRSAPREMASTER: diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 5e227f45315..099caac605f 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,8 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; +import com.sun.crypto.provider.ChaCha20Poly1305Parameters; + import jdk.internal.misc.InnocuousThread; import sun.security.util.Debug; import sun.security.util.ResourcesMgr; @@ -600,6 +602,8 @@ private static void register(Descriptor d) { m(CKM_AES_KEY_GEN)); d(KG, "Blowfish", P11KeyGenerator, m(CKM_BLOWFISH_KEY_GEN)); + d(KG, "ChaCha20", P11KeyGenerator, + m(CKM_CHACHA20_KEY_GEN)); // register (Secret)KeyFactories if there are any mechanisms // for a particular algorithm that we support @@ -626,6 +630,11 @@ private static void register(Descriptor d) { d(AGP, "GCM", "sun.security.util.GCMParameters", m(CKM_AES_GCM)); + d(AGP, "ChaCha20-Poly1305", + "com.sun.crypto.provider.ChaCha20Poly1305Parameters", + s("1.2.840.113549.1.9.16.3.18", "OID.1.2.840.113549.1.9.16.3.18"), + m(CKM_CHACHA20_POLY1305)); + d(KA, "DH", P11KeyAgreement, s("DiffieHellman"), m(CKM_DH_PKCS_DERIVE)); d(KA, "ECDH", "sun.security.pkcs11.P11ECDHKeyAgreement", @@ -642,6 +651,8 @@ private static void register(Descriptor d) { m(CKM_AES_CBC)); d(SKF, "Blowfish", P11SecretKeyFactory, m(CKM_BLOWFISH_CBC)); + d(SKF, "ChaCha20", P11SecretKeyFactory, + m(CKM_CHACHA20_POLY1305)); // XXX attributes for Ciphers (supported modes, padding) d(CIP, "ARCFOUR", P11Cipher, s("RC4"), @@ -709,6 +720,10 @@ private static void register(Descriptor d) { d(CIP, "Blowfish/CBC/PKCS5Padding", P11Cipher, m(CKM_BLOWFISH_CBC)); + d(CIP, "ChaCha20-Poly1305", P11AEADCipher, + s("1.2.840.113549.1.9.16.3.18", "OID.1.2.840.113549.1.9.16.3.18"), + m(CKM_CHACHA20_POLY1305)); + d(CIP, "RSA/ECB/PKCS1Padding", P11RSACipher, s("RSA"), m(CKM_RSA_PKCS)); d(CIP, "RSA/ECB/NoPadding", P11RSACipher, @@ -1165,7 +1180,8 @@ public Object newInstance0(Object param) throws } else if (type == CIP) { if (algorithm.startsWith("RSA")) { return new P11RSACipher(token, algorithm, mechanism); - } else if (algorithm.endsWith("GCM/NoPadding")) { + } else if (algorithm.endsWith("GCM/NoPadding") || + algorithm.startsWith("ChaCha20-Poly1305")) { return new P11AEADCipher(token, algorithm, mechanism); } else { return new P11Cipher(token, algorithm, mechanism); @@ -1218,6 +1234,8 @@ public Object newInstance0(Object param) throws return new sun.security.util.ECParameters(); } else if (algorithm == "GCM") { return new sun.security.util.GCMParameters(); + } else if (algorithm == "ChaCha20-Poly1305") { + return new ChaCha20Poly1305Parameters(); // from SunJCE } else { throw new NoSuchAlgorithmException("Unsupported algorithm: " + algorithm); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java index 1e546462163..db5474fa888 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 2002 Graz University of Technology. All rights reserved. @@ -155,6 +155,11 @@ public CK_MECHANISM(long mechanism, CK_CCM_PARAMS params) { init(mechanism, params); } + public CK_MECHANISM(long mechanism, + CK_SALSA20_CHACHA20_POLY1305_PARAMS params) { + init(mechanism, params); + } + // For PSS. the parameter may be set multiple times, use the // CK_MECHANISM(long) constructor and setParameter(CK_RSA_PKCS_PSS_PARAMS) // methods instead of creating yet another constructor diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_SALSA20_CHACHA20_POLY1305_PARAMS.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_SALSA20_CHACHA20_POLY1305_PARAMS.java new file mode 100644 index 00000000000..280279ba821 --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_SALSA20_CHACHA20_POLY1305_PARAMS.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11.wrapper; + +/** + * This class represents the necessary parameters required by the + * CKM_CHACHA20_POLY1305 and CKM_SALSA20_POLY1305 mechanisms as defined in + * CK_SALSA20_CHACHA20_POLY1305_PARAMS structure.

+ * PKCS#11 structure: + *

+ * typedef struct CK_SALSA20_CHACHA20_POLY1305_PARAMS {
+ *   CK_BYTE_PTR  pNonce;
+ *   CK_ULONG     ulNonceLen;
+ *   CK_BYTE_PTR  pAAD;
+ *   CK_ULONG     ulAADLen;
+ * } CK_SALSA20_CHACHA20_POLY1305_PARAMS;
+ * 
+ * + * @since 17 + */ +public class CK_SALSA20_CHACHA20_POLY1305_PARAMS { + + private final byte[] nonce; + private final byte[] aad; + + public CK_SALSA20_CHACHA20_POLY1305_PARAMS(byte[] nonce, byte[] aad) { + this.nonce = nonce; + this.aad = aad; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append(Constants.INDENT); + sb.append("Nonce: "); + if (nonce == null) { + sb.append("null"); + } else { + sb.append("0x"); + for (byte b: nonce) { + sb.append(String.format("%02x", b)); + } + } + sb.append(Constants.NEWLINE); + sb.append(Constants.INDENT); + sb.append("AAD: "); + if (aad == null) { + sb.append("null"); + } else { + sb.append("0x"); + for (byte b: aad) { + sb.append(String.format("%02x", b)); + } + } + return sb.toString(); + } +} diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c index 59901210471..666c5eb9b3b 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 2002 Graz University of Technology. All rights reserved. @@ -1159,6 +1159,73 @@ jCCMParamsToCKCCMParamPtr(JNIEnv *env, jobject jParam, CK_ULONG *pLength) return NULL; } +/* + * converts the Java CK_SALSA20_CHACHA20_POLY1305_PARAMS object to a + * CK_SALSA20_CHACHA20_POLY1305_PARAMS pointer + * + * @param env - used to call JNI functions to get the Java classes and objects + * @param jParam - the Java CK_SALSA20_CHACHA20_POLY1305_PARAMS object to + * convert + * @param pLength - length of the allocated memory of the returned pointer + * @return pointer to the new CK_SALSA20_CHACHA20_POLY1305_PARAMS structure + */ +CK_SALSA20_CHACHA20_POLY1305_PARAMS_PTR +jSalsaChaChaPolyParamsToCKSalsaChaChaPolyParamPtr( + JNIEnv *env, jobject jParam, CK_ULONG *pLength) +{ + CK_SALSA20_CHACHA20_POLY1305_PARAMS_PTR ckParamPtr; + jclass jParamsClass; + jfieldID fieldID; + jobject jNonce, jAad; + + if (pLength != NULL) { + *pLength = 0; + } + + // retrieve java values + jParamsClass = (*env)->FindClass(env, + CLASS_SALSA20_CHACHA20_POLY1305_PARAMS); + if (jParamsClass == NULL) { return NULL; } + if (!(*env)->IsInstanceOf(env, jParam, jParamsClass)) { + return NULL; + } + fieldID = (*env)->GetFieldID(env, jParamsClass, "nonce", "[B"); + if (fieldID == NULL) { return NULL; } + jNonce = (*env)->GetObjectField(env, jParam, fieldID); + fieldID = (*env)->GetFieldID(env, jParamsClass, "aad", "[B"); + if (fieldID == NULL) { return NULL; } + jAad = (*env)->GetObjectField(env, jParam, fieldID); + // allocate memory for CK_SALSA20_CHACHA20_POLY1305_PARAMS pointer + ckParamPtr = calloc(1, sizeof(CK_SALSA20_CHACHA20_POLY1305_PARAMS)); + if (ckParamPtr == NULL) { + throwOutOfMemoryError(env, 0); + return NULL; + } + + // populate using java values + jByteArrayToCKByteArray(env, jNonce, &(ckParamPtr->pNonce), + &(ckParamPtr->ulNonceLen)); + if ((*env)->ExceptionCheck(env)) { + goto cleanup; + } + + jByteArrayToCKByteArray(env, jAad, &(ckParamPtr->pAAD), + &(ckParamPtr->ulAADLen)); + if ((*env)->ExceptionCheck(env)) { + goto cleanup; + } + + if (pLength != NULL) { + *pLength = sizeof(CK_SALSA20_CHACHA20_POLY1305_PARAMS); + } + return ckParamPtr; +cleanup: + free(ckParamPtr->pNonce); + free(ckParamPtr->pAAD); + free(ckParamPtr); + return NULL; +} + /* * converts a Java CK_MECHANISM object into a CK_MECHANISM pointer * pointer. @@ -1437,6 +1504,11 @@ CK_VOID_PTR jMechParamToCKMechParamPtrSlow(JNIEnv *env, jobject jParam, case CKM_AES_CCM: ckpParamPtr = jCCMParamsToCKCCMParamPtr(env, jParam, ckpLength); break; + case CKM_CHACHA20_POLY1305: + ckpParamPtr = + jSalsaChaChaPolyParamsToCKSalsaChaChaPolyParamPtr(env, + jParam, ckpLength); + break; case CKM_RSA_PKCS_OAEP: ckpParamPtr = jRsaPkcsOaepParamToCKRsaPkcsOaepParamPtr(env, jParam, ckpLength); break; diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c index cba1bf309b7..27006b9da0e 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 2002 Graz University of Technology. All rights reserved. @@ -323,6 +323,11 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) { free(((CK_CCM_PARAMS*)tmp)->pNonce); free(((CK_CCM_PARAMS*)tmp)->pAAD); break; + case CKM_CHACHA20_POLY1305: + TRACE0("[ CK_SALSA20_CHACHA20_POLY1305_PARAMS ]\n"); + free(((CK_SALSA20_CHACHA20_POLY1305_PARAMS*)tmp)->pNonce); + free(((CK_SALSA20_CHACHA20_POLY1305_PARAMS*)tmp)->pAAD); + break; case CKM_TLS_PRF: case CKM_NSS_TLS_PRF_GENERAL: TRACE0("[ CK_TLS_PRF_PARAMS ]\n"); diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h index 513810d10a0..7159fc982a2 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 2002 Graz University of Technology. All rights reserved. @@ -256,6 +256,8 @@ void printDebug(const char *format, ...); #define CLASS_AES_CTR_PARAMS "sun/security/pkcs11/wrapper/CK_AES_CTR_PARAMS" #define CLASS_GCM_PARAMS "sun/security/pkcs11/wrapper/CK_GCM_PARAMS" #define CLASS_CCM_PARAMS "sun/security/pkcs11/wrapper/CK_CCM_PARAMS" +#define CLASS_SALSA20_CHACHA20_POLY1305_PARAMS \ + "sun/security/pkcs11/wrapper/CK_SALSA20_CHACHA20_POLY1305_PARAMS" #define CLASS_RSA_PKCS_PSS_PARAMS "sun/security/pkcs11/wrapper/CK_RSA_PKCS_PSS_PARAMS" #define CLASS_RSA_PKCS_OAEP_PARAMS "sun/security/pkcs11/wrapper/CK_RSA_PKCS_OAEP_PARAMS" diff --git a/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPoly.java b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPoly.java new file mode 100644 index 00000000000..076660b3923 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPoly.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8255410 + * @library /test/lib .. + * @modules jdk.crypto.cryptoki + * @run main/othervm TestChaChaPoly + * @summary test for PKCS#11 ChaCha20-Poly1305 Cipher. + */ + +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.ChaCha20ParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.NoSuchPaddingException; + +import jdk.test.lib.Utils; + +public class TestChaChaPoly extends PKCS11Test { + + private static final byte[] NONCE + = HexToBytes("012345670123456701234567"); + private static final SecretKeySpec KEY = new SecretKeySpec( + HexToBytes("0123456701234567012345670123456701234567012345670123456701234567"), + "ChaCha20"); + private static final ChaCha20ParameterSpec CHACHA20_PARAM_SPEC + = new ChaCha20ParameterSpec(NONCE, 0); + private static final IvParameterSpec IV_PARAM_SPEC + = new IvParameterSpec(NONCE); + private static final String ALGO = "ChaCha20-Poly1305"; + private static final SecureRandom RAND = new SecureRandom(); + private static Provider p; + + @Override + public void main(Provider p) throws Exception { + System.out.println("Testing " + p.getName()); + try { + Cipher.getInstance(ALGO, p); + } catch (NoSuchAlgorithmException nsae) { + System.out.println("Skip; no support for " + ALGO); + return; + } + this.p = p; + testTransformations(); + testInit(); + testAEAD(); + testGetBlockSize(); + testGetIV(); + testInterop("SunJCE"); + } + + private static void testTransformations() throws Exception { + System.out.println("== transformations =="); + + checkTransformation(p, ALGO, true); + checkTransformation(p, ALGO + "/None/NoPadding", true); + checkTransformation(p, ALGO + "/ECB/NoPadding", false); + checkTransformation(p, ALGO + "/None/PKCS5Padding", false); + } + + private static void checkTransformation(Provider p, String t, + boolean expected) throws Exception { + try { + Cipher.getInstance(t, p); + if (!expected) { + throw new RuntimeException( "Should reject transformation: " + + t); + } else { + System.out.println("Accepted transformation: " + t); + } + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + if (!expected) { + System.out.println("Rejected transformation: " + t); + } else { + throw new RuntimeException("Should accept transformation: " + + t, e); + } + } + } + + private static void testInit() throws Exception { + testInitOnCrypt(Cipher.ENCRYPT_MODE); + testInitOnCrypt(Cipher.DECRYPT_MODE); + } + + private static void testInitOnCrypt(int opMode) throws Exception { + System.out.println("== init (" + getOpModeName(opMode) + ") =="); + + // Need to acquire new Cipher object as ChaCha20-Poly1305 cipher + // disallow reusing the same key and iv pair + Cipher.getInstance(ALGO, p).init(opMode, KEY, IV_PARAM_SPEC); + Cipher c = Cipher.getInstance(ALGO, p); + c.init(opMode, KEY, IV_PARAM_SPEC, RAND); + AlgorithmParameters params = c.getParameters(); + Cipher.getInstance(ALGO, p).init(opMode, KEY, params, RAND); + + try { + // try with invalid param + Cipher.getInstance(ALGO, p).init(opMode, KEY, CHACHA20_PARAM_SPEC); + throw new RuntimeException("Should reject non-IvparameterSpec"); + } catch (InvalidAlgorithmParameterException e) { + System.out.println("Expected IAPE - " + e); + } + } + + private static void testAEAD() throws Exception { + byte[] expectedPt = HexToBytes("01234567"); + byte[] ct = testUpdateAAD(Cipher.ENCRYPT_MODE, expectedPt); + byte[] pt = testUpdateAAD(Cipher.DECRYPT_MODE, ct); + if (pt != null && !Arrays.equals(pt, expectedPt)) { + System.out.println("ciphertext: " + Arrays.toString(ct)); + System.out.println("plaintext: " + Arrays.toString(pt)); + throw new RuntimeException("AEAD failed"); + } + } + + private static byte[] testUpdateAAD(int opMode, byte[] input) + throws Exception { + String opModeName = getOpModeName(opMode); + System.out.println("== updateAAD (" + opModeName + ") =="); + + byte[] aad = HexToBytes("0000"); + ByteBuffer aadBuf = ByteBuffer.wrap(aad); + + Cipher ccp = Cipher.getInstance(ALGO, p); + try { + ccp.updateAAD(aadBuf); + throw new RuntimeException( + "Should throw ISE for setting AAD on uninit'ed Cipher"); + } catch (IllegalStateException e) { + System.out.println("Expected ISE - " + e); + } + + ccp.init(opMode, KEY, IV_PARAM_SPEC); + ccp.update(input); + try { + ccp.updateAAD(aad); + throw new RuntimeException( + "Should throw ISE for setting AAD after update"); + } catch (IllegalStateException e) { + System.out.println("Expected ISE - " + e); + } + + ccp.init(opMode, KEY, IV_PARAM_SPEC); + ccp.updateAAD(aadBuf); + return ccp.doFinal(input); + } + + private static void testGetBlockSize() throws Exception { + testGetBlockSize(Cipher.ENCRYPT_MODE); + testGetBlockSize(Cipher.DECRYPT_MODE); + } + + private static void testGetBlockSize(int opMode) throws Exception { + System.out.println("== getBlockSize (" + getOpModeName(opMode) + ") =="); + Cipher c = Cipher.getInstance(ALGO, p); + if (c.getBlockSize() != 0) { + throw new RuntimeException("Block size must be 0"); + } + } + + private static void testGetIV() throws Exception { + testGetIV(Cipher.ENCRYPT_MODE); + testGetIV(Cipher.DECRYPT_MODE); + } + + private static void testGetIV(int opMode) throws Exception { + System.out.println("== getIv (" + getOpModeName(opMode) + ") =="); + + try { + Cipher.getInstance(ALGO, p).getIV(); + Cipher.getInstance(ALGO, p).getParameters(); + } catch (Exception e) { + throw new RuntimeException("Should not throw ex", e); + } + // first init w/ key only + AlgorithmParameters params = null; + for (int i = 0; i < 6; i++) { + System.out.println("IV test# " + i); + Cipher c = Cipher.getInstance(ALGO, p); + byte[] expectedIV = NONCE; + try { + switch (i) { + case 0: { + c.init(opMode, KEY); + expectedIV = null; // randomly-generated + break; + } + case 1: { + c.init(opMode, KEY, RAND); + expectedIV = null; // randomly-generated + break; + } + case 2: { + c.init(opMode, KEY, IV_PARAM_SPEC); + params = c.getParameters(); + if (params == null) { + throw new RuntimeException("Params should not be null"); + } + break; + } + case 3: c.init(opMode, KEY, IV_PARAM_SPEC, RAND); break; + case 4: c.init(opMode, KEY, params); break; + case 5: c.init(opMode, KEY, params, RAND); break; + } + checkIV(c, expectedIV); + System.out.println("=> Passed"); + } catch (GeneralSecurityException e) { + if (opMode == Cipher.DECRYPT_MODE && i < 2) { + System.out.println("=> Passed: Expected Ex thrown"); + } else { + throw new RuntimeException("Should not throw ex", e); + } + } + } + } + + private static void checkIV(Cipher c, byte[] expectedIv) { + // the specified cipher has been initialized; the returned IV and + // AlgorithmParameters object should be non-null + byte[] iv = c.getIV(); + AlgorithmParameters params = c.getParameters(); + // fail if either is null + if (iv == null || params == null) { + throw new RuntimeException("getIV()/getParameters() should " + + "not return null"); + } + + // check iv matches if not null + if (expectedIv != null && !Arrays.equals(expectedIv, iv)) { + throw new RuntimeException("IV should match expected value"); + } + + try { + byte[] iv2 = params.getParameterSpec(IvParameterSpec.class).getIV(); + if (!Arrays.equals(iv, iv2)) { + throw new RuntimeException("IV values should be consistent"); + } + } catch (InvalidParameterSpecException ipe) { + // should never happen + throw new AssertionError(); + } + } + + private static void testInterop(String interopProv) throws Exception { + testInterop(Cipher.getInstance(ALGO, p), + Cipher.getInstance(ALGO, interopProv)); + testInterop(Cipher.getInstance(ALGO, interopProv), + Cipher.getInstance(ALGO, p)); + } + + private static void testInterop(Cipher encCipher, Cipher decCipher) + throws Exception { + System.out.println("Interop: " + encCipher.getProvider().getName() + + " -> " + encCipher.getProvider().getName()); + byte[] pt = HexToBytes("012345678901234567890123456789"); + encCipher.init(Cipher.ENCRYPT_MODE, KEY); + byte[] ct = encCipher.doFinal(pt); + decCipher.init(Cipher.DECRYPT_MODE, KEY, encCipher.getParameters()); + byte[] pt2 = decCipher.doFinal(ct); + if (!Arrays.equals(pt, pt2)) { + System.out.println("HexDump/pt: " + toHexString(pt)); + System.out.println("HexDump/pt2: " + toHexString(pt2)); + throw new RuntimeException("Recovered data should match"); + } + System.out.println("=> Passed"); + } + + private static String getOpModeName(int opMode) { + switch (opMode) { + case Cipher.ENCRYPT_MODE: + return "ENCRYPT"; + + case Cipher.DECRYPT_MODE: + return "DECRYPT"; + + case Cipher.WRAP_MODE: + return "WRAP"; + + case Cipher.UNWRAP_MODE: + return "UNWRAP"; + + default: + return ""; + } + } + + public static void main(String[] args) throws Exception { + main(new TestChaChaPoly(), args); + } +} diff --git a/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyKAT.java b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyKAT.java new file mode 100644 index 00000000000..da1d91cc3de --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyKAT.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8255410 + * @library /test/lib .. + * @modules jdk.crypto.cryptoki + * @build jdk.test.lib.Convert + * @run main/othervm TestChaChaPolyKAT + * @summary ChaCha20-Poly1305 Cipher Implementation (KAT) + */ + +import java.util.*; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Cipher; +import javax.crypto.spec.ChaCha20ParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.AEADBadTagException; +import java.nio.ByteBuffer; +import jdk.test.lib.Convert; + +public class TestChaChaPolyKAT extends PKCS11Test { + + public static class TestData { + public TestData(String name, String keyStr, String nonceStr, int ctr, + int dir, String inputStr, String aadStr, String outStr) { + testName = Objects.requireNonNull(name); + key = HexToBytes(Objects.requireNonNull(keyStr)); + nonce = HexToBytes(Objects.requireNonNull(nonceStr)); + if ((counter = ctr) < 0) { + throw new IllegalArgumentException( + "counter must be 0 or greater"); + } + direction = dir; + if ((direction != Cipher.ENCRYPT_MODE) && + (direction != Cipher.DECRYPT_MODE)) { + throw new IllegalArgumentException( + "Direction must be ENCRYPT_MODE or DECRYPT_MODE"); + } + input = HexToBytes(Objects.requireNonNull(inputStr)); + aad = (aadStr != null) ? HexToBytes(aadStr) : null; + expOutput = HexToBytes(Objects.requireNonNull(outStr)); + } + + public final String testName; + public final byte[] key; + public final byte[] nonce; + public final int counter; + public final int direction; + public final byte[] input; + public final byte[] aad; + public final byte[] expOutput; + } + + private static final String ALGO = "ChaCha20-Poly1305"; + + public static final List aeadTestList = + new LinkedList() {{ + add(new TestData("RFC 7539 Sample AEAD Test Vector", + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + "070000004041424344454647", + 1, Cipher.ENCRYPT_MODE, + "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + + "637265656e20776f756c642062652069742e", + "50515253c0c1c2c3c4c5c6c7", + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + + "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + + "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + + "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + + "0691")); + add(new TestData("RFC 7539 A.5 Sample Decryption", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + "000000000102030405060708", + 1, Cipher.DECRYPT_MODE, + "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + + "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + + "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + + "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + + "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + + "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + + "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + + "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + + "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38", + "f33388860000000000004e91", + "496e7465726e65742d4472616674732061726520647261667420646f63756d65" + + "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + + "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + + "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + + "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + + "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + + "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + + "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + + "726573732e2fe2809d")); + }}; + + @Override + public void main(Provider p) throws Exception { + System.out.println("Testing " + p.getName()); + + try { + Cipher.getInstance(ALGO, p); + } catch (NoSuchAlgorithmException nsae) { + System.out.println("Skip; no support for " + ALGO); + return; + } + + int testsPassed = 0; + int testNumber = 0; + + System.out.println("----- AEAD Tests -----"); + for (TestData test : aeadTestList) { + System.out.println("*** Test " + ++testNumber + ": " + + test.testName); + if (runAEADTest(p, test)) { + testsPassed++; + } + } + System.out.println(); + + System.out.println("Total tests: " + testNumber + + ", Passed: " + testsPassed + ", Failed: " + + (testNumber - testsPassed)); + if (testsPassed != testNumber) { + throw new RuntimeException("One or more tests failed. " + + "Check output for details"); + } + } + + private static boolean runAEADTest(Provider p, TestData testData) + throws GeneralSecurityException { + boolean result = false; + + Cipher mambo = Cipher.getInstance(ALGO, p); + SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20"); + IvParameterSpec mamboSpec = new IvParameterSpec(testData.nonce); + + mambo.init(testData.direction, mamboKey, mamboSpec); + + byte[] out = new byte[mambo.getOutputSize(testData.input.length)]; + int outOff = 0; + try { + mambo.updateAAD(testData.aad); + outOff += mambo.update(testData.input, 0, testData.input.length, + out, outOff); + outOff += mambo.doFinal(out, outOff); + } catch (AEADBadTagException abte) { + // If we get a bad tag or derive a tag mismatch, log it + // and register it as a failure + System.out.println("FAIL: " + abte); + return false; + } + + if (!Arrays.equals(out, 0, outOff, testData.expOutput, 0, outOff)) { + System.out.println("ERROR - Output Mismatch!"); + System.out.println("Expected:\n" + + dumpHexBytes(testData.expOutput, 16, "\n", " ")); + System.out.println("Actual:\n" + + dumpHexBytes(out, 16, "\n", " ")); + System.out.println(); + } else { + result = true; + } + + return result; + } + + /** + * Dump the hex bytes of a buffer into string form. + * + * @param data The array of bytes to dump to stdout. + * @param itemsPerLine The number of bytes to display per line + * if the {@code lineDelim} character is blank then all bytes + * will be printed on a single line. + * @param lineDelim The delimiter between lines + * @param itemDelim The delimiter between bytes + * + * @return The hexdump of the byte array + */ + private static String dumpHexBytes(byte[] data, int itemsPerLine, + String lineDelim, String itemDelim) { + return dumpHexBytes(ByteBuffer.wrap(data), itemsPerLine, lineDelim, + itemDelim); + } + + private static String dumpHexBytes(ByteBuffer data, int itemsPerLine, + String lineDelim, String itemDelim) { + StringBuilder sb = new StringBuilder(); + if (data != null) { + data.mark(); + int i = 0; + while (data.remaining() > 0) { + if (i % itemsPerLine == 0 && i != 0) { + sb.append(lineDelim); + } + sb.append(String.format("%02X", data.get())).append(itemDelim); + i++; + } + data.reset(); + } + + return sb.toString(); + } + + public static void main(String[] args) throws Exception { + main(new TestChaChaPolyKAT(), args); + } +} diff --git a/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyNoReuse.java b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyNoReuse.java new file mode 100644 index 00000000000..0a760535631 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyNoReuse.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8255410 + * @library /test/lib .. + * @run main/othervm TestChaChaPolyNoReuse + * @summary Test PKCS#11 ChaCha20-Poly1305 Cipher Implementation + * (key/nonce reuse check) + */ + +import java.util.*; +import javax.crypto.Cipher; +import java.security.spec.AlgorithmParameterSpec; +import java.security.Provider; +import java.security.NoSuchAlgorithmException; +import javax.crypto.spec.ChaCha20ParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.AEADBadTagException; +import javax.crypto.SecretKey; +import java.security.InvalidKeyException; +import java.security.InvalidAlgorithmParameterException; + +public class TestChaChaPolyNoReuse extends PKCS11Test { + + private static final String KEY_ALGO = "ChaCha20"; + private static final String CIPHER_ALGO = "ChaCha20-Poly1305"; + + /** + * Basic TestMethod interface definition + */ + public interface TestMethod { + /** + * Runs the actual test case + * + * @param provider the provider to provide the requested Cipher obj. + * + * @return true if the test passes, false otherwise. + */ + boolean run(Provider p); + } + + public static class TestData { + public TestData(String name, String keyStr, String nonceStr, int ctr, + int dir, String inputStr, String aadStr, String outStr) { + testName = Objects.requireNonNull(name); + key = HexToBytes(keyStr); + nonce = HexToBytes(nonceStr); + if ((counter = ctr) < 0) { + throw new IllegalArgumentException( + "counter must be 0 or greater"); + } + direction = dir; + if (direction != Cipher.ENCRYPT_MODE) { + throw new IllegalArgumentException( + "Direction must be ENCRYPT_MODE"); + } + input = HexToBytes(inputStr); + aad = (aadStr != null) ? HexToBytes(aadStr) : null; + expOutput = HexToBytes(outStr); + } + + public final String testName; + public final byte[] key; + public final byte[] nonce; + public final int counter; + public final int direction; + public final byte[] input; + public final byte[] aad; + public final byte[] expOutput; + } + + public static final List aeadTestList = + new LinkedList() {{ + add(new TestData("RFC 7539 Sample AEAD Test Vector", + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + "070000004041424344454647", + 1, Cipher.ENCRYPT_MODE, + "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + + "637265656e20776f756c642062652069742e", + "50515253c0c1c2c3c4c5c6c7", + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + + "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + + "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + + "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + + "0691")); + }}; + + /** + * Make sure we do not use this Cipher object without initializing it + * at all + */ + public static final TestMethod noInitTest = new TestMethod() { + @Override + public boolean run(Provider p) { + System.out.println("----- No Init Test -----"); + try { + Cipher cipher = Cipher.getInstance(CIPHER_ALGO, p); + TestData testData = aeadTestList.get(0); + + // Attempting to use the cipher without initializing it + // should throw an IllegalStateException + try { + cipher.updateAAD(testData.aad); + throw new RuntimeException( + "Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + // Do nothing, this is what we expected to happen + } + } catch (Exception exc) { + System.out.println("Unexpected exception: " + exc); + exc.printStackTrace(); + return false; + } + + return true; + } + }; + + /** + * Attempt to run two full encryption operations without an init in + * between. + */ + public static final TestMethod encTwiceNoInit = new TestMethod() { + @Override + public boolean run(Provider p) { + System.out.println("----- Encrypt 2nd time without init -----"); + try { + AlgorithmParameterSpec spec; + Cipher cipher = Cipher.getInstance(CIPHER_ALGO, p); + TestData testData = aeadTestList.get(0); + spec = new IvParameterSpec(testData.nonce); + SecretKey key = new SecretKeySpec(testData.key, KEY_ALGO); + + // Initialize and encrypt + cipher.init(testData.direction, key, spec); + cipher.updateAAD(testData.aad); + cipher.doFinal(testData.input); + System.out.println("First encryption complete"); + + // Now attempt to encrypt again without changing the key/IV + // This should fail. + try { + cipher.updateAAD(testData.aad); + } catch (IllegalStateException ise) { + // Do nothing, this is what we expected to happen + } + try { + cipher.doFinal(testData.input); + throw new RuntimeException( + "Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + // Do nothing, this is what we expected to happen + } + } catch (Exception exc) { + System.out.println("Unexpected exception: " + exc); + exc.printStackTrace(); + return false; + } + + return true; + } + }; + + /** + * Encrypt once successfully, then attempt to init with the same + * key and nonce. + */ + public static final TestMethod encTwiceInitSameParams = new TestMethod() { + @Override + public boolean run(Provider p) { + System.out.println("----- Encrypt, then init with same params " + + "-----"); + try { + AlgorithmParameterSpec spec; + Cipher cipher = Cipher.getInstance(CIPHER_ALGO, p); + TestData testData = aeadTestList.get(0); + spec = new IvParameterSpec(testData.nonce); + SecretKey key = new SecretKeySpec(testData.key, KEY_ALGO); + + // Initialize then encrypt + cipher.init(testData.direction, key, spec); + cipher.updateAAD(testData.aad); + cipher.doFinal(testData.input); + System.out.println("First encryption complete"); + + // Initializing after the completed encryption with + // the same key and nonce should fail. + try { + cipher.init(testData.direction, key, spec); + throw new RuntimeException( + "Expected IKE or IAPE not thrown"); + } catch (InvalidKeyException | + InvalidAlgorithmParameterException e) { + // Do nothing, this is what we expected to happen + } + } catch (Exception exc) { + System.out.println("Unexpected exception: " + exc); + exc.printStackTrace(); + return false; + } + + return true; + } + }; + + public static final List testMethodList = + Arrays.asList(noInitTest, encTwiceNoInit, encTwiceInitSameParams); + + @Override + public void main(Provider p) throws Exception { + System.out.println("Testing " + p.getName()); + try { + Cipher.getInstance(CIPHER_ALGO, p); + } catch (NoSuchAlgorithmException nsae) { + System.out.println("Skip; no support for " + CIPHER_ALGO); + return; + } + + int testsPassed = 0; + int testNumber = 0; + + for (TestMethod tm : testMethodList) { + testNumber++; + boolean result = tm.run(p); + System.out.println("Result: " + (result ? "PASS" : "FAIL")); + if (result) { + testsPassed++; + } + } + + System.out.println("Total Tests: " + testNumber + + ", Tests passed: " + testsPassed); + if (testsPassed < testNumber) { + throw new RuntimeException( + "Not all tests passed. See output for failure info"); + } + } + + public static void main(String[] args) throws Exception { + main(new TestChaChaPolyNoReuse(), args); + } +} diff --git a/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyOutputSize.java b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyOutputSize.java new file mode 100644 index 00000000000..57a7b9a4606 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/Cipher/TestChaChaPolyOutputSize.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8255410 + * @summary Check ChaCha20-Poly1305 cipher output size + * @library /test/lib .. + * @build jdk.test.lib.Convert + * @run main TestChaChaPolyOutputSize + */ + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.SecureRandom; +import java.security.Provider; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Cipher; +import javax.crypto.spec.ChaCha20ParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class TestChaChaPolyOutputSize extends PKCS11Test { + + private static final SecureRandom SR = new SecureRandom(); + + private static final SecretKeySpec KEY = new SecretKeySpec(new byte[32], + "ChaCha20"); + + private static final String ALGO = "ChaCha20-Poly1305"; + + public static void main(String args[]) throws Exception { + main(new TestChaChaPolyOutputSize(), args); + } + + @Override + public void main(Provider p) throws GeneralSecurityException { + System.out.println("Testing " + p.getName()); + try { + Cipher.getInstance(ALGO, p); + } catch (NoSuchAlgorithmException nsae) { + System.out.println("Skip; no support for " + ALGO); + return; + } + testGetOutSize(p); + testMultiPartAEADDec(p); + } + + private static void testGetOutSize(Provider p) + throws GeneralSecurityException { + + Cipher ccp = Cipher.getInstance(ALGO, p); + ccp.init(Cipher.ENCRYPT_MODE, KEY, + new IvParameterSpec(getRandBuf(12))); + + // Encryption lengths are calculated as the input length plus the tag + // length (16). + testOutLen(ccp, 0, 16); + testOutLen(ccp, 5, 21); + testOutLen(ccp, 5120, 5136); + // perform an update, then test with a final block + byte[] input = new byte[5120]; + SR.nextBytes(input); + byte[] updateOut = ccp.update(input); + testOutLen(ccp, 1024, 1040 + + (5120 - (updateOut == null? 0 : updateOut.length))); + + // Decryption lengths are handled differently for AEAD mode. The length + // should be zero for anything up to and including the first 16 bytes + // (since that's the tag). Anything above that should be the input + // length plus any unprocessed input (via update calls), minus the + // 16 byte tag. + ccp.init(Cipher.DECRYPT_MODE, KEY, new IvParameterSpec(getRandBuf(12))); + testOutLen(ccp, 0, 0); + testOutLen(ccp, 5, 0); + testOutLen(ccp, 16, 0); + testOutLen(ccp, 5120, 5104); + // Perform an update, then test with the length of a final chunk + // of data. + updateOut = ccp.update(input); + testOutLen(ccp, 1024, 6128 - (updateOut == null? 0 : updateOut.length)); + } + + private static void testMultiPartAEADDec(Provider p) + throws GeneralSecurityException { + IvParameterSpec ivps = new IvParameterSpec(getRandBuf(12)); + + // Encrypt some data so we can test decryption. + byte[] pText = getRandBuf(2048); + ByteBuffer pTextBase = ByteBuffer.wrap(pText); + + Cipher enc = Cipher.getInstance(ALGO, p); + enc.init(Cipher.ENCRYPT_MODE, KEY, ivps); + ByteBuffer ctBuf = ByteBuffer.allocateDirect( + enc.getOutputSize(pText.length)); + enc.doFinal(pTextBase, ctBuf); + + // Create a new direct plain text ByteBuffer which will catch the + // decrypted data. + ByteBuffer ptBuf = ByteBuffer.allocateDirect(pText.length); + + // Set the cipher text buffer limit to roughly half the data so we can + // do an update/final sequence. + ctBuf.position(0).limit(1024); + + Cipher dec = Cipher.getInstance(ALGO, p); + dec.init(Cipher.DECRYPT_MODE, KEY, ivps); + dec.update(ctBuf, ptBuf); + System.out.println("CTBuf: " + ctBuf); + System.out.println("PTBuf: " + ptBuf); + ctBuf.limit(ctBuf.capacity()); + dec.doFinal(ctBuf, ptBuf); + + // NOTE: do not use flip() which will set limit based on current + // position. ptBuf curr pos = 2048 vs pTextBase pos = 0 + ptBuf.flip(); + pTextBase.flip(); + System.out.println("PT Base:" + pTextBase); + System.out.println("PT Actual:" + ptBuf); + + if (pTextBase.compareTo(ptBuf) != 0) { + StringBuilder sb = new StringBuilder(); + sb.append("Plaintext mismatch: Original: "). + append(pTextBase.toString()).append("\nActual :"). + append(ptBuf); + throw new RuntimeException(sb.toString()); + } + } + + private static void testOutLen(Cipher c, int inLen, int expOut) { + int actualOut = c.getOutputSize(inLen); + if (actualOut != expOut) { + throw new RuntimeException("Cipher " + c + ", in: " + inLen + + ", expOut: " + expOut + ", actual: " + actualOut); + } + } + + private static byte[] getRandBuf(int len) { + byte[] buf = new byte[len]; + SR.nextBytes(buf); + return buf; + } +} diff --git a/test/jdk/sun/security/pkcs11/Cipher/TestKATForGCM.java b/test/jdk/sun/security/pkcs11/Cipher/TestKATForGCM.java index 67e94cecf68..55806f7723d 100644 --- a/test/jdk/sun/security/pkcs11/Cipher/TestKATForGCM.java +++ b/test/jdk/sun/security/pkcs11/Cipher/TestKATForGCM.java @@ -39,18 +39,6 @@ public class TestKATForGCM extends PKCS11Test { - // Utility methods - private static byte[] HexToBytes(String hexVal) { - if (hexVal == null) return new byte[0]; - byte[] result = new byte[hexVal.length()/2]; - for (int i = 0; i < result.length; i++) { - // 2 characters at a time - String byteVal = hexVal.substring(2*i, 2*i +2); - result[i] = Integer.valueOf(byteVal, 16).byteValue(); - } - return result; - } - private static class TestVector { SecretKey key; byte[] plainText; diff --git a/test/jdk/sun/security/pkcs11/KeyGenerator/TestChaCha20.java b/test/jdk/sun/security/pkcs11/KeyGenerator/TestChaCha20.java new file mode 100644 index 00000000000..cdb18dcee61 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/KeyGenerator/TestChaCha20.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8255410 + * @modules jdk.crypto.cryptoki + * @summary Check ChaCha20 key generator. + * @library /test/lib .. + * @run main/othervm TestChaCha20 + */ +import java.security.Provider; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidParameterException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.ChaCha20ParameterSpec; + +public class TestChaCha20 extends PKCS11Test { + + private static final String ALGO = "ChaCha20"; + + public static void main(String[] args) throws Exception { + main(new TestChaCha20(), args); + } + + @Override + public void main(Provider p) throws Exception { + System.out.println("Testing " + p.getName()); + KeyGenerator kg; + try { + kg = KeyGenerator.getInstance(ALGO, p); + } catch (NoSuchAlgorithmException nsae) { + System.out.println("Skip; no support for " + ALGO); + return; + } + + try { + kg.init(new ChaCha20ParameterSpec(new byte[12], 0)); + throw new RuntimeException( + "ChaCha20 key generation should not need any paramSpec"); + } catch (InvalidAlgorithmParameterException e) { + System.out.println("Expected IAPE: " + e.getMessage()); + } + + for (int keySize : new int[] { 32, 64, 128, 256, 512, 1024 }) { + try { + kg.init(keySize); + if (keySize != 256) { + throw new RuntimeException(keySize + " is invalid keysize"); + } + } catch (InvalidParameterException e) { + if (keySize == 256) { + throw new RuntimeException("IPE thrown for valid keySize"); + } else { + System.out.println("Expected IPE thrown for " + keySize); + } + } + } + + //kg.init(256); + SecretKey key = kg.generateKey(); + byte[] keyValue = key.getEncoded(); + System.out.println("Key: " + toHexString(keyValue)); + if (keyValue.length != 32) { + throw new RuntimeException("The size of generated key must be 256"); + } + } +} diff --git a/test/jdk/sun/security/pkcs11/PKCS11Test.java b/test/jdk/sun/security/pkcs11/PKCS11Test.java index b914c80581a..7ae0b684017 100644 --- a/test/jdk/sun/security/pkcs11/PKCS11Test.java +++ b/test/jdk/sun/security/pkcs11/PKCS11Test.java @@ -111,6 +111,40 @@ public static enum ECCState { None, Basic, Extended }; static double nss3_version = -1; static Provider pkcs11 = newPKCS11Provider(); + // Utility methods + // Used to backport HexFormat from 17. + protected static byte[] HexToBytes(String hexVal) { + if (hexVal == null) return new byte[0]; + byte[] result = new byte[hexVal.length()/2]; + for (int i = 0; i < result.length; i++) { + // 2 characters at a time + String byteVal = hexVal.substring(2*i, 2*i +2); + result[i] = Integer.valueOf(byteVal, 16).byteValue(); + } + return result; + } + private static void byte2hex(byte b, StringBuffer buf) { + char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + int high = ((b & 0xf0) >> 4); + int low = (b & 0x0f); + buf.append(hexChars[high]); + buf.append(hexChars[low]); + } + protected static String toHexString(byte[] block) { + StringBuffer buf = new StringBuffer(); + + int len = block.length; + + for (int i = 0; i < len; i++) { + byte2hex(block[i], buf); + if (i < len-1) { + buf.append(":"); + } + } + return buf.toString(); + } + public static Provider newPKCS11Provider() { ServiceLoader sl = ServiceLoader.load(java.security.Provider.class); Iterator iter = sl.iterator(); diff --git a/test/jdk/sun/security/pkcs11/SecretKeyFactory/TestGeneral.java b/test/jdk/sun/security/pkcs11/SecretKeyFactory/TestGeneral.java new file mode 100644 index 00000000000..bca594c66bd --- /dev/null +++ b/test/jdk/sun/security/pkcs11/SecretKeyFactory/TestGeneral.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8255410 + * @summary test the general SecretKeyFactory functionality + * @library /test/lib .. + * @modules jdk.crypto.cryptoki + * @run main/othervm TestGeneral + */ + +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.Arrays; +import javax.crypto.SecretKeyFactory; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +public class TestGeneral extends PKCS11Test { + + private enum TestResult { + PASS, + FAIL, + TBD // unused for now + } + + public static void main(String[] args) throws Exception { + main(new TestGeneral(), args); + } + + private void test(String algorithm, SecretKey key, Provider p, + TestResult expected) throws RuntimeException { + System.out.println("Testing " + algorithm + " SKF from " + p.getName()); + SecretKeyFactory skf; + try { + skf = SecretKeyFactory.getInstance(algorithm, p); + } catch (NoSuchAlgorithmException e) { + System.out.println("Not supported, skipping: " + e); + return; + } + try { + SecretKey key2 = skf.translateKey(key); + if (expected == TestResult.FAIL) { + throw new RuntimeException("translateKey() should FAIL"); + } + System.out.println("=> translated key"); + if (!key2.getAlgorithm().equalsIgnoreCase(algorithm)) { + throw new RuntimeException("translated key algorithm mismatch"); + } + System.out.println("=> checked key algorithm"); + + // proceed to check encodings if format match + if (key2.getFormat().equalsIgnoreCase(key.getFormat())) { + if (key2.getEncoded() != null && + !Arrays.equals(key.getEncoded(), key2.getEncoded())) { + throw new RuntimeException( + "translated key encoding mismatch"); + } + System.out.println("=> checked key format and encoding"); + } + } catch (Exception e) { + if (expected == TestResult.PASS) { + e.printStackTrace(); + throw new RuntimeException("translateKey() should pass", e); + } + } + } + + @Override + public void main(Provider p) throws Exception { + + byte[] rawBytes = new byte[32]; + new SecureRandom().nextBytes(rawBytes); + + SecretKey aes_128Key = new SecretKeySpec(rawBytes, 0, 16, "AES"); + SecretKey aes_256Key = new SecretKeySpec(rawBytes, 0, 32, "AES"); + SecretKey bf_128Key = new SecretKeySpec(rawBytes, 0, 16, "Blowfish"); + SecretKey cc20Key = new SecretKeySpec(rawBytes, 0, 32, "ChaCha20"); + + // fixed key length + test("AES", aes_128Key, p, TestResult.PASS); + test("AES", aes_256Key, p, TestResult.PASS); + test("AES", cc20Key, p, TestResult.FAIL); + + test("ChaCha20", aes_128Key, p, TestResult.FAIL); + test("ChaCha20", aes_256Key, p, TestResult.FAIL); + test("ChaCha20", cc20Key, p, TestResult.PASS); + + // variable key length + // Different PKCS11 impls may have different ranges + // of supported key sizes for variable-key-length + // algorithms. + test("Blowfish", aes_128Key, p, TestResult.FAIL); + test("Blowfish", cc20Key, p, TestResult.FAIL); + test("Blowfish", bf_128Key, p, TestResult.PASS); + } +} diff --git a/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java b/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java index beae7253df3..12e06b19c4b 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java +++ b/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java @@ -107,35 +107,6 @@ public void main(Provider p) throws Exception { } - /* - * Converts a byte to hex digit and writes to the supplied buffer - */ - private void byte2hex(byte b, StringBuffer buf) { - char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', - '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - int high = ((b & 0xf0) >> 4); - int low = (b & 0x0f); - buf.append(hexChars[high]); - buf.append(hexChars[low]); - } - - /* - * Converts a byte array to hex string - */ - private String toHexString(byte[] block) { - StringBuffer buf = new StringBuffer(); - - int len = block.length; - - for (int i = 0; i < len; i++) { - byte2hex(block[i], buf); - if (i < len-1) { - buf.append(":"); - } - } - return buf.toString(); - } - private static final byte alicePubKeyEnc[] = { (byte)0x30, (byte)0x82, (byte)0x01, (byte)0x24, (byte)0x30, (byte)0x81, (byte)0x99, (byte)0x06, From fa297c89463bf7986154d1cc31b71906a75d45b1 Mon Sep 17 00:00:00 2001 From: J9 Build Date: Thu, 3 Mar 2022 04:05:31 +0000 Subject: [PATCH 7/7] Update OPENJDK_TAG to merged level jdk-11.0.15+4 Signed-off-by: J9 Build --- closed/openjdk-tag.gmk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/closed/openjdk-tag.gmk b/closed/openjdk-tag.gmk index d40f044660d..b8daa70939c 100644 --- a/closed/openjdk-tag.gmk +++ b/closed/openjdk-tag.gmk @@ -1 +1 @@ -OPENJDK_TAG := jdk-11.0.15+3 +OPENJDK_TAG := jdk-11.0.15+4