InaccessibleWSDLException with handshake_failure alert despite setting keystore and truststore - java

I'm developing a Springboot application that will communicate with a server having SOAP Web service. Mutual authentication or 2-Way SSL authentication is used for the communication. I have looked the suggestions given here and here and have set the following: -
In application.properties
server.ssl.key-alias=testclient
server.ssl.key-password=password
server.ssl.key-store=classpath:testclientselfsigned
server.ssl.key-store-password=password
server.ssl.key-store-type=JKS
server.ssl.trust-store=classpath:myTruststore
server.ssl.trust-store-type=JKS
server.ssl.trust-store-password=password
In my code calling the web service
static {
URL urlTruststore = ClassLoader.getSystemResource("myTruststore");
URL urlKeystore = ClassLoader.getSystemResource("testclientselfsigned");
if (urlTruststore != null) {
logger.info("URL TRUST STORE:\t" + urlTruststore.getFile());
System.setProperty("javax.net.ssl.trustStore", urlTruststore.getFile());
System.setProperty("javax.net.ssl.trustStorePassword","password");
}
if (urlKeystore != null) {
logger.info("URL KEY STORE:\t" + urlKeystore.getFile());
System.setProperty("javax.net.ssl.keyStore",urlKeystore.getFile());
System.setProperty("javax.net.ssl.keyStorePassword","password");
}
String WS_URL = "https://uatexample.testserv.com/uat/ssl/custService";
URL url = new URL(WS_URL);
QName qname = new QName("http://www.sampleserv.com/services", "custService");
Service service = Service.create(url, qname);
CustService client = service.getPort(CustService.class);
It is at this point CustService client = service.getPort(CustService.class); that I'm getting the following exception
com.sun.xml.internal.ws.wsdl.parser.InaccessibleWSDLException: 2 counts of InaccessibleWSDLException.
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
Please note that myTruststore.jks contains the certificate sent to me by the server** and testclientselfsigned.jks contains the client certificate.
I have tried all the suggestions I found here to add a Keystore and Truststore in my code, but nothing worked. However, I have tried the same in Soap-UI and it worked.

Related

Java Grizzly server and Jersey client Received fatal alert: certificate_unknown

I have a Java application with an embedded SSL server and client.
My application uses client authentication to determine the identity of the client, so the server is configured with wantClientAuth=true and needClientAuth=true. The server is also configured with a server identity (cert/key pair). The server certificate SubjectDN does NOT contain the server's hostname in the CN portion of the distinguished name. The server certificate also does NOT contain the server's IP address in an x.509 alternate names extension.
My client is configured with a client identity. It's configured to NOT perform hostname verification. It's also configured with a trust-all trust manager (temporarily) defined in the usual manner. On the client side, the error received is:
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
All of the attempted fixes I've made up to this point have only succeeded in making it fail more often.
I found this command line in another stackoverflow question and tried connecting:
openssl s_client -connect 10.200.84.48:9298 -cert cert.pem -key key.pem -state -debug
This works! I'm able to establish a connection using the openssl client and the client's private key and cert, but when I try to use my Java client to do it, it fails with the above error.
I'm certain I'm using the correct keys and certs on both ends.
For debugging purposes, I added print statements to the "trust-all" client-side trust store and I notice that none of the three methods are ever getting called to validate the server's cert (which it should do regardless of the content of the cert).
I did the same in the server-side trust store, which is dynamically managed, because client identities come and go. I understand that a new trust manager must be built whenever the trust store content is modified because the trust manager copies trust store content rather than holding a reference to the provided KeyStore object, so my code does this. When a client attempts to connect, the server does call checkClientTrusted and getAcceptedIssuers and the certificate contents displayed are correct.
Here's the really weird part - it works intermittently. Sometimes I get successful connections and data interchanges, and sometimes it fails with the title error (seen in the server's JSSE debug output) and the associated client-side errors about PKIX path building mentioned above.
One more fact: The server is using a grizzly embedded server created from SSLEngineConfigurator, and the client is a pure Jersey client, configured with an SSLContext.
I'm at a total loss. Has anyone seen anything like this before? Can I provide any more information which might help you understand the context better?
Update:
Here's a snippet from the server-side JSSE debug log:
javax.net.ssl|FINE|25|grizzly-nio-kernel(7) SelectorRunner|2022-05-24 03:06:01.221 UTC|Alert.java:238|Received alert message (
"Alert": {
"level" : "fatal",
"description": "certificate_unknown"
}
)
javax.net.ssl|SEVERE|25|grizzly-nio-kernel(7) SelectorRunner|2022-05-24 03:06:01.221 UTC|TransportContext.java:316|Fatal (CERTIFICATE_UNKNOWN): Received fatal alert: certificate_unknown (
"throwable" : {
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alert.createSSLException(Alert.java:131)
at sun.security.ssl.Alert.createSSLException(Alert.java:117)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:311)
The fact that the server "Received fatal alert: certificate_unknown" tells me that the client is the one generating the alert and causing the problem. It seems the client does not like the server's certificate, event though I'm using a trust-all trust manager defined as follows:
RestClientImpl(#Nonnull Endpoint endpoint, #Nonnull Credentials clientCreds,
#Nullable KeyStore trustStore, #Nonnull Configuration cfg, #Nonnull ExecutorService es) {
this.endpoint = endpoint;
ClientBuilder builder = ClientBuilder.newBuilder();
setupClientSecurity(builder, clientCreds, trustStore);
this.client = builder
.executorService(es)
.register(JsonProcessingFeature.class)
.register(LoggingFeature.class)
.property(LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_CLIENT, log.getName())
.connectTimeout(cfg.getLong(CFG_REST_CLIENT_TMOUT_CONNECT_MILLIS), TimeUnit.MILLISECONDS)
.readTimeout(cfg.getLong(CFG_REST_CLIENT_TMOUT_READ_MILLIS), TimeUnit.MILLISECONDS)
.build();
this.baseUri = "https://" + endpoint.getAddress() + ':' + endpoint.getPort() + '/' + BASE_PATH;
log.debug("client created for endpoint={}, identity={}: client-side truststore {}active; "
+ "hostname verification {}active", endpoint, osvIdentity,
clientSideTrustStoreActive ? "" : "NOT ", hostnameVerifierActive ? "" : "NOT ");
}
private void setupClientSecurity(ClientBuilder builder, #Nonnull Credentials clientCreds,
#Nullable KeyStore trustStore) {
try {
SSLContext sslContext = makeSslContext(clientCreds, trustStore);
builder.sslContext(sslContext);
if (trustStore != null) {
hostnameVerifierActive = true;
} else {
builder.hostnameVerifier((hostname, session) -> true);
}
} catch (IOException | GeneralSecurityException e) {
log.error("Failed to create SSL context with specified client credentials and "
+ "server certificate for endpoint={}, osv identity={}", endpoint, osvIdentity);
throw new IllegalArgumentException("Failed to create SSL context for connection to endpoint="
+ endpoint + ", osv identity=" + osvIdentity, e);
}
}
private SSLContext makeSslContext(#Nonnull Credentials clientCreds, #Nullable KeyStore trustStore)
throws IOException, GeneralSecurityException {
SSLContext context = SSLContext.getInstance(SSL_PROTOCOL); // TLSv1.2
X509Certificate clientCert = clientCreds.getCertificate();
PrivateKey privateKey = clientCreds.getPrivateKey();
// initialize key store with client private key and certificate
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry(CLIENT_CERT_ALIAS, clientCert);
keyStore.setKeyEntry(CLIENT_KEY_ALIAS, privateKey, KEYSTORE_PASSWORD, new Certificate[] {clientCert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD);
KeyManager[] km = kmf.getKeyManagers();
// initialize trust store with server cert or with no-verify trust manager if no server cert provided
TrustManager[] tm;
if (trustStore != null) {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
tm = tmf.getTrustManagers();
clientSideTrustStoreActive = true;
} else {
tm = new TrustManager[] {
new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
log.debug("client-side trust manager: getAcceptedIssuers (returning empty cert list)");
return new X509Certificate[0];
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
log.debug("client-side trust manager: checkClientTrusted authType={}, certs={}",
authType, certs);
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
log.debug("client-side trust manager: checkServerTrusted authType={}, certs={}",
authType, certs);
}
}
};
}
context.init(km, tm, null);
return context;
}
As it happens, the answer to this question is related to the way the client is used, not how it's configured. The client is pretty mainstream, built with mostly default settings. The only unique (and relevant) configuration aspect is that it's using a custom SSLContext.
This JDK 1.8.0 bug, which has been open since 2016, indicates the root cause of the issue. https://bugs.openjdk.java.net/browse/JDK-8160347
The bug was filed against 1.8.0_92-b14. I'm testing my code on 1.8.0_312-b07. It appears the bug is still present in JSSE after 6 years!
Thankfully, the user that submitted the bug also submitted a workaround: Simply call HttpsURLConnection.getDefaultSSLSocketFactory() once before allowing multiple threads to simultaneously hit your client. I tried this and now my client works flawlessly. Hope this helps someone.

Connect to websocket with self-signed certificate in java

I need to use Java to connect to a WebSocket server that is using a self-signed certificate. I'm trying to use the Jetty library and am pretty new at Java but I am finding it very difficult to figure out what needs to be done. I can connect using NodeJS very simply:
const WebSocket = require('ws');
const ws = new WebSocket('wss://192.168.100.220:9000/', ['ws-valence'], {
rejectUnauthorized: false,
});
However, modifying the example I found on the Jetty docs doesn't get me very far.
I implemented a basic client that works well with an echo test server, like in the example linked above. Then I went on to configure it with my own protocol and IP Address:
private static void connectToBasestation() {
// String destUri = "ws://echo.websocket.org";
String basestationUri = "wss://192.168.100.220:9000/";
SslContextFactory ssl = new SslContextFactory(); // ssl config
ssl.setTrustAll(true); // trust all certificates
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client
BasestationSocket socket = new BasestationSocket();
ArrayList<String> protocols = new ArrayList<String>();
protocols.add("ws-valence");
try
{
client.start();
URI bsUri = new URI(basestationUri);
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols(protocols);
client.connect(socket, bsUri, request);
System.out.printf("Connecting to : %s%n", bsUri);
// wait for closed socket connection.
socket.awaitClose(5,TimeUnit.SECONDS);
}
catch (Throwable t)
{
t.printStackTrace();
}
finally
{
try
{
client.stop();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
However, I'm getting an UpgradeException with 0 null as the values and my onConnect method is never getting called. I'm guessing this is a security issue, but I can't be certain since the server is an old machine -- a bit of a black box. But I'm thinking maybe something is wrong with my approach? Can anyone lend any advice here?
Edit 1: Included trustful SSL factory as suggested. It did not change anything, including the stack trace from below.
Edit 3: There is a similar question listed above, but this is different since 1) I'm getting a different error code and 2) Adding a trustful SSL factory does not solve the issue.
Edit 2: Here is the stack trace I am getting from my OnError below:
Caused by: javax.net.ssl.SSLException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1666)
at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1634)
at sun.security.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1800)
at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1083)
at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:907)
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:681)
at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:128)
at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:73)
at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:133)
at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:155)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:291)
at org.eclipse.jetty.io.ssl.SslConnection$3.succeeded(SslConnection.java:151)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
... 3 more
org.eclipse.jetty.websocket.api.UpgradeException: 0 null
at org.eclipse.jetty.websocket.client.WebSocketUpgradeRequest.onComplete(WebSocketUpgradeRequest.java:522)
at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:216)
at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:208)
at org.eclipse.jetty.client.HttpReceiver.terminateResponse(HttpReceiver.java:470)
at org.eclipse.jetty.client.HttpReceiver.abort(HttpReceiver.java:552)
at org.eclipse.jetty.client.HttpChannel.abortResponse(HttpChannel.java:156)
at org.eclipse.jetty.client.HttpSender.terminateRequest(HttpSender.java:381)
at org.eclipse.jetty.client.HttpSender.abort(HttpSender.java:566)
at org.eclipse.jetty.client.HttpSender.anyToFailure(HttpSender.java:350)
at org.eclipse.jetty.client.HttpSender$CommitCallback.failed(HttpSender.java:717)
at org.eclipse.jetty.client.http.HttpSenderOverHTTP$HeadersCallback.failed(HttpSenderOverHTTP.java:310)
at org.eclipse.jetty.io.WriteFlusher$PendingState.fail(WriteFlusher.java:263)
at org.eclipse.jetty.io.WriteFlusher.onFail(WriteFlusher.java:516)
at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint$FailWrite.run(SslConnection.java:1251)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:762)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:680)
at java.lang.Thread.run(Thread.java:748)
A TLS/SSL handshake error is rather generic.
You don't know what part of the TLS/SSL handshake the issue occurred in.
You can use -Djavax.net.debug=all command line option on Java to see the raw details of the TLS/SSL handshake, and this might be a good place to start troubleshooting your issues with.
Some options ...
For certificate name issues
If you connect to a server and the provided certificate does not
match the hostname you used in the URI to connect, this is a violation of
the endpoint identification algorithm present in Java itself.
Example Scenario:
You connect to wss://192.168.1.0:8443/chat
The certificate reports itself as chatserver.acme.com
This is a violation, as the hostname in the URI 192.168.1.0 does not match the certificate chatserver.acme.com
This is especially common when testing with wss://localhost or wss://127.0.0.1
You can tell Java to not perform the Endpoint Identification check like this ...
SslContextFactory.Client ssl = new SslContextFactory.Client(); // ssl config
ssl.setEndpointIdentificationAlgorithm(null); // disable endpoint identification algorithm.
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client
⚠️ WARNING: This is not recommended, and can easily allow for man-in-the-middle attacks!
For a certificate trust issues
Try enabling trust for all certificates.
Enable SSL/TLS for WebSocket Client
Trust All Certificates on the SSL/TLS Configuration
Example (assuming Jetty 9.4.19.v20190610 or newer):
SslContextFactory.Client ssl = new SslContextFactory.Client(); // ssl config
ssl.setTrustAll(true); // trust all certificates
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client
⚠️ WARNING: This is not recommended, and can easily allow for man-in-the-middle attacks!
For certificate algorithm issues
The algorithm used to create the certificate will limit the available Cipher Suites made available during the TLS/SSL handshake.
For example, If the server only had a DSA certificate (known vulnerable), then none of the RSA or ECDSA certificates would be available.
The number of bits used to create the certificate is also relevant, as if the server certificate had too few, then Java itself will reject it.
If you are in control of the server certificate, make sure you have generated a certificate that contains both a RSA and ECDSA certificate, with at least 2048 bits for RSA (or more), and 256 bits for ECDSA.
For a cipher suite issues
Try an empty Cipher Suite exclusion list on the Jetty side.
⚠️ WARNING: This lets you use KNOWN vulnerable Cipher Suites!
Enable SSL/TLS for WebSocket Client
Blank out the Cipher Suite Exclusion List
Example (assuming Jetty 9.4.19.v20190610 or newer):
SslContextFactory.Client ssl = new SslContextFactory.Client(); // ssl config
ssl.setExcludeCipherSuites(); // blank out the default excluded cipher suites
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client
⚠️ WARNING: This is not recommended, and any modern computer (even cell phones) can easily read your encrypted traffic

