I followed these threads and others with similar examples:
- How to create a secured TCP connection via TLS v.1.2 in Java
- Accept server's self-signed ssl certificate in Java client
I've already achieved a client/server simple Java program using any locally-generated self-signed certificates for both client and server. By the way I see they negotiate TLS 1.3 instead of 1.2. That's good.
But I see they are only using server certificate (client is configured to accept any, OK). I want them to require client to authenticate too. And allow client certificate to be any self-signed one, not signed by any CA. So I tried the server to use sslServerSocket.setNeedClientAuth(true)
or access to client certificate info. Then I get errors like this:
Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
at java.base/sun.security.ssl.SSLSessionImpl.getPeerPrincipal(SSLSessionImpl.java:1122)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
So I've been playing around with these tools to test both sides of my implementation:
ncat --ssl
ncat --ssl --listen
openssl s_client
openssl s_server
My conclusion is that my client is working good, authenticating on other servers when required. But my server needs to either provide the base CA to the client (I don't want this) OR just accept any client with no client authentication (that's what I have now, it's not my goal).
I want my server to require client certificate and accept any. How can I achieve this? Thanks!
Motivation: I want to implement some Syncthing well documented features using Java instead of Go. They use custom-certificate authentication on both sides of the connection.
CodePudding user response:
So now it's working.
First I was forcing handshake only on client side. It seems to be OK, but adding it in server side produces better error messages.
So I added on server side this last line after accepting connection (Groovy code):
ss.setNeedClientAuth(true)
SSLSocket socket = ss.accept() as SSLSocket
socket.startHandshake()
Then I got a more indicating error when a client arrives:
Exception in thread "main" javax.net.ssl.SSLException: Cannot read the array length because "caCerts" is null
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:133)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:371)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:314)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:309)
at java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1702)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:465)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:421)
at javax.net.ssl.SSLSocket$startHandshake.call(Unknown Source)
So after a while I found something suspicious in the piece of code to accept connections from ANY certificate:
def x509TrustAllCerts = new X509TrustManager() {
X509Certificate[] getAcceptedIssuers() {
return null // <--- SUSPICIOUS!!
}
void checkClientTrusted(X509Certificate[] certs, String authType) { }
void checkServerTrusted(X509Certificate[] certs, String authType) { }
}
TrustManager[] trustAllCerts = new TrustManager[] { x509TrustAllCerts }
So I made the simplest fix changing null
to empty array:
def x509TrustAllCerts = new X509TrustManager() {
X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0] // <-- FIX
}
void checkClientTrusted(X509Certificate[] certs, String authType) { }
void checkServerTrusted(X509Certificate[] certs, String authType) { }
}
TrustManager[] trustAllCerts = new TrustManager[] { x509TrustAllCerts }
Now it's working the way I intended.