How do I get/inject the current SSLSession in Jersey server? - java

I have a JAX-RS application (Jersey 2.22) in which I need to get the peer SSL client certificates (the server is enabled for mutual SSL authentication).
What I'm trying to achieve is something in the lines of:
#GET
#Produces(MediaType.TEXT_PLAIN)
public String clientCertificates() {
SSLContext sslContext = SSLContext.getDefault();
SSLSessionContext clientContext = sslContext.getClientSessionContext();
byte[] sessionId = clientContext.getIds().nextElement();
SSLSession clientSession = clientContext.getSession(sessionId);
Certificate[] certificates = clientSession.getPeerCertificates();
return "The client provided " + certificates.length + " certificates.";
}
but this throws a NoSuchElementException at clientContext.getIds().nextElement() even if the client DID provide a valid SSL certificate that the server accepted to establish the session (I'm trying this with Glassfish 4, WildFly 9 and WildFly 10).
Note: The web.xml CLIENT-CERT auth-method is NOT what I need: I need to get hands a reference to the client certificates.

I found this (unchecked) method to get the provided certificates in a ContainerRequestFilter from a ContainerRequestContext, if any:
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
X509Certificate[] certificates = (X509Certificate[]) requestContext.getProperty("javax.servlet.request.X509Certificate");
Principal principal = certificates[0].getSubjectX500Principal();
}
}

Related

Springboot SLL API consume

I am trying to consume an API from a 3rd party server. The 3rd party sent me an SSL certificated named certificate.p12 which is the cert file which I use to do the handshake. I have created a custom RestTemplate with SSL as follows:
#Configuration
public class CustomRestTemplate {
private static final String PASSWORD = "fake_password";
private static final String RESOURCE_PATH = "keystore/certificate.p12";
private static final String KEY_TYPE = "PKCS12";
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
char[] password = PASSWORD.toCharArray();
SSLContext sslContext = SSLContextBuilder
.create()
// .loadKeyMaterial(keyStore(RESOURCE_PATH, password), password)
.loadKeyMaterial(keyStore(getClass().getClassLoader().getResource(RESOURCE_PATH).getFile(), password), password)
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.build();
HttpClient client = HttpClients
.custom()
.setSSLContext(sslContext)
.build();
return builder
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
.build();
}
private KeyStore keyStore(String file, char[] password) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KEY_TYPE);
File key = ResourceUtils.getFile(file);
try (InputStream in = new FileInputStream(key)) {
keyStore.load(in, password);
}
return keyStore;
}
}
I then call the endpoint using the following code:
#Component
#Service
public class TransactionService implements TransactionInterface {
#Autowired
private CustomRestTemplate restTemplate = new CustomRestTemplate();
private static final String BASE_URL = "https://41.x.x.x:xxxx/";
#Override
public List<Transaction> getUnsentTransactions(int connectionId) throws Exception {
HttpEntity<?> httpEntity = new HttpEntity<>(null, new HttpHeaders());
ResponseEntity<Transaction[]> resp = restTemplate
.restTemplate(new RestTemplateBuilder())
.exchange(BASE_URL + "path/end_point/" + connectionId, HttpMethod.GET, httpEntity, Transaction[].class);
return Arrays.asList(resp.getBody());
}
}
I get an the following stacktrace when trying to consume the api:
org.springframework.web.client.ResourceAccessException: I/O error on GET request for \"https://41.x.x.x:xxxx/path/endpoint/parameters\": Certificate for <41.x.x.x> doesn't match any of the subject alternative names: [some_name_here, some_name_here];
I do not have much experience with TLS or SSL certificates. I am really stuck at the moment and hoping I can get some help here. The 3rd party provided me with a testing site where I can test the endpoints and after importing the certificate.p12 file into my browser I can reach the endpoints using their testing site but my Springboot application still does not reach the endpoint.
Do I need to copy the cert into a specific folder? This does not seem like the case because I get a FileNotFoundException if I change the path or filename and I get a password incorrect error if I enter the wrong password for the certificate.p12 file. I tried using Postman to test it but Postman returns the same stacktrace as my web application.
Looking at the information above, am I missing something? Is the keystore not being created during runtime? Do I need to bind the certificate to the JVM or my outgoing request?
Any help would be appreciated.
Thanks
It looks like you are trying to connect to a server which doesn't have a valid name in the certificate. For example, if you are connecting to "stackoverflow.com", the certificate needs that domain in the "subject" or the "subject alternative names" field.
Even a testing site should have a valid certificate, but if that's not possible (as it's a third party site and you can't change it yourself), you can disable the verification using this question
Of course, this should only be done for testing.

Use https to call a webservice

