Java Grizzly server and Jersey client Received fatal alert: certificate_unknown - java

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.

Related

How to connect 'spring boot application (2.3.4.RELEASE) with elasticsearch 7.9.2' using HTTPS

I'm new to spring-boot & Elasticsearch technology stack and I want to establish secure HTTPS connection
between my spring-boot app & elastic search server which runs locally.
These are the configurations that I have done in elasticsearch.yml
Giving credintials for elasticsearch server
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
For secure inter nodes connection inside elasticsearch cluster
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
For secure Https connection with clients and elasticsearch clustrer
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: elastic-certificates.p12
xpack.security.http.ssl.truststore.path: elastic-certificates.p12
xpack.security.http.ssl.client_authentication: optional
Enabling PKI authentication
xpack.security.authc.realms.pki.pki1.order: 1
I have generated CA and client certificate which signed by generated CA according to this link
https://www.elastic.co/blog/elasticsearch-security-configure-tls-ssl-pki-authentication
And I have added CA to my java keystore.
This is the java code i'm using to establish connectivity with elasticsearch server.
#Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(RestClientConfig.class);
private static final String CERT_FILE = "client.p12";
private static final String CERT_PASSWORD = "";
private static final String USER_NAME = "elastic";
private static final String USER_PASS = "pwd";
#Override
#Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200") // set the address of the Elasticsearch cluster
.usingSsl(createSSLContext()) // use the SSLContext with the client cert
.withBasicAuth(USER_NAME, USER_PASS) // use the headers for authentication
.build();
return RestClients.create(clientConfiguration).rest();
}
private SSLContext createSSLContext() {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyManager[] keyManagers = getKeyManagers();
sslContext.init(keyManagers, null, null);
return sslContext;
} catch (Exception e) {
LOG.error("cannot create SSLContext", e);
}
return null;
}
private KeyManager[] getKeyManagers()
throws KeyStoreException, NoSuchAlgorithmException, IOException, CertificateException, UnrecoverableKeyException {
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(CERT_FILE)) {
KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
clientKeyStore.load(inputStream, CERT_PASSWORD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(clientKeyStore, CERT_PASSWORD.toCharArray());
return kmf.getKeyManagers();
}
}
And my client certificate called "client.p12" included in resources folder in the spring-boot application. In elasticsearch side everything seems to be fine.
But when I run the spring-boot app it gives this warning & exception
Cannot create index: Host name 'localhost' does not match the certificate subject provided by the peer (CN=instance); nested exception is java.lang.RuntimeException:
Host name 'localhost' does not match the certificate subject provided by the peer (CN=instance)
java.io.IOException: Host name 'localhost' does not match the certificate subject provided by the peer (CN=instance)
Caused by: javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer (CN=instance)
I'm doing this because I have an idea to migrate elastic search server to a another VPS hosting later.
this is the command I used for generate client certificate
bin/elasticsearch-certutil cert --ca
config/elastic-stack-ca.p12
-name "CN=something,OU=Consulting Team,DC=mydomain,DC=com"
ENTER
client.p12 ENTER
ENTER
searching this error in internet I figured out there is a issue with my client certificate. It has something to do with SAN names with elastic nodes. But I have very little knowledge
about these certificate stuff. So it's really confusion for me. If Some one can give me in-detailed explanation why this is occurring & solution to this I'm really appreciating it & grateful. A Proper link will also be helpful. This question
already asked in
How to connect 'spring boot 2.1 with elasticsearch 6.6' with cluster node 'https'?
link
But no luck for me.
Your issue looks similar to another issue, see here: Certificate for <localhost> doesn't match any of the subject alternative names
So I would assume that if you add the SAN extension localhost as DNS and the ip address of localhost to the elasticsearch certificate it should work. So adding the following additional parameters: --dns localhost --ip 127.0. 0.1. Can you give the command below a try and share your results here?
bin/elasticsearch-certutil cert --ca config/elastic-stack-ca.p12 -name "CN=something,OU=Consulting Team,DC=mydomain,DC=com" --dns localhost --ip 127.0. 0.1
By the way you have configured Elasticsearch to optionally request the client certificate, but not marked as required see: xpack.security.http.ssl.client_authentication: optional. So it is not required and therefor it does not make sense to create a KeyManager and construct a sslcontext from it. But I think you need to export the public certificate of the certificate of Elasticsearch and supply that to your http client by transforming it to a TrustManager and creating a sslcontext from it. I have a working example here: ElasticSearch RestHighLevelClient SSL example.

