Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » Eclipse Scout » Scout Ui html client + dedicated certificate
Scout Ui html client + dedicated certificate [message #1835551] Sun, 06 December 2020 18:52 Go to next message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
I was reorganizing my testing workflow to be containerized. The idea was to be able to run Scout client/server in separate containers and/or eclipse when needed.

After few evenings of debugging certificate related issues I decided to approach it from different direction. Scout Ui html client is using jvm default key/trust store. Containers can (and will) have different jvms and different ways to configure key/trust stores. On my host system there are over 130 certificates in default truststore. From security point of view only one is required...

So, I implemented a dedicated key and trust store for Scout Ui html client. Control over used protocol and cipher suites is extra bonus.

Code is (mostly) functionally tested. Performance and stability has not been tested. Most of the code is public domain, with the exception of parts that have to be EPL-1.0 (they are marked).

Ironically my original issue was not resolved - as it eventually turned out, modules *.app.dev and *.app.war are using different set of keys for authentication...

How to use: just place following files in helloworld.shared module. (the original code is in *.shared module even though it is used only in *.ui.html )

Any comments are welcomed.

ui.tls.properties
# Scout UI HTML Client dedicated security certificate configuration
# configuration properties for class: ApacheHttpTransportFactoryMK2 and DefaultUiHtmlTrustManagersFactory
# This is client part of Scout client <-> Scout server communication (Scout server part is taken care by servlet container)

# Import from config.properties file with:
#import[0]=classpath:ui_tls.properties

# Extra security info:
# https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#jsse-cipher-suite-names
# https://security.googleblog.com/2014/04/speeding-up-and-strengthening-https.html
# https://security.googleblog.com/2018/10/modernizing-transport-security.html
# https://www.openssl.org/blog/blog/2018/02/08/tlsv1.3/

# protocol recommendation: TLSv1.2+ (TLSv1.0 and TLSv1.1 are obsolete)
scout.ui.tunnel.security.protocol=TLSv1.2,TLSv1.3

# cipher suites recommendation: *CHACHA20_POLY1305* (for speed), *AES*GCM*, *ECDHE*
# cipher suites TLSv1.3: TLS_CHACHA20_POLY1305_SHA256,TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256
# cipher suites TLSv1.2: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256,TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256,TLS_PSK_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256

# with *CHACHA* cipher suites
#scout.ui.tunnel.security.ciphers=TLS_CHACHA20_POLY1305_SHA256,TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
# without *CHACHA* cipher suites
scout.ui.tunnel.security.ciphers=TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256

# Keystore configuration
# Property scout.ui.tunnel.security.keystore.file is optional
# if not set: JVM default configuration is used (remaining *keystore* and *truststore* properties are ignored)
# if set: provided keystore is used (remaining *keystore* properties are required)

# Default: PKCS12
#scout.ui.tunnel.security.keystore.type=PKCS12

scout.ui.tunnel.security.keystore.provider=SUN
scout.ui.tunnel.security.keystore.file=src/main/resources/scoutclient_ks.p12
scout.ui.tunnel.security.keystore.password=pass123
scout.ui.tunnel.security.keystore.cert.alias=scoutclient
scout.ui.tunnel.security.keystore.cert.password=pass345

# Truststore configuration
# Property scout.ui.tunnel.security.truststore.file is optional
# if not set: all certificates are accepted (remaining *truststore* properties are ignored)
# if set: certificate trust is verified (remaining *truststore* properties are required)

# Default: PKCS12
#scout.ui.tunnel.security.truststore.type=PKCS12

scout.ui.tunnel.security.truststore.provider=SUN
scout.ui.tunnel.security.truststore.file=src/main/resources/scoutclient_ts.p12
scout.ui.tunnel.security.truststore.password=pass123

# Disabling this may be useful for testing environment without proper DNS setup (connection name vs certificate CN/SAN)
# 0 - disabled
# 1 - (default) enabled DefaultHostnameVerifier
scout.ui.tunnel.security.hostname_validation=1


# Matching Tomcat Connector configuration:
#<Connector
#	port="8443"
#	protocol="org.apache.coyote.http11.Http11Nio2Protocol"
#	sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation"
#	maxThreads="150"
#	SSLEnabled="true"
#	scheme="https"
#	secure="true"
#	connectionTimeout="4000" 
#	maxKeepAliveRequests="-1">                                                                                     
#       <SSLHostConfig 
#            certificateVerification="required"
#            protocols="TLSv1.2,+TLSv1.3"
#            ciphers="TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256,TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256,TLS_PSK_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
#            truststoreFile="conf/scoutserver_ts.p12"
#            truststorePassword="pass123"
#            truststoreType="PKCS12"
#        		<Certificate
#        			type="RSA"
#        			certificateKeystoreType="PKCS12"
#        			certificateKeystoreFile="conf/scoutserver_ks.p12"
#        			certificateKeystorePassword="pass123"
#        			certificateKeyAlias="scoutserver"
#        			certificateKeyPassword="pass345"/>
#        </SSLHostConfig>
#</Connector>



/*
 * This class is EPL-1.0 (direct derivative of class ApacheHttpTransportFactory which is EPL-1.0)
 * http://www.eclipse.org/legal/epl-v10.html
 */
package io.poc.scout.http;

import java.util.Arrays;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;

import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
import org.eclipse.scout.rt.platform.Replace;
import org.eclipse.scout.rt.platform.config.CONFIG;
import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.util.StringUtility;
import org.eclipse.scout.rt.shared.http.ApacheHttpTransportFactory;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

import io.poc.scout.http.property.UITunnelProperties;
import io.poc.scout.http.security.ScoutSSLSocketFactory;

@Replace
public class ApacheHttpTransportFactoryMK2 extends ApacheHttpTransportFactory {

	private static final XLogger LOG = XLoggerFactory.getXLogger(ApacheHttpTransportFactoryMK2.class);

	@Override
	protected SSLConnectionSocketFactory createSSLConnectionSocketFactory() {
		LOG.entry();
		SSLConnectionSocketFactory result = null;

		result = new SSLConnectionSocketFactory(getSSLSocketFactory(), getAllowedProtocols(), getAllowedCipherSuites(), getHostnameVerifier());

		return LOG.exit(result);
	}

	protected SSLSocketFactory getSSLSocketFactory() {
		LOG.entry();
		SSLSocketFactory result = null;

		String tmp_ks_file = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystoreFileProperty.class);

		if (StringUtility.isNullOrEmpty(tmp_ks_file)) {
			// use default SSLSocketFactory
			LOG.warn("Using default SSLSocketFactory. Consider configuring your own keystore and truststore.");
			result = (SSLSocketFactory) SSLSocketFactory.getDefault();

		} else {
			// use ScoutSocketFactory
			result = ScoutSSLSocketFactory.getDefault();
		}

		return LOG.exit(result);
	}

	protected String[] getAllowedProtocols() {
		LOG.entry();
		String[] result = null;

		String protocols = CONFIG.getPropertyValue(UITunnelProperties.UITunnelConnectionProtocolProperty.class);
		LOG.trace("protocol property: {}", protocols);
		result = protocols.split(",");
		Arrays.stream(result).forEach(x -> LOG.debug("enabled protocol: {}", x));

		return LOG.exit(result);
	}

	protected String[] getAllowedCipherSuites() {
		LOG.entry();
		String[] result = null;

		String ciphers = CONFIG.getPropertyValue(UITunnelProperties.UITunnelConnectionCiphersProperty.class);
		LOG.trace("ciphers property: {}", ciphers);
		result = ciphers.split(",");
		Arrays.stream(result).forEach(x -> LOG.debug("enabled cipher suite: {}", x));

		return LOG.exit(result);
	}

	protected HostnameVerifier getHostnameVerifier() {
		LOG.entry();
		HostnameVerifier result = null;

		String hostnameValidation = CONFIG.getPropertyValue(UITunnelProperties.UITunnelHostnameValidationProperty.class);

		switch (hostnameValidation) {

			// disable hostname verification (accept all hostnames)
			case "0" : {

				result = new HostnameVerifier() {

					@Override
					public boolean verify(String arg0, SSLSession arg1) {
						return true;
					}

				};

				break;
			}

			// default hostname verification based on Apache Http Client library (as it was before)
			case "1" : {
				result = new DefaultHostnameVerifier(PublicSuffixMatcherLoader.getDefault());
				break;
			}

			default : {
				String msg = "Unrecognized value of property: " + UITunnelProperties.UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY;
				LOG.error(msg);
				throw new PlatformException(msg);
			}

		}

		return LOG.exit(result);
	}

}