I am currently working on a webservice that uses http! I have been asked to change (to use ) https instead to call this webservice!
I am using eclipse kepler and JBoss EAP6.1
I found in the internet that I have to create a keystore and edit the server.xml file.
The thing is that i can't find the xml file in this JBOss version [ i have a standalone.xml file is it the same ? ]
and for the generation of the keystore where do i have to do it ?
Thank you for you ansewers!
if I am on the wrong way, would you please re-direct me to right path ?
Thanks again !
Get the certificate of the HTTPS url. (You can do it by typing the URL in the browser and then extracting the certificate from the browser certificate installation location). After this add this certificate to the JRE of your application which is used by JBOSS server. Most probably this will be the JRE you have given in the system environment. You can google to get how to install certificate in the keystore. May be this will work.
You're calling a remote webservice via https, right?
Ok, you could import the certificate of the remote service in the keystore (plenty of guides about that, look at this other question for an example)
OR
You can bypass the whole https certificate thing (launch this static method before the remote call):
/**
* Bypassing SSL certificate check
*
* #throws Exception
*/
public static void doTrustToCertificates() throws Exception {
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, 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 '" + session.getPeerHost() + "'.");
}
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
In addition to answer of #Rahul, you can import certificate (.cer) file using following command on command prompt for windows OS :
(Assuming you have set required Java paths)
keytool -importcert -file <path of certificate>\<YourCertificateName>.cer -keystore D:\java\jdk1.7.0_40\jre\lib\security\cacerts -alias <certificateAliasName> -storepass <Password>
usually default <password> is 'changeit'.
In case webservice is used for third party client then you can use HttpClient to interact. I am not sure what kind of operation you are performing with that webservice. I assume you want to send some xml to that URL. You can refer following code :
HttpPost httppost = new HttpPost(url);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
CloseableHttpClient client = HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider).build();
StringEntity entity = null;
try {
entity = new StringEntity(xmlToSend);
} catch (UnsupportedEncodingException e) {
LOG.error("Unsupported Encoding ", e);
}
entity.setContentType("text/xml");
httppost.setEntity(entity);
try{
CloseableHttpResponse response = client.execute(httppost);
returnCode = response.getStatusLine().getStatusCode();
EntityUtils.consume(entity);
LOG.debug("HttpResponse :" + EntityUtils.toString(response.getEntity()));
}catch(IOException e){
LOG.error("Error occured while sending the xml");
}

How to bypass Https SSLHandshakeException with Tomee

