Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Handle certificates mentioned in HTTP connection during local entry deployment. #2243

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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 @@ -21,16 +21,34 @@

import org.apache.axiom.om.OMElement;
import org.apache.axis2.deployment.DeploymentException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.io.FileUtils;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.config.Entry;
import org.apache.synapse.config.xml.EntryFactory;
import org.apache.synapse.config.xml.EntrySerializer;
import org.apache.synapse.config.xml.MultiXMLConfigurationBuilder;
import org.apache.synapse.transport.dynamicconfigurations.KeyStoreReloaderHolder;
import org.apache.synapse.transport.nhttp.config.SslSenderTrustStoreHolder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Iterator;
import java.util.Properties;

import javax.xml.namespace.QName;

/**
* Handles the <code>LocalEntry</code> deployment and undeployment tasks
*
Expand All @@ -39,6 +57,10 @@
public class LocalEntryDeployer extends AbstractSynapseArtifactDeployer {

private static Log log = LogFactory.getLog(LocalEntryDeployer.class);
private static final String RESOURCES_IDENTIFIER = "resources:";
private static final String CONVERTED_RESOURCES_IDENTIFIER = "gov:mi-resources" + File.separator;
private static final String HTTP_CONNECTION_IDENTIFIER = "http.init";
private static final String CERTIFICATE_EXTENSION = ".crt";

@Override
public String deploySynapseArtifact(OMElement artifactConfig, String fileName,
Expand Down Expand Up @@ -66,6 +88,7 @@ public String deploySynapseArtifact(OMElement artifactConfig, String fileName,
}
log.info("LocalEntry named '" + e.getKey()
+ "' has been deployed from file : " + fileName);
handleHttpConnectorCertificates(artifactConfig);
return e.getKey();
} else {
handleSynapseArtifactDeploymentError("LocalEntry Deployment Failed. The artifact " +
Expand All @@ -79,6 +102,66 @@ public String deploySynapseArtifact(OMElement artifactConfig, String fileName,
return null;
}

private void handleHttpConnectorCertificates(OMElement element) throws DeploymentException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn;t have to be specific to http connector right? If so shall we change the method name?


OMElement httpInitElement =
element.getFirstChildWithName(new QName(SynapseConstants.SYNAPSE_NAMESPACE, HTTP_CONNECTION_IDENTIFIER));
if (httpInitElement != null) {
Iterator childElementIterator = httpInitElement.getChildElements();
while (childElementIterator.hasNext()) {
OMElement childElement = (OMElement) childElementIterator.next();
String childElementValue = childElement.getText();
String transformedElementValue = getTransformedElementValue(childElementValue);
if (transformedElementValue.endsWith(CERTIFICATE_EXTENSION)) {
loadCertificateFileToStore(transformedElementValue);
}
}
}
}

private void loadCertificateFileToStore(String certificateFileResourceKey) throws DeploymentException {

String certificateFilePath = getSynapseConfiguration().getRegistry().getRegistryEntry(certificateFileResourceKey).getName();
File certificateFile = new File(certificateFilePath);
String certificateAlias = certificateFile.getName().split("\\.")[0];
try {
FileInputStream certificateFileInputStream = FileUtils.openInputStream(new File(certificateFilePath));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use try block with resources to close the stream automatically? Relevant to other input/output streams.

SslSenderTrustStoreHolder sslSenderTrustStoreHolder = SslSenderTrustStoreHolder.getInstance();
KeyStore sslSenderTrustStore = sslSenderTrustStoreHolder.getKeyStore();
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate certificate = certificateFactory.generateCertificate(certificateFileInputStream);
sslSenderTrustStore.setCertificateEntry(certificateAlias, certificate);

FileOutputStream fileOutputStream = new FileOutputStream(sslSenderTrustStoreHolder.getLocation());
sslSenderTrustStore.store(fileOutputStream, sslSenderTrustStoreHolder.getPassword().toCharArray());

FileInputStream fileInputStream = new FileInputStream(sslSenderTrustStoreHolder.getLocation());
InputStream dest = IOUtils.toBufferedInputStream(fileInputStream);
fileInputStream.close();
sslSenderTrustStore.load(dest, sslSenderTrustStoreHolder.getPassword().toCharArray());
Comment on lines +138 to +141
Copy link
Member

@Bhashinee Bhashinee Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these redundant truststore loads as we do it in line 145?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. They represent 2 things. First load is to load the certificate to the JKS and the second load is used to load the new JKS and create a new connection.

dest.close();

sslSenderTrustStoreHolder.setKeyStore(sslSenderTrustStore);
KeyStoreReloaderHolder.getInstance().reloadAllKeyStores();
} catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than throwing a generic runtime exception, shall we add a more descriptive message?

ex: ("Failed to load certificate file to trust store", e)

}
}

/**
* Transforms the given element value if it indicates a resource file.
*
* @param elementValue the value of the element to be transformed
* @return the transformed element value
*/
private String getTransformedElementValue(String elementValue) {
String transformedElementValue = elementValue.trim();
if (transformedElementValue.startsWith(RESOURCES_IDENTIFIER)) {
transformedElementValue = transformedElementValue.replace(RESOURCES_IDENTIFIER, CONVERTED_RESOURCES_IDENTIFIER);
}
return transformedElementValue;
}