SSLHandshakeException while posting to https site [duplicate]

I connected with VPN to setup the inventory API to get product list and it works fine. Once I get the result from the web-service and i bind to UI. And also I integrated PayPal with my application for make Express checkout when I make a call for payment I'm facing this error. I use servlet for back-end process. Can any one say how to fix this issue?
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
First, you need to obtain the public certificate from the server you're trying to connect to. That can be done in a variety of ways, such as contacting the server admin and asking for it, using OpenSSL to download it, or, since this appears to be an HTTP server, connecting to it with any browser, viewing the page's security info, and saving a copy of the certificate. (Google should be able to tell you exactly what to do for your specific browser.)
Now that you have the certificate saved in a file, you need to add it to your JVM's trust store. At $JAVA_HOME/jre/lib/security/ for JREs or $JAVA_HOME/lib/security for JDKs, there's a file named cacerts, which comes with Java and contains the public certificates of the well-known Certifying Authorities. To import the new cert, run keytool as a user who has permission to write to cacerts:
keytool -import -file <the cert file> -alias <some meaningful name> -keystore <path to cacerts file>
It will most likely ask you for a password. The default password as shipped with Java is changeit. Almost nobody changes it. After you complete these relatively simple steps, you'll be communicating securely and with the assurance that you're talking to the right server and only the right server (as long as they don't lose their private key).
Whenever we are trying to connect to URL,
if server at the other site is running on https protocol and is mandating that we should communicate via information provided in certificate then
we have following option:
1) ask for the certificate(download the certificate), import this certificate in trustore. Default trustore java uses can be found in \Java\jdk1.6.0_29\jre\lib\security\cacerts, then if we retry to connect to the URL connection would be accepted.
2) In normal business cases, we might be connecting to internal URLS in organizations and we know that they are correct.
In such cases, you trust that it is the correct URL, In such cases above, code can be used which will not mandate to store the certificate to connect to particular URL.
for the point no 2 we have to follow below steps :
1) write below method which sets HostnameVerifier for HttpsURLConnection which returns true for all cases meaning we are trusting the trustStore.
// trusting all certificate
public void doTrustToCertificates() throws Exception {
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
return;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
return;
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
if (!urlHostName.equalsIgnoreCase(session.getPeerHost())) {
System.out.println("Warning: URL host '" + urlHostName + "' is different to SSLSession host '" + session.getPeerHost() + "'.");
}
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
2) write below method, which calls doTrustToCertificates before trying to connect to URL
// connecting to URL
public void connectToUrl(){
doTrustToCertificates();//
URL url = new URL("https://www.example.com");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
System.out.println("ResponseCode ="+conn.getResponseCode());
}
This call will return response code = 200 means connection is successful.
For more detail and sample example you can refer to URL.
I believe that you are trying to connect to a something using SSL but that something is providing a certificate which is not verified by root certification authorities such as verisign.. In essence by default secure connections can only be established if the person trying to connect knows the counterparties keys or some other verndor such as verisign can step in and say that the public key being provided is indeed right..
ALL OS's trust a handful of certification authorities and smaller certificate issuers need to be certified by one of the large certifiers making a chain of certifiers if you get what I mean...
Anyways coming back to the point.. I had a similiar problem when programming a java applet and a java server ( Hopefully some day I will write a complete blogpost about how I got all the security to work :) )
In essence what I had to do was to extract the public keys from the server and store it in a keystore inside my applet and when I connected to the server I used this key store to create a trust factory and that trust factory to create the ssl connection. There are alterante procedures as well such as adding the key to the JVM's trusted host and modifying the default trust store on start up..
I did this around two months back and dont have source code on me right now.. use google and you should be able to solve this problem. If you cant message me back and I can provide you the relevent source code for the project .. Dont know if this solves your problem since you havent provided the code which causes these exceptions. Furthermore I was working wiht applets thought I cant see why it wont work on Serverlets...
P.S I cant get source code before the weekend since external SSH is disabled in my office :(
SSLHandshakeException can be resolved 2 ways.
Incorporating SSL
Get the SSL (by asking the source system administrator, can also
be downloaded by openssl command, or any browsers downloads the
certificates)
Add the certificate into truststore (cacerts) located at
JRE/lib/security
provide the truststore location in vm arguments as
"-Djavax.net.ssl.trustStore="
Ignoring SSL
For this #2, please visit my other answer on another stackoverflow website:
How to ingore SSL verification Ignore SSL Certificate Errors with Java
This is what I did to POST a JSON to a URL with insecure/invalid SSL certs using latest JDK 11 HttpClient:
// 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(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
try {
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Creat HttpClient with new SSLContext.
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(3 * 1000))
.sslContext(sc) // SSL context 'sc' initialised as earlier
.build();
// Create request.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(<URL>))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(<PAYLOAD JSON STRING>))
.build();
//Send Request
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
//Access response JSON
String responseJson = response.body();
} catch (Exception e) {
throw new Exception(e);
}

