Skip to content

Commit

Permalink
Merge pull request #2243 from chathuranga-jayanath-99/integrate-only-…
Browse files Browse the repository at this point in the history
…ssl-loader

Handle certificates mentioned in HTTP connection during local entry deployment.
  • Loading branch information
arunans23 authored Dec 6, 2024
2 parents 882e3b9 + 7a2806c commit 54672a1
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 5 deletions.
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);
handleSSLSenderCertificates(artifactConfig);
return e.getKey();
} else {
handleSynapseArtifactDeploymentError("LocalEntry Deployment Failed. The artifact " +
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<KeyStoreReloader> 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();
}
}
}
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 All @@ -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();
}
}
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
Loading

0 comments on commit 54672a1

Please sign in to comment.