Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Optionally bypass certificate validation #189

Merged
merged 1 commit into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,26 @@

package org.jpos.http.client;

import static org.jpos.util.Logger.log;

import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.security.cert.CertificateExpiredException;

import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
Expand All @@ -33,6 +48,8 @@
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;

Expand All @@ -51,6 +68,7 @@
import org.jpos.transaction.AbortParticipant;
import org.jpos.util.Destroyable;
import org.jpos.util.Log;
import org.jpos.util.LogEvent;
import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.transaction.Context;
Expand Down Expand Up @@ -79,13 +97,16 @@ public class HttpQuery extends Log implements AbortParticipant, Configurable, De
private String responseName;
private String statusName;
private String contentTypeName;
private String trustAllCertsName;
private String basicAuthenticationName;
private RedirectStrategy redirectStrategy;
private boolean ignoreNullRequest;

// A shared client for the instance.
// Shared clients for the instance.
// Created at configuration time; destroyed when this participant is destroyed.
private CloseableHttpAsyncClient httpClient = null;
// This client will be used when certificate validation bypassing is needed
private CloseableHttpAsyncClient unsecureHttpClient = null;

public HttpQuery () {
super();
Expand Down Expand Up @@ -129,7 +150,8 @@ public int prepare (long id, Serializable o) {
}
}

getHttpClient().execute(httpRequest, httpCtx, new FutureCallback<HttpResponse>() {
boolean trustAllCerts = ctx.getString(trustAllCertsName, "false").equals("true");
getHttpClient(trustAllCerts).execute(httpRequest, httpCtx, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse result) {
ctx.log (result.getStatusLine());
Expand Down Expand Up @@ -197,6 +219,8 @@ public void setConfiguration (Configuration cfg) throws ConfigurationException {
preemptiveAuth = cfg.getBoolean("preemptiveAuth", preemptiveAuth);
basicAuthenticationName = cfg.get("basicAuthenticationName", ".HTTP_BASIC_AUTHENTICATION");

trustAllCertsName = cfg.get("trustAllCerts", "HTTP_TRUST_ALL_CERTS");

// ctx name under which extra http headers could exist at runtime
// the object could be a List<String> or String[] (in the "name:value" format)
// or a Map<String,String>
Expand All @@ -223,20 +247,62 @@ else if ("lax".equals(redirProp))
throw new ConfigurationException("'redirect-strategy' must be 'lax' or 'default'");
}

public CloseableHttpAsyncClient getHttpClient() {
public CloseableHttpAsyncClient getHttpClient(boolean trustAllCerts) {
if (httpClient == null) {
setHttpClient(getClientBuilder().build());
setHttpClient(getClientBuilder(false).build());
httpClient.start();
}
return httpClient;
if (unsecureHttpClient == null) {
setUnsecureHttpClient(getClientBuilder(true).build());
unsecureHttpClient.start();
}
return trustAllCerts ? unsecureHttpClient: httpClient;
}

public void setHttpClient(CloseableHttpAsyncClient httpClient) {
this.httpClient = httpClient;
}

protected HttpAsyncClientBuilder getClientBuilder() {
return HttpAsyncClients.custom().useSystemProperties().setRedirectStrategy(redirectStrategy);
public void setUnsecureHttpClient(CloseableHttpAsyncClient httpClient) {
this.unsecureHttpClient = httpClient;
}

protected HttpAsyncClientBuilder getClientBuilder(boolean trustAllCerts) {
HttpAsyncClientBuilder builder = HttpAsyncClients.custom().useSystemProperties()
.setRedirectStrategy(redirectStrategy);
if (trustAllCerts) {
disableSSLVerification(builder);
}
return builder;
}

private HttpAsyncClientBuilder disableSSLVerification(HttpAsyncClientBuilder builder) {
TrustManager[] wrappedTrustManagers = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(X509Certificate[] certs, String authType) {
log(new LogEvent(certs.toString() + " " + authType));
}
}
};

SSLContext sc;
try {
sc = SSLContext.getInstance("TLS");
sc.init(null, wrappedTrustManagers, new SecureRandom());
return builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).setSSLContext(sc);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
LogEvent evt = new LogEvent(this, "warn");
evt.addMessage(e);
log(evt);
return builder;
}
}

private String getURL (Context ctx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.jpos.q2.Q2;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionManager;
import org.jpos.transaction.TransactionStatusEvent;
import org.jpos.transaction.TransactionStatusListener;
import org.jpos.util.NameRegistrar;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -124,4 +126,70 @@ public void testBasicAuthBadPasswd() {
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_UNAUTHORIZED), sc, "Status code should be 401");
}

@Test
public void testTrustRevokedCertificate() {
Context ctx = new Context();
ctx.put("HTTP_URL", "https://revoked.badssl.com/");
ctx.put("HTTP_METHOD", "GET");
ctx.put("HTTP_TRUST_ALL_CERTS", "true");
mgr.queue(ctx);
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_OK), sc, "Status code should be 200");
}

@Test
public void testTrustPinnedKeyNotInCertificate() {
Context ctx = new Context();
ctx.put("HTTP_URL", "https://pinning-test.badssl.com/");
ctx.put("HTTP_METHOD", "GET");
ctx.put("HTTP_TRUST_ALL_CERTS", "true");
mgr.queue(ctx);
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_OK), sc, "Status code should be 200");
}

@Test
public void testTrustSelfSignedCertificate() {
Context ctx = new Context();
ctx.put("HTTP_URL", "https://self-signed.badssl.com/");
ctx.put("HTTP_METHOD", "GET");
ctx.put("HTTP_TRUST_ALL_CERTS", "true");
mgr.queue(ctx);
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_OK), sc, "Status code should be 200");
}

@Test
public void testTrustUntrustedRootCertificate() {
Context ctx = new Context();
ctx.put("HTTP_URL", "https://untrusted-root.badssl.com/");
ctx.put("HTTP_METHOD", "GET");
ctx.put("HTTP_TRUST_ALL_CERTS", "true");
mgr.queue(ctx);
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_OK), sc, "Status code should be 200");
}

@Test
public void testTrustExpiredCertificate() {
Context ctx = new Context();
ctx.put("HTTP_URL", "https://expired.badssl.com/");
ctx.put("HTTP_METHOD", "GET");
ctx.put("HTTP_TRUST_ALL_CERTS", "true");
mgr.queue(ctx);
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_OK), sc, "Status code should be 200");
}

@Test
public void testTrustWrongHostCertificate() {
Context ctx = new Context();
ctx.put("HTTP_URL", "https://wrong.host.badssl.com/");
ctx.put("HTTP_METHOD", "GET");
ctx.put("HTTP_TRUST_ALL_CERTS", "true");
mgr.queue(ctx);
Integer sc = ctx.get ("HTTP_STATUS", 10000L);
assertEquals (Integer.valueOf(HttpStatus.SC_OK), sc, "Status code should be 200");
}
}