I'm new to setting up certificates, the endpoint requires a valid certificate issued by I.CA. I have a .pfx with the password and can get the data from the endpoint in Postman. However I fail to achieve the same from my local server running on localhost:8080.
I'm using okhttp version 4.7.2
Here is a breakdown of the code I'm using, the loading does not cause any errors and is just for demo code adjusted. I'm having in the keystore 38 providers and only the cert under keystoreSpi that I loaded. Both key and cert are not null and seems to contain the certificate. The endpoint just return a 403 (according to docu means authorization headers ('x-api-key', 'Authorization' or 'x-client-cert') is not valid.) Since I confirmed with Postman that the api key and the token is valid (token flow is working), it can be just the missing cert. But I do not know what else is missing for the request.
KeyStore keystore = KeyStore.getInstance("PKCS12");
try (InputStream is = Files.newInputStream(Paths.get("filepath\\cert.pfx"))) {
keystore.load(is, "password".toCharArray());
} catch (Exception e) {
LOG.error(e.getMessage());
}
String alias = "myAlias";
Key key = keystore.getKey(alias, "password".toCharArray());
Certificate cert = keystore.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
HeldCertificate heldCertificate = new HeldCertificate.Builder()
.keyPair(new KeyPair(publicKey, (PrivateKey) key))
.build();
HandshakeCertificates handshakeCerts = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.heldCertificate(heldCertificate)
.build();
OkHttpClient client = new OkHttpClient().newBuilder().sslSocketFactory(handshakeCerts.sslSocketFactory(), handshakeCerts.trustManager())
.build();
Request request = new Request.Builder()
.url("https://apiendpoint/name")
.method("GET", null)
.addHeader("x-api-key", "Bearer apiKey")
.addHeader("x-correlation-id", "corId")
.addHeader("Authorization", "Bearer " + accessToken.getAccessToken())
.build();
Response response = client.newCall(request).execute();
Edit
I'm getting for response.handshake().peerPrincipal().getName() as CN the name of the endpoint I have to integrate. But response.handshake().localPrincipal().getName() is null. How do is set the localPrincipal to include the certificate that I loaded.
Here is the successful postman call.
To confirm the client authentication is working you should look at the localPrincipal in the handshake.
Call call = client.newCall(new Request.Builder().url(server.url("/")).build());
Response response = call.execute();
assertThat(response.handshake().peerPrincipal()).isEqualTo(
new X500Principal("CN=Local Host"));
assertThat(response.handshake().localPrincipal()).isEqualTo(
new X500Principal("CN=Jethro Willis"));
assertThat(response.body().string()).isEqualTo("abc");
It might be that your client certificate is not being accepted, and falling back allowing the connection but failing the request.
In your request, this header looks troubling, are you sure you provide this as is, which Authorization includes the actual access token?
.addHeader("x-api-key", "Bearer apiKey")
See https://swagger.io/docs/specification/authentication/api-keys/
The actual handshake including client happens at a layer below OkHttp, so if it's a JDK app then you might need to enable debug flags for that. Wireshark or similar can also help you debug if you are really motivated.
Related
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();
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
});
I'm developing a mobile app that involves parsing data from JSON object via Kimono Labs. I am having problem when it comes to SSL and certificates when accessing the APIs at Kimono Labs.
According to Kimono Labs, calling the API requires setting the header "Authorization: Bearer " header at the call.
GET Request
Calling an auth API using a GET request is just like calling any kimono api, but you will additionally need to pass your secure token in the Authentication request header, like so: Authorization: Bearer {YOUR_SECURE_TOKEN}
I'm using the following code to get InputStream
HttpUriRequest request = new HttpGet(apiURL);
request.addHeader("authorization", "Bearer " + securityToken);
HttpClient httpclient = new DefaultHttpClient();
InputStream is = httpclient.execute(request).getEntity().getContent();
Executing this code gives me an exception: javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
Alternatively I tried implementing with the following code:
URL url = new URL(apiURL);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Authorization", "Bearer " + securityToken);
urlConnection.setRequestMethod("GET"); urlConnection.setConnectTimeout(activity.getResources().getInteger(R.integer.internet_timeout)); urlConnection.setReadTimeout(activity.getResources().getInteger(R.integer.internet_timeout));
InputStream inputStream = urlConnection.getInputStream();
With this code I am having the exception javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Any ideas?
I am able to "hack" solve it by allowing all certificates via CustomTrustManager and CustomAllVerifier returning true for all attempts but heard this is totally unsafe and is not recommended for production. Is this true in my case?
Thank you in advance.
I ran into exactly the same problem with a node.js build using kimono as api. The problem is that the intermediate signed cert is not trusted by Mozilla, ergo nodejs throws a leaf error.
I solved it by doing the following, find which cert is being used, download the pem for it and add it to your root CA's before doing the request to kimono, its a hell of a lot safer than disabling TLS/SSL security checks.
Just a heads up! Turned out that this is a temporary problem with kimonolabs' server.
This is my Code:
public class JavaSoapUi {
public static void main(String[] args) throws Exception {
String requestUrl = "myurl";
URLConnection connection =new URL(requestUrl).openConnection();
System.out.println( "orignal url: " + connection.getURL() );
connection.connect();
System.out.println( "connected url: " + connection.getURL() );
HttpPost httpPost = new HttpPost(requestUrl);
String username = "user";
String password = "pass";
String encoded = DatatypeConverter.printBase64Binary((username + ":" + password).getBytes("UTF-8"));
httpPost.addHeader("AUTHORIZATION", "Basic " + encoded);
HttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse response = httpClient.execute(httpPost);
System.out.println("Response" + response.getStatusLine().toString());
HttpEntity entity = response.getEntity();
String responseString = EntityUtils.toString(entity, "UTF-8");
System.out.println("ResponseString" + responseString);
EntityUtils.consume(entity);
}
And I am getting output Error :HTTP/1.1 301 Moved Permanently
If I used the username and password in the SOAP UI By using the Basic Authentication I can then get the output. But when I am using the same username and password in the Java code I am getting the error.
Any one help me.
Your Authoritzation http-header looks good to me due you added the header like is defined in RFC. Furthermore if the problem was with your Authoritzation then your call must receive an Error HTTP 401 Unauthorized instead you're receiving Error HTTP 301 - Moved permanently so probably the error is with your url. Check the location http-header of your 301 error response to see the correct url for your service.
EDIT BASED ON OP COMMENT
As I said you check the location header from 301 error response, and seems that url is an https://, if you change the url, now you're receiving Error: unable to find valid certification path to requested target this is due your client has to trust in a server certificate, to do so, download the server certificate from your url and load it in the trust store of the JRE where you're executing your client with the follow command (the correct way is to add the certificate authority of your server certificate, however to perform a test load directly server certificate it's enough).
keytool -import -alias somealias -file serverCertificate.cer -keystore JRE_HOME/lib/security/cacerts
Note that keytool is inside java distribution in JAVA_HOME/bin/keytool, and default password for JRE_HOME/lib/security/cacerts trust store is changeit.
Hope this helps,
I was a bit envious of our Ruby on Rails team who needs to consume the same web services requiring HTTPS request be signed with a certificate. They grab a gem, sign the request with a single line of code, dishes are done...
Java, however, requires that we import the cert to our keystore (trying to avoid this), or create one in memory...that's fine. But, once I do that, it seems like the only option to actually signing requests is using the HttpUrlConnection class. HttpUrlConnection example
I'd like to be able to sign requests using my existing code, which uses apache's DefaultHttpClient client = new DefaultHttpClient(); - but I don't see a way to have the Http client sign requests using an in memory keystore.
Anyone faced this?
Have a look at AuthSSLProtocolSocketFactory.java. You should be able to perform SSL client authentication like this (untested code);
Protocol.registerProtocol("https",
new Protocol("https", new AuthSSLProtocolSocketFactory(keystoreUrl, keystorePassword, truststoreUrl, truststorePassword), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.whatever.com/");
try {
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine());
} finally {
httpget.releaseConnection();
}
Your client certificate goes into keystore pointed by keystoreUrl. Read more about this on HttpClient SSL Guide.