SOAP WebServiceTemplate call with self-signed certificate - SunCertPathBuilderException - java

The API I'm connecting to in the test environment requires a specific certificate from the client. The certificate is self-signed, so I get this error:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
I received a certificate from the vendor in .pfx, and then I created a keystore with
keytool -importkeystore -srckeystore cert_dev.pfx -srcstoretype pkcs12 -destkeystore clientcert.jks -deststoretype JKS
WebServiceTemplate configuration
#Bean
public WebServiceTemplate webServiceTemplate() throws Exception {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setMarshallerProperties(Map.of(JAXB_FORMATTED_OUTPUT, true));
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setDefaultUri(properties.getBaseUrl());
webServiceTemplate.setMessageSender(sender());
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
return webServiceTemplate;
}
#Bean
public HttpsUrlConnectionMessageSender sender() throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(
properties.getAuthentication().getKeyStore().getInputStream(),
properties.getAuthentication().getKeyStorePassword().toCharArray()
);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(ks, properties.getAuthentication().getKeyStorePassword().toCharArray());
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(
properties.getAuthentication().getTrustStore().getInputStream(),
properties.getAuthentication().getTrustStorePassword().toCharArray()
);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(ts);
HttpsUrlConnectionMessageSender messageSender = new HttpsUrlConnectionMessageSender();
messageSender.setKeyManagers(keyManagerFactory.getKeyManagers());
messageSender.setTrustManagers(trustManagerFactory.getTrustManagers());
messageSender.setHostnameVerifier((hostname, sslSession) -> {
return true;
});
return messageSender;
}
and I receive
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
What is wrong with the configuration?
SoapUI handles the requests perfectly.

Related

Calling RestTemplate APIs using custom Certificate and Key

I am calling an external API via RestTemplate that requires SSL Certificate. Already the external API provider has provided me with the certificate file (company.crt) and its key file (company.key). While adding the certificate, key and password in Postman, I am able to successfully call the API. But when I am calling using RestTemplate inside a SpringBoot project, even after adding SSLContext, I am receiving an error.
The steps I have followed:
Created a company.p12 store file from company.crt and company.key file using openssl:
openssl pkcs12 -export -name servercert -in company.crt -inkey company.key -out company.p12
Converted company.p12 to company.jks store file using Keytool:
keytool -importkeystore -destkeystore company.jks -srckeystore company.p12 -srcstoretype pkcs12 -alias companycert
Have created the config inside application.properties file as, after placing company.jks inside resource folder of SpringBoot project:
http.client.ssl.trust-store=classpath:company.jks
http.client.ssl.trust-store-password=Password
Then, I have created a configuration for RestTemplate as:
#Value("${http.client.ssl.trust-store}")
private Resource keyStore;
#Value("${http.client.ssl.trust-store-password}")
private String keyStorePassword;
#Bean
RestTemplate restTemplate() throws Exception {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(
keyStore.getURL(),
keyStorePassword.toCharArray()
).build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
Calling an external APIs as:
#Autowired
RestTemplate restTemplate;
#GetMapping("/api")
public Object callAPI() {
final String ENDPOINT = "https://some-api.domain.com:8533/api/key/";
Object response = restTemplate.exchange(ENDPOINT, HttpMethod.GET, request, Object.class);
return response;
}
The error after calling an API via RestTemplate:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Got it resolved:
application.properties configuration: Add Key store properties
http.client.ssl.trust-store=classpath:company.jks
http.client.ssl.trust-store-password=Password
http.client.ssl.key-store=classpath:company.p12
http.client.ssl.key-store-password=Password
Then, inside RestTemplate configuration:
#Bean
RestTemplate restTemplate() throws Exception {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(
trustStore.getURL(),
trustStorePassword.toCharArray(), acceptingTrustStrategy
)
.loadKeyMaterial(
keyStore.getURL(),
keyStorePassword.toCharArray(),
keyStorePassword.toCharArray())
.build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}

Authentication between netflix-zuul and microservice with certificates

I have configured in my application netflix-zuul to call other services and it is necessary to include in the application a certificate.
I am trying to do the same as in this question (using netflix-zuul with a certificate) but I am having problems..
For example, in this link it indicates that a .keystore is used but the files that I have are a .cer and a .key and I don't see how to apply a certificate with the files that I have.
Could you help me to connect the certificate I have with the netflix-zuul proxy of my application?
Thank you very much to all!
EDIT -> I have tried the solution of this link, my code:
#Bean
public CloseableHttpClient httpClient() throws Throwable {
InputStream is = new FileInputStream(KEYSTOREPATH);
//KEYSTOREPATH = String KEYSTOREPATH = "E:/ARC/myapp/src/main/java/com/myapp/services/myapp/myapp.cer";
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return HttpClients.custom().setSSLContext(sslContext).build();
}
And when executing the path configured in netflix-zuul I get this error:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1460)
... 105 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
... 111 common frames omitted
You can try building the SSLContext and trust the certificate from the .cer file as mentioned/described here :
Using SSLContext with just a CA certificate and no keystore
Javier, I was able to build the SSLContext as below. Are you still having the issue.
#Test
public void sslContext() {
Class demoClass = Demo.class;
InputStream is = demoClass.getResourceAsStream("/test.cert");
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
} catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException | KeyManagementException e) {
e.printStackTrace();
}
}