/*
 * This class is public domain
 */
package io.poc.scout.http.property;

import org.eclipse.scout.rt.platform.config.AbstractStringConfigProperty;

public class UITunnelProperties {

	public static final String UI_TUNNEL_SECURITY_PROPERTIES_NS = "scout.ui.tunnel.security";

	public static final String UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".protocol";
	public static final String UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY_DESC = "Connection security protocol";
	public static final String UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY_DEFAULT = "TLSv1.2,TLSv1.3";

	public static final String UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".ciphers";
	public static final String UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY_DESC = "Connection allowed ciphers";
	public static final String UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY_DEFAULT = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";

	public static final String UI_TUNNEL_KEYSTORE_TYPE_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".keystore.type";
	public static final String UI_TUNNEL_KEYSTORE_TYPE_PROPERTY_DESC = "Keystore type";
	public static final String UI_TUNNEL_KEYSTORE_TYPE_PROPERTY_DEFAULT = "PKCS12";

	public static final String UI_TUNNEL_KEYSTORE_PROVIDER_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".keystore.provider";
	public static final String UI_TUNNEL_KEYSTORE_PROVIDER_PROPERTY_DESC = "Keystore provider";

	public static final String UI_TUNNEL_KEYSTORE_FILE_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".keystore.file";
	public static final String UI_TUNNEL_KEYSTORE_FILE_PROPERTY_DESC = "Keystore file location";

