From 8ecab304ebb4428afa9a3b24cf765d9d0e088819 Mon Sep 17 00:00:00 2001 From: John Jiang Date: Tue, 18 Jul 2023 12:40:17 +0800 Subject: [PATCH] TKSS-290: PKCS12KeyStore supports HmacPBESM3 --- README.md | 6 +- README_cn.md | 6 +- .../com/sun/crypto/provider/HmacCore.java | 340 ++++++++++++++++ .../crypto/provider/HmacPKCS12PBECore.java | 95 +++++ .../provider/PKCS12PBECipherCoreUtil.java | 136 +++++++ .../kona/crypto/KonaCryptoProvider.java | 2 + .../crypto/provider/HmacPKCS12PBE_SM3.java | 12 + .../kona/sun/security/util/PBEUtil.java | 362 ++++++++++++++++++ kona-pkix/README.md | 5 +- kona-pkix/README_cn.md | 3 +- .../sun/security/pkcs12/PKCS12KeyStore.java | 8 +- .../tencent/kona/pkix/tool/KeyToolTest.java | 16 +- 12 files changed, 972 insertions(+), 19 deletions(-) create mode 100644 kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacCore.java create mode 100644 kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacPKCS12PBECore.java create mode 100644 kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/PKCS12PBECipherCoreUtil.java create mode 100644 kona-crypto/src/main/java/com/tencent/kona/crypto/provider/HmacPKCS12PBE_SM3.java create mode 100644 kona-crypto/src/main/java/com/tencent/kona/sun/security/util/PBEUtil.java diff --git a/README.md b/README.md index c3cf9268..9c8d2abd 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ English | **[中文]** Tencent Kona SM Suite is a set of Java security providers, which service the ShangMi applications in Java ecosystem. This suite contains four providers: - [KonaCrypto],which implements SM2, SM3 and SM4 algorithms based on Java Cryptography Architecture. -- [KonaPKIX],which supports ShangMi algorithms on loading certificate and certificate chain verification. It also can load and write key store files containing ShangMi certificates. Additionally, this component provides two utility classes: - - KeyTool, which is the same as `keytool` in JDK, can generate private keys, and create certificates and key store files. It can use `PBEWithHmacSM3AndSM4` to encrypt private keys and keystore files. - - KeyStoreTool, which can import the existing [PEM]-encoded private keys and certificates to keystore files. +- [KonaPKIX],which supports ShangMi algorithms on loading certificate and certificate chain verification. It also can load and write keystores containing ShangMi certificates. Additionally, this component provides two utility classes: + - KeyTool, which is the same as `keytool` in JDK, can generate private keys, and create certificates and keystores. It can use `PBEWithHmacSM3AndSM4` to encrypt private keys and keystores, and use `HmacPBESM3` to validate the integrity of keystores. + - KeyStoreTool, which can import the existing [PEM]-encoded private keys and certificates to keystores. - [KonaSSL] implements China's Transport Layer Cryptographic Protocol, and also applies ShangMi algorithms to TLS 1.3 based on RFC 8998. - [Kona], which wraps all the features in `KonaCrypto`,`KonaPKIX` and `KonaSSL`, so it has to depend on one or more of them. Generally, **this provider is recommended**. diff --git a/README_cn.md b/README_cn.md index 84433507..37742209 100644 --- a/README_cn.md +++ b/README_cn.md @@ -7,9 +7,9 @@ 腾讯Kona国密套件是一组Java安全特性的Provider实现,主要服务于Java生态中的国密应用场景。具体地,该套件包含有四个Provider: - [KonaCrypto],它遵循标准的[JCA]框架实现了国密基础算法SM2,SM3和SM4。 -- [KonaPKIX],它实现了国密证书的解析与验证,并可加载和创建包含国密证书的密钥库文件。它需要依赖`KonaCrypto`。另外,该组件还提供了两个工具类: - - KeyTool,它的功能与JDK中的`keytool`相同,可以生成密钥对,创建证书以及密钥库文件。它支持使用`PBEWithHmacSM3AndSM4`算法对私钥和密钥库文件进行加密。 - - KeyStoreTool,它可以将已有的[PEM]格式的私钥和证书导入密钥库文件。 +- [KonaPKIX],它实现了国密证书的解析与验证,并可加载和创建包含国密证书的密钥库。它需要依赖`KonaCrypto`。另外,该组件还提供了两个工具类: + - KeyTool,它的功能与JDK中的`keytool`相同,可以生成密钥对,创建证书以及密钥库。它支持使用`PBEWithHmacSM3AndSM4`算法对私钥和密钥库进行加密,也可使用`HmacPBESM3`算法验证密钥库的完整性。 + - KeyStoreTool,它可以将已有的[PEM]格式的私钥和证书导入密钥库。 - [KonaSSL],它实现了中国的传输层密码协议(TLCP),并遵循RFC 8998规范将国密基础算法应用到了TLS 1.3协议中。它需要依赖`KonaCrypto`和`KonaPKIX`。 - [Kona],它将`KonaCrypto`,`KonaPKIX`和`KonaSSL`中的特性进行了简单的封装,所以它需要根据实际需求去依赖这些Provider中的一个或多个。一般地,**建议使用这个Provider**。 diff --git a/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacCore.java b/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacCore.java new file mode 100644 index 00000000..41e7d819 --- /dev/null +++ b/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacCore.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2002, 2020, 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 com.tencent.kona.com.sun.crypto.provider; + +import java.util.Arrays; + +import java.nio.ByteBuffer; + +import javax.crypto.MacSpi; +import javax.crypto.SecretKey; +import java.security.*; +import java.security.spec.*; + +import com.tencent.kona.crypto.CryptoInsts; +import sun.security.x509.AlgorithmId; + +/** + * This class constitutes the core of HMAC- algorithms, where + * is the digest algorithm used by HMAC as in RFC 2104 + * "HMAC: Keyed-Hashing for Message Authentication". + * + * It also contains implementation classes for: + * - HmacMD5 + * - HmacSHA1 + * - HMAC with SHA-2 family of digests, i.e. HmacSHA224, HmacSHA256, + * HmacSHA384, HmacSHA512, HmacSHA512/224, HmacSHA512/256, and + * - HMAC with SHA-3 family of digests, i.e. HmacSHA3-224, HmacSHA3-256, + * HmacSHA3-384, HmacSHA3-512 + * + * @author Jan Luehe + */ +abstract class HmacCore extends MacSpi implements Cloneable { + + private MessageDigest md; + private byte[] k_ipad; // inner padding - key XORd with ipad + private byte[] k_opad; // outer padding - key XORd with opad + private boolean first; // Is this the first data to be processed? + + private final int blockLen; + + /** + * Standard constructor, creates a new HmacCore instance instantiating + * a MessageDigest of the specified name. + */ + HmacCore(String digestAlgo, int bl) throws NoSuchAlgorithmException { + MessageDigest md = CryptoInsts.getMessageDigest(digestAlgo); + if (!(md instanceof Cloneable)) { + // use SUN provider if the most preferred one does not support + // cloning + Provider sun = Security.getProvider("SUN"); + if (sun != null) { + md = MessageDigest.getInstance(digestAlgo, sun); + } else { + String noCloneProv = md.getProvider().getName(); + // if no Sun provider, use provider list + md = null; + Provider[] provs = Security.getProviders(); + for (Provider p : provs) { + try { + if (!p.getName().equals(noCloneProv)) { + MessageDigest md2 = + MessageDigest.getInstance(digestAlgo, p); + if (md2 instanceof Cloneable) { + md = md2; + break; + } + } + } catch (NoSuchAlgorithmException nsae) { + continue; + } + } + if (md == null) { + throw new NoSuchAlgorithmException + ("No Cloneable digest found for " + digestAlgo); + } + } + } + this.md = md; + this.blockLen = bl; + this.k_ipad = new byte[blockLen]; + this.k_opad = new byte[blockLen]; + first = true; + } + + /** + * Returns the length of the HMAC in bytes. + * + * @return the HMAC length in bytes. + */ + protected int engineGetMacLength() { + return this.md.getDigestLength(); + } + + /** + * Initializes the HMAC with the given secret key and algorithm parameters. + * + * @param key the secret key. + * @param params the algorithm parameters. + * + * @exception InvalidKeyException if the given key is inappropriate for + * initializing this MAC. + * @exception InvalidAlgorithmParameterException if the given algorithm + * parameters are inappropriate for this MAC. + */ + protected void engineInit(Key key, AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException + ("HMAC does not use parameters"); + } + + if (!(key instanceof SecretKey)) { + throw new InvalidKeyException("Secret key expected"); + } + + byte[] secret = key.getEncoded(); + if (secret == null) { + throw new InvalidKeyException("Missing key data"); + } + + // if key is longer than the block length, reset it using + // the message digest object. + if (secret.length > blockLen) { + byte[] tmp = md.digest(secret); + // now erase the secret + Arrays.fill(secret, (byte)0); + secret = tmp; + } + + // XOR k with ipad and opad, respectively + for (int i = 0; i < blockLen; i++) { + int si = (i < secret.length) ? secret[i] : 0; + k_ipad[i] = (byte)(si ^ 0x36); + k_opad[i] = (byte)(si ^ 0x5c); + } + + // now erase the secret + Arrays.fill(secret, (byte)0); + secret = null; + + engineReset(); + } + + /** + * Processes the given byte. + * + * @param input the input byte to be processed. + */ + protected void engineUpdate(byte input) { + if (first == true) { + // compute digest for 1st pass; start with inner pad + md.update(k_ipad); + first = false; + } + + // add the passed byte to the inner digest + md.update(input); + } + + /** + * Processes the first len bytes in input, + * starting at offset. + * + * @param input the input buffer. + * @param offset the offset in input where the input starts. + * @param len the number of bytes to process. + */ + protected void engineUpdate(byte input[], int offset, int len) { + if (first == true) { + // compute digest for 1st pass; start with inner pad + md.update(k_ipad); + first = false; + } + + // add the selected part of an array of bytes to the inner digest + md.update(input, offset, len); + } + + /** + * Processes the input.remaining() bytes in the ByteBuffer + * input. + * + * @param input the input byte buffer. + */ + protected void engineUpdate(ByteBuffer input) { + if (first == true) { + // compute digest for 1st pass; start with inner pad + md.update(k_ipad); + first = false; + } + + md.update(input); + } + + /** + * Completes the HMAC computation and resets the HMAC for further use, + * maintaining the secret key that the HMAC was initialized with. + * + * @return the HMAC result. + */ + protected byte[] engineDoFinal() { + if (first == true) { + // compute digest for 1st pass; start with inner pad + md.update(k_ipad); + } else { + first = true; + } + + try { + // finish the inner digest + byte[] tmp = md.digest(); + + // compute digest for 2nd pass; start with outer pad + md.update(k_opad); + // add result of 1st hash + md.update(tmp); + + md.digest(tmp, 0, tmp.length); + return tmp; + } catch (DigestException e) { + // should never occur + throw new ProviderException(e); + } + } + + /** + * Resets the HMAC for further use, maintaining the secret key that the + * HMAC was initialized with. + */ + protected void engineReset() { + if (first == false) { + md.reset(); + first = true; + } + } + + /* + * Clones this object. + */ + public Object clone() throws CloneNotSupportedException { + HmacCore copy = (HmacCore) super.clone(); + copy.md = (MessageDigest) md.clone(); + copy.k_ipad = k_ipad.clone(); + copy.k_opad = k_opad.clone(); + return copy; + } + + // nested static class for the HmacSHA224 implementation + public static final class HmacSHA224 extends HmacCore { + public HmacSHA224() throws NoSuchAlgorithmException { + super("SHA-224", 64); + } + } + + // nested static class for the HmacSHA256 implementation + public static final class HmacSHA256 extends HmacCore { + public HmacSHA256() throws NoSuchAlgorithmException { + super("SHA-256", 64); + } + } + + // nested static class for the HmacSHA384 implementation + public static final class HmacSHA384 extends HmacCore { + public HmacSHA384() throws NoSuchAlgorithmException { + super("SHA-384", 128); + } + } + + // nested static class for the HmacSHA512 implementation + public static final class HmacSHA512 extends HmacCore { + public HmacSHA512() throws NoSuchAlgorithmException { + super("SHA-512", 128); + } + } + + // nested static class for the HmacSHA512/224 implementation + public static final class HmacSHA512_224 extends HmacCore { + public HmacSHA512_224() throws NoSuchAlgorithmException { + super("SHA-512/224", 128); + } + } + + // nested static class for the HmacSHA512/256 implementation + public static final class HmacSHA512_256 extends HmacCore { + public HmacSHA512_256() throws NoSuchAlgorithmException { + super("SHA-512/256", 128); + } + } + + // nested static class for the HmacSHA3-224 implementation + public static final class HmacSHA3_224 extends HmacCore { + public HmacSHA3_224() throws NoSuchAlgorithmException { + super("SHA3-224", 144); + } + } + + // nested static class for the HmacSHA3-256 implementation + public static final class HmacSHA3_256 extends HmacCore { + public HmacSHA3_256() throws NoSuchAlgorithmException { + super("SHA3-256", 136); + } + } + + // nested static class for the HmacSHA3-384 implementation + public static final class HmacSHA3_384 extends HmacCore { + public HmacSHA3_384() throws NoSuchAlgorithmException { + super("SHA3-384", 104); + } + } + + // nested static class for the HmacSHA3-512 implementation + public static final class HmacSHA3_512 extends HmacCore { + public HmacSHA3_512() throws NoSuchAlgorithmException { + super("SHA3-512", 72); + } + } +} diff --git a/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacPKCS12PBECore.java b/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacPKCS12PBECore.java new file mode 100644 index 00000000..eb29f54d --- /dev/null +++ b/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/HmacPKCS12PBECore.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2003, 2023, 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 com.tencent.kona.com.sun.crypto.provider; + +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.PBEKeySpec; +import java.security.*; +import java.security.spec.*; +import java.util.Arrays; + +import com.tencent.kona.jdk.internal.misc.SharedSecretsUtil; +import com.tencent.kona.sun.security.util.PBEUtil; + +/** + * This is an implementation of the HMAC algorithms as defined + * in PKCS#12 v1.1 standard (see RFC 7292 Appendix B.4). + * + * @author Valerie Peng + */ +public abstract class HmacPKCS12PBECore extends HmacCore { + + private final String algorithm; + private final int bl; + + /** + * Standard constructor, creates a new HmacSHA1 instance. + */ + public HmacPKCS12PBECore(String algorithm, int bl) throws NoSuchAlgorithmException { + super(algorithm, bl); + this.algorithm = algorithm; + this.bl = bl; + } + + /** + * Initializes the HMAC with the given secret key and algorithm parameters. + * + * @param key the secret key. + * @param params the algorithm parameters. + * + * @exception InvalidKeyException if the given key is inappropriate for + * initializing this MAC. + * @exception InvalidAlgorithmParameterException if the given algorithm + * parameters are inappropriate for this MAC. + */ + protected void engineInit(Key key, AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException { + char[] password = null; + byte[] derivedKey = null; + SecretKeySpec cipherKey = null; + PBEKeySpec keySpec = PBEUtil.getPBAKeySpec(key, params); + try { + password = keySpec.getPassword(); + derivedKey = PKCS12PBECipherCoreUtil.derive( + password, keySpec.getSalt(), + keySpec.getIterationCount(), engineGetMacLength(), + PKCS12PBECipherCoreUtil.MAC_KEY, algorithm, bl); + cipherKey = new SecretKeySpec(derivedKey, "HmacSHA1"); + super.engineInit(cipherKey, null); + } finally { + if (cipherKey != null) { + SharedSecretsUtil.cryptoSpecClearSecretKeySpec(cipherKey); + } + if (derivedKey != null) { + Arrays.fill(derivedKey, (byte) 0); + } + if (password != null) { + Arrays.fill(password, '\0'); + } + keySpec.clearPassword(); + } + } +} diff --git a/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/PKCS12PBECipherCoreUtil.java b/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/PKCS12PBECipherCoreUtil.java new file mode 100644 index 00000000..3b442c13 --- /dev/null +++ b/kona-crypto/src/main/java/com/tencent/kona/com/sun/crypto/provider/PKCS12PBECipherCoreUtil.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2003, 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 + * 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 com.tencent.kona.com.sun.crypto.provider; + +import java.security.MessageDigest; +import java.util.Arrays; + +public class PKCS12PBECipherCoreUtil { + + static final int MAC_KEY = 3; + + // Uses supplied hash algorithm + static byte[] derive(char[] chars, byte[] salt, int ic, int n, int type, + String hashAlgo, int blockLength) { + + // Add in trailing NULL terminator. Special case: + // no terminator if password is "\0". + int length = chars.length*2; + if (length == 2 && chars[0] == 0) { + chars = new char[0]; + length = 0; + } else { + length += 2; + } + + byte[] passwd = new byte[length]; + for (int i = 0, j = 0; i < chars.length; i++, j+=2) { + passwd[j] = (byte) ((chars[i] >>> 8) & 0xFF); + passwd[j+1] = (byte) (chars[i] & 0xFF); + } + byte[] key = new byte[n]; + + try { + MessageDigest sha = MessageDigest.getInstance(hashAlgo); + + int v = blockLength; + int u = sha.getDigestLength(); + int c = roundup(n, u) / u; + byte[] D = new byte[v]; + int s = roundup(salt.length, v); + int p = roundup(passwd.length, v); + byte[] I = new byte[s + p]; + + Arrays.fill(D, (byte)type); + concat(salt, I, 0, s); + concat(passwd, I, s, p); + Arrays.fill(passwd, (byte) 0x00); + + byte[] Ai; + byte[] B = new byte[v]; + + int i = 0; + for (; ; i++, n -= u) { + sha.update(D); + sha.update(I); + Ai = sha.digest(); + for (int r = 1; r < ic; r++) + Ai = sha.digest(Ai); + System.arraycopy(Ai, 0, key, u * i, Math.min(n, u)); + if (i + 1 == c) { + break; + } + concat(Ai, B, 0, v); + addOne(v, B); // add 1 into B + + for (int j = 0; j < I.length; j += v) { + addTwo(v, B, I, j); // add B into I from j + } + } + Arrays.fill(I, (byte)0); + } catch (Exception e) { + throw new RuntimeException("internal error: " + e); + } + return key; + } + + // Add 1 to b (as integer) + private static void addOne(int len, byte[] b) { + for (int i = len - 1; i >= 0; i--) { + if ((b[i] & 0xff) != 255) { + b[i]++; + break; + } else { + b[i] = 0; + } + } + } + + // Add src (as integer) to dst from offset (as integer) + private static void addTwo(int len, byte[] src, byte[] dst, int offset) { + int carry = 0; + for (int i = len - 1; i >= 0; i--) { + int sum = (src[i] & 0xff) + (dst[i + offset] & 0xff) + carry; + carry = sum >> 8; + dst[i + offset] = (byte)sum; + } + } + + private static int roundup(int x, int y) { + return ((x + (y - 1)) / y) * y; + } + + private static void concat(byte[] src, byte[] dst, int start, int len) { + if (src.length == 0) { + return; + } + int loop = len / src.length; + int off, i; + for (i = 0, off = 0; i < loop; i++, off += src.length) + System.arraycopy(src, 0, dst, off + start, src.length); + System.arraycopy(src, 0, dst, off + start, len - off); + } +} diff --git a/kona-crypto/src/main/java/com/tencent/kona/crypto/KonaCryptoProvider.java b/kona-crypto/src/main/java/com/tencent/kona/crypto/KonaCryptoProvider.java index e5f29905..3cc76752 100644 --- a/kona-crypto/src/main/java/com/tencent/kona/crypto/KonaCryptoProvider.java +++ b/kona-crypto/src/main/java/com/tencent/kona/crypto/KonaCryptoProvider.java @@ -76,6 +76,8 @@ private static void putEntries(Provider provider) { "com.tencent.kona.crypto.provider.PBES2Parameters$HmacSM3AndSM4"); provider.put("Alg.Alias.AlgorithmParameters.PBEWithHmacSM3AndSM4_128", "PBEWithHmacSM3AndSM4"); + provider.put("Mac.HmacPBESM3", + "com.tencent.kona.crypto.provider.HmacPKCS12PBE_SM3"); provider.put("SecretKeyFactory.PBEWithHmacSM3AndSM4", "com.tencent.kona.crypto.provider.PBEKeyFactory$PBEWithHmacSM3AndSM4"); provider.put("Alg.Alias.SecretKeyFactory.PBEWithHmacSM3AndSM4_128", diff --git a/kona-crypto/src/main/java/com/tencent/kona/crypto/provider/HmacPKCS12PBE_SM3.java b/kona-crypto/src/main/java/com/tencent/kona/crypto/provider/HmacPKCS12PBE_SM3.java new file mode 100644 index 00000000..7820f222 --- /dev/null +++ b/kona-crypto/src/main/java/com/tencent/kona/crypto/provider/HmacPKCS12PBE_SM3.java @@ -0,0 +1,12 @@ +package com.tencent.kona.crypto.provider; + +import com.tencent.kona.com.sun.crypto.provider.HmacPKCS12PBECore; + +import java.security.NoSuchAlgorithmException; + +public class HmacPKCS12PBE_SM3 extends HmacPKCS12PBECore { + + public HmacPKCS12PBE_SM3() throws NoSuchAlgorithmException { + super("SM3", 64); + } +} diff --git a/kona-crypto/src/main/java/com/tencent/kona/sun/security/util/PBEUtil.java b/kona-crypto/src/main/java/com/tencent/kona/sun/security/util/PBEUtil.java new file mode 100644 index 00000000..984eeb97 --- /dev/null +++ b/kona-crypto/src/main/java/com/tencent/kona/sun/security/util/PBEUtil.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. + * 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 com.tencent.kona.sun.security.util; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.interfaces.PBEKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; + +public final class PBEUtil { + + /* + * PBES2Params is an auxiliary class that represents the state needed for + * PBES2 operations (iterations count, salt and IV) and its (re) + * initialization logic. Users of this class are CipherSpi implementors that + * support PBES2 cryptography (RFC #8018), such as PBES2Core (SunJCE) and + * P11PBECipher (SunPKCS11). + * + * CipherSpi implementors must call ::getPBEKeySpec in every engine + * initialization (CipherSpi::engineInit override) to reset the state and + * get new values in a PBEKeySpec instance. These new values are taken + * from parameters, defaults or generated randomly. + * + * After engine initialization, values in effect can be extracted with + * ::getAlgorithmParameters (as AlgorithmParameters) or ::getIvSpec (as + * IvParameterSpec). + */ + public static final class PBES2Params { + private static final int DEFAULT_SALT_LENGTH = 20; + private static final int DEFAULT_ITERATIONS = 4096; + + private int iCount; + private byte[] salt; + private IvParameterSpec ivSpec; + + /* + * Initialize a PBES2Params instance. May generate random salt and + * IV if not passed and the operation is encryption. If initialization + * fails, values are reset. Used by PBES2Params and P11PBECipher + * (SunPKCS11). + */ + public void initialize(int blkSize, int opmode, int iCount, byte[] salt, + AlgorithmParameterSpec ivSpec, SecureRandom random) + throws InvalidAlgorithmParameterException { + try { + boolean doEncrypt = opmode == Cipher.ENCRYPT_MODE || + opmode == Cipher.WRAP_MODE; + if (ivSpec instanceof IvParameterSpec) { + IvParameterSpec iv = (IvParameterSpec) ivSpec; + this.ivSpec = iv; + } else if (ivSpec == null && doEncrypt) { + byte[] ivBytes = new byte[blkSize]; + random.nextBytes(ivBytes); + this.ivSpec = new IvParameterSpec(ivBytes); + } else { + throw new InvalidAlgorithmParameterException("Wrong " + + "parameter type: IvParameterSpec " + + (doEncrypt ? "or null " : "") + "expected"); + } + this.iCount = iCount == 0 ? DEFAULT_ITERATIONS : iCount; + if (salt == null) { + if (doEncrypt) { + salt = new byte[DEFAULT_SALT_LENGTH]; + random.nextBytes(salt); + } else { + throw new InvalidAlgorithmParameterException("Salt " + + "needed for decryption"); + } + } + this.salt = salt; + } catch (InvalidAlgorithmParameterException e) { + this.ivSpec = null; + this.iCount = 0; + this.salt = null; + throw e; + } + } + + /* + * Obtain an IvParameterSpec for Cipher services. This method returns + * null when the state is not initialized. Used by PBES2Core (SunJCE) + * and P11PBECipher (SunPKCS11). + */ + public IvParameterSpec getIvSpec() { + return ivSpec; + } + + /* + * Obtain AlgorithmParameters for Cipher services. This method will + * initialize PBES2Params if needed, generating new values randomly or + * assigning from defaults. If PBES2Params is initialized, existing + * values will be returned. Used by PBES2Core (SunJCE) and + * P11PBECipher (SunPKCS11). + */ + public AlgorithmParameters getAlgorithmParameters(int blkSize, + String pbeAlgo, Provider algParamsProv, SecureRandom random) { + AlgorithmParameters params; + try { + if (iCount == 0 && salt == null && ivSpec == null) { + initialize(blkSize, Cipher.ENCRYPT_MODE, 0, null, null, + random); + } + params = AlgorithmParameters.getInstance(pbeAlgo, + algParamsProv); + params.init(new PBEParameterSpec(salt, iCount, ivSpec)); + } catch (NoSuchAlgorithmException nsae) { + // should never happen + throw new RuntimeException("AlgorithmParameters for " + + pbeAlgo + " not configured"); + } catch (InvalidParameterSpecException ipse) { + // should never happen + throw new RuntimeException("PBEParameterSpec not supported"); + } catch (InvalidAlgorithmParameterException iape) { + // should never happen + throw new RuntimeException("Error initializing PBES2Params"); + } + return params; + } + + /* + * Initialize PBES2Params and obtain a PBEKeySpec for Cipher services. + * Data from the key, parameters, defaults or random may be used for + * initialization. Used by PBES2Core (SunJCE) and P11PBECipher + * (SunPKCS11). + */ + public PBEKeySpec getPBEKeySpec(int blkSize, int keyLength, int opmode, + Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("Null key"); + } + byte[] passwdBytes; + char[] passwdChars = null; + if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) || + (passwdBytes = key.getEncoded()) == null) { + throw new InvalidKeyException("Missing password"); + } + try { + int iCountInit; + byte[] saltInit; + AlgorithmParameterSpec ivSpecInit; + // Extract from the supplied PBE params, if present + if (params instanceof PBEParameterSpec) { + PBEParameterSpec pbeParams = (PBEParameterSpec) params; + // salt should be non-null per PBEParameterSpec + iCountInit = check(pbeParams.getIterationCount()); + saltInit = check(pbeParams.getSalt()); + ivSpecInit = pbeParams.getParameterSpec(); + } else if (params == null) { + // Try extracting from the key if present. If unspecified, + // PBEKey returns 0 and null respectively. + if (key instanceof PBEKey) { + PBEKey pbeKey = (PBEKey) key; + iCountInit = check(pbeKey.getIterationCount()); + saltInit = check(pbeKey.getSalt()); + } else { + iCountInit = 0; + saltInit = null; + } + ivSpecInit = null; + } else { + throw new InvalidAlgorithmParameterException( + "Wrong parameter type: PBE expected"); + } + initialize(blkSize, opmode, iCountInit, saltInit, ivSpecInit, + random); + passwdChars = new char[passwdBytes.length]; + for (int i = 0; i < passwdChars.length; i++) { + passwdChars[i] = (char) (passwdBytes[i] & 0x7f); + } + return new PBEKeySpec(passwdChars, salt, iCount, keyLength); + } finally { + // password char[] was cloned in PBEKeySpec constructor, + // so we can zero it out here + if (passwdChars != null) Arrays.fill(passwdChars, '\0'); + if (passwdBytes != null) Arrays.fill(passwdBytes, (byte)0x00); + } + } + + /* + * Obtain an AlgorithmParameterSpec from an AlgorithmParameters + * instance, for Cipher services. Used by PBES2Core (SunJCE) and + * P11PBECipher (SunPKCS11). + */ + public static AlgorithmParameterSpec getParameterSpec( + AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + AlgorithmParameterSpec pbeSpec = null; + if (params != null) { + try { + pbeSpec = params.getParameterSpec(PBEParameterSpec.class); + } catch (InvalidParameterSpecException ipse) { + throw new InvalidAlgorithmParameterException( + "Wrong parameter type: PBE expected"); + } + } + return pbeSpec; + } + + private static byte[] check(byte[] salt) + throws InvalidAlgorithmParameterException { + if (salt != null && salt.length < 8) { + throw new InvalidAlgorithmParameterException( + "Salt must be at least 8 bytes long"); + } + return salt; + } + + private static int check(int iCount) + throws InvalidAlgorithmParameterException { + if (iCount < 0) { + throw new InvalidAlgorithmParameterException( + "Iteration count must be a positive number"); + } + return iCount; + } + } + + /* + * Obtain a PBEKeySpec for Mac services, after key and parameters + * validation. Used by HmacPKCS12PBECore (SunJCE) and P11Mac (SunPKCS11). + */ + public static PBEKeySpec getPBAKeySpec(Key key, + AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException { + char[] passwdChars; + byte[] salt = null; + int iCount = 0; + if (key instanceof PBEKey) { + PBEKey pbeKey = (PBEKey) key; + passwdChars = pbeKey.getPassword(); + salt = pbeKey.getSalt(); // maybe null if unspecified + iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified + } else if (key instanceof SecretKey) { + byte[] passwdBytes; + if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) || + (passwdBytes = key.getEncoded()) == null) { + throw new InvalidKeyException("Missing password"); + } + passwdChars = new char[passwdBytes.length]; + for (int i = 0; i < passwdChars.length; i++) { + passwdChars[i] = (char) (passwdBytes[i] & 0x7f); + } + Arrays.fill(passwdBytes, (byte)0x00); + } else { + throw new InvalidKeyException("SecretKey of PBE type required"); + } + + try { + if (params == null) { + // should not auto-generate default values since current + // javax.crypto.Mac api does not have any method for caller to + // retrieve the generated defaults. + if ((salt == null) || (iCount == 0)) { + throw new InvalidAlgorithmParameterException( + "PBEParameterSpec required for salt " + + "and iteration count"); + } + } else if (!(params instanceof PBEParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "PBEParameterSpec type required"); + } else { + PBEParameterSpec pbeParams = (PBEParameterSpec) params; + // make sure the parameter values are consistent + if (salt != null) { + if (!Arrays.equals(salt, pbeParams.getSalt())) { + throw new InvalidAlgorithmParameterException( + "Inconsistent value of salt " + + "between key and params"); + } + } else { + salt = pbeParams.getSalt(); + } + if (iCount != 0) { + if (iCount != pbeParams.getIterationCount()) { + throw new InvalidAlgorithmParameterException( + "Different iteration count " + + "between key and params"); + } + } else { + iCount = pbeParams.getIterationCount(); + } + } + // For security purpose, we need to enforce a minimum length + // for salt; just require the minimum salt length to be 8-byte + // which is what PKCS#5 recommends and openssl does. + if (salt.length < 8) { + throw new InvalidAlgorithmParameterException( + "Salt must be at least 8 bytes long"); + } + if (iCount <= 0) { + throw new InvalidAlgorithmParameterException( + "IterationCount must be a positive number"); + } + return new PBEKeySpec(passwdChars, salt, iCount); + } finally { + Arrays.fill(passwdChars, '\0'); + } + } + + /* + * Check that the key implements the PBEKey interface. If params is an + * instance of PBEParameterSpec, validate consistency with the key's + * derivation data. Used by P11Mac and P11PBECipher (SunPKCS11). + */ + public static void checkKeyAndParams(Key key, + AlgorithmParameterSpec params, String algorithm) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (key instanceof PBEKey) { + PBEKey pbeKey = (PBEKey) key; + if (params instanceof PBEParameterSpec) { + PBEParameterSpec pbeParams = (PBEParameterSpec) params; + if (pbeParams.getIterationCount() != + pbeKey.getIterationCount() || + !Arrays.equals(pbeParams.getSalt(), pbeKey.getSalt())) { + throw new InvalidAlgorithmParameterException( + "Salt or iteration count parameters are " + + "not consistent with PBE key"); + } + } + } else { + throw new InvalidKeyException( + "Cannot use a " + algorithm + " service with a key that " + + "does not implement javax.crypto.interfaces.PBEKey"); + } + } +} diff --git a/kona-pkix/README.md b/kona-pkix/README.md index 55580315..d43f1fb7 100644 --- a/kona-pkix/README.md +++ b/kona-pkix/README.md @@ -191,10 +191,11 @@ java -cp <...> KeyTool \ -infile ee.csr -outfile ee.crt ``` -When the type of the keystore file is `PKCS12`, KeyTool can also use `PBEWithHmacSM3AndSM4` to encrypt the private keys and keystore files. Since the `KeyStoreSpi` of JDK does not provide relevant interfaces for setting these encryption algorithms, the following two system properties are provided: +When the type of the keystore is `PKCS12`, KeyTool can also use `PBEWithHmacSM3AndSM4` to encrypt the private keys and keystores, and use `HmacPBESM3` to validate the integrity of keystores. Since the `KeyStoreSpi` of JDK does not provide relevant interfaces for setting these encryption algorithms, the following two system properties are provided: -- `com.tencent.kona.keystore.pkcs12.certPbeAlgorithm`, setting the PBE algorithm for encrypting keystore files. +- `com.tencent.kona.keystore.pkcs12.certPbeAlgorithm`, setting the PBE algorithm for encrypting keystores. - `com.tencent.kona.keystore.pkcs12.keyPbeAlgorithm`, setting the PBE algorithm for encrypting private keys. +- `com.tencent.kona.keystore.pkcs12.macAlgorithm`,setting the MAC algorithm for ensuring the integrity of keystores. #### KeyStoreTool To facilitate users in importing the existing private keys and certificates using ShangMi algorithms to a keystore, another tool `com.tencent.kona.pkix.tool.KeyStoreTool` is provided. The usage of this tool is as follows: diff --git a/kona-pkix/README_cn.md b/kona-pkix/README_cn.md index be88b040..c9b49041 100644 --- a/kona-pkix/README_cn.md +++ b/kona-pkix/README_cn.md @@ -190,10 +190,11 @@ java -cp <...> KeyTool \ -infile ee.csr -outfile ee.crt ``` -当密钥库文件的类型为`PKCS12`时,KeyTool还可以使用`PBEWithHmacSM3AndSM4`对私钥和密钥库文件进行加密。由于JDK的`KeyStoreSpi`并没有为设置该加密算法提供相关的接口,所以提供了如下两个系统属性: +当密钥库文件的类型为`PKCS12`时,KeyTool还可以使用`PBEWithHmacSM3AndSM4`对私钥和密钥库进行加密,以及可使用`HmacPBESM3`算法验证密钥库的完整性。由于JDK的`KeyStoreSpi`并没有为设置这些算法提供相关的接口,所以提供了如下系统属性: - `com.tencent.kona.keystore.pkcs12.certPbeAlgorithm`,设置加密密钥库的PBE算法。 - `com.tencent.kona.keystore.pkcs12.keyPbeAlgorithm`,设置加密私钥的PBE算法。 +- `com.tencent.kona.keystore.pkcs12.macAlgorithm`,设置验证密钥库完整性的MAC算法。 #### KeyStoreTool 为了方便用户将已有的国密私钥和证书导入密钥库文件中,提供了另一个工具,即`com.tencent.kona.pkix.tool.KeyStoreTool`。该工具的用法如下, diff --git a/kona-pkix/src/main/java/com/tencent/kona/sun/security/pkcs12/PKCS12KeyStore.java b/kona-pkix/src/main/java/com/tencent/kona/sun/security/pkcs12/PKCS12KeyStore.java index 011bd54f..4186e28e 100644 --- a/kona-pkix/src/main/java/com/tencent/kona/sun/security/pkcs12/PKCS12KeyStore.java +++ b/kona-pkix/src/main/java/com/tencent/kona/sun/security/pkcs12/PKCS12KeyStore.java @@ -36,11 +36,11 @@ import java.security.KeyStoreException; import java.security.PKCS12Attribute; import java.security.PrivateKey; -import java.security.PrivilegedAction; +//import java.security.PrivilegedAction; import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; import java.security.SecureRandom; -import java.security.Security; +//import java.security.Security; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -110,11 +110,11 @@ public final class PKCS12KeyStore extends KeyStoreSpi { private static final String DEFAULT_CERT_PBE_ALGORITHM = GetPropertyAction.privilegedGetProperty( "com.tencent.kona.keystore.pkcs12.certPbeAlgorithm", - "PBEWithHmacSM3AndSM4"); + "PBEWithHmacSHA256AndAES_256"); private static final String DEFAULT_KEY_PBE_ALGORITHM = GetPropertyAction.privilegedGetProperty( "com.tencent.kona.keystore.pkcs12.keyPbeAlgorithm", - "PBEWithHmacSM3AndSM4"); + "PBEWithHmacSHA256AndAES_256"); private static final String DEFAULT_MAC_ALGORITHM = GetPropertyAction.privilegedGetProperty( "com.tencent.kona.keystore.pkcs12.macAlgorithm", diff --git a/kona-pkix/src/test/java/com/tencent/kona/pkix/tool/KeyToolTest.java b/kona-pkix/src/test/java/com/tencent/kona/pkix/tool/KeyToolTest.java index a8494633..414f7343 100644 --- a/kona-pkix/src/test/java/com/tencent/kona/pkix/tool/KeyToolTest.java +++ b/kona-pkix/src/test/java/com/tencent/kona/pkix/tool/KeyToolTest.java @@ -96,16 +96,16 @@ public void testGenCertChainOnJKS() throws Throwable { public void testPBEAlgorithmOnPKCS12() throws Throwable { genKeyPair(path("SM3AndSM4.ks"), "PKCS12", "SM3AndSM4", "EC", "curveSM2", "SM3withSM2", - "PBEWithHmacSM3AndSM4", "PBEWithHmacSM3AndSM4"); + "PBEWithHmacSM3AndSM4", "PBEWithHmacSM3AndSM4", "HmacPBESM3"); genKeyPair(path("SM3AndSM4-SHA256AndAES.ks"), "PKCS12", "SM3AndSM4-SHA256AndAES", "EC", "curveSM2", "SM3withSM2", - "PBEWithHmacSM3AndSM4", "PBEWithHmacSHA256AndAES_256"); + "PBEWithHmacSM3AndSM4", "PBEWithHmacSHA256AndAES_256", "HmacPBESHA256"); genKeyPair(path("SHA256AndAES.ks"), "PKCS12", "SHA256AndAES", "EC", "curveSM2", "SM3withSM2", - "PBEWithHmacSHA256AndAES_256", "PBEWithHmacSHA256AndAES_256"); + "PBEWithHmacSHA256AndAES_256", "PBEWithHmacSHA256AndAES_256", "HmacPBESM3"); genKeyPair(path("SHA256AndAES-SM3AndSM4.ks"), "PKCS12", "SHA256AndAES-SM3AndSM4", "EC", "curveSM2", "SM3withSM2", - "PBEWithHmacSHA256AndAES_256", "PBEWithHmacSM3AndSM4"); + "PBEWithHmacSHA256AndAES_256", "PBEWithHmacSM3AndSM4", "HmacPBESM3"); } private void testGenCertChain(String storeType) throws Throwable { @@ -182,12 +182,12 @@ private static void genKeyPair(Path keystorePath, String storeType, String alias, String keyAlg, String group, String sigAlg) throws Throwable { genKeyPair(keystorePath, storeType, alias, keyAlg, group, sigAlg, - null, null); + null, null, null); } private static void genKeyPair(Path keystorePath, String storeType, String alias, String keyAlg, String group, String sigAlg, - String certPbeAlg, String keyPbeAlg) + String certPbeAlg, String keyPbeAlg, String macAlg) throws Throwable { List args = new ArrayList<>(); @@ -231,6 +231,10 @@ private static void genKeyPair(Path keystorePath, String storeType, jvmOptions.add( "-Dcom.tencent.kona.keystore.pkcs12.keyPbeAlgorithm=" + keyPbeAlg); } + if (macAlg != null) { + jvmOptions.add( + "-Dcom.tencent.kona.keystore.pkcs12.macAlgorithm=" + macAlg); + } OutputAnalyzer oa = TestUtils.java(jvmOptions, KeyTool.class, args); try {