This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 01f11a4a3960fa07a66725ac53945ec8338855b0 Author: Benoit Tellier <[email protected]> AuthorDate: Wed Mar 23 10:48:45 2022 +0700 JAMES-3738 Extract Encryption instanciation and configuration out of AbstractConfigurableAsyncServer --- .../apache/james/protocols/netty/Encryption.java | 4 + .../protocols/lib/LegacyJavaEncryptionFactory.java | 81 ++++++++++++ .../org/apache/james/protocols/lib/SslConfig.java | 145 +++++++++++++++++++++ .../lib/netty/AbstractConfigurableAsyncServer.java | 101 +------------- 4 files changed, 237 insertions(+), 94 deletions(-) diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/Encryption.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/Encryption.java index 95baeda558..19b8b18441 100644 --- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/Encryption.java +++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/Encryption.java @@ -34,6 +34,10 @@ import io.netty.handler.ssl.SslHandler; */ public interface Encryption { + interface Factory { + Encryption create() throws Exception; + } + @VisibleForTesting static Encryption createTls(SSLContext context) { return createTls(context, null, ClientAuth.NONE); diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java new file mode 100644 index 0000000000..49dcaf643b --- /dev/null +++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/LegacyJavaEncryptionFactory.java @@ -0,0 +1,81 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF 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.james.protocols.lib; + +import java.util.Optional; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509ExtendedKeyManager; + +import org.apache.james.filesystem.api.FileSystem; +import org.apache.james.protocols.netty.Encryption; + +import nl.altindag.ssl.SSLFactory; +import nl.altindag.ssl.util.PemUtils; + +public class LegacyJavaEncryptionFactory implements Encryption.Factory { + private final FileSystem fileSystem; + private final SslConfig sslConfig; + + public LegacyJavaEncryptionFactory(FileSystem fileSystem, SslConfig sslConfig) { + this.fileSystem = fileSystem; + this.sslConfig = sslConfig; + } + + @Override + public Encryption create() throws Exception { + SSLFactory.Builder sslFactoryBuilder = SSLFactory.builder() + .withSslContextAlgorithm("TLS"); + if (sslConfig.getKeystore() != null) { + char[] passwordAsCharArray = Optional.ofNullable(sslConfig.getSecret()) + .orElse("") + .toCharArray(); + sslFactoryBuilder.withIdentityMaterial( + fileSystem.getFile(sslConfig.getKeystore()).toPath(), + passwordAsCharArray, + passwordAsCharArray, + sslConfig.getKeystoreType()); + } else { + X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial( + fileSystem.getResource(sslConfig.getCertificates()), + fileSystem.getResource(sslConfig.getPrivateKey()), + Optional.ofNullable(sslConfig.getSecret()) + .map(String::toCharArray) + .orElse(null)); + + sslFactoryBuilder.withIdentityMaterial(keyManager); + } + + if (sslConfig.getClientAuth() != null && sslConfig.getTruststore() != null) { + sslFactoryBuilder.withTrustMaterial( + fileSystem.getFile(sslConfig.getTruststore()).toPath(), + sslConfig.getTruststoreSecret(), + sslConfig.getKeystoreType()); + } + + SSLContext context = sslFactoryBuilder.build().getSslContext(); + + if (sslConfig.useStartTLS()) { + return Encryption.createStartTls(context, sslConfig.getEnabledCipherSuites(), sslConfig.getClientAuth()); + } else { + return Encryption.createTls(context, sslConfig.getEnabledCipherSuites(), sslConfig.getClientAuth()); + } + } +} diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/SslConfig.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/SslConfig.java new file mode 100644 index 0000000000..7f5121bedb --- /dev/null +++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/SslConfig.java @@ -0,0 +1,145 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF 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.james.protocols.lib; + +import org.apache.commons.configuration2.HierarchicalConfiguration; +import org.apache.commons.configuration2.ex.ConfigurationException; +import org.apache.commons.configuration2.tree.ImmutableNode; +import org.apache.james.protocols.api.ClientAuth; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SslConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(SslConfig.class); + + public static SslConfig parse(HierarchicalConfiguration<ImmutableNode> config) throws ConfigurationException { + boolean useStartTLS = config.getBoolean("tls.[@startTLS]", false); + boolean useSSL = config.getBoolean("tls.[@socketTLS]", false); + + ClientAuth clientAuth; + if (config.getProperty("tls.clientAuth") != null || config.getKeys("tls.clientAuth").hasNext()) { + clientAuth = ClientAuth.NEED; + } else { + clientAuth = ClientAuth.NONE; + } + + if (useSSL && useStartTLS) { + throw new ConfigurationException("startTLS is only supported when using plain sockets"); + } + + if (useStartTLS || useSSL) { + String[] enabledCipherSuites = config.getStringArray("tls.supportedCipherSuites.cipherSuite"); + String keystore = config.getString("tls.keystore", null); + String privateKey = config.getString("tls.privateKey", null); + String certificates = config.getString("tls.certificates", null); + String keystoreType = config.getString("tls.keystoreType", "JKS"); + if (keystore == null && (privateKey == null || certificates == null)) { + throw new ConfigurationException("keystore or (privateKey and certificates) needs to get configured"); + } + String secret = config.getString("tls.secret", null); + + String truststore = config.getString("tls.clientAuth.truststore", null); + String truststoreType = config.getString("tls.clientAuth.truststoreType", "JKS"); + char[] truststoreSecret = config.getString("tls.clientAuth.truststoreSecret", "").toCharArray(); + LOGGER.info("TLS enabled with auth {} using truststore {}", clientAuth, truststore); + + return new SslConfig(useStartTLS, useSSL, clientAuth, keystore, keystoreType, privateKey, certificates, secret, truststore, truststoreType, enabledCipherSuites, truststoreSecret); + } else { + return new SslConfig(useStartTLS, useSSL, clientAuth, null, null, null, null, null, null, null, null, null); + } + } + + private final boolean useStartTLS; + private final boolean useSSL; + private final ClientAuth clientAuth; + private final String keystore; + private final String keystoreType; + private final String privateKey; + private final String certificates; + private final String secret; + private final String truststore; + private final String truststoreType; + private final String[] enabledCipherSuites; + private final char[] truststoreSecret; + + public SslConfig(boolean useStartTLS, boolean useSSL, ClientAuth clientAuth, String keystore, String keystoreType, String privateKey, + String certificates, String secret, String truststore, String truststoreType, String[] enabledCipherSuites, char[] truststoreSecret) { + this.useStartTLS = useStartTLS; + this.useSSL = useSSL; + this.clientAuth = clientAuth; + this.keystore = keystore; + this.keystoreType = keystoreType; + this.privateKey = privateKey; + this.certificates = certificates; + this.secret = secret; + this.truststore = truststore; + this.truststoreType = truststoreType; + this.enabledCipherSuites = enabledCipherSuites; + this.truststoreSecret = truststoreSecret; + } + + public ClientAuth getClientAuth() { + return clientAuth; + } + + public boolean useStartTLS() { + return useStartTLS; + } + + public String[] getEnabledCipherSuites() { + return enabledCipherSuites; + } + + public boolean useSSL() { + return useSSL; + } + + public String getKeystore() { + return keystore; + } + + public String getKeystoreType() { + return keystoreType; + } + + public String getPrivateKey() { + return privateKey; + } + + public String getCertificates() { + return certificates; + } + + public String getSecret() { + return secret; + } + + public String getTruststore() { + return truststore; + } + + public String getTruststoreType() { + return truststoreType; + } + + public char[] getTruststoreSecret() { + return truststoreSecret; + } +} diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java index de8b5f2397..3935072f87 100644 --- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java +++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java @@ -31,8 +31,6 @@ import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.management.MBeanServer; import javax.management.ObjectName; -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509ExtendedKeyManager; import org.apache.commons.configuration2.Configuration; import org.apache.commons.configuration2.HierarchicalConfiguration; @@ -41,7 +39,8 @@ import org.apache.commons.configuration2.tree.ImmutableNode; import org.apache.commons.lang3.StringUtils; import org.apache.james.filesystem.api.FileSystem; import org.apache.james.lifecycle.api.Configurable; -import org.apache.james.protocols.api.ClientAuth; +import org.apache.james.protocols.lib.LegacyJavaEncryptionFactory; +import org.apache.james.protocols.lib.SslConfig; import org.apache.james.protocols.lib.jmx.ServerMBean; import org.apache.james.protocols.netty.AbstractAsyncServer; import org.apache.james.protocols.netty.AbstractChannelPipelineFactory; @@ -57,8 +56,6 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelOption; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; -import nl.altindag.ssl.SSLFactory; -import nl.altindag.ssl.util.PemUtils; /** @@ -90,31 +87,13 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe protected int connPerIP; - private boolean useStartTLS; - private boolean useSSL; - - private ClientAuth clientAuth; - protected int connectionLimit; private String helloName; - private String keystore; - private String keystoreType; - private String privateKey; - private String certificates; - - private String secret; - - private String truststore; - private String truststoreType; - private char[] truststoreSecret; - + private SslConfig sslConfig; protected Encryption encryption; - - private String[] enabledCipherSuites; - private ChannelHandlerFactory frameHandlerFactory; private EventExecutorGroup executorGroup; @@ -231,35 +210,7 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe } } - useStartTLS = config.getBoolean("tls.[@startTLS]", false); - useSSL = config.getBoolean("tls.[@socketTLS]", false); - - if (config.getProperty("tls.clientAuth") != null || config.getKeys("tls.clientAuth").hasNext()) { - clientAuth = ClientAuth.NEED; - } else { - clientAuth = ClientAuth.NONE; - } - - if (useSSL && useStartTLS) { - throw new ConfigurationException("startTLS is only supported when using plain sockets"); - } - - if (useStartTLS || useSSL) { - enabledCipherSuites = config.getStringArray("tls.supportedCipherSuites.cipherSuite"); - keystore = config.getString("tls.keystore", null); - privateKey = config.getString("tls.privateKey", null); - certificates = config.getString("tls.certificates", null); - keystoreType = config.getString("tls.keystoreType", "JKS"); - if (keystore == null && (privateKey == null || certificates == null)) { - throw new ConfigurationException("keystore or (privateKey and certificates) needs to get configured"); - } - secret = config.getString("tls.secret", null); - - truststore = config.getString("tls.clientAuth.truststore", null); - truststoreType = config.getString("tls.clientAuth.truststoreType", "JKS"); - truststoreSecret = config.getString("tls.clientAuth.truststoreSecret", "").toCharArray(); - LOGGER.info("TLS enabled with auth {} using truststore {}", clientAuth, truststore); - } + sslConfig = SslConfig.parse(config); Optional.ofNullable(config.getBoolean("gracefulShutdown", null)).ifPresent(this::setGracefulShutdown); @@ -301,10 +252,6 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe return port; } - public boolean useSSL() { - return useSSL; - } - @PreDestroy public final void destroy() { @@ -398,43 +345,9 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe * @throws Exception */ protected void buildSSLContext() throws Exception { - if (useStartTLS || useSSL) { - SSLFactory.Builder sslFactoryBuilder = SSLFactory.builder() - .withSslContextAlgorithm("TLS"); - if (keystore != null) { - char[] passwordAsCharArray = Optional.ofNullable(secret) - .orElse("") - .toCharArray(); - sslFactoryBuilder.withIdentityMaterial( - fileSystem.getFile(keystore).toPath(), - passwordAsCharArray, - passwordAsCharArray, - keystoreType); - } else { - X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial( - fileSystem.getResource(certificates), - fileSystem.getResource(privateKey), - Optional.ofNullable(secret) - .map(String::toCharArray) - .orElse(null)); - - sslFactoryBuilder.withIdentityMaterial(keyManager); - } - - if (clientAuth != null && truststore != null) { - sslFactoryBuilder.withTrustMaterial( - fileSystem.getFile(truststore).toPath(), - truststoreSecret, - truststoreType); - } - - SSLContext context = sslFactoryBuilder.build().getSslContext(); - - if (useStartTLS) { - encryption = Encryption.createStartTls(context, enabledCipherSuites, clientAuth); - } else { - encryption = Encryption.createTls(context, enabledCipherSuites, clientAuth); - } + if (sslConfig.useSSL() || sslConfig.useStartTLS()) { + encryption = new LegacyJavaEncryptionFactory(fileSystem, sslConfig) + .create(); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