SSL connection using a not-certified certificate - how to import programmatically [duplicate]

A module I'm adding to our large Java application has to converse with another company's SSL-secured website. The problem is that the site uses a self-signed certificate. I have a copy of the certificate to verify that I'm not encountering a man-in-the-middle attack, and I need to incorporate this certificate into our code in such a way that the connection to the server will be successful.
Here's the basic code:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
Without any additional handling in place for the self-signed certificate, this dies at conn.getOutputStream() with the following exception:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Ideally, my code needs to teach Java to accept this one self-signed certificate, for this one spot in the application, and nowhere else.
I know that I can import the certificate into the JRE's certificate authority store, and that will allow Java to accept it. That's not an approach I want to take if I can help; it seems very invasive to do on all of our customer's machines for one module they may not use; it would affect all other Java applications using the same JRE, and I don't like that even though the odds of any other Java application ever accessing this site are nil. It's also not a trivial operation: on UNIX I have to obtain access rights to modify the JRE in this way.
I've also seen that I can create a TrustManager instance that does some custom checking. It looks like I might even be able to create a TrustManager that delegates to the real TrustManager in all instances except this one certificate. But it looks like that TrustManager gets installed globally, and I presume would affect all other connections from our application, and that doesn't smell quite right to me, either.
What is the preferred, standard, or best way to set up a Java application to accept a self-signed certificate? Can I accomplish all of the goals I have in mind above, or am I going to have to compromise? Is there an option involving files and directories and configuration settings, and little-to-no code?
Create an SSLSocket factory yourself, and set it on the HttpsURLConnection before connecting.
...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...
You'll want to create one SSLSocketFactory and keep it around. Here's a sketch of how to initialize it:
/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ...
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();
If you need help creating the key store, please comment.
Here's an example of loading the key store:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();
To create the key store with a PEM format certificate, you can write your own code using CertificateFactory, or just import it with keytool from the JDK (keytool won't work for a "key entry", but is just fine for a "trusted entry").
keytool -import -file selfsigned.pem -alias server -keystore server.jks
I read through LOTS of places online to solve this thing.
This is the code I wrote to make it work:
ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
app.certificateString is a String that contains the Certificate, for example:
static public String certificateString=
"-----BEGIN CERTIFICATE-----\n" +
"MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
"BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
... a bunch of characters...
"5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
"-----END CERTIFICATE-----";
I have tested that you can put any characters in the certificate string, if it is self signed, as long as you keep the exact structure above. I obtained the certificate string with my laptop's Terminal command line.
If creating a SSLSocketFactory is not an option, just import the key into the JVM
Retrieve the public key:
$openssl s_client -connect dev-server:443, then create a file dev-server.pem that looks like
-----BEGIN CERTIFICATE-----
lklkkkllklklklklllkllklkl
lklkkkllklklklklllkllklkl
lklkkkllklk....
-----END CERTIFICATE-----
Import the key: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem.
Password: changeit
Restart JVM
Source: How to solve javax.net.ssl.SSLHandshakeException?
We copy the JRE's truststore and add our custom certificates to that truststore, then tell the application to use the custom truststore with a system property. This way we leave the default JRE truststore alone.
The downside is that when you update the JRE you don't get its new truststore automatically merged with your custom one.
You could maybe handle this scenario by having an installer or startup routine that verifies the truststore/jdk and checks for a mismatch or automatically updates the truststore. I don't know what happens if you update the truststore while the application is running.
This solution isn't 100% elegant or foolproof but it's simple, works, and requires no code.
I've had to do something like this when using commons-httpclient to access an internal https server with a self-signed certificate. Yes, our solution was to create a custom TrustManager that simply passed everything (logging a debug message).
This comes down to having our own SSLSocketFactory that creates SSL sockets from our local SSLContext, which is set up to have only our local TrustManager associated with it. You don't need to go near a keystore/certstore at all.
So this is in our LocalSSLSocketFactory:
static {
try {
SSL_CONTEXT = SSLContext.getInstance("SSL");
SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
} catch (KeyManagementException e) {
throw new RuntimeException("Unable to initialise SSL context", e);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });
return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}
Along with other methods implementing SecureProtocolSocketFactory. LocalSSLTrustManager is the aforementioned dummy trust manager implementation.

