Java - 只有 TLS 的 SSLServerSocket

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28743482/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 14:06:48  来源:igfitidea点击:

Java - SSLServerSocket with only TLS

javasocketsssljava-8

提问by Ramazan

I am trying to open an SSLServerSocketwith custom keystore/truststore and with only TLSv1.2enabled. Here is my related code for opening such socket:

我正在尝试SSLServerSocket使用自定义密钥库/信任库打开一个并且仅TLSv1.2启用。这是我打开此类套接字的相关代码:

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
}

the loadKeyStorefunction is,

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;
}

I accept connections in a different thread as

我接受不同线程中的连接

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
}

The problem is, when I enter https://HOST:PORTat Firefox, it says:

问题是,当我在 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

Firefox 无法保证您在 HOST 上的数据安全,因为它使用SSLv3,这是一种已损坏的安全协议。高级信息:ssl_error_no_cypher_overlap

How can I open a server socket that accepts only TLSv1.2 connections?

如何打开仅接受 TLSv1.2 连接的服务器套接字?

P.S. I have tried changing "TLSv1.2" strings in the code to "TLS", one by one, but nothing changed.

PS我已经尝试将代码中的“TLSv1.2”字符串一一更改为“TLS”,但没有任何改变。

EDIT:I edited the code as follows:

编辑:我编辑的代码如下:

serverSocket = sslContext.getServerSocketFactory().createServerSocket(port, backlog, host);
((SSLServerSocket)serverSocket).setEnabledProtocols(new String[]{"TLSv1.2"});
for (String s: ((SSLServerSocket)serverSocket).getEnabledCipherSuites()) {
    System.out.println(s);
}

and the output is,

输出是,

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

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_SHATLS_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

I am not sure, but it seems the problem is not about missing enabled cipher suites. Right?

我不确定,但似乎问题不在于缺少启用的密码套件。对?

EDIT2:I have tried openssl s_client -connect HOST:PORT, and the result is output

EDIT2:我试过了openssl s_client -connect HOST:PORT,结果是 输出

回答by Bruno

(There's a very similar question which I've answered here.)

(我在这里回答了一个非常相似的问题。)

Essentially, SSLContext.getInstance("TLSv1.2")can return an instance that supports other protocols too.

本质上,SSLContext.getInstance("TLSv1.2")也可以返回一个支持其他协议的实例。

If you want to use a specific set of protocols, you need to use setEnabledProtocols(...), which is what you've done following your first edit. You now get some cipher suites with a name starting with SSL_, but that's just the name, these are still valid for TLS 1.2. As the Java Cryptography Architecture Standard Algorithm Name Documentation for JDK 8says:

如果您想使用一组特定的协议,则需要使用setEnabledProtocols(...),这是您在第一次编辑后所做的。您现在会得到一些名称以 开头的密码套件SSL_,但这只是名称,这些对 TLS 1.2 仍然有效。正如JDK 8Java 加密体系结构标准算法名称文档所说:

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_.

一些 JSSE 密码套件名称是在 TLSv1.0 最终确定之前定义的,因此被赋予 SSL_ 前缀。TLS RFC 中提到的以 TLS_ 为前缀的名称在功能上等同于以 SSL_ 为前缀的 JSSE 密码套件。

Your last problem ("no peer certificate available", along with the handshake failure) seems to suggest there is no certificate (with its private key) found in the keystore you're trying to use.

您的最后一个问题(“没有可用的对等证书”,以及握手失败)似乎表明在您尝试使用的密钥库中找不到证书(及其私钥)。

Indeed, although the cipher suites you're mentioning are enabled, they will be automatically disabled if they cannot be used. All are either RSA or DSS cipher suites, meaning they need a certificate with an RSA or DSA key withits private key to be usable. If such a certificate with private key entry cannot be found in the keystore, it won't be usable by the KeyManagerand SSLContext. Hence, they will be disabled when the handshake is actually attempted. This would typically result in an exception being thrown in the middle of the handshake on the server side ("javax.net.ssl.SSLHandshakeException: no cipher suites in common"), and the error messages you get on the client side via OpenSSL.

确实,虽然您提到的密码套件已启用,但如果无法使用,它们将自动禁用。所有的RSA或DSS加密套件,这意味着他们需要与RSA或DSA密钥的证书自己的私钥可以使用。如果用私有密钥进入这样的证书不能在密钥库中找到,它不会被使用KeyManagerSSLContext。因此,当实际尝试握手时,它们将被禁用。这通常会导致在服务器端 (" javax.net.ssl.SSLHandshakeException: no cipher suites in common")的握手过程中抛出异常,以及您通过 OpenSSL 在客户端获得的错误消息。