	public static final String UI_TUNNEL_KEYSTORE_PASSWORD_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".keystore.password";
	public static final String UI_TUNNEL_KEYSTORE_PASSWORD_PROPERTY_DESC = "Keystore password";

	public static final String UI_TUNNEL_KEYSTORE_CERT_ALIAS_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".keystore.cert.alias";
	public static final String UI_TUNNEL_KEYSTORE_CERT_ALIAS_PROPERTY_DESC = "Certificate alias";

	public static final String UI_TUNNEL_KEYSTORE_CERT_PASSWORD_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".keystore.cert.password";
	public static final String UI_TUNNEL_KEYSTORE_CERT_PASSWORD_PROPERTY_DESC = "Certificate private key password";

	public static final String UI_TUNNEL_TRUSTSTORE_TYPE_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".truststore.type";
	public static final String UI_TUNNEL_TRUSTSTORE_TYPE_PROPERTY_DESC = "Truststore type";
	public static final String UI_TUNNEL_TRUSTSTORE_TYPE_PROPERTY_DEFAULT = "PKCS12";

	public static final String UI_TUNNEL_TRUSTSTORE_PROVIDER_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".truststore.provider";
	public static final String UI_TUNNEL_TRUSTSTORE_PROVIDER_PROPERTY_DESC = "Truststore provider";

	public static final String UI_TUNNEL_TRUSTSTORE_FILE_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".truststore.file";
	public static final String UI_TUNNEL_TRUSTSTORE_FILE_PROPERTY_DESC = "Truststore file location";

	public static final String UI_TUNNEL_TRUSTSTORE_PASSWORD_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".truststore.password";
	public static final String UI_TUNNEL_TRUSTSTORE_PASSWORD_PROPERTY_DESC = "Truststore password";