Connect to TLSv1.2 enabled server (https://10.15.15.3:index.jsp) using WebTarget

I have enabled TLSv1.2 in my server (View site information says 'Not Secure with red caution icon')
And I'm using the following code to connect from Java class
Client client = ClientBuilder.newClient();
WebTarget authTarget = client.target(restResource).path(path);
Invocation.Builder invocationBuilder = authTarget.request(MediaType.APPLICATION_JSON);
Response response = invocationBuilder.post(Entity.entity(null,MediaType.APPLICATION_JSON_TYPE));
I receive the following exception.
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
What am I missing here, I'm new to https connection.

org.jolokia.client.exception.J4pException: IO-Error while contacting the server: javax.net.ssl.SSLException: hostname in certificate didn't match

I am using jolokia client to connect to my fuse server, which is using https for web. I am getting the below exception.
org.jolokia.client.exception.J4pException: IO-Error while contacting the server: javax.net.ssl.SSLException: hostname in certificate didn't match: <10.16.205.20> !=
at org.jolokia.client.J4pClient.mapException(J4pClient.java:333)
at org.jolokia.client.J4pClient.execute(J4pClient.java:198)
at org.jolokia.client.J4pClient.execute(J4pClient.java:168)
at org.jolokia.client.J4pClient.execute(J4pClient.java:117)
I have already imported the certificate of 10.16.205.20 to my local truststrore (cacerts) from where my client application is running jolokia client. I have also verified the hosts file have entry for the domain that is being used in the certificate on 10.16.205.20 server. I am using the below code to connect.
J4pClient client = J4pClient.url(baseUrl).user(user.getName()).password(user.getPassword()).authenticator(new BasicAuthenticator().preemptive()).build();
J4pExecRequest request = new J4pExecRequest("org.apache.karaf:type=bundles,name=root","list");
J4pExecResponse response = client.execute(request);
JSONObject obj = response.asJSONObject();
((CloseableHttpClient)client.getHttpClient()).close();
This code is running fine with the server deployed with http. Please let me know, if I am missing something.
You need to let your client use a ConnectionSocketFactory that bypasses this check.
For instance take a look at the following code (Code is Kotlin but you can easily translate it to java, I guess)
val sslCtx: SSLContext = SSLContexts
.custom()
.loadTrustMaterial(null, TrustSelfSignedStrategy())
.build()
val cf: ConnectionSocketFactory = SSLConnectionSocketFactory(sslCtx, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
J4pClient.url(s.jolokiaUrl)
.sslConnectionSocketFactory(cf)
.connectionTimeout(timeout.toMillis().toInt())
.build()

certificate_unknown exception in ssl

I have a server and client, they both communicate between each other using ssl. The previous self signed sslkeystore expired at the server, so I generated a new one with the same details with the extended validity . But now when I try to communicate to the server from the client, I get the following exception.
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1991)
It was working till I updated the keystore.
Its creating the exception from the following block of code. When I try to read from the BufferedInputStream bis.
private byte[] readBytesFromStream(BufferedInputStream bis) throws IOException {
byte[] lengthBytes = new byte[4];
bis.read(lengthBytes);
int length = ByteBuffer.wrap(lengthBytes).getInt();
System.out.print("length : " + length);
byte[] data = new byte[length];
bis.read(data);
return data;
}
Should I do something else to reflect the updated keystore.
I had to make the public key of the server trusted by the keystore in the client. Then onwards it started working. Also I found an excellent tool online which will help to deal with this kind of things with keystores and certificates its called portecle (https://sourceforge.net/projects/portecle/)

Categories