Android Two Way SSL Authentication With Volley

I am trying to make a two way SSL authentication to a server.
The server has SSL implemented to I need to make a HTTPS connection to the server. The server is with a third party vendor so i don't have server access. Although they have given me a server certificate test.crt.
Then I created my own self signed certificate by following the following commands.
generating keystore
keytool -genkey -alias myssl -keystore /home/user/mykey.keystore -validity 365
generating cer file
keytool -export -alias myssl -keystore /home/user/mykey.keystore -file /home/user/mykey.cer
import the cer file into the bks file
keytool -import -alias test_cer -file /home/user/mykey.cer -keystore /home/user/mykey.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /home/user/bcprov-jdk15on-146.jar
Now I have 3 files : mykey.keystore, mykey.cer and mykey.bks.
So i gave the .cer file to the server vendors and they imported the certificate into their truststore.
Now, in my android application I try to connect to the server. I put the certificate from the server test.crt into the trust store and my mykey.cer into the key store and pass the trust manager and the key manager into the SSLContext.
Below is the relevant code.
allowTrustedSSL(this.activity);
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(method,url,dataToSend, onResponseListener,onErrorListener);
/**
* Method implements self-signed certificates
* #param context
*/
public static void allowTrustedSSL(Context context){
/**
* We shall accept traffic with any host names
*/
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
Logger.d(TAG, "verify() called with: hostname = [" + hostname + "], session = [" + session + "]");
return true;
}
});
try{
//the NoSSLv3SocketFactory will disable the SSLv3
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslSocketFactoryGenerator(context));
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);
} catch (Exception e) {
Logger.e(TAG, "allowTrustedSSL: "+e.getMessage());
e.printStackTrace();
}
}
public static SSLSocketFactory sslSocketFactoryGenerator(Context context) throws
UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, IOException, KeyManagementException {
//the test.crt file
TrustManagerFactory trustManagerFactory = getTrustManagerFactory(context.getResources().openRawResource(R.raw.test));
//the mykey.cer file
KeyManagerFactory keyManagerFactory = getKeyManagerFactory(context.getResources().openRawResource(R.raw.mykey),
MYKEY_PASS.toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),null);
return sslContext.getSocketFactory();
}
public static TrustManagerFactory getTrustManagerFactory(InputStream fileInput) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca;
try {
ca = cf.generateCertificate(fileInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
fileInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
return tmf;
}
public static KeyManagerFactory getKeyManagerFactory(InputStream fileInput,char[] password)
throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca;
try {
ca = cf.generateCertificate(fileInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
fileInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String keyManAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManAlgorithm);
keyManagerFactory.init(keyStore, password);
return keyManagerFactory;
}
But when i start the connection to the server all I am getting is SSLException: Connection closed by peer. I have googled a lot for a proper documentation or example for implementing the two way ssl from android. And I have lost many precious days on figuring it out on myself.
Is my process of creating the certificates incorrect?

SSLHandshakeException: ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:

i try to request a local service with https without certificate check. But i got this execption.
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
here is a part of code:
try {
RestTemplate restTemplate = new RestTemplate();
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> hostname.equals("IPADDRESS"));
responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
} catch (HttpClientErrorException e) {
LOGGER.error(e.toString());
}
what is wrong here?
This problem is due to incomplete trust path for the server certificate: the server certificate is probably not trusted by the client.
Usually the fix is to import the server certificate into the client trust store. The default trustStore is in jre/lib/security/cacerts but is is a better practice to use your own keystore
You can create an SSLSocketFactory and add to your connection before connecting or apply to all connections using the static method
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
This is an example to create the socket factory
/* Load the keyStore that includes the server 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();
Example of loading the keyStore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();
The trust store can also be configured using system properties
System.setProperty("javax.net.ssl.trustStore", "pathtoyourjavakeystorefile");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
The simplest way to create the key store file is using the GUI tool Portecle. New KeyStore > Import Trusted certificates
You can import the root certificate of the chain if you want to 'trust' all certificates from root, or import only the server certificate. For a self-signed certificate, import it directly
Hy,
i resolved it with this code part:
private void disableCertificateVerification() {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException {
}
#Override
public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
}
} };
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> hostname.equals("IPADDRESS"));
} catch (NoSuchAlgorithmException | KeyManagementException e) {
LOGGER.error(e.toString());
}
}
and i called this function before i created the RestTemplate.

Reading Client Certificate in Servlet

I have a Client Server Communication scenario in JBOSS and browser as client(JAVA PROGRAM). Initially when the connection is made, Client sends its Certificate to Server. Server extracts the public key of client from certificate and thus communication will continue.
Now my question is
How to send certificate(.cer) from Client to Server?
How to receive the certificate and extract its public key in Server?
How to send certificate(.cer) from Client to Server?
Client certificate (.cer, .crt, .pem) and it's corresponding private key (.key) should be packaged into PKCS#12 (.p12, .pfx) or JKS (.jks) container first (keystore). You also should have server's CA certicate packaged as JKS (truststore).
Example using HttpClient 3.x:
HttpClient client = new HttpClient();
// truststore
KeyStore trustStore = KeyStore.getInstance("JKS", "SUN");
trustStore.load(TestSupertype.class.getResourceAsStream("/client-truststore.jks"), "amber%".toCharArray());
String alg = KeyManagerFactory.getDefaultAlgorithm();
TrustManagerFactory fac = TrustManagerFactory.getInstance(alg);
fac.init(trustStore);
// keystore
KeyStore keystore = KeyStore.getInstance("PKCS12", "SunJSSE");
keystore.load(X509Test.class.getResourceAsStream("/etomcat_client.p12"), "etomcat".toCharArray());
String keyAlg = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyFac = KeyManagerFactory.getInstance(keyAlg);
keyFac.init(keystore, "etomcat".toCharArray());
// context
SSLContext ctx = SSLContext.getInstance("TLS", "SunJSSE");
ctx.init(keyFac.getKeyManagers(), fac.getTrustManagers(), new SecureRandom());
SslContextedSecureProtocolSocketFactory secureProtocolSocketFactory = new SslContextedSecureProtocolSocketFactory(ctx);
Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) secureProtocolSocketFactory, 8443));
// test get
HttpMethod get = new GetMethod("https://127.0.0.1:8443/etomcat_x509");
client.executeMethod(get);
// get response body and do what you need with it
byte[] responseBody = get.getResponseBody();
You may find working example in this project see X509Test class.
With HttpClient 4.x configuration and syntax would be slightly different:
HttpClient httpclient = new DefaultHttpClient();
// truststore
KeyStore ts = KeyStore.getInstance("JKS", "SUN");
ts.load(PostService.class.getResourceAsStream("/truststore.jks"), "amber%".toCharArray());
// if you remove me, you've got 'javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated' on missing truststore
if(0 == ts.size()) throw new IOException("Error loading truststore");
// tmf
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
// keystore
KeyStore ks = KeyStore.getInstance("PKCS12", "SunJSSE");
ks.load(PostService.class.getResourceAsStream("/" + certName), certPwd.toCharArray());
// if you remove me, you've got 'javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated' on missing keystore
if(0 == ks.size()) throw new IOException("Error loading keystore");
// kmf
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, certPwd.toCharArray());
// SSL
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// socket
SSLSocketFactory socketFactory = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme sch = new Scheme("https", 8443, socketFactory);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);
// request
HttpMethod get = new GetMethod("https://localhost:8443/foo");
client.executeMethod(get);
IOUtils.copy(get.getResponseBodyAsStream(), System.out);
How to receive the certificate and extract its public key in Server?
You server must be configurated to require X.509 client certificate authentication. Then during SSL handshake servlet container will recieve certificate, check it against trustore and provide it to application as a request attribute.
In usual case with single certificate you could use this method in servlet environment to extract certificate:
protected X509Certificate extractCertificate(HttpServletRequest req) {
X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
if (null != certs && certs.length > 0) {
return certs[0];
}
throw new RuntimeException("No X.509 client certificate found in request");
}

Categories