简体   繁体   English

SSL socket连接与客户端认证

[英]SSL socket connection with client authentication

I have an application server running some utility commands, which is programmed in C.我有一个运行一些实用程序命令的应用程序服务器,这些命令是在 C 中编程的。 I have to connect to the server through Java client program using Java SSL socket with client authentication.我必须通过 Java 客户端程序使用 Java SSL 套接字与客户端身份验证连接到服务器。 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

I have been provided the server key and certificate.我已获得服务器密钥和证书。 I have combined the key and certificate into a PKCS12 format file:我已将密钥和证书组合成一个 PKCS12 格式文件:

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

Then loading the resulting PKCS12 file into a JSSE keystore with keytool:然后使用 keytool 将生成的 PKCS12 文件加载到 JSSE 密钥库中:

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

On the server side log:在服务器端日志:

SSL open_server: could not accept SSL connection: sslv3 alert certificate unknown

Running command:运行命令:

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?有谁知道这个问题的原因?

Updated the client source code:更新了客户端源代码:

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

Now creating the truststore as shown in the link: https://jdbc.postgresql.org/documentation/head/ssl-client.html现在创建信任库,如链接所示: https://jdbc.postgresql.org/documentation/head/ssl-client.html

Steps to create:创建步骤:

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

Note - The server side is using TLSv1 protocol注意 - 服务器端使用 TLSv1 协议

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.我想要的是服务器对客户端的crt进行身份验证。

The login protocol with server;与服务器的登录协议; the SSL we use is only to authenticate not to secure the transmission:我们使用的 SSL 仅用于验证而不是保护传输:

    -------------------------------------------------------------
    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.我认为你的设置有几个问题。

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.要正确配置 SSL 与 JSSE 的连接,您需要几件事,具体取决于您是否需要验证服务器、客户端或执行相互验证。

Let's suppose the later and more complete use case of mutual authentication.让我们假设后来更完整的相互身份验证用例。

The objective is to configure a SSLSocketFactory that you can use to contact your server.目标是配置一个SSLSocketFactory ,您可以使用它来联系您的服务器。

To configure a SSLSocketFactory , you need a SSLContext .要配置SSLSocketFactory ,您需要一个SSLContext

This element in turn with require at least two elements for the mutual authentication use case, a KeyManagerFactory , required for client side SSL authentication, ie, the server to trust the client, and TrustManagerFactory , required for configuring the client to trust the server.这个元素反过来需要至少两个元素用于相互身份验证用例,一个KeyManagerFactory ,需要客户端 SSL 身份验证,即服务器信任客户端,以及TrustManagerFactory ,需要配置客户端信任服务器。

Both KeyManagerFactory and TrustManagerFactory require a properly configured keystore with the necessary cryptographic material. KeyManagerFactoryTrustManagerFactory都需要正确配置的密钥库和必要的加密材料。

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.请注意,以与服务器案例类似的方式,您还需要为您的客户端创建一个公钥和私钥对,当然,与服务器不同。

The related code you provided with OpenSSL and keytool looks appropriate.您与 OpenSSL 和keytool一起提供的相关代码看起来很合适。 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. 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.

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.还请考虑查看来自 IBM 的 DeveloperWorks 的这篇精彩文章,除了解释前面提到的许多要点之外,还将在必要时为您的客户端和服务器生成密钥库提供很好的指导。

Please, also be aware that, depending on your server code, you may need to configure it to trust the provided client certificate.另请注意,根据您的服务器代码,您可能需要将其配置为信任提供的客户端证书。

According to your comments you are using a server side code similar to the one provided by Postgresql 8.1.根据您的评论,您使用的服务器端代码类似于 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.请参阅在该数据库中配置 SSL 的相关文档,如果您使用一些类似的服务器端代码,它可能会有所帮助。

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.最好的方法可能是生成从您的服务器信任的根证书派生的客户端证书,而不是使用自签名证书。

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 证书相关的私钥相关:首先,创建一个根自签名证书,您的 CA 证书,配置您的服务器端 C 代码以信任它,然后派生客户端和服务器端来自该 CA 的 SSL 加密材料:它可能会简化您的设置并使一切正常工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM