From 0c7cf3f9db5a42a541149ce12716c293ee8bbb86 Mon Sep 17 00:00:00 2001 From: xiezhaokun <1016340276@qq.com> Date: Tue, 22 Aug 2023 13:38:29 +0800 Subject: [PATCH] Add support for password-based encryption scheme 2 params (PBES2) (#13539) Motivation: Add support for password-based encryption scheme 2 params (PBES2) Modification: Describe the modifications you've done. Result: Fixes #13536 Co-authored-by: Norman Maurer Co-authored-by: Chris Vest --- .../io/netty5/handler/ssl/SslContext.java | 41 ++++++++++++++++--- .../io/netty5/handler/ssl/SslContextTest.java | 7 ++++ .../io/netty5/handler/ssl/generate-certs.sh | 7 ++++ .../handler/ssl/rsa_pbes2_enc_pkcs8.key | 30 ++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 handler/src/test/resources/io/netty5/handler/ssl/rsa_pbes2_enc_pkcs8.key diff --git a/handler/src/main/java/io/netty5/handler/ssl/SslContext.java b/handler/src/main/java/io/netty5/handler/ssl/SslContext.java index 95a8782b9a6..a83d90c87b9 100644 --- a/handler/src/main/java/io/netty5/handler/ssl/SslContext.java +++ b/handler/src/main/java/io/netty5/handler/ssl/SslContext.java @@ -27,8 +27,6 @@ import io.netty5.util.DefaultAttributeMap; import io.netty5.util.internal.EmptyArrays; - - import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.NoSuchPaddingException; @@ -48,6 +46,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyException; @@ -103,6 +102,8 @@ public abstract class SslContext { private final boolean startTls; private final AttributeMap attributes = new DefaultAttributeMap(); + private static final String OID_PKCS5_PBES2 = "1.2.840.113549.1.5.13"; + private static final String PBES2 = "PBES2"; /** * Returns the default server-side implementation provider currently in use. @@ -1094,16 +1095,39 @@ protected static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key } EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); + String pbeAlgorithm = getPBEAlgorithm(encryptedPrivateKeyInfo); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(pbeAlgorithm); PBEKeySpec pbeKeySpec = new PBEKeySpec(password); SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec); - Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); + Cipher cipher = Cipher.getInstance(pbeAlgorithm); cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters()); return encryptedPrivateKeyInfo.getKeySpec(cipher); } + private static String getPBEAlgorithm(EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) { + AlgorithmParameters parameters = encryptedPrivateKeyInfo.getAlgParameters(); + String algName = encryptedPrivateKeyInfo.getAlgName(); + // Java 8 ~ 16 returns OID_PKCS5_PBES2 + // Java 17+ returns PBES2 + if (parameters != null && + (OID_PKCS5_PBES2.equals(algName) || PBES2.equals(algName))) { + /* + * This should be "PBEWithAnd". + * Relying on the toString() implementation is potentially + * fragile but acceptable in this case since the JRE depends on + * the toString() implementation as well. + * In the future, if necessary, we can parse the value of + * parameters.getEncoded() but the associated complexity and + * unlikeliness of the JRE implementation changing means that + * Tomcat will use to toString() approach for now. + */ + return parameters.toString(); + } + return encryptedPrivateKeyInfo.getAlgName(); + } + /** * Generates a new {@link KeyStore}. * @@ -1131,12 +1155,19 @@ protected static PrivateKey toPrivateKey(File keyFile, String keyPassword) throw NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, KeyException, IOException { + return toPrivateKey(keyFile, keyPassword, true); + } + + static PrivateKey toPrivateKey(File keyFile, String keyPassword, boolean tryBouncyCastle) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, + InvalidAlgorithmParameterException, + KeyException, IOException { if (keyFile == null) { return null; } // try BC first, if this fail fallback to original key extraction process - if (BouncyCastlePemReader.isAvailable()) { + if (tryBouncyCastle && BouncyCastlePemReader.isAvailable()) { PrivateKey pk = BouncyCastlePemReader.getPrivateKey(keyFile, keyPassword); if (pk != null) { return pk; diff --git a/handler/src/test/java/io/netty5/handler/ssl/SslContextTest.java b/handler/src/test/java/io/netty5/handler/ssl/SslContextTest.java index 1eae50d217f..6fa5a60a65b 100644 --- a/handler/src/test/java/io/netty5/handler/ssl/SslContextTest.java +++ b/handler/src/test/java/io/netty5/handler/ssl/SslContextTest.java @@ -172,6 +172,13 @@ public void testPkcs8Des3EncryptedRsa() throws Exception { assertNotNull(key); } + @Test + public void testPkcs8Pbes2() throws Exception { + PrivateKey key = SslContext.toPrivateKey(new File(getClass().getResource("rsa_pbes2_enc_pkcs8.key") + .getFile()), "12345678", false); + assertNotNull(key); + } + @Test public void testPkcs1UnencryptedRsaEmptyPassword() throws Exception { assertThrows(IOException.class, new Executable() { diff --git a/handler/src/test/resources/io/netty5/handler/ssl/generate-certs.sh b/handler/src/test/resources/io/netty5/handler/ssl/generate-certs.sh index 89bf20fa855..ac82cff071c 100755 --- a/handler/src/test/resources/io/netty5/handler/ssl/generate-certs.sh +++ b/handler/src/test/resources/io/netty5/handler/ssl/generate-certs.sh @@ -71,8 +71,15 @@ openssl gendsa -out dsa_pkcs1_unencrypted.key dsaparam.pem openssl gendsa -des3 -out dsa_pkcs1_des3_encrypted.key -passout pass:example dsaparam.pem openssl gendsa -aes128 -out dsa_pkcs1_aes_encrypted.key -passout pass:example dsaparam.pem +# PBES2 +openssl genrsa -out rsa_pbes2.key +openssl req -new -subj "/CN=NettyTest" -key rsa_pbes2.key -out rsa_pbes2.csr +openssl x509 -req -days 36500 -in rsa_pbes2.csr -signkey rsa_pbes2.key -out rsa_pbes2.crt +openssl pkcs8 -topk8 -inform PEM -in rsa_pbes2.key -outform pem -out rsa_pbes2_enc_pkcs8.key -v2 aes-256-cbc -passin pass:12345678 -passout pass:12345678 + # Clean up intermediate files rm intermediate.csr rm mutual_auth_ca.key mutual_auth_invalid_client.key mutual_auth_client.key mutual_auth_server.key mutual_auth_invalid_intermediate_ca.key mutual_auth_intermediate_ca.key rm mutual_auth_invalid_client.pem mutual_auth_client.pem mutual_auth_server.pem mutual_auth_client_cert_chain.pem mutual_auth_invalid_intermediate_ca.pem mutual_auth_intermediate_ca.pem mutual_auth_invalid_client_cert_chain.pem rm dsaparam.pem +rm rsa_pbes2.crt rsa_pbes2.csr rsa_pbes2.key diff --git a/handler/src/test/resources/io/netty5/handler/ssl/rsa_pbes2_enc_pkcs8.key b/handler/src/test/resources/io/netty5/handler/ssl/rsa_pbes2_enc_pkcs8.key new file mode 100644 index 00000000000..05e2079fbae --- /dev/null +++ b/handler/src/test/resources/io/netty5/handler/ssl/rsa_pbes2_enc_pkcs8.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIJddddGJqvzACAggA +MB0GCWCGSAFlAwQBKgQQ0SIWTVvrPdriTZKRZuWb7QSCBNDoU6Y1qeABJPV726zv +gymMUAZdfxqBlbS865q3RlnHO2xm4082l7VwFK9QGVFIxURx4QU76qjhsoJfmGiv +pAvkZCln90iguY0ssnAzBFi8B2AgBcZrIG1OMQpR0L/hjveIvorg4/vb5rIgyIdn ++3u7I077yF3udPnt0jUYcfonzsOglwa3FVun2yqM7/gAzGkQu/CeOshSQaJ9EV/0 +ZQmemVCJbmm7I2iEq6RUXgBW8hPjo1cwuQznE2jBx3SlhQoSrWTGdy9WXLqwxrHX +e2W6LqaXqFOrgmtPz5h3JzhUh0AGtNS0SsB6AsyKD548NP7NGRlmbUXo8iQXS/Z6 +QmwIh1bt67VecHKka17ZYGBQt/2/zcQddRvlVeT3SbRgGCGQeDnXWhfJIMb6bFDe +vdr0L44zyW6/OwYQU1RNBFclrjtFIAVQEI8L/BVciowsJ7J6W2UsxR3hrLtPjO9o +zH7bp85TlSaSZW3T2HfAPu6isMECzVzZ+x8qnpuDoHjIOZID630Amw1v2/gfFZTA +mXn4gld1JKUBrvBfXN4cjTDC1eO1zvzEZB6VZw6ePhggWijySoLUO+E/mJpP5mNA +4OQSMxNQ+UcZH+MT093cOJeOleOlbc9weIeNhXgONX/pbnq1gV23tN3mZbGIJojY +GZoH32ft76x+DxrMZrjtjL7dOUL5QjjUlpJ6319aaLFf6Z/AIWXBOyHC1i9l+lKv +2hpZ/YS+eyExy/axwx0J4eH+7csDrz63F1A9hRrIxx2wKdYjsKWR21Hb/mst/U4z +1MGWeIX1hqAe7VYUiZBZldnOYdmNG/sRtcHMrW9zqkJUuW25YjuGm9gVbIVw/YOY +lOd/9puSiRzuJLX02p1o1PN17+5rzMkpE4bVpd6Pvbex+oMSUVvd+V845bCB0qCt +eA5TUBi2gbLvj7TqoA+C0zoXGtXD1Ea/7hnmwC5Gzl4m6YmvZRpxXA6Mzs62CYgt +KWJSiuuTfSop/2B8+nnkZQAGKxvXkpFLXyGXfP+Y22X00BB43GtMB2ZPzmliQ+TA +bFePkPBoXqytJR9vrbWJIIzcxaYWTwqN1vZJzIgdFjK2yOiqopGi0sG0zjn8xryv +ZqraVnTznb5xUGojezIbtWwMNIRrmNU9b1HMtpMsnuNNPQy/UhgDqgM6bQOh23Q8 +7DQRqXGlNqJc22ne1E/gN5IxdbrgoE6jnnoAzOlFRD1XdhLBW3hb2DpzTFAveDKi +ti5UHucXrURILD1ee9CtyKcYQajr3XNp0tMJJQFCybnX+zOgH6HXsrfXFT8CghDt +aeo7TBfhVC4MadvggLNWmyjyGJd7KeDGuiXVDsWr9icesDCUgC/T2Lq58boT70BM +T14pABDOqSylJdL0qWV7m8yZ0fNAkrTB/+2qdi4B632NIQ+ZGRZy5WK09jRUtVm0 ++EZMoX6pAjBQR4RwgbyNTJDIt/tXOopBAoEjRc8qWotlJozc3RkpGBHgT8POZT60 +Jxu3QaE9rJq8kfrO/e5gq7zDy4AK/ck1+aVxfiwM1D7vPvFt0WwztdvEGz3Hjpkv +qtj8ePpDBrBo1ISJqVmTi3pvRz/kuKFYvoYdWq2Wluz1NGZdCETwNYIao1/hfP52 +NydFNZTmdrun22ezf6yVGiCaEw== +-----END ENCRYPTED PRIVATE KEY-----