	public static final String UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY = UI_TUNNEL_SECURITY_PROPERTIES_NS + ".hostname_validation";
	public static final String UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY_DESC = "Hostname validation";
	public static final String UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY_DEFAULT = "1";

	private UITunnelProperties() {
	}

	public static class UITunnelConnectionProtocolProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY_DESC;
		}

		@Override
		public String getDefaultValue() {
			return UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY_DEFAULT;
		}

	}

	public static class UITunnelConnectionCiphersProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY_DESC;
		}

		@Override
		public String getDefaultValue() {
			return UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY_DEFAULT;
		}

	}

	public static class UITunnelKeystoreTypeProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_KEYSTORE_TYPE_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_KEYSTORE_TYPE_PROPERTY_DESC;
		}

		@Override
		public String getDefaultValue() {
			return UI_TUNNEL_KEYSTORE_TYPE_PROPERTY_DEFAULT;
		}

	}

	public static class UITunnelKeystoreProviderProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_KEYSTORE_PROVIDER_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_KEYSTORE_PROVIDER_PROPERTY_DESC;
		}

		// @Override
		// public String getDefaultValue() {
		// return UI_TUNNEL_KEYSTORE_PROVIDER_PROPERTY_DEFAULT;
		// }

	}

	public static class UITunnelKeystoreFileProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_KEYSTORE_FILE_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_KEYSTORE_FILE_PROPERTY_DESC;
		}

	}

	public static class UITunnelKeystorePasswordProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_KEYSTORE_PASSWORD_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_KEYSTORE_PASSWORD_PROPERTY_DESC;
		}

	}

	public static class UITunnelKeystoreCertAliasProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_KEYSTORE_CERT_ALIAS_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_KEYSTORE_CERT_ALIAS_PROPERTY_DESC;
		}

	}

	public static class UITunnelKeystoreCertPasswordProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_KEYSTORE_CERT_PASSWORD_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_KEYSTORE_CERT_PASSWORD_PROPERTY_DESC;
		}

	}

	public static class UITunnelTruststoreTypeProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_TRUSTSTORE_TYPE_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_TRUSTSTORE_TYPE_PROPERTY_DESC;
		}

		@Override
		public String getDefaultValue() {
			return UI_TUNNEL_TRUSTSTORE_TYPE_PROPERTY_DEFAULT;
		}

	}

	public static class UITunnelTruststoreProviderProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_TRUSTSTORE_PROVIDER_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_TRUSTSTORE_PROVIDER_PROPERTY_DESC;
		}

		// @Override
		// public String getDefaultValue() {
		// return UI_TUNNEL_TRUSTSTORE_PROVIDER_PROPERTY_DEFAULT;
		// }

	}

	public static class UITunnelTruststoreFileProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_TRUSTSTORE_FILE_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_TRUSTSTORE_FILE_PROPERTY_DESC;
		}

	}

	public static class UITunnelTruststorePasswordProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_TRUSTSTORE_PASSWORD_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_TRUSTSTORE_PASSWORD_PROPERTY_DESC;
		}

	}

	public static class UITunnelHostnameValidationProperty extends AbstractStringConfigProperty {

		@Override
		public String getKey() {
			return UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY;
		}

		@Override
		public String description() {
			return UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY_DESC;
		}

		@Override
		public String getDefaultValue() {
			return UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY_DEFAULT;
		}

	}

}


/*
 * This class is public domain
 */
package io.poc.scout.http.property;

import org.eclipse.scout.rt.platform.config.IConfigurationValidator;
import org.eclipse.scout.rt.platform.exception.InitializationException;
import org.eclipse.scout.rt.platform.util.StringUtility;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

public class UITunnelPropertyValidator implements IConfigurationValidator {

	private static final XLogger LOG = XLoggerFactory.getXLogger(UITunnelPropertyValidator.class);

