SSLContext for java web application - java

In my desktop java application there is a class that connects with other system through HTTPS connection using a client certificate. Part of this connection is the SSLContext which receives the KeyManager[] and the TrustManager[] in order to implement the user certificate and the trsut store to get the handshack with the remote server. This class uses the Windows repository to get the list of certificates available on the machine so the user can pick the right one to connect. The problem is that I'm migrating this application to a cloud web server (tomcat) and the procedute to get the certificate is quite different. With tomcat I'm able to forward the user to a https page that requests a valid certificate issued by a CA. Once the user access this page, the browser pops up a window with the certificates available on the machine, so the user can pick one authenticate. My problem now is to create this SSLContext once, from the browser authentication, I can get only the x509 cert selected by the user, but without the Private Key. My question is. Am I missing something to get the certificate Private Key? I know that windows repository does not share the private key, but when this procedure is called from a desktop application, at least the "resume or header" (RSAPrivateKey[size = 2048 bits, type = Exchange, container = {########}) of the key is provided, which still works. But through the browser, I cannot get this information. Or is there another way to create the KeyManager[] with just the x509 certificate without provide the private key?
here is a piece of the code which creates the connection with the server..
// create the connection
SocketFactoryDinamico socketFactory = new SocketFactoryDinamico(X509certificate, PrivateKey);
socketFactory.setFileCacerts(getClass().getResourceAsStream("cacerts"));
KeyManager[] keyManagers = socketFactory.createKeyManagers();
TrustManager[] trustManagers = socketFactory.createTrustManagers();
SSLContext sslc = SSLContext.getInstance("TLS");
sslc.init(keyManagers, trustManagers, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory());
String url = "https://someserver.com";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
and here is the code which gets the x509 certificate on the .jsp...
X509Certificate[] certs = (X509Certificate[])
request.getAttribute("javax.servlet.request.X509Certificate");
if (null != certs && certs.length > 0) {
X509Certificate cert = certs[0];
}
and here is the server configuration to request the certificate authentication
<Connector
clientAuth="true"
port="8443"
protocol="HTTP/1.1"
SSLEnabled="true"
scheme="https"
secure="true"
keystoreFile="C:/JavaWeb/tomcat"
keystoreType="JKS" keystorePass="pswd"
truststoreFile="C:/JavaWeb/myTrustStore"
truststoreType="JKS" truststorePass="changeit"
SSLVerifyCLient="require" SSLVerifyDepth="10" sslProtocol="TLS"
/>
I´ve tried to make this connection from the client side with javascript, however I get this error:
Access to XMLHttpRequest at 'https://remoteserver.com' from origin 'http://localhost' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Here is my code:
var httpRequest = new XMLHttpRequest();
httpRequest.open("POST", "https://remoteserver.com");
httpRequest.setRequestHeader("Role-Type", "role");
httpRequest.onreadystatechange = function () {
if (this.readyState === 4) {
if (httpRequest.status === 200) {
console.log('Status:', this.status);
console.log('Headers:', this.getAllResponseHeaders());
console.log('Body:', this.responseText);
console.log(httpRequest);
} else {
console.log("Erro");
console.log(httpRequest);
}
}
};
httpRequest.send();

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.

How to use trusted certificate with nginx and tomcat?

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.

Java8 Secure WebSocket Handshake Problems (Tyrus and Jetty)

I'm attempting to implement a WebSocket Client in an application that supports secure transmissions through SSL. The application already supports standard SSL connections over HTTP, by implementing custom Key and Trust managers (these custom implementations are in place to prompt the user for a certificate when needed).
I'm having trouble getting a secure connection to our remote WebSocket endpoint. The failure is occurring during the handshake. I've tried two different implementations of the WebSocket API (both Tyrus and Jetty), and both fail in the same way, which, of course, leads me to point to our SSL implementation.
As I mentioned, the failure is occurring during the handshake. It seems that the connection cannot figure out that there are client certificates that are signed by the supported authorities returned from the server. I'm stumped to figure out if I haven't supplied the client certificates to the WebSocket API correctly, or if our custom Key/Trust managers are even getting used.
Here's a dump of the SSL Debug logs:
*** CertificateRequest
Cert Types: RSA, DSS
Cert Authorities:
(list of about 15 cert authorities supported by the server)
*** ServerHelloDone
Warning: no suitable certificate found - continuing without client authentication
*** CertificateChain
<empty>
***
I've set breakpoints in our TrustManager implementation, to determine if they are ever getting called, and it seems that they are not being called at this point.
I've been attempting to debug this for a few days now, and am running out of things to try.
Any suggestions would be greatly appreciated!
Here's a snippet of the Jetty Code:
SSLContext context = SSLContext.getInstance("TLS");
// getKeyManagers / getTrustManagers retrieves an
// array containing the custom key and trust manager
// instances:
KeyManager[] km = getKeyManagers();
TrustManager[] tm = getTrustManagers();
context.init(km, tm, null);
SslContextFactory contextFactory = new SslContextFactory();
contextFactory.setContext(context);
WebSocketClient client = new WebSocketClient(contextFactory);
SimpleEchoClient echoClient = new SimpleEchoClient();
try {
client.start();
ClientUpgradeRequest request = new ClientUpgradeRequest();
Future<Session> connection = client.connect(echoClient, uri, request);
Session session = connection.get();
// if everything works, do stuff here
session.close();
client.destroy();
} catch (Exception e) {
LOG.error(e);
}
can you try with rejectUnAuthorized:false so that your certificates for which your browser is unable to authorize will skip the authorization.
var ws = new WebSocket('wss://localhost:xxxx', {
protocolVersion: 8,
origin: 'https://localhost:xxxx',
rejectUnauthorized: false
});

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 !
}

Retrieve client cert in Servlet when using mutual authentication?

I am building a web application using Java and Tomcat 7.0.
I have a self-signed certificate (in the future I'll get an official one) on the server side, and I've added a client's root certificate to its truststore. I've already set a required two-way authentication for https protocol on port 3443 with the following lines on the server.xml file:
<Connector port="3443" scheme="https" secure="true" SSLEnabled="true"
truststoreFile="server.keystore" truststorePass="keystore password"
keystoreFile="server.keystore" keystorePass="keystore password"
clientAuth="true" keyAlias="serverkey"
sslProtocol="TLS"/>
This is working and I can only access the system with a valid certificate.
I was now wondering how I can get a property of this used certificate on my Servlet to log the user in based on his certificate. All certificates used in this context will have a different CN so I want to use that to identify the user.
You will need to import java.security.cert.X509Certificate and . In your doGet(...) method, use the following:
String cn = null;
X509Certificate[] certs = (X509Certificate[]) req
.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null) {
String dn = certs[0].getSubjectX500Principal().getName();
// parse the CN out from the DN (distinguished name)
Pattern p = Pattern.compile("(^|,)CN=([^,]*)(,|$)");
Matcher matcher = p.matcher(dn);
if(matcher.find()) {
cn = matcher.group(2);
}
} else {
// no certificate provided
}

Categories