Java - 仅使用 TLS 的 SSLServerSocket
Java - SSLServerSocket with only TLS
我正在尝试打开带有自定义 keystore/truststore 且仅启用 TLSv1.2
的 SSLServerSocket
。
这是我打开此类套接字的相关代码:
SSLContext sslContext = null;
ServerSocket serverSocket = null;
KeyManagerFactory kmf = null;
KeyStore keystore = loadKeyStore(KEYSTORE_FILE);
if (keystore == null) {
// throw exception
}
char[] psw = System.console().readPassword("Enter password for the key materials in file \"%s\":", KEYSTORE_FILE);
try {
kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(keystore, psw);
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
e.printStackTrace();
kmf = null;
// throw exception
}
try {
sslContext = SSLContext.getInstance("TLSv1.2");
System.out.println(kmf==null); // prints false
sslContext.init(kmf==null?null:kmf.getKeyManagers(), null, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
// throw exception
}
try {
serverSocket = sslContext.getServerSocketFactory().createServerSocket(PORT, BACKLOG, HOST);
((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
} catch (IOException e) {
// throw exception
}
loadKeyStore
函数是,
private static KeyStore loadKeyStore(String filename) {
KeyStore keystore = null;
FileInputStream fis = null;
try {
keystore = KeyStore.getInstance("JKS");
char[] psw = System.console().readPassword("Enter password for the KeyStore file \"%s\":", filename);
if (psw != null) {
fis = new FileInputStream(filename);
keystore.load(fis, psw);
}
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
keystore = null;
LogManager.getLogger().fatal("cannot load KeyStore from file \"" + filename + "\".", e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
LogManager.getLogger().error("cannot close file " + filename, e);
}
fis = null;
}
}
return keystore;
}
我在另一个线程中接受连接作为
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
if (!stopped) {
logger.error("exception while accepting connections.", e);
}
break;
}
// start new threads to handle this connection
}
问题是,当我在 Firefox 中输入 https://HOST:PORT 时,它显示:
Firefox cannot guarantee the safety of your data on HOST because it
uses SSLv3, a broken security protocol. Advanced info:
ssl_error_no_cypher_overlap
如何打开只接受 TLSv1.2 连接的服务器套接字?
P.S。我尝试将代码中的 "TLSv1.2" 个字符串一个一个地更改为 "TLS",但没有任何改变。
编辑: 我将代码编辑如下:
serverSocket = sslContext.getServerSocketFactory().createServerSocket(port, backlog, host);
((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
for (String s: ((SSLServerSocket)serverSocket).getEnabledCipherSuites()) {
System.out.println(s);
}
输出是,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
TLS_ECDHE_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_SHA
TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA
SSL_RSA_WITH_RC4_128_MD5 TLS_EMPTY_RENEGOTIATION_INFO_SCSV
我不确定,但问题似乎与缺少启用的密码套件无关。对吧?
EDIT2: 我已经试过了 openssl s_client -connect HOST:PORT
,结果是
(我 answered here 有一个非常相似的问题。)
本质上,SSLContext.getInstance("TLSv1.2")
也可以 return 支持其他协议的实例。
如果您想使用一组特定的协议,您需要使用 setEnabledProtocols(...)
,这是您在第一次编辑后所做的。您现在获得了一些名称以 SSL_
开头的密码套件,但这只是名称,它们对 TLS 1.2 仍然有效。作为 Java 密码体系结构
JDK 8 的标准算法名称文档说:
Some JSSE cipher suite names were defined before TLSv1.0 was finalized, and were therefore given the SSL_ prefix. The names mentioned in the TLS RFCs prefixed with TLS_ are functionally equivalent to the JSSE cipher suites prefixed with SSL_.
你的最后一个问题(没有可用的对等证书”,以及握手失败)似乎表明在你的密钥库中找不到证书(及其私钥)正在尝试使用。
的确,虽然你说的密码套件是启用的,但如果不能使用它们会自动禁用。所有这些都是 RSA 或 DSS 密码套件,这意味着它们需要带有 RSA 或 DSA 密钥 和 其私钥的证书才能使用。如果在密钥库中找不到带有私钥条目的此类证书,KeyManager
和 SSLContext
将无法使用它。因此,当实际尝试握手时,它们将被禁用。这通常会导致在服务器端握手过程中抛出异常(“javax.net.ssl.SSLHandshakeException: no cipher suites in common
”),以及您通过 OpenSSL 在客户端获得的错误消息。
我正在尝试打开带有自定义 keystore/truststore 且仅启用 TLSv1.2
的 SSLServerSocket
。
这是我打开此类套接字的相关代码:
SSLContext sslContext = null;
ServerSocket serverSocket = null;
KeyManagerFactory kmf = null;
KeyStore keystore = loadKeyStore(KEYSTORE_FILE);
if (keystore == null) {
// throw exception
}
char[] psw = System.console().readPassword("Enter password for the key materials in file \"%s\":", KEYSTORE_FILE);
try {
kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(keystore, psw);
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
e.printStackTrace();
kmf = null;
// throw exception
}
try {
sslContext = SSLContext.getInstance("TLSv1.2");
System.out.println(kmf==null); // prints false
sslContext.init(kmf==null?null:kmf.getKeyManagers(), null, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
// throw exception
}
try {
serverSocket = sslContext.getServerSocketFactory().createServerSocket(PORT, BACKLOG, HOST);
((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
} catch (IOException e) {
// throw exception
}
loadKeyStore
函数是,
private static KeyStore loadKeyStore(String filename) {
KeyStore keystore = null;
FileInputStream fis = null;
try {
keystore = KeyStore.getInstance("JKS");
char[] psw = System.console().readPassword("Enter password for the KeyStore file \"%s\":", filename);
if (psw != null) {
fis = new FileInputStream(filename);
keystore.load(fis, psw);
}
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
keystore = null;
LogManager.getLogger().fatal("cannot load KeyStore from file \"" + filename + "\".", e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
LogManager.getLogger().error("cannot close file " + filename, e);
}
fis = null;
}
}
return keystore;
}
我在另一个线程中接受连接作为
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
if (!stopped) {
logger.error("exception while accepting connections.", e);
}
break;
}
// start new threads to handle this connection
}
问题是,当我在 Firefox 中输入 https://HOST:PORT 时,它显示:
Firefox cannot guarantee the safety of your data on HOST because it uses SSLv3, a broken security protocol. Advanced info: ssl_error_no_cypher_overlap
如何打开只接受 TLSv1.2 连接的服务器套接字?
P.S。我尝试将代码中的 "TLSv1.2" 个字符串一个一个地更改为 "TLS",但没有任何改变。
编辑: 我将代码编辑如下:
serverSocket = sslContext.getServerSocketFactory().createServerSocket(port, backlog, host);
((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
for (String s: ((SSLServerSocket)serverSocket).getEnabledCipherSuites()) {
System.out.println(s);
}
输出是,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDH_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_MD5 TLS_EMPTY_RENEGOTIATION_INFO_SCSV
我不确定,但问题似乎与缺少启用的密码套件无关。对吧?
EDIT2: 我已经试过了 openssl s_client -connect HOST:PORT
,结果是
(我 answered here 有一个非常相似的问题。)
本质上,SSLContext.getInstance("TLSv1.2")
也可以 return 支持其他协议的实例。
如果您想使用一组特定的协议,您需要使用 setEnabledProtocols(...)
,这是您在第一次编辑后所做的。您现在获得了一些名称以 SSL_
开头的密码套件,但这只是名称,它们对 TLS 1.2 仍然有效。作为 Java 密码体系结构
JDK 8 的标准算法名称文档说:
Some JSSE cipher suite names were defined before TLSv1.0 was finalized, and were therefore given the SSL_ prefix. The names mentioned in the TLS RFCs prefixed with TLS_ are functionally equivalent to the JSSE cipher suites prefixed with SSL_.
你的最后一个问题(没有可用的对等证书”,以及握手失败)似乎表明在你的密钥库中找不到证书(及其私钥)正在尝试使用。
的确,虽然你说的密码套件是启用的,但如果不能使用它们会自动禁用。所有这些都是 RSA 或 DSS 密码套件,这意味着它们需要带有 RSA 或 DSA 密钥 和 其私钥的证书才能使用。如果在密钥库中找不到带有私钥条目的此类证书,KeyManager
和 SSLContext
将无法使用它。因此,当实际尝试握手时,它们将被禁用。这通常会导致在服务器端握手过程中抛出异常(“javax.net.ssl.SSLHandshakeException: no cipher suites in common
”),以及您通过 OpenSSL 在客户端获得的错误消息。