	@Override
	public boolean isValid(String key, String value) {

		if (key.equalsIgnoreCase(UITunnelProperties.UI_TUNNEL_CONNECTION_PROTOCOL_PROPERTY)) {
			LOG.debug("Property: [{}] value: [{}]", key, value);

			if (StringUtility.isNullOrEmpty(value)) {
				String msg = "Incorrect value for property: " + key;
				LOG.error(msg);
				throw new InitializationException(msg);
			} else {
				return true;
			}
		}

		if (key.equalsIgnoreCase(UITunnelProperties.UI_TUNNEL_CONNECTION_CIPHERS_PROPERTY)) {
			LOG.debug("Property: [{}] value: [{}]", key, value);

			if (StringUtility.isNullOrEmpty(value)) {
				String msg = "Incorrect value for property: " + key;
				LOG.error(msg);
				throw new InitializationException(msg);
			} else {
				return true;
			}
		}

		if (key.equalsIgnoreCase(UITunnelProperties.UI_TUNNEL_HOSTNAME_VALIDATION_PROPERTY)) {

			if (value != null && value.length() == 1 && "01".indexOf(value) >= 0) {
				LOG.debug("Property: [{}] value: [{}] is valid", key, value);
				if (value.equalsIgnoreCase("0"))
					LOG.warn("Hostname validation is disabled");

				return true;
			} else {
				String msg = "Incorrect value for property: " + key;
				LOG.error(msg);
				throw new InitializationException(msg);
			}
		}

		return false;
	}

}


