具有客户端身份验证的SSL套接字连接 [英] SSL socket connection with client authentication

查看:104
本文介绍了具有客户端身份验证的SSL套接字连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个运行一些实用程序命令的应用程序服务器,这些命令使用C语言编写.我必须使用Java SSL socket通过Java客户端程序连接到服务器客户端身份验证.服务器端的密钥是使用以下命令创建的:

I have an application server running some utility commands, which is programmed in C. I have to connect to the server through Java client program using Java SSL socket with client authentication. The key on the server side was created using:

   openssl req -new -text -out ser.req
   openssl rsa -in privkey.pem -out ser.key
   openssl req -x509 -in ser.req -text -key ser.key -out ser.crt

已向我提供了服务器密钥和证书.我已将密钥和证书组合在一起转换成PKCS12格式的文件:

I have been provided the server key and certificate. I have combined the key and certificate into a PKCS12 format file:

openssl pkcs12 -inkey ser.key -in ser.crt -export -out ser.pkcs12

然后使用keytool将生成的PKCS12文件加载到JSSE密钥库中:

Then loading the resulting PKCS12 file into a JSSE keystore with keytool:

keytool -importkeystore -srckeystore ser.pkcs12 -srcstoretype PKCS12 -destkeystore ser.keystore

但是当我尝试连接时,出现以下错误:

But when I try to connect, I get the following error:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:324)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:267)
    at sun.security.ssl.TransportContext.fatal(TransportContext.java:262)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
    at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:377)
    at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
    at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422)
    at sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
    at sun.security.ssl.SSLTransport.decode(SSLTransport.java:149)
    at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1143)
    at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1054)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:394)
    at SSLSocketClient.main(SSLSocketClient.java:67)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:456)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:323)
    at sun.security.validator.Validator.validate(Validator.java:271)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:315)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:223)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
    at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:638)
    ... 11 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:451)
    ... 17 more

在服务器端日志上:

SSL open_server:无法接受SSL连接:sslv3警报证书未知

运行命令:

java -Djavax.net.ssl.keyStore =/path/to/ser.keystore -Djavax.net.ssl.keyStorePassword = passwd SSLSocketClient< server-ip>< port>

有人知道这个问题的原因吗?

Does anyone know the cause of this problem?

更新了客户端源代码:

import java.net.*;
import java.io.*;
import javax.net.ssl.*;

import java.security.cert.CertificateFactory;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.SocketFactory;

public class SSLSocketClient {

   public static void main(String [] args) throws Exception {
      String serverName = args[0];
      int port = Integer.parseInt(args[1]);
      try {

        SSLSocketFactory sf =
                (SSLSocketFactory)SSLSocketFactory.getDefault();

        Socket client = new Socket(serverName, port);

        System.out.println("Connected to " + client.getRemoteSocketAddress());
        OutputStream outToServer = client.getOutputStream();
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(outToServer));

        writeData(out);
        out.flush();

        InputStream inFromServer = client.getInputStream();
        DataInputStream in = new DataInputStream(inFromServer);

        
        readData(in);
        outToServer = client.getOutputStream();
        out = new DataOutputStream(new BufferedOutputStream(outToServer));
        writeData2(out);
        out.flush();
        
        Socket newClient = sf.createSocket(client, serverName, port, false);

        client.close();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

    private static void writeData(DataOutputStream out) throws IOException {
         char CMD_CHAR_U = 'U';
         byte b = (byte) (0x00ff & CMD_CHAR_U);

         out.writeByte(b);          // <U>
    }

    private static void writeData2(DataOutputStream out) throws IOException {
         char CMD_CHAR_S = 'S';
         byte b = (byte) (0x00ff & CMD_CHAR_S);

         out.writeByte(b);          // <S>
    }

    private static void readData(DataInputStream in) throws IOException {
        char sChar = (char) in.readByte(); 
        System.out.println("<S>\t\t" + sChar);
    }
}

现在创建信任库,如链接所示: https://jdbc.postgresql.org/documentation/head/ssl-client.html

Now creating the truststore as shown in the link: https://jdbc.postgresql.org/documentation/head/ssl-client.html

创建步骤:

openssl x509 -in server.crt -out server.crt.der -outform der
keytool -keystore mystore -alias clientstore -import -file server.crt.der
java -Djavax.net.ssl.trustStore=mystore -Djavax.net.ssl.trustStorePassword=mypassword com.mycompany.MyApp

注意-服务器端正在使用TLSv1协议

Note - The server side is using TLSv1 protocol

但仍然无法实现.我究竟做错了什么?我想要的是服务器对客户端的crt进行身份验证.

But still not able to make it through. What am I doing wrong? What I want is the server to authenticate the crt of the client.

服务器的登录协议;我们使用的SSL仅用于身份验证不保证传输的安全性:

The login protocol with server; the SSL we use is only to authenticate not to secure the transmission:

    -------------------------------------------------------------
    client                                            server 
    -------------------------------------------------------------

    sock = connect()                                 sock = accept()
                      <U><LOGIN_SSL=501>
                 --------------------------------->
                       'S'|'E'
                 <---------------------------------
                       'S'
                 --------------------------------->
    SSL_connect(sock)                               SSL_accept(sock)

