diff --git a/modules/core/src/main/java/org/apache/synapse/deployers/LocalEntryDeployer.java b/modules/core/src/main/java/org/apache/synapse/deployers/LocalEntryDeployer.java index ab6df62101..97539f8094 100644 --- a/modules/core/src/main/java/org/apache/synapse/deployers/LocalEntryDeployer.java +++ b/modules/core/src/main/java/org/apache/synapse/deployers/LocalEntryDeployer.java @@ -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 LocalEntry deployment and undeployment tasks * @@ -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, @@ -66,6 +88,7 @@ public String deploySynapseArtifact(OMElement artifactConfig, String fileName, } log.info("LocalEntry named '" + e.getKey() + "' has been deployed from file : " + fileName); + handleSSLSenderCertificates(artifactConfig); return e.getKey(); } else { handleSynapseArtifactDeploymentError("LocalEntry Deployment Failed. The artifact " + @@ -79,6 +102,78 @@ public String deploySynapseArtifact(OMElement artifactConfig, String fileName, return null; } + private void handleSSLSenderCertificates(OMElement element) throws DeploymentException { + + 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)) { + loadCertificateFileToSSLSenderTrustStore(transformedElementValue); + loadUpdatedSSL(); + } + } + } + } + + private void loadCertificateFileToSSLSenderTrustStore(String certificateFileResourceKey) throws DeploymentException { + + String certificateFilePath = getSynapseConfiguration().getRegistry().getRegistryEntry(certificateFileResourceKey).getName(); + File certificateFile = new File(certificateFilePath); + String certificateAlias = certificateFile.getName().split("\\.")[0]; + SslSenderTrustStoreHolder sslSenderTrustStoreHolder = SslSenderTrustStoreHolder.getInstance(); + if (sslSenderTrustStoreHolder.isValid()) { + try (FileInputStream certificateFileInputStream = FileUtils.openInputStream(new File(certificateFilePath))) { + KeyStore sslSenderTrustStore = sslSenderTrustStoreHolder.getKeyStore(); + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Certificate certificate = certificateFactory.generateCertificate(certificateFileInputStream); + sslSenderTrustStore.setCertificateEntry(certificateAlias, certificate); + + try (FileOutputStream fileOutputStream = new FileOutputStream(sslSenderTrustStoreHolder.getLocation())) { + sslSenderTrustStore.store(fileOutputStream, sslSenderTrustStoreHolder.getPassword().toCharArray()); + } + } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) { + throw new DeploymentException("Failed to load certificate file to store: " + certificateFilePath, e); + } + } + } + + private void loadUpdatedSSL() throws DeploymentException { + SslSenderTrustStoreHolder sslSenderTrustStoreHolder = SslSenderTrustStoreHolder.getInstance(); + KeyStore sslSenderTrustStore = sslSenderTrustStoreHolder.getKeyStore(); + if (sslSenderTrustStoreHolder.isValid()) { + try ( + FileInputStream fileInputStream = new FileInputStream(sslSenderTrustStoreHolder.getLocation()); + InputStream bufferedInputStream = IOUtils.toBufferedInputStream(fileInputStream) + ) { + sslSenderTrustStore.load(bufferedInputStream, sslSenderTrustStoreHolder.getPassword().toCharArray()); + sslSenderTrustStoreHolder.setKeyStore(sslSenderTrustStore); + KeyStoreReloaderHolder.getInstance().reloadAllKeyStores(); + } catch (IOException | CertificateException | NoSuchAlgorithmException e) { + throw new DeploymentException("Failed to load updated SSL configuration from the trust store at: " + sslSenderTrustStoreHolder.getLocation(), 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) { diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/IKeyStoreLoader.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/IKeyStoreLoader.java new file mode 100644 index 0000000000..11977260ef --- /dev/null +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/IKeyStoreLoader.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.transport.dynamicconfigurations; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.description.ParameterInclude; + +public interface IKeyStoreLoader { + + void loadKeyStore(ParameterInclude transport) throws AxisFault; +} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/KeyStoreReloader.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/KeyStoreReloader.java new file mode 100644 index 0000000000..a077b35174 --- /dev/null +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/KeyStoreReloader.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +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() throws AxisFault { + + keyStoreLoader.loadKeyStore(transportOutDescription); + } +} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/KeyStoreReloaderHolder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/KeyStoreReloaderHolder.java new file mode 100644 index 0000000000..dbfbe984e6 --- /dev/null +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/dynamicconfigurations/KeyStoreReloaderHolder.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.transport.dynamicconfigurations; + +import org.apache.axis2.AxisFault; + +import java.util.ArrayList; +import java.util.List; + +public class KeyStoreReloaderHolder { + + private static KeyStoreReloaderHolder instance = new KeyStoreReloaderHolder(); + private List keyStoreLoaders; + + private KeyStoreReloaderHolder() { + keyStoreLoaders = new ArrayList<>(); + } + + public static KeyStoreReloaderHolder getInstance() { + return instance; + } + + public void addKeyStoreLoader(KeyStoreReloader keyStoreLoader) { + keyStoreLoaders.add(keyStoreLoader); + } + + public void reloadAllKeyStores() throws AxisFault { + for (KeyStoreReloader keyStoreLoader : keyStoreLoaders) { + keyStoreLoader.update(); + } + } +} diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java index 66fde8487c..d85def1532 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/ClientConnFactoryBuilder.java @@ -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); @@ -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); diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolder.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolder.java index ae1a4dcb6d..5177acbb46 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolder.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolder.java @@ -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. */ @@ -26,6 +28,7 @@ public class SslSenderTrustStoreHolder { private SslSenderTrustStoreHolder() {} + private KeyStore keyStore; private String location; private String password; private String type; @@ -33,7 +36,7 @@ private SslSenderTrustStoreHolder() {} public static SslSenderTrustStoreHolder getInstance() { if (instance == null) { - synchronized (TrustStoreHolder.class) { + synchronized (SslSenderTrustStoreHolder.class) { if (instance == null) { instance = new SslSenderTrustStoreHolder(); } @@ -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; } @@ -65,4 +83,9 @@ public void setType(String type) { public String getType() { return this.type; } + + public boolean isValid() { + return keyStore != null && location != null && !location.isEmpty() && + password != null && !password.isEmpty(); + } } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java index 5fe55383d0..f10541807c 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSSLSender.java @@ -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); } @@ -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); + } } diff --git a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSender.java b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSender.java index 3af961c03f..ccb24756c1 100644 --- a/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSender.java +++ b/modules/transports/core/nhttp/src/main/java/org/apache/synapse/transport/passthru/PassThroughHttpSender.java @@ -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 diff --git a/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolderTest.java b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolderTest.java new file mode 100644 index 0000000000..4389f5fd17 --- /dev/null +++ b/modules/transports/core/nhttp/src/test/java/org/apache/synapse/transport/nhttp/config/SslSenderTrustStoreHolderTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.synapse.transport.nhttp.config; + +import org.junit.Assert; +import org.junit.Test; + +public class SslSenderTrustStoreHolderTest { + + @Test + public void testGetInstance() { + SslSenderTrustStoreHolder instance = SslSenderTrustStoreHolder.getInstance(); + SslSenderTrustStoreHolder instance2 = SslSenderTrustStoreHolder.getInstance(); + Assert.assertEquals("Instances should be the same.", instance, instance2); + } +}