Re: Scout Ui html client + dedicated certificate [message #1835552 is a reply to message #1835551] Sun, 06 December 2020 18:56 Go to previous message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
/*
 * This class is public domain
 */
package io.poc.scout.http.security;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.stream.Stream;

import javax.crypto.Cipher;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.config.CONFIG;
import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.util.StringUtility;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

import io.poc.scout.http.property.UITunnelProperties;

public class ScoutSSLSocketFactory extends SSLSocketFactory {

	private static final XLogger LOG = XLoggerFactory.getXLogger(ScoutSSLSocketFactory.class);

	private SSLSocketFactory factory = null;

	private String[] protocols = null;
	private String[] cipher_suites = null;

	private String ks_type = null;
	private String ks_provider = null;
	private String ks_file = null;
	private String ks_password = null;
	private String ks_cert_alias = null;
	private String ks_cert_password = null;

	// private String ts_type = null;
	// private String ts_provider = null;
	// private String ts_file = null;
	// private String ts_password = null;

	public ScoutSSLSocketFactory() {
		LOG.entry();

		getConfigProperties();

		try {

			if (LOG.isDebugEnabled()) {
				extraDebugInfo();
			}

			LOG.debug("creating SSLContext using protocol: {}", protocols[0]);
			SSLContext ctx = SSLContext.getInstance(protocols[0]);
			ctx.init(prepareKeyManagers(), prepareTrustManagers(), new SecureRandom());
			factory = ctx.getSocketFactory();

		} catch (NoSuchAlgorithmException e) {
			String msg = "Cannot create ScoutSSLSocketFactory";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (KeyManagementException e) {
			String msg = "Cannot create ScoutSSLSocketFactory";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);
		}

		LOG.exit();
	}

	public static SSLSocketFactory getDefault() {
		LOG.entry();
		SSLSocketFactory result = null;

		result = new ScoutSSLSocketFactory();

		return LOG.exit(result);
	}

	@Override
	public String[] getDefaultCipherSuites() {
		LOG.entry();
		String[] result = null;

		result = factory.getDefaultCipherSuites();

		return LOG.exit(result);
	}

	@Override
	public String[] getSupportedCipherSuites() {
		LOG.entry();
		String[] result = null;

		result = factory.getSupportedCipherSuites();

		return LOG.exit(result);
	}

	@Override
	public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
		SSLSocket result = (SSLSocket) factory.createSocket(arg0, arg1, arg2, arg3);
		result.setEnabledProtocols(protocols);
		result.setEnabledCipherSuites(cipher_suites);
		return result;
	}

	@Override
	public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
		SSLSocket result = (SSLSocket) factory.createSocket(arg0, arg1);
		result.setEnabledProtocols(protocols);
		result.setEnabledCipherSuites(cipher_suites);
		return result;
	}

	@Override
	public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
		SSLSocket result = (SSLSocket) factory.createSocket(arg0, arg1);
		result.setEnabledProtocols(protocols);
		result.setEnabledCipherSuites(cipher_suites);
		return result;
	}

	@Override
	public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException {
		SSLSocket result = (SSLSocket) factory.createSocket(arg0, arg1, arg2, arg3);
		result.setEnabledProtocols(protocols);
		result.setEnabledCipherSuites(cipher_suites);
		return result;
	}

	@Override
	public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
		LOG.entry(arg0, arg1, arg2, arg3);
		SSLSocket result = (SSLSocket) factory.createSocket(arg0, arg1, arg2, arg3);
		result.setEnabledProtocols(protocols);
		result.setEnabledCipherSuites(cipher_suites);
		return LOG.exit(result);
	}

	/*
	 * load properties from config.properties file (from *.ui.html.* module)
	 */
	private void getConfigProperties() {
		LOG.entry();

		protocols = CONFIG.getPropertyValue(UITunnelProperties.UITunnelConnectionProtocolProperty.class).split(",");
		cipher_suites = CONFIG.getPropertyValue(UITunnelProperties.UITunnelConnectionCiphersProperty.class).split(",");

		ks_type = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystoreTypeProperty.class);
		ks_provider = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystoreProviderProperty.class);
		ks_file = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystoreFileProperty.class);
		ks_password = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystorePasswordProperty.class);
		ks_cert_alias = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystoreCertAliasProperty.class);
		ks_cert_password = CONFIG.getPropertyValue(UITunnelProperties.UITunnelKeystoreCertPasswordProperty.class);

		LOG.exit();
	}

	private KeyManager[] prepareKeyManagers() {
		LOG.entry();
		KeyManager[] result = null;

		try {
			// prepare cert_password
			LOG.debug("preparing password");
			char[] cert_password_char = null;

			if (StringUtility.isNullOrEmpty(ks_cert_password)) {
				cert_password_char = new char[0];
			} else {
				cert_password_char = ks_cert_password.toCharArray();
			}

			// get certificate key and chain
			LOG.debug("getting certificate key and chain");
			KeyStore ks = CertUtil.loadKeystore(ks_type, ks_provider, ks_file, ks_password);

			if (ks.getCertificate(ks_cert_alias) == null) {
				throw new UnrecoverableKeyException("No certificate found with alias: " + ks_cert_alias);
			}
			Certificate[] cert_chain = ks.getCertificateChain(ks_cert_alias);
			Key cert_priv_key = ks.getKey(ks_cert_alias, cert_password_char);

			Stream.of(cert_chain).forEach(x -> LOG.debug("cert: {}", ((X509Certificate) x).getSubjectDN()));
			LOG.debug("priv key alg: {} format: {}", cert_priv_key.getAlgorithm(), cert_priv_key.getFormat());

			// create new KeyStore and add extracted certificates and key
			LOG.debug("creating new keystore");
			KeyStore newKeystore = CertUtil.createKeystore(ks.getType(), ks.getProvider().getName(), ks_password);
			LOG.debug("adding cert chain to new keystore");
			newKeystore.setKeyEntry(ks_cert_alias, cert_priv_key, cert_password_char, cert_chain);

			// prepare KeyManagers
			String defaultAlg = KeyManagerFactory.getDefaultAlgorithm();
			LOG.debug("default KeyManagerFactory algorithm: {}", defaultAlg);
			KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(defaultAlg);

			keyManagerFactory.init(newKeystore, cert_password_char);
			result = keyManagerFactory.getKeyManagers();

		} catch (KeyStoreException e) {
			String msg = "Error preparing KeyManagers";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (UnrecoverableKeyException e) {
			String msg = "Error preparing KeyManagers";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (NoSuchAlgorithmException e) {
			String msg = "Error preparing KeyManagers";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);
		}

		return LOG.exit(result);
	}

	private TrustManager[] prepareTrustManagers() {
		LOG.entry();
		TrustManager[] result = null;

		IUiHtmlTrustManagersFactory trustManagersFactory = BEANS.get(IUiHtmlTrustManagersFactory.class);
		result = trustManagersFactory.getTrustManagers();

		return LOG.exit(result);
	}

	private void extraDebugInfo() {
		LOG.entry();

		try {

			boolean unlimited_crypto_policy = !(Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") < Integer.MAX_VALUE);
			LOG.debug("unlimited_crypto_policy: {}", unlimited_crypto_policy);
			LOG.debug("AES max key length: {}", Cipher.getMaxAllowedKeyLength("AES"));

		} catch (NoSuchAlgorithmException e) {
			String msg = "Problem determining JVM crypto.policy. Possible JVM problem";
			LOG.error(msg, e);
		}

		Arrays.stream(Security.getProviders()).forEach(x -> LOG.debug("available provider: [{}] info: [{}]", x.getName(), x.getInfo()));
		Arrays.stream(((SSLSocketFactory) SSLSocketFactory.getDefault()).getSupportedCipherSuites()).forEach(x -> LOG.debug("suppported cipher suite: {}", x));
		Arrays.stream(((SSLSocketFactory) SSLSocketFactory.getDefault()).getDefaultCipherSuites()).forEach(x -> LOG.debug("default cipher suite: {}", x));

		LOG.exit();
	}

}



/*
 * This class is public domain
 */
package io.poc.scout.http.security;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

public class TrustAllTrustManager implements X509TrustManager {

	private static final XLogger LOG = XLoggerFactory.getXLogger(TrustAllTrustManager.class);

	public TrustAllTrustManager() {
		LOG.entry();
		LOG.warn("You should implement a proper TrustManager");
		LOG.exit();
	}

	@Override
	public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
		LOG.entry(chain, authType);
		LOG.exit();
	}

	@Override
	public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
		LOG.entry(chain, authType);
		LOG.exit();
	}

	@Override
	public X509Certificate[] getAcceptedIssuers() {
		LOG.entry();
		return LOG.exit(new X509Certificate[]{});
	}

}


