我有Java SSL套接字和带有OpenSSL的c客户端(java客户端可以正常使用这个Java服务器)。握手失败,我得到Java异常:
javax.net.ssl.SSLHandshakeException: no cipher suites in common
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1904)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:279)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:269)
at sun.security.ssl.ServerHandshaker.chooseCipherSuite(ServerHandshaker.java:901)
at sun.security.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:629)
at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:167)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:901)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:837)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1023)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:889)
at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:154)
at java.io.BufferedReader.readLine(BufferedReader.java:317)
at java.io.BufferedReader.readLine(BufferedReader.java:382)
at EchoServer.main(EchoServer.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
以下是创建服务器SSL套接字的方法:
public class EchoServer {
public static void main(String[] arstring) {
try {
final KeyStore keyStore = KeyStore.getInstance("JKS");
final InputStream is = new FileInputStream("/Path/mySrvKeystore.jks");
keyStore.load(is, "123456".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm());
kmf.init(keyStore, "123456".toCharArray());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory .getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new java.security.SecureRandom());
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(9997);
SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();
sslsocket.setEnabledCipherSuites(sc.getServerSocketFactory().getSupportedCipherSuites());
InputStream inputstream = sslsocket.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
String string = null;
while ((string = bufferedreader.readLine()) != null) {
System.out.println(string);
System.out.flush();
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
C客户:
BIO *certbio = NULL;
BIO *outbio = NULL;
SSL_METHOD *ssl_method;
SSL_CTX *ssl_ctx;
SSL *ssl;
int sd;
// These function calls initialize openssl for correct work.
OpenSSL_add_all_algorithms();
ERR_load_BIO_strings();
ERR_load_crypto_strings();
SSL_load_error_strings();
// Create the Input/Output BIO's.
certbio = BIO_new(BIO_s_file());
outbio = BIO_new_fp(stdout, BIO_NOCLOSE);
// initialize SSL library and register algorithms
if(SSL_library_init() < 0)
BIO_printf(outbio, "Could not initialize the OpenSSL library !\n");
ssl_method = (SSL_METHOD*)TLSv1_2_method();
// Try to create a new SSL context
if ( (ssl_ctx = SSL_CTX_new(ssl_method)) == NULL)
BIO_printf(outbio, "Unable to create a new SSL context structure.\n");
// flags
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_CIPHER_SERVER_PREFERENCE);
// Create new SSL connection state object
ssl = SSL_new(ssl_ctx);
// Make the underlying TCP socket connection
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
const char *dest_url = this->host.c_str();
address.sin_addr.s_addr = inet_addr(dest_url);
address.sin_port = htons(port);
sd = socket(AF_INET, SOCK_STREAM, 0);
int connect_result = ::connect(sd, (struct sockaddr*)&address, sizeof(address));
if (connect_result != 0) {
BIO_printf(outbio, "Failed to connect over TCP with error %i\n", connect_result);
throw IOException("Connection refused");
} else {
BIO_printf(outbio, "Successfully made the TCP connection to: %s:%i\n", dest_url, port);
}
// Attach the SSL session to the socket descriptor
SSL_set_fd(ssl, sd);
// Try to SSL-connect here, returns 1 for success
int ssl_connect_result = SSL_connect(ssl);
if (ssl_connect_result != 1)
BIO_printf(outbio, "Error: Could not build a SSL session to: %s:%i with error %i\n", dest_url, port, ssl_connect_result);
else
BIO_printf(outbio, "Successfully enabled SSL/TLS session to: %s\n", dest_url);
这是客户端的输出:
错误:无法将SSL会话构建为:127.0.0.1:9997,错误为-1
更新1
int ssl_connect_result = SSL_connect(ssl);
if (ssl_connect_result != 1) {
int error_code = SSL_get_error(ssl, ssl_connect_result); // =1
BIO_printf(outbio, "Error: Could not build a SSL session to: %s:%i with error %i (%i)\n", dest_url, port, ssl_connect_result, error_code);
} else {
BIO_printf(outbio, "Successfully enabled SSL/TLS session to: %s\n", dest_url);
}
输出是:
错误:无法构建SSL会话:127.0.0.1:9997,错误为-1(1)
更新2
我忘了注意我使用的是由JDK keytool
生成的自签名证书。
更新3
我注意到我错过了一些行,我已经补充道:
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
但仍然没有运气 - 得到相同的错误-1。
更新4
以下是上面的服务器代码接受的Java客户端:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(ip, port);
sslsocket.setEnabledCipherSuites(sslsocketfactory.getSupportedCipherSuites());
InputStream inputstream = System.in;
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
OutputStream outputstream = sslsocket.getOutputStream();
OutputStreamWriter outputstreamwriter = new OutputStreamWriter(outputstream);
String string = null;
outputstreamwriter.write("hello");
outputstreamwriter.flush();
while ((string = bufferedreader.readLine()) != null) {
outputstreamwriter.write(string);
outputstreamwriter.flush();
}
sslsocket.close();
我已经检查过我无法在网络中截获的数据包中看到普通数据,因此它会执行一些数据加密。
答案 0 :(得分:3)
我不相信Java服务器也会接受Java客户端,除非Java客户端同样.setEnabledCipherSuites (all-supported)
- 如果是这样的话,它使用的匿名(未经身份验证的)密码套件对于主动攻击是不安全的尽管许多人仍然陷入大约1980年的被动威胁模型,但今天的主动攻击很常见。这就是为什么JSSE的默认密码列表不包括匿名密码 - 除非你覆盖它。为什么OpenSSL的默认密码列表也排除了它们 - 你没有覆盖它们。
(添加)用较小的词来解释,匿名密码套件是加密的(这里有一些例外,因为它们永远不是首选,因此不相关)但未经过身份验证。单词&#34; unauthenticated&#34;意味着&#34;未经过身份验证&#34 ;;它并不意味着&#34;没有加密&#34;。单词&#34;未加密&#34;用于表示&#34;未加密&#34;。 &#34;未加密&#34;意味着只看通道的东西,比如Wireshark,可以看到明文。 &#34;未经过身份验证&#34;意味着拦截(可能会转移)您的流量的攻击者可能会导致您建立“安全”#34;与攻击者在中间进行会话,他们可以解密您的数据,按照自己的意愿复制和/或更改数据,重新加密并发送,如果不是这样的话,您会认为它是正确且私密的。吨。谷歌或在这里搜索(我认为主要是安全。超级用户),例如&#34;中间人攻击&#34;,&#34; ARP欺骗&#34;,&#34; MAC恶搞&#34;, &#34; DNS中毒&#34;,&#34; BGP攻击&#34;等
当前的问题是您没有使用密钥库。您可以使用密钥和信任管理器创建SSLContext
,然后从SSLServerSocketFactory.getDefault()
创建不使用上下文的套接字。 改为使用sc.getServerSocketFactory()
。
(添加)为什么?每个SSLSocket
(和SSLServerSocket
)都链接到SSLContext
,其中包括私钥和证书(s)或使用的链和证书。 (SSL / TLS连接通常只对服务器进行身份验证,因此实际上服务器只需要一个密钥和链,而客户端只需要根证书,但Java对两者都使用相同的密钥库文件格式。很容易只编码两者。)由于您的代码已将特定SSLContext sc
设置为包含合适的密钥和证书,sc.getServerSocketFactory()
创建一个工厂,创建SSLServerSocket
,然后创建一个SSLSocket
(对于每个连接,如果多于一个)使用该密钥 - 并且(只要客户端支持的密码列表允许它,并且在这里它就可以了。)
(添加) SSLServerSocketFactory.getDefault()
使用默认 SSL上下文创建工厂,从而创建套接字,默认情况下包含 NO键 - 和-chain ,尽管您可以使用系统属性对其进行更改,如http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html中巧妙隐藏的文档中所述。因此,它无法协商经过身份验证的密码套件。由于默认情况下Java和OpenSSL都禁用了未经身份验证的密码套件,因此这就是为什么你没有共同的密码套件&#34;除非你.setEnabled
在Java中包含未经身份验证和不安全的密码套件,并且仍然为OpenSSL客户端提供它,因为你没有做任何事情来启用那里未经验证和不安全的密码套件。
(添加)如果仔细查看Wireshark跟踪,您会在ServerHello
中看到所选密码套件使用DH_anon
或ECDH_anon
密钥交换 - &#34; anon&#34;是&#34; anonymous&#34;的缩写。这意味着&#34;未经过身份验证&#34;如上所述 - 并且服务器没有Certificate
消息,并且(除非您知道,否则不太明显)ServerKeyExchange
数据未签名。
另外我预测在握手完成后你是否让你的Java客户端检查sslsocket.getSession().getCipherSuite()
和/或sslsocket.getSession().getPeerCertificates()
,因为你没有明确地将它放在第一个套接字上-level I / O将是outputstreamwriter.flush()
,您将看到匿名密码套件,并且没有对等证书(它会抛出PeerNotAuthenticated)。
其他要点:
(1)通常,只要您从SSL_ERROR
获得SSL_get_error()
,或者从EVP_*
和BIO_*
这样的较低级别例程返回任何错误,就应该使用{{ 1}}例程来获取错误的详细信息并记录/显示它们;见https://www.openssl.org/docs/faq.html#PROG6和https://www.openssl.org/docs/manmaster/crypto/ERR_print_errors.html et amici。特别是因为你已经加载了错误字符串,因此避免了https://www.openssl.org/docs/faq.html#PROG7。在这种情况下,您已经从服务器端了解了足够多,因此不需要客户端详细信息。
(2)您不需要ERR_*
和_add_all_ciphers
,它们包含在_add_all_digests
中。
(3)_add_all_algorithms
和OP_NO_SSLv2
对3
没有影响,而TLSv1_2_method
对客户端没有影响。 (他们没有伤害,他们只是无用而且可能令人困惑。)
(4)一旦通过密码协商,OpenSSL 客户端将需要服务器的根证书;因为您打算使用自签名证书(一旦您修复服务器以完全使用密钥库),该证书就是它自己的根。在1.0.2(不是更早)中,您也可以使用非根信任锚,但默认情况下不会,但您还是没有。我假设OP_SERVER_CIPHER_PREFERENCE
是为此目的,但你永远不会在实际文件上打开它或用它做任何其他事情,无论如何certbio
库不能将BIO用于其信任库。你有三个选择:
将证书放在文件或使用特殊哈希名称的目录中,并将文件和/或目录名称传递给SSL
。如果您只想使用SSL_CTX_load_verify_locations
选项的一个根(您自己的)更容易。
将证书放入或添加到由OpenSSL库编译确定的默认文件或散列目录中,并调用CAfile
;这通常类似于SSL_CTX_set_default_verify_paths
或/etc/pki
。如果您想为多个程序或命令行/var/ssl
使用相同的证书,则此共享位置通常更容易。
使用BIO和/或其他方法(打开并)读取证书到内存中,构建自己的openssl
包含它们,并将其放入你的X509_STORE
。这比较复杂,所以除非您愿意,否则我不会扩展它。
(5)你的SSL_CTX
是(至少在这种情况下?)地址字符串,而不是URL;虽然相关的东西是不同的,但认为它们是相同的会导致更多的问题。对于大多数程序,最好使用经典dest_url
处理主机名称字符串并回退到gethostbyname
,或者更好地使用&#34; new&#34; (自20世纪90年代以来)inet_addr
可以处理名称和地址字符串以及IPv4和v6(自20世纪90年代以来也是新的但最终获得牵引力)。至少你应该检查getaddrinfo
返回inet_addr
,表明它失败了。