                      <R><LOGIN_SSL>
                 <---------------------------------

推荐答案

我认为您的设置存在一些问题.

I think you have several problems with your setup.

要正确配置与JSSE的SSL连接,您需要做几件事,具体取决于您是需要认证服务器,客户端还是执行相互认证.

To configure properly the SSL connection with JSSE you need several things depending if you need to authenticate the server, the client, or to perform mutual authentication.

让我们假设以后有更完整的相互认证用例.

Let's suppose the later and more complete use case of mutual authentication.

目标是配置用于与服务器联系的 SSLSocketFactory .

The objective is to configure a SSLSocketFactory that you can use to contact your server.

要配置 SSLSocketFactory ,您需要一个 SSLContext .

对于相互身份验证用例,该元素又至少需要两个元素,即客户端SSL身份验证(即,服务器信任客户端)所需的 KeyManagerFactory .TrustManagerFactory ,用于配置客户端以信任服务器.

This element in turn with require at least two elements for the mutual authentication use case, a KeyManagerFactory, required for client side SSL authentication, i.e., the server to trust the client, and TrustManagerFactory, required for configuring the client to trust the server.

KeyManagerFactory TrustManagerFactory 都需要正确配置的密钥库,并带有必要的加密材料.

Both KeyManagerFactory and TrustManagerFactory require a properly configured keystore with the necessary cryptographic material.

因此,第一步将包括生成这种加密材料.

So, the first step will consist on generating this cryptographic material.

您已经使用服务器证书创建了密钥库:

You already created a keystore with the server certificate:

keytool -keystore serverpublic.keystore -alias clientstore -import -file server.crt.der -storepass yourserverpublickeystorepassword

请注意,以与服务器情况类似的方式,您还需要为客户端创建一个公钥和私钥对,当然,这与服务器不同.

Please, be aware that, in a similar way as in the server case, you also need to create a public and private key pair for your client, of course, different than the server one.

您随OpenSSL和 keytool 提供的相关代码看起来很合适.请为客户端重复此过程:

The related code you provided with OpenSSL and keytool looks appropriate. Please, repeat the process for the client side:

openssl req -new -text -out client.csr
openssl rsa -in clientpriv.pem -out client.key
openssl req -x509 -in client.csr -text -key client.key -out client.crt
// You can use PKCS12 also with Java but it is also ok on this way
openssl pkcs12 -inkey client.key -in client.crt -export -out client.pkcs12
// Do not bother yourself and, in this use case, use always the same password for the key and keystore
keytool -importkeystore -srckeystore client.pkcs12 -srcstoretype PKCS12 -destkeystore client.keystore -storepass "yourclientkeystorepassword"

在正确的密钥库到位后,请尝试以下类似方法与服务器交互:

With the right keystores in place, try something like the following to interact with your server:

// First, let's configure the SSL for client authentication
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(
  new FileInputStream("/path/to/client.keystore"),
  "yourclientkeystorepassword".toCharArray()
);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); // SunX509
kmf.init(clientKeyStore, "yourclientkeystorepassword".toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

// Now, let's configure the client to trust the server
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
serverKeyStore.load(
  new FileInputStream("/path/to/serverpublic.keystore"),
  "yourserverpublickeystorepassword".toCharArray()
);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // SunX509
tmf.init(serverKeyStore);
TrustManager[] trustManagers = tmf.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null); // You can provide SecureRandom also if you wish

// Create the SSL socket factory and establish the connection
SSLSocketFactory sf = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket)sf.createSocket(serverName, port);

// Interact with your server. Place your code here
// Please, consider the following link for alternatives approaches on how to 
// interchange information with the server:
// https://web.mit.edu/java_v1.5.0_22/distrib/share/docs/guide/security/jsse/samples/sockets/client/SSLSocketClient.java
// It also suggest the use of startHandshake explicitly if your are using PrintWriter for the reason explained in the example an in the docs:
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLSocket.html

//...

// Close the socket
socket.close();

可以将所描述的方法扩展为使用更高级别的抽象组件(而不是套接字),例如

The described approach can be extended to use, instead of sockets, higher level of abstraction components like HttpsURLConnection and HTTP clients - with the exception of Apache HttpClient that handles SSL differently - like OkHttp which, under the hood, use SSLSocketFactory and related stuff.

也请考虑阅读此出色的文章来自IBM DeveloperWorks的/a>,除了解释前面提到的许多要点外,还将在必要时为您的客户端服务器的密钥库生成提供很好的指导.

Please, also consider review this great article from IBM's DeveloperWorks, in addition to explain many of the point aforementioned will provide you great guidance with the generation of keystores for your client an server if necessary.

请注意,根据您的服务器代码,您可能需要将其配置为信任提供的客户端证书.

Please, also be aware that, depending on your server code, you may need to configure it to trust the provided client certificate.

根据您的评论,您正在使用类似于Postgresql 8.1提供的服务器端代码.请参阅相关文档,以在该数据库中配置SSL(如果有)您正在使用一些类似的服务器端代码,可能会对您有所帮助.

According to your comments you are using a server side code similar to the one provided by Postgresql 8.1. Please, see the relevant documentation for configuring SSL in that database, if you are using some similar server side code it maybe could be of help.

最好的方法可能是从服务器信任的根证书生成客户端证书,而不是使用自签名证书.

Probably the best approach will be to generate a client certificate derived from the root certificate trusted by your server instead of using a self signed one.

我认为它也与您的服务器端SSL证书和相关联的私钥相关:首先,创建一个根自签名证书,您的CA证书,配置服务器端C代码以信任它,然后派生两个客户端以及来自该CA的服务器端SSL加密材料:可能会简化您的设置并确保一切正常运行.

I think that it will be also relevant for your server side SSL certificate an associated private key: first, create a root self signed certificate, your CA certificate, configure your server side C code to trust it, and then derive both client and server side SSL cryptographic material from that CA: probably it will simplify your setup and make everything work properly.

这篇关于具有客户端身份验证的SSL套接字连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