/*
 * This class is public domain
 */
package io.poc.scout.http.security;

import javax.net.ssl.TrustManager;

import org.eclipse.scout.rt.platform.ApplicationScoped;

/*
 * This interface can be used to implement custom rules for certificate trust verification
 * for example: OCSP query, multiple trust stores where each of them has different verification rules...
 */

@ApplicationScoped
public interface IUiHtmlTrustManagersFactory {

	public TrustManager[] getTrustManagers();

}


/*
 * This class is public domain
 */
package io.poc.scout.http.security;

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.eclipse.scout.rt.platform.Order;
import org.eclipse.scout.rt.platform.config.CONFIG;
import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.util.StringUtility;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

import io.poc.scout.http.property.UITunnelProperties;

@Order(5000)
public class DefaultUiHtmlTrustManagersFactory implements IUiHtmlTrustManagersFactory {

	private static final XLogger LOG = XLoggerFactory.getXLogger(DefaultUiHtmlTrustManagersFactory.class);

	@Override
	public TrustManager[] getTrustManagers() {
		LOG.entry();

		TrustManager[] result = null;

		// get properties
		String ts_type = CONFIG.getPropertyValue(UITunnelProperties.UITunnelTruststoreTypeProperty.class);
		String ts_provider = CONFIG.getPropertyValue(UITunnelProperties.UITunnelTruststoreProviderProperty.class);
		String ts_file = CONFIG.getPropertyValue(UITunnelProperties.UITunnelTruststoreFileProperty.class);
		String ts_password = CONFIG.getPropertyValue(UITunnelProperties.UITunnelTruststorePasswordProperty.class);

		try {

			if (StringUtility.isNullOrEmpty(ts_file)) {
				// trust all
				result = new TrustManager[]{new TrustAllTrustManager()};

			} else {
				// use provided truststore details for trust verification
				TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
				KeyStore trustStore = CertUtil.loadKeystore(ts_type, ts_provider, ts_file, ts_password);
				tmf.init(trustStore);

				result = tmf.getTrustManagers();
			}

		} catch (NoSuchAlgorithmException e) {
			String msg = "Error preparing TrustManagers";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (KeyStoreException e) {
			String msg = "Error preparing TrustManagers";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);
		}

		return LOG.exit(result);
	}

}



