Hi Colleagues,
With a help of TestNG test case, we are hosting CAMEL JETTY HTTPS service in
one camel context and invoking HTTPS call using CAMEL-HTTP4. This works if I
used the standard approach of providing
'org.apache.camel.util.jsse.KeyStoreParameters.KeyStoreParameters()' by giving
Server Keystore (for JETTY) and Client Trust Store (for HTTP4).
Use-case : Check the X509 Trust Certificate validation based on first the
default JVM Trust Manager and if that fails, then fall back to another
custom-truststore (containing X509 certificates).
Exception: java.security.InvalidAlgorithmParameterException: the trustAnchors
parameter must be non-empty
Complete code below (generate simple key-pair using keytool and extracted 'cer'
file into another JKS file to create client trust store)
Exception Stack Track after the code. If in MyTrustedManagersParameters class
below, we swap the commented line (return), it WORKS, but that doesn't solve
our use-case of merging two different TrustManagers.
Regards,
Arpit.
*********************************************************
TEST CASE
*********************************************************
public class TestHttpsX509Handshake {
private static final String PASSPHRASE = "changeit";
private static final String DIRECT_START_X509 = "direct:start-x509";
private static CamelContext jettyServerContext;
private static CamelContext http4ClientContext;
private static URL SERVER_KEYSTORE =
TestHttpsX509Handshake.class.getResource("server-keystore.jks");
private static URL CLIENT_TRUSTSTORE =
TestHttpsX509Handshake.class.getResource("client-truststore.jks");
private static int HTTPS_PORT;
private static final Logger LOG = LogManager.getLogger();
static {
System.setProperty("javax.net.debug", "all");
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation",
Boolean.TRUE.toString());
System.setProperty("sun.security.ssl.allowLegacyHelloMessages",
Boolean.TRUE.toString());
}
@BeforeClass
public static void beforeClass() throws Exception {
try {
HTTPS_PORT = AvailablePortFinder.getNextAvailable(12100);
jettyServerContext = new DefaultCamelContext();
setJettyServerKeyStoreParameters();
jettyServerContext.addRoutes(createServerRouteBuilder());
jettyServerContext.setTracing(true);
jettyServerContext.start();
http4ClientContext = new DefaultCamelContext();
setHttp4ClientTrustStoreParameters();
http4ClientContext.addRoutes(createClientRouteBuilder());
http4ClientContext.setTracing(true);
http4ClientContext.start();
} catch (Exception e) {
if (jettyServerContext != null) {
jettyServerContext.stop();
}
if (http4ClientContext != null) {
http4ClientContext.stop();
}
throw e;
}
}
@AfterClass
public static void afterClass() throws Exception {
jettyServerContext.stop();
http4ClientContext.stop();
}
@Test
public void testX509Communication() throws Exception {
ProducerTemplate template = http4ClientContext.createProducerTemplate();
DefaultExchange exchange = (DefaultExchange)
template.request(DIRECT_START_X509, new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody("HELLO WORLD");
}
});
if (exchange.getException() != null) {
Assert.fail("Unable to establish communication with server",
exchange.getException());
}
}
private static void setJettyServerKeyStoreParameters() {
KeyStoreParameters ksp = new KeyStoreParameters();
ksp.setResource(SERVER_KEYSTORE.getFile());
ksp.setPassword(PASSPHRASE);
KeyManagersParameters kmp = new KeyManagersParameters();
kmp.setKeyStore(ksp);
kmp.setKeyPassword(PASSPHRASE);
SSLContextParameters scp = new SSLContextParameters();
scp.setKeyManagers(kmp);
JettyHttpComponent9 jettyComponent =
jettyServerContext.getComponent("jetty", JettyHttpComponent9.class);
jettyComponent.setSslContextParameters(scp);
}
private static void setHttp4ClientTrustStoreParameters() throws Exception {
KeyStoreParameters ksp = new KeyStoreParameters();
ksp.setResource(CLIENT_TRUSTSTORE.getFile());
ksp.setPassword(PASSPHRASE);
TrustManagersParameters tmp = new
MyTrustedManagersParameters(CLIENT_TRUSTSTORE.getFile(), PASSPHRASE);
tmp.setKeyStore(ksp);
SSLContextParameters scp = new SSLContextParameters();
scp.setTrustManagers(tmp);
HttpComponent http4Component = http4ClientContext.getComponent("https4",
HttpComponent.class);
http4Component.setSslContextParameters(scp);
}
private static RouteBuilder createServerRouteBuilder() {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("jetty:https://localhost:" + HTTPS_PORT + "/api/v1").process(new
Processor() {
@Override
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
Assert.assertNotNull(body);
Assert.assertEquals(body, "HELLO WORLD");
}
});
}
};
}
private static RouteBuilder createClientRouteBuilder() {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from(DIRECT_START_X509).to("https4://localhost:" + HTTPS_PORT +
"/api/v1");
}
};
}
}
********************************************************************************************************
EXTENDED CAMEL org.apache.camel.util.jsse.TrustManagersParameters
********************************************************************************************************
public class MyTrustedManagersParameters extends TrustManagersParameters {
private String trustStorePath;
private String trustStorePassphrase;
public MyTrustedManagersParameters(String trustStorePath, String
trustStorePassphrase) {
this.trustStorePath = trustStorePath;
this.trustStorePassphrase = trustStorePassphrase;
}
public TrustManager[] createTrustManagers() throws GeneralSecurityException,
IOException {
CustomX509TrustManager customX509TrustManager = null;
KeyStoreParameters keyStoreParams = this.getKeyStore();
if(keyStoreParams != null &&
!StringUtils.isEmpty(keyStoreParams.getResource())) {
String existingTrustStore = keyStoreParams.getResource();
try(InputStream is =
TestHttpsX509Handshake.class.getResourceAsStream(existingTrustStore)) {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(is, keyStoreParams.getPassword().toCharArray());
customX509TrustManager = new CustomX509TrustManager(ks,
this.trustStorePath, this.trustStorePassphrase);
}
}
return new TrustManager[]{customX509TrustManager};
// return super.createTrustManagers();
}
}
********************************************************************************************************
CUSTOM X509 Trust manager implementation
********************************************************************************************************
/**
* Using Default X509 Trust Manager and Customer X509 Trust manager
*
* Based on -
* http://stackoverflow.com/questions/19005318/implementing-x509trustmanager-
* passing-on-part-of-the-verification-to-existing/19005844#19005844
*
*/
public class CustomX509TrustManager implements X509TrustManager {
private static final String JAVA_TRUST_STORE_PWD_SYS_PROP =
"javax.net.ssl.trustStorePassword";
private static final String JAVA_TRUST_STORE_SYS_PROP =
"javax.net.ssl.trustStore";
private X509TrustManager defaultTrustManager;
private String myTrustStorePath;
private String myTrustStorePassphrase;
private KeyStore myTrustStore;
static {
System.setProperty("javax.net.ssl.keyStore",
TestHttpsX509Handshake.class.getResource("server-keystore.jks").getFile());
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
}
public CustomX509TrustManager(KeyStore myTrustStore, String myTrustStorePath,
String myTrustStorePassphrase) throws GeneralSecurityException {
this.myTrustStore = myTrustStore;
this.myTrustStorePassphrase = myTrustStorePassphrase;
this.myTrustStorePath = myTrustStorePath;
fetchDefaultX509TrustManager();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// If you're planning to use client-cert auth,
// do the same as checking the server.
this.defaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
this.defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ex) {
String defaultTrustStorePath =
System.getProperty(JAVA_TRUST_STORE_SYS_PROP);
String defaltTrustStorePassphrase =
System.getProperty(JAVA_TRUST_STORE_PWD_SYS_PROP);
if (defaultTrustStorePath == null) {
System.setProperty(JAVA_TRUST_STORE_SYS_PROP, this.myTrustStorePath);
System.setProperty(JAVA_TRUST_STORE_PWD_SYS_PROP,
this.myTrustStorePassphrase);
}
try {
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(this.myTrustStore);
for (TrustManager mgr : trustManagerFactory.getTrustManagers()) {
if (mgr instanceof X509TrustManager) {
((X509TrustManager) mgr).checkServerTrusted(chain, authType);
}
}
} catch (KeyStoreException | NoSuchAlgorithmException e) {
throw new CertificateException("Unable to fetch X509 Trust Manager", e);
} finally {
if (defaultTrustStorePath == null) {
System.clearProperty(JAVA_TRUST_STORE_SYS_PROP);
} else {
System.setProperty(JAVA_TRUST_STORE_SYS_PROP, defaultTrustStorePath);
}
if (defaltTrustStorePassphrase == null) {
System.clearProperty(JAVA_TRUST_STORE_PWD_SYS_PROP);
} else {
System.setProperty(JAVA_TRUST_STORE_PWD_SYS_PROP,
defaltTrustStorePassphrase);
}
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// If you're planning to use client-cert auth,
// merge results from "defaultTm" and "myTm".
return this.defaultTrustManager.getAcceptedIssuers();
}
private void fetchDefaultX509TrustManager() throws GeneralSecurityException {
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
for (TrustManager mgr : trustManagerFactory.getTrustManagers()) {
if (mgr instanceof X509TrustManager) {
this.defaultTrustManager = (X509TrustManager) mgr;
}
}
}
}
********************************************************************************************************
Exception Stacktrace
********************************************************************************************************
Caused by: java.lang.RuntimeException: Unexpected error:
java.security.InvalidAlgorithmParameterException: the trustAnchors parameter
must be non-empty
at
sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:90)
at
sun.security.validator.Validator.getInstance(Validator.java:179)
at
sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:314)
at
sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:173)
at
sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:186)
at
sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:107)
at
com.successfactors.integrationframework.http.support.CustomX509TrustManager.checkServerTrusted(CustomX509TrustManager.java:62)
at
sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:897)
at
sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1454)
at
sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:213)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:913)
at
sun.security.ssl.Handshaker.process_record(Handshaker.java:849)
at
sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1035)
at
sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1344)
at
sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
... 59 more
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors
parameter must be non-empty
at
java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
at
java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
at
java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
at
sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:88)
... 73 more