How to solve javax.net.ssl.SSLHandshakeException Error?

I connected with VPN to setup the inventory API to get product list and it works fine. Once I get the result from the web-service and i bind to UI. And also I integrated PayPal with my application for make Express checkout when I make a call for payment I'm facing this error. I use servlet for back-end process. Can any one say how to fix this issue?
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
First, you need to obtain the public certificate from the server you're trying to connect to. That can be done in a variety of ways, such as contacting the server admin and asking for it, using OpenSSL to download it, or, since this appears to be an HTTP server, connecting to it with any browser, viewing the page's security info, and saving a copy of the certificate. (Google should be able to tell you exactly what to do for your specific browser.)
Now that you have the certificate saved in a file, you need to add it to your JVM's trust store. At $JAVA_HOME/jre/lib/security/ for JREs or $JAVA_HOME/lib/security for JDKs, there's a file named cacerts, which comes with Java and contains the public certificates of the well-known Certifying Authorities. To import the new cert, run keytool as a user who has permission to write to cacerts:
keytool -import -file <the cert file> -alias <some meaningful name> -keystore <path to cacerts file>
It will most likely ask you for a password. The default password as shipped with Java is changeit. Almost nobody changes it. After you complete these relatively simple steps, you'll be communicating securely and with the assurance that you're talking to the right server and only the right server (as long as they don't lose their private key).
Whenever we are trying to connect to URL,
if server at the other site is running on https protocol and is mandating that we should communicate via information provided in certificate then
we have following option:
1) ask for the certificate(download the certificate), import this certificate in trustore. Default trustore java uses can be found in \Java\jdk1.6.0_29\jre\lib\security\cacerts, then if we retry to connect to the URL connection would be accepted.
2) In normal business cases, we might be connecting to internal URLS in organizations and we know that they are correct.
In such cases, you trust that it is the correct URL, In such cases above, code can be used which will not mandate to store the certificate to connect to particular URL.
for the point no 2 we have to follow below steps :
1) write below method which sets HostnameVerifier for HttpsURLConnection which returns true for all cases meaning we are trusting the trustStore.
// trusting all certificate
public void doTrustToCertificates() throws Exception {
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
return;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
return;
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
if (!urlHostName.equalsIgnoreCase(session.getPeerHost())) {
System.out.println("Warning: URL host '" + urlHostName + "' is different to SSLSession host '" + session.getPeerHost() + "'.");
}
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
2) write below method, which calls doTrustToCertificates before trying to connect to URL
// connecting to URL
public void connectToUrl(){
doTrustToCertificates();//
URL url = new URL("https://www.example.com");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
System.out.println("ResponseCode ="+conn.getResponseCode());
}
This call will return response code = 200 means connection is successful.
For more detail and sample example you can refer to URL.
I believe that you are trying to connect to a something using SSL but that something is providing a certificate which is not verified by root certification authorities such as verisign.. In essence by default secure connections can only be established if the person trying to connect knows the counterparties keys or some other verndor such as verisign can step in and say that the public key being provided is indeed right..
ALL OS's trust a handful of certification authorities and smaller certificate issuers need to be certified by one of the large certifiers making a chain of certifiers if you get what I mean...
Anyways coming back to the point.. I had a similiar problem when programming a java applet and a java server ( Hopefully some day I will write a complete blogpost about how I got all the security to work :) )
In essence what I had to do was to extract the public keys from the server and store it in a keystore inside my applet and when I connected to the server I used this key store to create a trust factory and that trust factory to create the ssl connection. There are alterante procedures as well such as adding the key to the JVM's trusted host and modifying the default trust store on start up..
I did this around two months back and dont have source code on me right now.. use google and you should be able to solve this problem. If you cant message me back and I can provide you the relevent source code for the project .. Dont know if this solves your problem since you havent provided the code which causes these exceptions. Furthermore I was working wiht applets thought I cant see why it wont work on Serverlets...
P.S I cant get source code before the weekend since external SSH is disabled in my office :(
SSLHandshakeException can be resolved 2 ways.
Incorporating SSL
Get the SSL (by asking the source system administrator, can also
be downloaded by openssl command, or any browsers downloads the
certificates)
Add the certificate into truststore (cacerts) located at
JRE/lib/security
provide the truststore location in vm arguments as
"-Djavax.net.ssl.trustStore="
Ignoring SSL
For this #2, please visit my other answer on another stackoverflow website:
How to ingore SSL verification Ignore SSL Certificate Errors with Java
This is what I did to POST a JSON to a URL with insecure/invalid SSL certs using latest JDK 11 HttpClient:
// 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(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
try {
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Creat HttpClient with new SSLContext.
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(3 * 1000))
.sslContext(sc) // SSL context 'sc' initialised as earlier
.build();
// Create request.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(<URL>))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(<PAYLOAD JSON STRING>))
.build();
//Send Request
HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
//Access response JSON
String responseJson = response.body();
} catch (Exception e) {
throw new Exception(e);
}

