I have a spring-boot application and I am trying to open a secure websocket connection to a server, but I keep getting an error due to: No subject alternative names present.
Here is what I have tried:
StandardWebSocketClient client = new StandardWebSocketClient();
SSLContext sslContext = this.getSslContext("path/to/truststore", "path/to/keystore.p12", "password");
client.getUserProperties().put(SSL_CONTEXT_PROPERTY, sslContext);
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Sec-WebSocket-Key", "SGVsbG8sIHdvcmxkIQ==");
headers.add("Origin", "https://192.168.1.132:8445");
headers.add("Sec-WebSocket-Version", "13");
String url = "wss://192.168.1.132:8445/websocket";
URI uri = new URI(url);
ListenableFuture<WebSocketSession> future = client.doHandshake(new AbstractWebSocketHandler() {
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
receivedMessages += message.getPayload();
System.out.println(message.getPayload());
}
}, headers, uri);
WebSocketSession socketSession = future.get();
And here is the getSslContext method:
private SSLContext getSslContext(String trustStoreFile, String keystoreFile, String password)
throws GeneralSecurityException, IOException {
KeyStore keystore = KeyStore.getInstance("JKS");
try (InputStream in = new FileInputStream(keystoreFile)) {
keystore.load(in, password.toCharArray());
}
try (InputStream in = new FileInputStream(trustStoreFile)) {
keystore.load(in, password.toCharArray());
}
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keystore, password.toCharArray());
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(
keyManagerFactory.getKeyManagers(),
trustManagerFactory.getTrustManagers(),
new SecureRandom());
return sslContext;
}
Given this code, I am always getting the following error:
Caused by: java.security.cert.CertificateException: No subject alternative names present
at java.base/sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:141)
at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:100)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:455)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:429)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:283)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:141)
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1302)
I have also tried to replicate the request using the following curl command:
curl --include --no-buffer --header "Connection: Upgrade" --header "Upgrade: websocket" --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" --header "Origin: https://192.168.1.132:8445" --header "Sec-WebSocket-Version: 13" -Ss --cacert myCA.pem --cert SSLCert.pem:`cat SSLCert.pass` https://192.168.1.132:8445/websocket
The handshake seems to be successful:
HTTP/1.1 101
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
X-Application-Context: panel-command-server:default,postgres,cassandra,kafka:8080
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: qGEgH3En71di5rrssAZTmtRTyFk=
Any ideas on what might be the reason behind the No subject alternative names present error from the spring application?
Managed to fix the issue by overriding some methods from the trust manager to bypass the checks like in this answer: How to bypass ssl certificate checking in java.
This is the final version of the getSslContext I used:
private SSLContext getSslContext(String trustStoreFile, String keystoreFile, String password)
throws GeneralSecurityException, IOException {
KeyStore keystore = KeyStore.getInstance("JKS");
try (InputStream in = new FileInputStream(keystoreFile)) {
keystore.load(in, password.toCharArray());
}
try (InputStream in = new FileInputStream(trustStoreFile)) {
keystore.load(in, password.toCharArray());
}
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keystore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keystore);
// Get hold of the default trust manager
X509TrustManager x509Tm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
x509Tm = (X509TrustManager) tm;
break;
}
}
// Wrap it in your own class.
final X509TrustManager finalTm = x509Tm;
X509ExtendedTrustManager customTm = new X509ExtendedTrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(
keyManagerFactory.getKeyManagers(),
new TrustManager[]{customTm},
new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
return sslContext;
}