/*
 * This class is public domain
 */
package io.poc.scout.http.security;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;

import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.util.StringUtility;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

public class CertUtil {

	private static final XLogger LOG = XLoggerFactory.getXLogger(CertUtil.class);

	/*
	 * Create keystore object
	 */
	public static KeyStore createKeystore(String type, String provider, String password) {
		LOG.entry(type, provider, "***");
		KeyStore result = null;

		KeyStore ks = null;

		try {

			if (StringUtility.isNullOrEmpty(provider)) {
				// use default provider
				ks = KeyStore.getInstance(type);

			} else {
				// use specified provider
				ks = KeyStore.getInstance(type, provider);
			}

			if (StringUtility.isNullOrEmpty(password)) {
				ks.load(null, new char[0]);

			} else {
				ks.load(null, password.toCharArray());
			}

			result = ks;

		} catch (KeyStoreException e) {
			String msg = "Cannot create keystore";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (NoSuchProviderException e) {
			String msg = "Cannot create keystore";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (NoSuchAlgorithmException e) {
			String msg = "Cannot create keystore";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (CertificateException e) {
			String msg = "Cannot create keystore";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (IOException e) {
			String msg = "Cannot create keystore";
			LOG.error(msg, e);
			throw new PlatformException(msg, e);
		}

		return LOG.exit(result);
	}

	/*
	 * Load keystore from file
	 */
	public static KeyStore loadKeystore(String type, String provider, String location, String password) {
		LOG.entry(type, provider, location, "***");
		KeyStore result = null;

		KeyStore ks = null;

		try {

			if (StringUtility.isNullOrEmpty(provider)) {
				// use default provider
				ks = KeyStore.getInstance(type);

			} else {
				// use specified provider
				ks = KeyStore.getInstance(type, provider);
			}

			if (StringUtility.isNullOrEmpty(location)) {
				String msg = "keystore location cannot be null or empty";
				LOG.error(msg);
				throw new KeyStoreException(msg);

			} else {
				Path path = Paths.get(location).normalize().toAbsolutePath();
				LOG.debug("keystore absolute normalized path: {}", path.toString());

				try (InputStream readStream = Files.newInputStream(path, StandardOpenOption.READ)) {

					if (StringUtility.isNullOrEmpty(password)) {
						ks.load(readStream, new char[0]);

					} else {
						ks.load(readStream, password.toCharArray());

					}
				}
				LOG.debug("keystore successfully opened");

			}

			result = ks;

		} catch (KeyStoreException e) {
			String msg = "Cannot load keystore: " + location;
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (NoSuchProviderException e) {
			String msg = "Cannot load keystore: " + location;
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (NoSuchAlgorithmException e) {
			String msg = "Cannot load keystore: " + location;
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (CertificateException e) {
			String msg = "Cannot load keystore: " + location;
			LOG.error(msg, e);
			throw new PlatformException(msg, e);

		} catch (IOException e) {
			String msg = "Cannot load keystore: " + location;
			LOG.error(msg, e);
			throw new PlatformException(msg, e);
		}

		return LOG.exit(result);
	}

}

Previous Topic:Rich content in desktop notifications
Next Topic:Popup anchor as table cell
Goto Forum:
  


Current Time: Sat Jan 18 06:10:03 GMT 2025

Powered by FUDForum. Page generated in 0.03573 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top