How do I use a local HTTPS URL in java?

Using the Java URL class, I can connect to an external HTTPS server (such as our production site), but using a local URL I get following exception.
"SunCertPathBuilderException: unable to find valid certification path to requested target".
How do I get a valid certification path?
EDIT: I'm not using this URL to directly create a connection, I am passing the URL to an itext PDFReader, which is then having the connection issue.
Here was my solution that incorporates some of the ideas in this thread and peiced together with code from around the net.
All I do call this function and it sets the default Trust Manager and HostName Verifier for HttpsURLConnection. This might be undesirable for some because it will effect all HttpsURLConnections but I'm just writing a simple proxy so it worked for me.
private void setTrustAllCerts() throws Exception
{
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType ) { }
public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType ) { }
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance( "SSL" );
sc.init( null, trustAllCerts, new java.security.SecureRandom() );
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(
new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
});
}
catch ( Exception e ) {
//We can not recover from this exception.
e.printStackTrace();
}
}
You probably need to setup a HostnameVerifier. Before connecting, you need add it to the connection object
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
// check hostname/session
return true;
}
});
conn.connect();
// read/write...
There certainly are some implementations out there, if you need one. You might want to look at HttpClient project too.
Another thing to look at is the TrustManager you are using. The error message suggests that the certificate presented by the server is not signed by a trusted root. Since you don't have direct control over the SSL socket that is created, I think your best bet is to initialize your own SSLContext with a TrustManager that's been setup with the root CA of the server's certificate chain. Then set this context as the default.
This is assuming you are using Java 6. The API is more limited in Java 5. You can get a default SSLSocketFactory, but there's no standard way to set it.
The problem it's complaining about is that when you create an SSL connection, the server must present a valid certificate to the client. You can write an appropriate endpoint in Java (HTTPServerSocket will do it I think) but it would require some hacking about to set it up. It's probably easier to set up a local web server with anything that handles SSL correctly --- Apache, lighttp, whatever --- and create a self-signed cert using the openssl tools.
Updated
Here's an example from the Java Almanac. http://www.exampledepot.com/egs/javax.net.ssl/Server.html
An SSL server socket requires certificates that it will send to clients for authentication. The certificates must be contained in a keystore whose location must be explicitly specified (there is no default). Following the example we describe how to create and specify a keystore for the SSL server socket to use.
try {
int port = 443;
ServerSocketFactory ssocketFactory = SSLServerSocketFactory.getDefault();
ServerSocket ssocket = ssocketFactory.createServerSocket(port);
// Listen for connections
Socket socket = ssocket.accept();
// Create streams to securely send and receive data to the client
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// Read from in and write to out...
// Close the socket
in.close();
out.close();
} catch(IOException e) {
}
Specify the keystore of certificates using the javax.net.ssl.keyStore system property:
> java -Djavax.net.ssl.keyStore=mySrvKeystore -Djavax.net.ssl.keyStorePassword=123456 MyServer
It may also help you to add the certificate that the localhost server is using (I assume it's self-signed) to the JVM's keystore, using the "keytool" utility. This should have the effect of telling the JVM "you can trust this certificate".
I have ended up running a static method (only on dev) that installs a very trusting TrustManager (accepts everything), and also added a hostnameVerifier that always returns true (thanks sblundy).

Categories