How to use trusted certificate with nginx and tomcat? - java

I am integrating a merchant with our application. The merchant provides us with JKS, KEY, PEM and P12 file along with Certificate Password.
In the development server, the integration works with JKS certificate and Certificate Password which is implemented using HttpsURLConnection.
SSLContext sc = SSLContext.getInstance("TLSv1.2");
KeyManagerFactory kmf;
KeyStore ks;
char[] passphrase = keystore_password.toCharArray();
kmf = KeyManagerFactory.getInstance("SunX509");
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keystore_path), passphrase);
kmf.init(ks, passphrase);
sc.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HostnameVerifier hv = new HostnameVerifier() {
#Override
public boolean verify(String urlHostName, SSLSession session) {
if (!urlHostName.equalsIgnoreCase(session.getPeerHost())) {
logger.warn("Warning: URL host ' " + urlHostName + " ' is different to SSLSession host ' "
+ urlHostName + " '");
}
return true;
}
};
In the upper environment, the Tomcat is in DMZ Zone and interact external world via the Nginx only.
The tomcat request Nginx server with actual URL in a header and the header is parsed by Nginx and forward the request to URL and render the response to tomcat.
Question
How do I forward the request with credential via Nginx to merchant?

You can't "forward" it. To process HTTP requests based on the contents of the header, nginx must decrypt the incoming data and re-encrypt the outgoing, modified data. Since the whole point of a security protocol like SSL/TLS is that nobody other than the authorized endpoints can see or alter the data, nginx must terminate the client-side SSL/TLS session itself and create a separate server-side SSL/TLS session over which the HTTP-level data is forwarded.
Thus to authenticate to the 'merchant' server, it is nginx that must be configured with the client certificate including chain cert(s) if applicable and matching privatekey, see http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_certificate et seq.
(If you didn't already have them, you could convert JKS to PKCS12 with keytool -importkeystore and PKCS12 to PEM with openssl pkcs12 -- there are numerous existing Qs on both here and on other Stacks like superuser and serverfault.)
Whether the session from the (real) client to nginx is authenticated with the same cert, a different cert, or not authenticated with a cert at all, is up to the configuration of nginx.

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.

Setting a client certificate as a request property in a Java HTTP connection?

I have a Java application that connects to another Java app through a socket with SSL, so my client JVM already has the -Djavax.net.ssl.keyStore and -Djavax.net.ssl.trustStore properties set.
This application needs to make some HTTP requests to a web server that requires client authentication. I can open the connection by using a URLConnection in Java which returns an HTTPSURLConnectionImpl.
The client certificate I want to present to the web server in the request is different than the one set as my JVM system property. Is there a way I can set a client cert. as a request property in the HTTPSURLConnectionImpl ?
Setting a SSL "client certificate" is not adequate directly through HTTPSURLConnectionImpl's request properties, because a digital signature is also required to prove you own the certificate. SSL already does all that automatically, so to makes sense to use that layer.
You have two ways to solve your issue going forward.
Through configuration
You can add you client key and certificate to your JVM KeyStore, it should be picked up at Runtime when the server asks for your client-side SSL authentication. (SSL/TLS is designed for that : the server will ask for a client certificate that is signed by it's trusted authority, which allows the SSL Engine to choose the right certificate, even when your KeyStore holds many).
Through Code
You can roll you own SSLContext using custom made KeyStore/TrustStores.
This is a bit complex (I won't elaborate on how to build Keystore instances in Java), but the gist of it is here :
public static void main(String[] args) throws Exception {
KeyStore clientKeyStore = ... // Whatever
KeyStore clientTrustStore = ... // Whatever you need to load
// We build the KeyManager (SSL client credentials we can send)
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(clientKeyStore, "password".toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
// We build the TrustManager (Server certificates we trust)
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(clientTrustStore);
TrustManager[] tm = trustFactory.getTrustManagers();
// We build a SSLContext with both our trust/key managers
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(km, tm, null);
SSLSocketFactory sslSf = sslContext.getSocketFactory();
// We prepare a URLConnection
URL url = new URL("https://www.google.com");
URLConnection urlConnection = url.openConnection();
// Before actually opening the sockets, we affect the SSLSocketFactory
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(sslSf);
// Ready to go !
}

How to use X509 certificates for POST request over HttpsURLConnection?

I have
A self-signed server certificate (from a third-party organization I need to communicate with)
My client certificate, containing the secret key, signed by this server certificate.
Now I need to send a POST request via HTTPS using these certificates.
I managed to test the connection over https in Internet Explorer after I installed them in browser:
server cert - into the trusted CA
client cert - into the personal certs.
In java until now I used the code, given in SO: Java client certificates over HTTPS/SSL in the answer by neu242, i.e. accepted any certificate. But now the server side does accept this, i.e. I get SSL-handshake failure.
Thanks to SO: X509TrustManager Override without allowing ALL certs? I tried to return the server certificate in getAcceptedIssuers, but in vain. It throws
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
right after getAcceptedIssuers returns.
public X509Certificate[] getAcceptedIssuers() {
try {
X509Certificate scert;
try (InputStream inStream = new FileInputStream("..\\server.crt")) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
scert = (X509Certificate) cf.generateCertificate(inStream);
}
return new X509Certificate[]{scert};
} catch (Exception ex) {
writeLogFile(ex.getMessage());
return new X509Certificate[]{};
}
}
I guess I should specify the client certificate somehow, but cannot find any way to.
I may be wrong of course.
Hope someone can lead me the right direction.
In the end I managed to make it work.
As the server certificate is self-signed, I had to place it into a truststore. To avoid adding it to common JRE cacerts, I placed it into a truststore in a separate file with the following command (thanks to Common SSL issues in Java:
keytool -import -v -trustcacerts
-file servercert.crt -keystore server.jks
-keypass mypwd -storepass mypwd
Then I used the obtained truststore and the client certificate containing the secret key to initialize key stores and specify them to the SSL context (thanks to sql.ru: Received fatal alert: handshake_failure):
String pwd = "mypwd";
InputStream keyStoreUrl = new FileInputStream("client.p12");
InputStream trustStoreUrl = new FileInputStream("server.jks");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keyStoreUrl, pwd.toCharArray());
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, pwd.toCharArray());
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(trustStoreUrl, pwd.toCharArray());
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(keyManagerFactory.getKeyManagers(),
trustManagerFactory.getTrustManagers(),
new SecureRandom());
SSLContext.setDefault(sslContext);
Also I had to specify HostnameVerifier, as there were some inconsistency with the server certificate and this server's url:
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
//...
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
And that is it. Further on it was as simple as:
url = new URL(targetURL);
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=utf-8");
connection.setRequestProperty("Accept", "application/x-www-form-urlencoded");
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("Content-Length",
Integer.toString(Data.getBytes("utf8").length));
connection.setDoInput(true);
connection.setDoOutput(true);
connection.getOutputStream().write(Data.getBytes("utf8"));
// read response...

How to fix the "java.security.cert.CertificateException: No subject alternative names present" error?

I have a Java web service client, which consumes a web service via HTTPS.
import javax.xml.ws.Service;
#WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...")
public class ISomeService
extends Service
{
public ISomeService() {
super(__getWsdlLocation(), ISOMESERVICE_QNAME);
}
When I connect to the service URL (https://AAA.BBB.CCC.DDD:9443/ISomeService ), I get the exception java.security.cert.CertificateException: No subject alternative names present.
To fix it, I first ran openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt and got following content in file certs.txt:
CONNECTED(00000003)
---
Certificate chain
0 s:/CN=someSubdomain.someorganisation.com
i:/CN=someSubdomain.someorganisation.com
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=someSubdomain.someorganisation.com
issuer=/CN=someSubdomain.someorganisation.com
---
No client certificate CA names sent
---
SSL handshake has read 489 bytes and written 236 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-MD5
Server public key is 512 bit
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : RC4-MD5
Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Session-ID-ctx:
Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Key-Arg : None
Start Time: 1382521838
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
AFAIK, now I need to
extract the part of certs.txt between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----,
modify it so that the certificate name is equal to AAA.BBB.CCC.DDD and
then import the result using keytool -importcert -file fileWithModifiedCertificate (where fileWithModifiedCertificate is the result of operations 1 and 2).
Is this correct?
If so, how exactly can I make the certificate from step 1 work with IP-based adddress (AAA.BBB.CCC.DDD) ?
Update 1 (23.10.2013 15:37 MSK): In an answer to a similar question, I read the following:
If you're not in control of that server, use its host name (provided
that there is at least a CN matching that host name in the existing
cert).
What exactly does "use" mean?
I fixed the problem by disabling HTTPS checks using the approach presented here:
I put following code into the the ISomeService class:
static {
disableSslVerification();
}
private static void disableSslVerification() {
try
{
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
Since I'm using the https://AAA.BBB.CCC.DDD:9443/ISomeService for testing purposes only, it's a good enough solution, but do not do this in production.
Note that you can also disable SSL for "one connection at a time" ex:
// don't call disableSslVerification but use its internal code:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn instanceof HttpsURLConnection) {
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
httpsConn.setHostnameVerifier(allHostsValid);
httpsConn.setSSLSocketFactory(sc.getSocketFactory());
}
This is an old question, yet I had the same problem when moving from JDK 1.8.0_144 to jdk 1.8.0_191
We found a hint in the changelog:
Changelog
we added the following additional system property, which helped in our case to solve this issue:
-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true
I've the same problem and solved with this code.
I put this code before the first call to my webservices.
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
return hostname.equals("localhost"); // or return true
}
});
It's simple and works fine.
Here is the original source.
The verification of the certificate identity is performed against what the client requests.
When your client uses https://xxx.xxx.xxx.xxx/something (where xxx.xxx.xxx.xxx is an IP address), the certificate identity is checked against this IP address (in theory, only using an IP SAN extension).
If your certificate has no IP SAN, but DNS SANs (or if no DNS SAN, a Common Name in the Subject DN), you can get this to work by making your client use a URL with that host name instead (or a host name for which the cert would be valid, if there are multiple possible values). For example, if you cert has a name for www.example.com, use https://www.example.com/something.
Of course, you'll need that host name to resolve to that IP address.
In addition, if there are any DNS SANs, the CN in the Subject DN will be ignored, so use a name that matches one of the DNS SANs in this case.
To import the cert:
Extract the cert from the server, e.g. openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt This will extract certs in PEM format.
Convert the cert into DER format as this is what keytool expects, e.g. openssl x509 -in certs.txt -out certs.der -outform DER
Now you want to import this cert into the system default 'cacert' file. Locate the system default 'cacerts' file for your Java installation. Take a look at How to obtain the location of cacerts of the default java installation?
Import the certs into that cacerts file: sudo keytool -importcert -file certs.der -keystore <path-to-cacerts> Default cacerts password is 'changeit'.
If the cert is issued for an FQDN and you're trying to connect by IP address in your Java code, then this should probably be fixed in your code rather than messing with certificate itself. Change your code to connect by FQDN. If FQDN is not resolvable on your dev machine, simply add it to your hosts file, or configure your machine with DNS server that can resolve this FQDN.
I fixed this issue in a right way by adding the subject alt names in certificate rather than making any changes in code or disabling SSL unlike what other answers suggest here. If you see clearly the exception says the "Subject alt names are missing" so the right way should be to add them
Please look at this link to understand step by step.
The above error means that your JKS file is missing the required domain on which you are trying to access the application.You will need to Use Open SSL and the key tool to add multiple domains
Copy the openssl.cnf into a current directory
echo '[ subject_alt_name ]' >> openssl.cnf
echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/emailAddress=postmaster#example.com' -days 365
Export the public key (.pem) file to PKS12 format. This will prompt you for password
openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in
self-signed.pem -inkey private.key -name myalias -out keystore.p12
Create a.JKS from self-signed PEM (Keystore)
keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
Generate a Certificate from above Keystore or JKS file
keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
Since the above certificate is Self Signed and is not validated by CA, it needs to be added in Truststore(Cacerts file in below location for MAC, for Windows, find out where your JDK is installed.)
sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
Original answer posted on this link here.
You may not want to disable all ssl Verificatication and so you can just disable the hostName verification via this which is a bit less scary than the alternative:
HttpsURLConnection.setDefaultHostnameVerifier(
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
[EDIT]
As mentioned by conapart3 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER is now deprecated, so it may be removed in a later version, so you may be forced in the future to roll your own, although I would still say I would steer away from any solutions where all verification is turned off.
my problem with getting this error was resolved by using the full URL "qatest.ourCompany.com/webService" instead of just "qatest/webService". Reason was that our security certificate had a wildcard i.e. "*.ourCompany.com". Once I put in the full address the exception went away. Hope this helps.
As some one pointed before, I added the following code (with lambda) just before creating the RestTemplate object, and it works fine. IT is only for my internal testing class, so I will work around with a better solution for the production code.
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, sslSession) -> true);
We faced a similar issue recently "No subject alternative DNS name matching found", it was a nightmare because we were able to reproduce it only in Production servers, were access to debug is near to zero. The rest of environments were just working fine. Our stack was JDK 1.8.x+, JBoss EAP 7+, Java Spring Boot app and Okta as identity provider (the SSL handshake was failing when recovering the well-known configuration from Okta, where okta is available in AWS Cloud - virtual servers).
Finally, we discover that (no one knows why) the JBoss EAP application server that we were using it was having an additional JVM System Property:
jsse.enableSNIExtension = false
This was preventing to establish TLS connection and we were able to reproduce the issue by adding that same system property/value in other environments. So the solution was simple to remove that undesired property and value.
As per Java Security Doc, this property is set by default to true for Java 7+ (refer to https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization)
jsse.enableSNIExtension system property. Server Name Indication (SNI) is a TLS extension, defined in RFC 4366. It enables TLS connections to virtual servers, in which multiple servers for different network names are hosted at a single underlying network address.
Some very old SSL/TLS vendors may not be able handle SSL/TLS extensions. In this case, set this property to false to disable the SNI extension.
Have answered it already in https://stackoverflow.com/a/53491151/1909708.
This fails because neither the certificate common name (CN in certification Subject) nor any of the alternate names (Subject Alternative Name in the certificate) match with the target hostname or IP adress.
For e.g., from a JVM, when trying to connect to an IP address (WW.XX.YY.ZZ) and not the DNS name (https://stackoverflow.com), the HTTPS connection will fail because the certificate stored in the java truststore cacerts expects common name (or certificate alternate name like stackexchange.com or *.stackoverflow.com etc.) to match the target address.
Please check: https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#HostnameVerifier
HttpsURLConnection urlConnection = (HttpsURLConnection) new URL("https://WW.XX.YY.ZZ/api/verify").openConnection();
urlConnection.setSSLSocketFactory(socketFactory());
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setUseCaches(false);
urlConnection.setHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
});
urlConnection.getOutputStream();
Above, passed an implemented HostnameVerifier object which is always returns true:
new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
}
For Spring Boot RestTemplate:
add org.apache.httpcomponents.httpcore dependency
use NoopHostnameVerifier for SSL factory:
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(new URL("file:pathToServerKeyStore"), storePassword)
// .loadKeyMaterial(new URL("file:pathToClientKeyStore"), storePassword, storePassword)
.build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
RestTemplate restTemplate = new RestTemplate(factory);
This code will work like charm and use the restTemple object for rest of the code.
RestTemplate restTemplate = new RestTemplate();
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
#Override
public boolean isTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
restTemplate.setRequestFactory(requestFactory);
}
I also faced the same issue with a self signed certificate . By referring to few of the above solutions , i tried regenerating the certificate with the correct CN i.e the IP Address of the server .But still it didn't work for me .
Finally i tried regenerating the certificate by adding the SAN address to it via the below mentioned command
**keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048 -alias <IP_ADDRESS> -ext san=ip:<IP_ADDRESS>**
After that i started my server and downloaded the client certificates via the below mentioned openssl command
**openssl s_client -showcerts -connect <IP_ADDRESS>:443 < /dev/null | openssl x509 -outform PEM > myCert.pem**
Then i imported this client certificate to the java default keystore file (cacerts) of my client machine by the below mentioned command
**keytool -import -trustcacerts -keystore /home/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/jre/lib/security/cacerts -alias <IP_ADDRESS> -file ./mycert.pem**
I got to this question after if got this same error message. However in my case we had two URL's with different subdomains (http://example1.xxx.com/someservice and http://example2.yyy.com/someservice) which were directed to the same server. This server was having only one wildcard certificate for the *.xxx.com domain. When using the service via the second domain, the found certicate (*.xxx.com) does not match with the requested domain (*.yyy.com) and the error occurs.
In this case we should not try to fix such an errormessage by lowering SSL security, but should check the server and certificates on it.
I was going through 2 way SSL in springboot. I have made all correct configuration service tomcat server and service caller RestTemplate. but I was getting error as "java.security.cert.CertificateException: No subject alternative names present"
After going through solutions, I found, JVM needs this certificate otherwise it gives handshaking error.
Now, how to add this to JVM.
go to jre/lib/security/cacerts file. we need to add our server certificate file to this cacerts file of jvm.
Command to add server cert to cacerts file via command line in windows.
C:\Program Files\Java\jdk1.8.0_191\jre\lib\security>keytool -import -noprompt -trustcacerts -alias sslserver -file E:\spring_cloud_sachin\ssl_keys\sslserver.cer -keystore cacerts -storepass changeit
Check server cert is installed or not:
C:\Program Files\Java\jdk1.8.0_191\jre\lib\security>keytool -list -keystore cacerts
you can see list of certificates installed:
for more details: https://sachin4java.blogspot.com/2019/08/javasecuritycertcertificateexception-no.html
add the host entry with the ip corresponding to the CN in the certificate
CN=someSubdomain.someorganisation.com
now update the ip with the CN name where you are trying to access the url.
It worked for me.
When you have a certificate with both CN and Subject Alternative Names (SAN), if you make your request based on the CN content, then that particular content must also be present under SAN, otherwise it will fail with the error in question.
In my case CN had something, SAN had something else. I had to use SAN URL, and then it worked just fine.
I have resolved the said
MqttException (0) - javax.net.ssl.SSLHandshakeException: No
subjectAltNames on the certificate match
error by adding one (can add multiple) alternative subject name in the server certificate (having CN=example.com) which after prints the part of certificate as below:
Subject Alternative Name:
DNS: example.com
I used KeyExplorer on windows for generating my server certificate.
You can follow this link for adding alternative subject names (follow the only part for adding it).
I was referred to animo3991's answer and tweaked it to make my Bitbucket Backup Client 3.6.0 work for backing up my Bitbucket Server when before it was also hitting No subject alternative names present error.
The first command however must use alias tomcat, otherwise Bitbucket Server would not start up properly:
keytool -genkey -keyalg RSA -sigalg SHA256withRSA -keystore keystore.jks -keysize 2048 -alias tomcat -ext san=ip:<IP_ADDRESS>
openssl s_client -showcerts -connect <IP_ADDRESS>:443 < /dev/null | openssl x509 -outform PEM > myCert.pem
keytool -import -trustcacerts -keystore /etc/pki/ca-trust/extracted/java/cacerts -alias <IP_ADDRESS> -file ./myCert.pem
public class RESTfulClientSSL {
static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
#Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}};
public class NullHostNameVerifier implements HostnameVerifier {
/*
* (non-Javadoc)
*
* #see javax.net.ssl.HostnameVerifier#verify(java.lang.String,
* javax.net.ssl.SSLSession)
*/
#Override
public boolean verify(String arg0, SSLSession arg1) {
// TODO Auto-generated method stub
return true;
}
}
public static void main(String[] args) {
HttpURLConnection connection = null;
try {
HttpsURLConnection.setDefaultHostnameVerifier(new RESTfulwalkthroughCer().new NullHostNameVerifier());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
String uriString = "https://172.20.20.12:9443/rest/hr/exposed/service";
URL url = new URL(uriString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
//connection.setRequestMethod("POST");
BASE64Encoder encoder = new BASE64Encoder();
String username = "admin";
String password = "admin";
String encodedCredential = encoder.encode((username + ":" + password).getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedCredential);
connection.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
StringBuffer stringBuffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
stringBuffer.append(line);
}
String content = stringBuffer.toString();
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
Add your IP address in the hosts file.which is in the folder of C:\Windows\System32\drivers\etc.
Also add IP and Domain Name of the IP address.
example:
aaa.bbb.ccc.ddd abc#def.com

SSL Handshake errors when trying to connect to Apple's push servers via Java.

I've been trying the code from this question within a Eclipse/Maven/Jetty project, and I am getting SSLHandshakeException from within the try{} block near sslSocket.startHandshake(). I've tried it with both my .p12 file loaded into the keyStore as well as my .pem. Neither of them work. Both worked in a PHP prototype I built. Also wasn't clear if the two uses of were of the same password.
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(getClass().getResourceAsStream("cert.p12"), "<password>".toCharArray());
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance("SunX509");
keyMgrFactory.init(keyStore, "<password>".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyMgrFactory.getKeyManagers(), null, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(host, port);
String[] cipherSuites = sslSocket.getSupportedCipherSuites();
sslSocket.setEnabledCipherSuites(cipherSuites);
sslSocket.startHandshake();
}catch()...
I've checked the connection to Apples servers using
openssl s_client -connect gateway.sandbox.push.apple.com:2195
In return I get the following.
CONNECTED(00000003)
depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C
verify error:num=20:unable to get local issuer certificate
verify return:0
18183:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:/SourceCache/OpenSSL098/OpenSSL098-44/src/ssl/s3_pkt.c:1102:SSL alert number 40
18183:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:/SourceCache/OpenSSL098/OpenSSL098-44/src/ssl/s23_lib.c:182:
I'm not really sure what's going on. Any ideas?

Categories