I am trying to invoke a WS over SSL, from a tomee 1.6 server, but I get a SSLHandshakeError. The problem is that the certificate is self signed, and is not recognized by my JVM. As it is only for test purpose, and not production, I have been asked to bypass the certificate control.
I read a lot of stuff about how to proceed, and I have written that code :
a class NaiveSSLContext :
package fr.csf.ssl;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* A factory class which creates an {#link SSLContext} that
* naively accepts all certificates without verification.
*/
public class NaiveSSLContext
{
private NaiveSSLContext()
{}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance( String protocol) throws NoSuchAlgorithmException
{
SSLContext sslCtx = SSLContext.getInstance( protocol);
init( sslCtx);
return sslCtx;
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance( String protocol, Provider provider) throws NoSuchAlgorithmException
{
SSLContext sslCtx = SSLContext.getInstance( protocol, provider);
init( sslCtx);
return sslCtx;
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance( String protocol, String provider) throws NoSuchAlgorithmException, NoSuchProviderException
{
SSLContext sslCtx = SSLContext.getInstance( protocol, provider);
init( sslCtx);
return sslCtx;
}
/**
* Set NaiveTrustManager to the given context.
*/
private static void init( SSLContext context)
{
try
{
// Set NaiveTrustManager.
context.init( null, new TrustManager[] { new NaiveTrustManager() }, new java.security.SecureRandom());
System.out.println( "------------- Initialisation du NaiveSSLContext ---------------------");
}
catch( java.security.KeyManagementException e)
{
throw new RuntimeException( "Failed to initialize an SSLContext.", e);
}
}
/**
* A {#link TrustManager} which trusts all certificates naively.
*/
private static class NaiveTrustManager implements X509TrustManager
{
#Override
public X509Certificate[] getAcceptedIssuers()
{
System.out.println( "------------- NaiveTrustManager.getAcceptedIssuers() ---------------------");
return null;
}
#Override
public void checkClientTrusted( X509Certificate[] certs, String authType)
{
System.out.println( "------------- NaiveTrustManager.checkClientTrusted( " + certs.toString() + ", " + authType
+ ") ---------------------");
}
#Override
public void checkServerTrusted( X509Certificate[] certs, String authType)
{
System.out.println( "------------- NaiveTrustManager.checkServerTrusted( " + certs.toString() + ", " + authType
+ ") ---------------------");
}
}
}
and another class NaiveSSLSocketFactory :
package fr.csf.ssl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
public class NaiveSSLSocketFactory extends javax.net.ssl.SSLSocketFactory
{
private javax.net.ssl.SSLSocketFactory factory;
public NaiveSSLSocketFactory() throws NoSuchAlgorithmException
{
javax.net.ssl.SSLContext sslCtx = NaiveSSLContext.getInstance( "SSL");
factory = sslCtx.getSocketFactory();
}
private final String[] enabledProtocols = new String[]
{ "SSLv3", "TLSv1" };
#Override
public Socket createSocket( Socket s, String host, int port, boolean autoClose) throws IOException
{
Socket socket = factory.createSocket( s, host, port, autoClose);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( String host, int port) throws IOException, UnknownHostException
{
Socket socket = factory.createSocket( host, port);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( InetAddress host, int port) throws IOException
{
Socket socket = factory.createSocket( host, port);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException
{
Socket socket = factory.createSocket( host, port, localHost, localPort);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
{
Socket socket = factory.createSocket( address, port, localAddress, localPort);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public String[] getDefaultCipherSuites()
{
String[] cipherSuites = factory.getDefaultCipherSuites();
return cipherSuites;
}
#Override
public String[] getSupportedCipherSuites()
{
String[] cipherSuites = factory.getSupportedCipherSuites();
return cipherSuites;
}
}
The problem is that I can't find out how to make the JVM use my Naive* classes instead of the default ones. I have tried different methods, but neither of them work :
First try :
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory( new NaiveSSLSocketFactory());
My log traces in the checkClientTruted methods are never displayed. It seems that my NaiveSSLSocketFactory is never called.
2nd try :
java.security.Security.setProperty( "ssl.SocketFactory.provider", new NaiveSSLSocketFactory().getClass().getName());
I experienced a ClassNotFoundException due to a ClassLoader problem, but after this problem was fixed, the same problem remains.
I eventually found a blog where it was said that CXF client had to do a little more configuration stuff :
<http-conf:conduit name="*.http-conduit" >
<http-conf:tlsClientParameters
useHttpsURLConnectionDefaultSslSocketFactory="true"
/>
</http-conf:conduit>
As I use a Tomee1.6 server, my program is a CXF client. So that must be the solution. But where do I have to write this configuration properties ? I can't find any xml file in Tomee, related with CXF. There's only e cxf.properties file, which is nearly empty.
First, Tomcat isn't involved with your consumption of a web service - in fact it's really not involved with with any outbound connections your application is making.
I know of two ways to achieve your desired results provided by CXF in a way that won't affect any other outbound SSL connections running on the same JVM:
add the self-signed certificate to the CXF client's conduit trust
store, or
install a "do-nothing" trust manager to to the CXF
client's TLS parameters
The first method is preferable as the second will trust any endpoint your client connects with.
To implement the first method, create a key store containing the certificate you wish to trust (and for good measure, include any intermediary certificates). Then add this trust store as outlined in CXF handbook section Configuring SSL Support. Your conduit configuration will look something like this:
<http:conduit name="{http://apache.org/hello_world}HelloWorld.http-conduit">
<http:tlsClientParameters>
<sec:trustManagers>
<sec:keyStore type="JKS" password="password"
file="my/file/dir/Truststore.jks"/>
</sec:trustManagers>
</http:tlsClientParameters>
<http:client AutoRedirect="true" Connection="Keep-Alive"/>
</http:conduit>
Note that the conduit name in the example above is obviously just an example. See the update to my answer here regarding another question as how to specify the conduit name. Also note that I did not include a cipher suite filter as I believe it will default to some set of values, which is potentially unsafe if you're using Java 6 or older .. but that's a whole other topic.
Also, you can eschew Spring configuration of CXF entirely and do all of the above programmatically using CXF client APIs.
I also highly suggest using a tool like KeyStore Explorer to extract certificate (and intermediaries) from the target endpoint and import them into your new trust store.
Finally, I would like to point out, in reference to your initial solution, the danger of using JVM-wide installation of things like SSL socket factories and trust managers as supported by the JDK API. There is a possibility of perilous consequences of doing so when running inside of a container supporting multiple applications: you can subvert the security profile of other applications. One of the benefits of using a framework like CXF is that it provides means to customize SSL/TLS configurations for each application client (or server) instance.

Getting images from https with Java

Is there a way to get images from a https url with Java?
What I am trying so far:
URL url = new URL("https://ns6.host.md:8443/sitepreview/http/zugo.md/media/images/thumb/23812__yu400x250.jpg");
System.out.println("Image: " + ImageIO.read(url));
But, I am getting:
Exception in thread "main" javax.imageio.IIOException: Can't get input stream from URL!
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No
Caused by: java.security.cert.CertificateException: No name matching ns6.host.md found
How can I go through that? I have more than 6k images on that url that I have to fetch.
There are two problems. You can use your browser to access the site, and see the errors.
The server certificate is self-signed, not trusted by Java. you can add it to the trust store.
The server certificate does not match the host name "ns6.host.md", and you need a HostnameVerifier that ignores it.
The other answer says the same thing, and it provides code, which unfortunately uses some private APIs.
Example how to solve it in bayou HttpClient, if anyone is interested:
https://gist.github.com/zhong-j-yu/22af353e2c5a5aed5857
public static void main(String[] args) throws Exception
{
HttpClient client = new HttpClientConf()
.sslContext(new SslConf().trustAll().createContext()) // trust self-signed certs
.sslEngineConf(engine -> disableHostNameVerification(engine))
.trafficDump(System.out::print)
.newClient();
// typically, app creates one client and use it for all requests
String url = "https://ns6.host.md:8443/sitepreview/http/zugo.md/media/images/thumb/23812__yu400x250.jpg";
HttpResponse response = client.doGet(url).sync();
ByteBuffer bb = response.bodyBytes(Integer.MAX_VALUE).sync();
InputStream is = new ByteArrayInputStream(bb.array(), bb.arrayOffset()+bb.position(), bb.remaining());
BufferedImage image = ImageIO.read(is);
}
static void disableHostNameVerification(SSLEngine engine)
{
SSLParameters sslParameters = engine.getSSLParameters();
{
// by default, it's set to "HTTPS", and the server certificate must match the request host.
// disable it for this example, since the server certificate is ill constructed.
sslParameters.setEndpointIdentificationAlgorithm(null);
}
engine.setSSLParameters(sslParameters);
}
You have a SSL problem which you have to solve first, before you fetch the Image. It says that didn't find any trusted host with the name ns6.host.md in your trusted store in JAVA_HOME/jre/lib/security. You could add to your TrustStore the public key of that host or just ignore SSL errors if that's the case:
HttpsURLConnection.setDefaultHostnameVerifier(getUnsecureHostNameVerifier());
try {
SSLContext e = SSLContext.getInstance("TLS");
e.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(e);
HttpsURLConnection.setDefaultSSLSocketFactory(e.getSocketFactory());
} catch (Exception var1) {
throw new Exception("SSL Error", var1);
}
public static class DefaultTrustManager implements X509TrustManager {
public DefaultTrustManager() {
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}

Java SOAP service over SSL with client certificates

So I've been trying to setup a Java SOAP servlet with JAX-WS and SSL. I got the actual service running over SSL, but I need it to authenticate based on the client certificate, and right now it's accepting all connections against it.
Here's my code so far:
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
System.out.println("yay1");
}
public void checkServerTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
System.out.println("yay2");
}
public X509Certificate[] getAcceptedIssuers() {
throw new UnsupportedOperationException("Not supported yet.");
}
};
String uri = "http://127.0.0.1:8083/SoapContext/SoapPort";
Object implementor = new Main();
Endpoint endpoint = Endpoint.create(implementor);
SSLContext ssl = SSLContext.getInstance("TLS");
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore store = KeyStore.getInstance("JKS");
store.load(new FileInputStream("serverkeystore"),"123456".toCharArray());
keyFactory.init(store, "123456".toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(store);
ssl.init(keyFactory.getKeyManagers(),
new TrustManager[] { tm }, null);
HttpsConfigurator configurator = new HttpsConfigurator(ssl);
HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress(8083), 8083);
httpsServer.setHttpsConfigurator(configurator);
HttpContext httpContext = httpsServer.createContext("/SoapContext/SoapPort");
httpsServer.start();
endpoint.publish(httpContext);
And I'm testing it with this PHP code:
$soapClient = new SoapClient("https://localhost:8083/SoapContext/SoapPort?wsdl", array('local_cert' => "newcert.pem"));
$soapClient->add(array('i' => '1', 'j' => '2'));
Unfortunately, it errors out with this when I include the local_cert:
SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://localhost:8083/SoapContext/SoapPort?wsdl' : failed to load external entity "https://localhost:8083/SoapContext/SoapPort?wsdl"
It does connect successfully if I don't include local_cert, but it never calls my custom TrustManager, so it accepts all incoming connections.
What am I doing wrong? Thanks!
I do not know PHP but your TrustManager in the web service is not doing any authentication.
So the connection should not be rejected due to client authentication issues.
Does the new_cert.pem you are referencing in your client, contain the private key?
If not then this could be the problem.
I suggest you take a wireshark and see the communication.
I suspect you will not see a rejection coming from your server.
The failure should be local on your client

Categories