@Override
public String updateSynapseArtifact(OMElement artifactConfig, String fileName,
String existingArtifactName, Properties properties) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.apache.synapse.transport.dynamicconfigurations;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add the license headers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check other places as well.


import org.apache.axis2.AxisFault;
import org.apache.axis2.description.ParameterInclude;

public interface IKeyStoreLoader {

void loadKeyStore(ParameterInclude transport) throws AxisFault;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.apache.synapse.transport.dynamicconfigurations;

import org.apache.axis2.AxisFault;
import org.apache.axis2.description.ParameterInclude;

public class KeyStoreReloader {

private IKeyStoreLoader keyStoreLoader;
private ParameterInclude transportOutDescription;

public KeyStoreReloader(IKeyStoreLoader keyStoreLoader, ParameterInclude transportOutDescription) {

this.keyStoreLoader = keyStoreLoader;
this.transportOutDescription = transportOutDescription;

registerListener(transportOutDescription);
}

private void registerListener(ParameterInclude transportOutDescription) {

KeyStoreReloaderHolder.getInstance().addKeyStoreLoader(this);
}

public void update() {

try {
keyStoreLoader.loadKeyStore(transportOutDescription);
} catch (AxisFault e) {
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we throwing a runtime exception here? instead throw an AxisFault with a proper message.

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.apache.synapse.transport.dynamicconfigurations;

import java.util.ArrayList;
import java.util.List;

public class KeyStoreReloaderHolder {

private static KeyStoreReloaderHolder instance = new KeyStoreReloaderHolder();
private List<KeyStoreReloader> keyStoreLoaders;

private KeyStoreReloaderHolder() {
keyStoreLoaders = new ArrayList<>();
}

public static KeyStoreReloaderHolder getInstance() {
return instance;
}

public void addKeyStoreLoader(KeyStoreReloader keyStoreLoader) {
keyStoreLoaders.add(keyStoreLoader);
}

public void reloadAllKeyStores() {
for (KeyStoreReloader keyStoreLoader : keyStoreLoaders) {
keyStoreLoader.update();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,18 @@ private SSLContext createSSLContext(OMElement keyStoreElt, OMElement trustStoreE
if (log.isDebugEnabled()) {
log.debug(name + " Loading Trust Keystore from : " + location);
}
SslSenderTrustStoreHolder.getInstance().setLocation(location);
SslSenderTrustStoreHolder.getInstance().setPassword(passwordElement.getText());
SslSenderTrustStoreHolder.getInstance().setType(type);

trustStore.load(fis, storePassword.toCharArray());
TrustManagerFactory trustManagerfactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerfactory.init(trustStore);
trustManagers = trustManagerfactory.getTrustManagers();

SslSenderTrustStoreHolder sslSenderTrustStoreHolder = SslSenderTrustStoreHolder.getInstance();
sslSenderTrustStoreHolder.setKeyStore(trustStore);
sslSenderTrustStoreHolder.setLocation(location);
sslSenderTrustStoreHolder.setPassword(storePassword);
SslSenderTrustStoreHolder.getInstance().setType(type);
} catch (GeneralSecurityException gse) {
log.error(name + " Error loading Key store : " + location, gse);
throw new AxisFault("Error loading Key store : " + location, gse);
Expand Down Expand Up @@ -472,6 +476,11 @@ private SSLContext createSSLContext(OMElement keyStoreElt, OMElement trustStoreE
trustManagerfactory.init(trustStore);
trustManagers = trustManagerfactory.getTrustManagers();

SslSenderTrustStoreHolder sslSenderTrustStoreHolder = SslSenderTrustStoreHolder.getInstance();
sslSenderTrustStoreHolder.setKeyStore(trustStore);
sslSenderTrustStoreHolder.setLocation(location);
sslSenderTrustStoreHolder.setPassword(storePassword);

} catch (GeneralSecurityException gse) {
log.error(name + " Error loading Key store : " + location, gse);
throw new AxisFault("Error loading Key store : " + location, gse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package org.apache.synapse.transport.nhttp.config;

import java.security.KeyStore;

/**
* The SSL Sender TrustStore Holder class to store the client trust store's configurable details.
*/
Expand All @@ -26,14 +28,15 @@ public class SslSenderTrustStoreHolder {

private SslSenderTrustStoreHolder() {}

private KeyStore keyStore;
private String location;
private String password;
private String type;

public static SslSenderTrustStoreHolder getInstance() {

if (instance == null) {
synchronized (TrustStoreHolder.class) {
synchronized (SslSenderTrustStoreHolder.class) {
if (instance == null) {
instance = new SslSenderTrustStoreHolder();
}
Expand All @@ -42,6 +45,21 @@ public static SslSenderTrustStoreHolder getInstance() {
return instance;
}

public static void resetInstance() {

instance = null;
}

public KeyStore getKeyStore() {

return keyStore;
}

public void setKeyStore(KeyStore keyStore) {

this.keyStore = keyStore;
}

public void setLocation(String location) {
this.location = location;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@
import org.apache.axis2.description.ParameterInclude;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.synapse.transport.certificatevalidation.cache.CertCache;
import org.apache.synapse.transport.dynamicconfigurations.IKeyStoreLoader;
import org.apache.synapse.transport.dynamicconfigurations.KeyStoreReloader;
import org.apache.synapse.transport.dynamicconfigurations.SSLProfileLoader;
import org.apache.synapse.transport.dynamicconfigurations.SenderProfileReloader;
import org.apache.synapse.transport.http.conn.Scheme;
import org.apache.synapse.transport.nhttp.config.ClientConnFactoryBuilder;
import org.apache.synapse.transport.nhttp.config.TrustStoreHolder;

public class PassThroughHttpSSLSender extends PassThroughHttpSender implements SSLProfileLoader {
public class PassThroughHttpSSLSender extends PassThroughHttpSender implements SSLProfileLoader, IKeyStoreLoader {

@Override
public void init(ConfigurationContext configurationContext,
TransportOutDescription transportOutDescription) throws AxisFault {
super.init(configurationContext, transportOutDescription);
new KeyStoreReloader(this, transportOutDescription);
new SenderProfileReloader(this, transportOutDescription);
}

Expand Down Expand Up @@ -60,4 +63,10 @@ public void reloadConfig(ParameterInclude transport) throws AxisFault {
reloadDynamicSSLConfig((TransportOutDescription) transport);
}

@Override
public void loadKeyStore(ParameterInclude transport) throws AxisFault {
CertCache.resetCache();
TrustStoreHolder.resetInstance();
reloadSSL((TransportOutDescription) transport);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,18 @@ public void reloadDynamicSSLConfig(TransportOutDescription transport) throws Axi
}
}

public void reloadSSL(TransportOutDescription transport) throws AxisFault {
log.info("PassThroughHttpSender SSL Config..");
ClientConnFactoryBuilder connFactoryBuilder =
initConnFactoryBuilder(transport, this.configurationContext).parseSSL();
connFactory = connFactoryBuilder.createConnFactory(targetConfiguration.getHttpParams());

handler.setConnFactory(connFactory);
ioEventDispatch.setConnFactory(connFactory);

log.info("Pass-through " + namePrefix + " Sender updated with SSL Configuration Updates ...");
}

/**
* Set content type headers along with the charactor encoding if content type header is not preserved
* @param msgContext message context
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.apache.synapse.transport.nhttp.config;

import org.junit.Assert;
import org.junit.Test;

public class SslSenderTrustStoreHolderTest {

@Test
public void testGetInstance() {
chathuranga-jayanath-99 marked this conversation as resolved.
Show resolved Hide resolved
SslSenderTrustStoreHolder instance = SslSenderTrustStoreHolder.getInstance();
SslSenderTrustStoreHolder instance2 = SslSenderTrustStoreHolder.getInstance();
Assert.assertEquals(instance, instance2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add an assert message here.

}
}
Loading