diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/KeyManagerUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/KeyManagerUtil.java index 16e77d23f..9cc5b0bc5 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/KeyManagerUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/KeyManagerUtil.java @@ -70,6 +70,26 @@ public class KeyManagerUtil { } } + /** + * 从KeyStore中获取{@link KeyManagerFactory} + * + * @param keyStore KeyStore + * @param password 密码 + * @param algorithm 算法,{@code null}表示默认算法,如SunX509 + * @param provider 算法提供者,{@code null}使用JDK默认 + * @return {@link KeyManager}列表 + */ + public static KeyManagerFactory getKeyManagerFactory(final KeyStore keyStore, final char[] password, + final String algorithm, final Provider provider) { + final KeyManagerFactory keyManagerFactory = getKeyManagerFactory(algorithm, provider); + try { + keyManagerFactory.init(keyStore, password); + } catch (final KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) { + throw new HutoolException(e); + } + return keyManagerFactory; + } + /** * 从KeyStore中获取{@link KeyManager}列表 * @@ -77,7 +97,7 @@ public class KeyManagerUtil { * @param password 密码 * @return {@link KeyManager}列表 */ - public static KeyManager[] getDefaultKeyManagers(final KeyStore keyStore, final char[] password) { + public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password) { return getKeyManagers(keyStore, password, null, null); } @@ -90,13 +110,8 @@ public class KeyManagerUtil { * @param provider 算法提供者,{@code null}使用JDK默认 * @return {@link KeyManager}列表 */ - public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password, final String algorithm, final Provider provider) { - final KeyManagerFactory keyManagerFactory = getKeyManagerFactory(algorithm, provider); - try { - keyManagerFactory.init(keyStore, password); - } catch (final KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) { - throw new HutoolException(e); - } - return keyManagerFactory.getKeyManagers(); + public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password, + final String algorithm, final Provider provider) { + return getKeyManagerFactory(keyStore, password, algorithm, provider).getKeyManagers(); } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextBuilder.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextBuilder.java index 739860dc3..6a0ca059c 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextBuilder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextBuilder.java @@ -24,10 +24,7 @@ import org.dromara.hutool.core.text.StrUtil; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; -import java.security.GeneralSecurityException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.security.*; /** @@ -51,6 +48,7 @@ public class SSLContextBuilder implements SSLProtocols, Builder { private KeyManager[] keyManagers; private TrustManager[] trustManagers; private SecureRandom secureRandom; + private Provider provider; /** @@ -114,6 +112,17 @@ public class SSLContextBuilder implements SSLProtocols, Builder { return this; } + /** + * 设置 Provider + * + * @param provider Provider,{@code null}表示使用默认或全局Provider + * @return this + */ + public SSLContextBuilder setProvider(final Provider provider) { + this.provider = provider; + return this; + } + /** * 构建{@link SSLContext} * @@ -133,7 +142,8 @@ public class SSLContextBuilder implements SSLProtocols, Builder { * @since 5.7.22 */ public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { - final SSLContext sslContext = SSLContext.getInstance(protocol); + final SSLContext sslContext = null != this.provider ? + SSLContext.getInstance(protocol, provider) : SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextUtil.java index 391377def..827c6e1d9 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/SSLContextUtil.java @@ -22,6 +22,7 @@ import org.dromara.hutool.core.io.IORuntimeException; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; /** @@ -88,6 +89,33 @@ public class SSLContextUtil { trustManager == null ? null : new TrustManager[]{trustManager}); } + /** + * 创建和初始化{@link SSLContext} + * + * @param keyStore KeyStore + * @param password 密码 + * @return {@link SSLContext} + * @throws IORuntimeException 包装 GeneralSecurityException异常 + */ + public static SSLContext createSSLContext(final KeyStore keyStore, final char[] password) throws IORuntimeException { + return createSSLContext( + KeyManagerUtil.getKeyManagers(keyStore, password), + TrustManagerUtil.getTrustManagers(keyStore) + ); + } + + /** + * 创建和初始化{@link SSLContext} + * + * @param keyManagers 密钥管理器,{@code null}表示默认 + * @param trustManagers 信任管理器, {@code null}表示默认 + * @return {@link SSLContext} + * @throws IORuntimeException 包装 GeneralSecurityException异常 + */ + public static SSLContext createSSLContext(final KeyManager[] keyManagers, final TrustManager[] trustManagers) throws IORuntimeException { + return createSSLContext(null, keyManagers, trustManagers); + } + /** * 创建和初始化{@link SSLContext} * diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/TrustManagerUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/TrustManagerUtil.java index defecf923..9bb9738eb 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/TrustManagerUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/ssl/TrustManagerUtil.java @@ -75,7 +75,8 @@ public class TrustManagerUtil { * @return {@link X509TrustManager} or {@code null} * @since 6.0.0 */ - public static X509TrustManager getTrustManager(final KeyStore keyStore, final String algorithm, final Provider provider) { + public static X509TrustManager getTrustManager(final KeyStore keyStore, final String algorithm, + final Provider provider) { final TrustManager[] tms = getTrustManagers(keyStore, algorithm, provider); for (final TrustManager tm : tms) { if (tm instanceof X509TrustManager) { @@ -94,7 +95,19 @@ public class TrustManagerUtil { * @since 6.0.0 */ public static TrustManager[] getDefaultTrustManagers() { - return getTrustManagers(null, null, null); + return getTrustManagers(null); + } + + /** + * 获取指定的{@link TrustManager}
+ * 此方法主要用于获取自签证书的{@link TrustManager} + * + * @param keyStore {@link KeyStore} + * @return {@link TrustManager} or {@code null} + * @since 6.0.0 + */ + public static TrustManager[] getTrustManagers(final KeyStore keyStore) { + return getTrustManagers(keyStore, null, null); } /** @@ -107,7 +120,22 @@ public class TrustManagerUtil { * @return {@link TrustManager} or {@code null} * @since 6.0.0 */ - public static TrustManager[] getTrustManagers(final KeyStore keyStore, String algorithm, final Provider provider) { + public static TrustManager[] getTrustManagers(final KeyStore keyStore, final String algorithm, + final Provider provider) { + return getTrustManagerFactory(keyStore, algorithm, provider).getTrustManagers(); + } + + /** + * 获取指定的{@link TrustManagerFactory} + * + * @param keyStore {@link KeyStore} + * @param algorithm 算法名称,如"SunX509",{@code null}表示默认SunX509 + * @param provider 算法提供者,如bc,{@code null}表示默认SunJSSE + * @return {@link TrustManager} or {@code null} + * @since 6.0.0 + */ + public static TrustManagerFactory getTrustManagerFactory(final KeyStore keyStore, String algorithm, + final Provider provider) { final TrustManagerFactory tmf; if(StrUtil.isEmpty(algorithm)){ @@ -128,6 +156,6 @@ public class TrustManagerUtil { throw new HutoolException(e); } - return tmf.getTrustManagers(); + return tmf; } } diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyStoreUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyStoreUtil.java index 9c5f5ee4a..f2e387d52 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyStoreUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/KeyStoreUtil.java @@ -17,7 +17,9 @@ package org.dromara.hutool.crypto; import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.core.io.file.FileNameUtil; import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.crypto.provider.GlobalProviderFactory; import java.io.File; @@ -35,11 +37,12 @@ import java.security.Provider; public class KeyStoreUtil { /** - * Java密钥库(Java Key Store,JKS)KEY_STORE + * Java密钥库(Java Key Store,JKS)KEY_STORE,Java 平台特有的密钥库格式
+ * JKS 密钥库可以用 Java 的 keytool 工具进行管理。 */ public static final String TYPE_JKS = "JKS"; /** - * jceks + * JCEKS(Java Cryptography Extension Key Store) */ public static final String TYPE_JCEKS = "jceks"; /** @@ -100,6 +103,31 @@ public class KeyStoreUtil { return readKeyStore(TYPE_PKCS12, in, password); } + /** + * 读取KeyStore文件
+ * KeyStore文件用于数字证书的密钥对保存
+ * 证书类型根据扩展名自动判断,规则如下: + *
+	 *     .jks .keystore -> JKS
+	 *      .p12 .pfx等其它 -> PKCS12
+	 * 
+ * + * @param keyFile 证书文件 + * @param password 密码,null表示无密码 + * @return {@link KeyStore} + * @since 6.0.0 + */ + public static KeyStore readKeyStore(final File keyFile, final char[] password) { + final String suffix = FileNameUtil.getSuffix(keyFile); + final String type; + if(StrUtil.equalsIgnoreCase(suffix, "jks") || StrUtil.equalsIgnoreCase(suffix, "keystore")){ + type = TYPE_JKS; + }else{ + type = TYPE_PKCS12; + } + return readKeyStore(type, keyFile, password); + } + /** * 读取KeyStore文件
* KeyStore文件用于数字证书的密钥对保存
diff --git a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/cert/CertUtil.java b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/cert/CertUtil.java index e4ac12df2..0af0c28f9 100644 --- a/hutool-crypto/src/main/java/org/dromara/hutool/crypto/cert/CertUtil.java +++ b/hutool-crypto/src/main/java/org/dromara/hutool/crypto/cert/CertUtil.java @@ -30,6 +30,7 @@ import java.security.Provider; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; /** * 数字证书{@link Certificate}相关工具类 @@ -141,4 +142,35 @@ public class CertUtil { } return factory; } + + /** + * 判断一个证书是否是自签名的,即证书由自己签发。 + * @param cert 证书 + * @return true表示自签名的,false表示非自签名的 + */ + public static boolean isSelfSigned(final X509Certificate cert) { + return isSignedBy(cert, cert); + } + + /** + * 验证一个证书是否由另一个证书签发。
+ * 来自:sun.security.tools.KeyStoreUtil + * + * @param end 需要验证的终端证书 + * @param ca 用于验证的CA证书 + * @return 如果终端证书由CA证书签发,则返回true,否则返回false + */ + public static boolean isSignedBy(final X509Certificate end, final X509Certificate ca) { + // 检查CA证书的主题和终端证书的颁发者是否相同 + if (!ca.getSubjectX500Principal().equals(end.getIssuerX500Principal())) { + return false; + } + try { + // 使用CA证书的公钥验证终端证书 + end.verify(ca.getPublicKey()); + return true; + } catch (final Exception e) { + return false; + } + } } diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index cd77fa84b..371393a09 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -56,6 +56,12 @@ hutool-log ${project.parent.version} + + org.dromara.hutool + hutool-crypto + ${project.parent.version} + test + jakarta.xml.soap diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java index 50c2c6f42..525129b21 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java @@ -16,7 +16,10 @@ package org.dromara.hutool.http.server; +import org.dromara.hutool.core.net.ssl.SSLContextUtil; + import javax.net.ssl.SSLContext; +import java.security.KeyStore; /** * 服务器配置 @@ -115,6 +118,18 @@ public class ServerConfig { return sslContext; } + /** + * 设置证书库
+ * 此方法和{@link #setSslContext(SSLContext)}互斥 + * + * @param keyStore 证书库 + * @param passwd 密码 + * @return this + */ + public ServerConfig setKeystore(final KeyStore keyStore, final char[] passwd) { + return setSslContext(SSLContextUtil.createSSLContext(keyStore, passwd)); + } + /** * 设置SSL上下文 * diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/TomcatEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/TomcatEngine.java index 209152291..355d7b8d4 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/TomcatEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/TomcatEngine.java @@ -125,20 +125,34 @@ public class TomcatEngine extends AbstractServerEngine { // SSL配置 final SSLContext sslContext = config.getSslContext(); if(null != sslContext){ - final SSLHostConfig sslHostConfig = new SSLHostConfig(); - final SSLHostConfigCertificate sslHostConfigCertificate = - new SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA); - sslHostConfigCertificate.setSslContext(new JSSESSLContext(sslContext)); - sslHostConfig.addCertificate(sslHostConfigCertificate); - connector.addSslHostConfig(sslHostConfig); + protocol.setSSLEnabled(true); + protocol.setSecure(true); + protocol.addSslHostConfig(createSSLHostConfig(sslContext)); + connector.setScheme("https"); - connector.setSecure(true); connector.setPort(config.getPort()); } return connector; } + /** + * 创建SSL HostConfig + * + * @param sslContext SSLContext + * @return SSL HostConfig + */ + private static SSLHostConfig createSSLHostConfig(final SSLContext sslContext) { + final SSLHostConfig sslHostConfig = new SSLHostConfig(); + + final SSLHostConfigCertificate sslHostConfigCertificate = + new SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA); + sslHostConfigCertificate.setSslContext(new JSSESSLContext(sslContext)); + + sslHostConfig.addCertificate(sslHostConfigCertificate); + return sslHostConfig; + } + /** * 初始化Context * diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/JettyTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/JettyTest.java index 46495acb6..ff536da41 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/JettyTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/JettyTest.java @@ -1,12 +1,23 @@ package org.dromara.hutool.http.server.engine; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.net.ssl.SSLContextUtil; +import org.dromara.hutool.crypto.KeyStoreUtil; import org.dromara.hutool.http.server.ServerConfig; +import javax.net.ssl.SSLContext; +import java.security.KeyStore; + public class JettyTest { public static void main(final String[] args) { + final char[] pwd = "123456".toCharArray(); + final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd); + // 初始化SSLContext + final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd); + final ServerEngine engine = ServerEngineFactory.createEngine("jetty"); - engine.init(ServerConfig.of()); + engine.init(ServerConfig.of().setSslContext(sslContext)); engine.setHandler((request, response) -> { Console.log(request.getPath()); response.write("Hutool Jetty response test"); diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/SunServerTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/SunServerTest.java index 4bd464276..34b0b1911 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/SunServerTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/SunServerTest.java @@ -1,12 +1,23 @@ package org.dromara.hutool.http.server.engine; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.net.ssl.SSLContextUtil; +import org.dromara.hutool.crypto.KeyStoreUtil; import org.dromara.hutool.http.server.ServerConfig; +import javax.net.ssl.SSLContext; +import java.security.KeyStore; + public class SunServerTest { public static void main(String[] args) { + final char[] pwd = "123456".toCharArray(); + final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd); + // 初始化SSLContext + final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd); + final ServerEngine engine = ServerEngineFactory.createEngine("SunHttpServer"); - engine.init(ServerConfig.of()); + engine.init(ServerConfig.of().setSslContext(sslContext)); engine.setHandler((request, response) -> { Console.log(request.getPath()); response.write("Hutool Sun Server response test"); diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/TomcatTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/TomcatTest.java index a567923af..36b23cfc1 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/TomcatTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/TomcatTest.java @@ -1,12 +1,23 @@ package org.dromara.hutool.http.server.engine; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.net.ssl.SSLContextUtil; +import org.dromara.hutool.crypto.KeyStoreUtil; import org.dromara.hutool.http.server.ServerConfig; +import javax.net.ssl.SSLContext; +import java.security.KeyStore; + public class TomcatTest { - public static void main(String[] args) { + public static void main(final String[] args) throws Exception { + final char[] pwd = "123456".toCharArray(); + final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd); + // 初始化SSLContext + final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd); + final ServerEngine engine = ServerEngineFactory.createEngine("tomcat"); - engine.init(ServerConfig.of()); + engine.init(ServerConfig.of().setSslContext(sslContext)); engine.setHandler((request, response) -> { Console.log(request.getPath()); response.write("Hutool Tomcat response test"); diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java index 1b5ae94a7..962f777d8 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java @@ -16,13 +16,24 @@ package org.dromara.hutool.http.server.engine; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.net.ssl.SSLContextUtil; +import org.dromara.hutool.crypto.KeyStoreUtil; import org.dromara.hutool.http.server.ServerConfig; +import javax.net.ssl.SSLContext; +import java.security.KeyStore; + public class UndertowTest { public static void main(String[] args) { + final char[] pwd = "123456".toCharArray(); + final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd); + // 初始化SSLContext + final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd); + final ServerEngine engine = ServerEngineFactory.createEngine("undertow"); - engine.init(ServerConfig.of()); + engine.init(ServerConfig.of().setSslContext(sslContext)); engine.setHandler((request, response) -> { Console.log(request.getPath()); response.write("Hutool Undertow response test");