This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 9f1e8e2  JAMES-3672 : Support TLS authentication via client certificate
9f1e8e2 is described below

commit 9f1e8e200a6340854293871843eea656dadc7623
Author: Karsten Otto <[email protected]>
AuthorDate: Tue Nov 16 10:45:44 2021 +0100

    JAMES-3672 : Support TLS authentication via client certificate
---
 .../org/apache/james/protocols/api/ClientAuth.java |  30 +++
 .../org/apache/james/protocols/api/Encryption.java |  54 ++++-
 .../AbstractSSLAwareChannelPipelineFactory.java    |  31 +--
 .../netty/BasicChannelUpstreamHandler.java         |   8 +-
 .../apache/james/protocols/netty/NettyServer.java  |  17 +-
 .../docs/modules/ROOT/pages/configure/ssl.adoc     |  22 ++
 .../apache/james/imapserver/netty/IMAPServer.java  |   4 +-
 .../netty/ImapChannelUpstreamHandler.java          |  17 +-
 .../james/imapserver/netty/NettyImapSession.java   |  18 +-
 server/protocols/protocols-library/pom.xml         |   5 +
 .../lib/netty/AbstractConfigurableAsyncServer.java |  52 +++--
 ...bstractExecutorAwareChannelPipelineFactory.java |   5 +-
 .../lib/AbstractConfigurableAsyncServerTest.java   | 255 +++++++++++++++++++++
 .../protocols-library/src/test/resources/keystore  | Bin 0 -> 2245 bytes
 .../src/test/resources/testServerDefaults.xml      |   2 +
 .../src/test/resources/testServerDisabled.xml      |   2 +
 .../src/test/resources/testServerPlain.xml         |   9 +
 .../src/test/resources/testServerStartTLS.xml      |   9 +
 .../src/test/resources/testServerTLS.xml           |   9 +
 .../test/resources/testServerTLSDefaultAuth.xml    |  11 +
 .../src/test/resources/testServerTLSNeedAuth.xml   |  15 ++
 .../netty/ManageSieveChannelUpstreamHandler.java   |  28 +--
 .../managesieveserver/netty/ManageSieveServer.java |  11 +-
 src/site/xdoc/server/config-ssl-tls.xml            |  23 ++
 24 files changed, 515 insertions(+), 122 deletions(-)

diff --git 
a/protocols/api/src/main/java/org/apache/james/protocols/api/ClientAuth.java 
b/protocols/api/src/main/java/org/apache/james/protocols/api/ClientAuth.java
new file mode 100644
index 0000000..288096e
--- /dev/null
+++ b/protocols/api/src/main/java/org/apache/james/protocols/api/ClientAuth.java
@@ -0,0 +1,30 @@
+/****************************************************************
+ * 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.api;
+
+/** Options for certificate based client authentication of a TLS connection. */
+public enum ClientAuth {
+    /** Require a mandatory client certificate, and reject the connection if 
none provided. */
+    NEED,
+    /** Request an optional client certificate, but continue if none provided. 
*/
+    WANT,
+    /** Do not request any client certificate. */
+    NONE
+}
diff --git 
a/protocols/api/src/main/java/org/apache/james/protocols/api/Encryption.java 
b/protocols/api/src/main/java/org/apache/james/protocols/api/Encryption.java
index 8b0ed11..e061890 100644
--- a/protocols/api/src/main/java/org/apache/james/protocols/api/Encryption.java
+++ b/protocols/api/src/main/java/org/apache/james/protocols/api/Encryption.java
@@ -20,6 +20,9 @@
 package org.apache.james.protocols.api;
 
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+import org.apache.commons.lang3.ArrayUtils;
 
 /**
  * This class should be used to setup encrypted protocol handling
@@ -29,15 +32,17 @@ public final class Encryption {
     private final SSLContext context;
     private final boolean starttls;
     private final String[] enabledCipherSuites;
+    private final ClientAuth clientAuth;
 
-    private Encryption(SSLContext context, boolean starttls, String[] 
enabledCipherSuites) {
+    private Encryption(SSLContext context, boolean starttls, String[] 
enabledCipherSuites, ClientAuth clientAuth) {
         this.context = context;
         this.starttls = starttls;
         this.enabledCipherSuites = enabledCipherSuites;
+        this.clientAuth = clientAuth;
     }
 
     public static Encryption createTls(SSLContext context) {
-        return createTls(context, null);
+        return createTls(context, null, ClientAuth.NONE);
     }
 
     /**
@@ -46,13 +51,15 @@ public final class Encryption {
      *
      * @param enabledCipherSuites
      *            or <code>null</code> if all Ciphersuites should be allowed
+     * @param clientAuth
+     *            specifies certificate based client authentication mode
      */
-    public static Encryption createTls(SSLContext context, String[] 
enabledCipherSuites) {
-        return new Encryption(context, false, enabledCipherSuites);
+    public static Encryption createTls(SSLContext context, String[] 
enabledCipherSuites, ClientAuth clientAuth) {
+        return new Encryption(context, false, enabledCipherSuites, clientAuth);
     }
 
     public static Encryption createStartTls(SSLContext context) {
-        return createStartTls(context, null);
+        return createStartTls(context, null, ClientAuth.NONE);
     }
 
     /**
@@ -61,9 +68,11 @@ public final class Encryption {
      *
      * @param enabledCipherSuites
      *            or <code>null</code> if all Ciphersuites should be allowed
+     * @param clientAuth
+     *            specifies certificate based client authentication mode
      */
-    public static Encryption createStartTls(SSLContext context, String[] 
enabledCipherSuites) {
-        return new Encryption(context, true, enabledCipherSuites);
+    public static Encryption createStartTls(SSLContext context, String[] 
enabledCipherSuites, ClientAuth clientAuth) {
+        return new Encryption(context, true, enabledCipherSuites, clientAuth);
     }
 
     /**
@@ -94,4 +103,35 @@ public final class Encryption {
     public String[] getEnabledCipherSuites() {
         return enabledCipherSuites;
     }
+
+    /**
+     * Return the client authentication mode for the {@link Encryption}
+     * @return authentication mode
+     */
+    public ClientAuth getClientAuth() {
+        return clientAuth;
+    }
+
+    /**
+     * Create a new {@link SSLEngine} configured according to this class.
+     * @return sslengine
+     */
+    public SSLEngine createSSLEngine() {
+        SSLEngine engine = context.createSSLEngine();
+
+        // We need to copy the String array because of possible security 
issues.
+        // See https://issues.apache.org/jira/browse/PROTOCOLS-18
+        String[] cipherSuites = ArrayUtils.clone(enabledCipherSuites);
+
+        if (cipherSuites != null && cipherSuites.length > 0) {
+            engine.setEnabledCipherSuites(cipherSuites);
+        }
+        if (ClientAuth.NEED.equals(clientAuth)) {
+            engine.setNeedClientAuth(true);
+        }
+        if (ClientAuth.WANT.equals(clientAuth)) {
+            engine.setWantClientAuth(true);
+        }
+        return engine;
+    }
 }
diff --git 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
index 4631f28..0af923d 100644
--- 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
+++ 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
@@ -18,10 +18,10 @@
  ****************************************************************/
 package org.apache.james.protocols.netty;
 
-import javax.net.ssl.SSLContext;
+
 import javax.net.ssl.SSLEngine;
 
-import org.apache.commons.lang3.ArrayUtils;
+import org.apache.james.protocols.api.Encryption;
 import org.jboss.netty.channel.ChannelPipeline;
 import org.jboss.netty.channel.group.ChannelGroup;
 import org.jboss.netty.handler.execution.ExecutionHandler;
@@ -33,8 +33,7 @@ import org.jboss.netty.util.HashedWheelTimer;
  */
 public abstract class AbstractSSLAwareChannelPipelineFactory extends 
AbstractChannelPipelineFactory {
 
-    
-    private String[] enabledCipherSuites = null;
+    private Encryption secure;
 
     public AbstractSSLAwareChannelPipelineFactory(int timeout,
                                                   int maxConnections, int 
maxConnectsPerIp, ChannelGroup group, ExecutionHandler eHandler,
@@ -43,13 +42,11 @@ public abstract class 
AbstractSSLAwareChannelPipelineFactory extends AbstractCha
     }
 
     public AbstractSSLAwareChannelPipelineFactory(int timeout,
-            int maxConnections, int maxConnectsPerIp, ChannelGroup group, 
String[] enabledCipherSuites, ExecutionHandler eHandler,
-            ChannelHandlerFactory frameHandlerFactory, HashedWheelTimer 
hashedWheelTimer) {
+            int maxConnections, int maxConnectsPerIp, ChannelGroup group, 
Encryption secure,
+            ExecutionHandler eHandler, ChannelHandlerFactory 
frameHandlerFactory, HashedWheelTimer hashedWheelTimer) {
         this(timeout, maxConnections, maxConnectsPerIp, group, eHandler, 
frameHandlerFactory, hashedWheelTimer);
-        
-        // We need to copy the String array because of possible security 
issues.
-        // See https://issues.apache.org/jira/browse/PROTOCOLS-18
-        this.enabledCipherSuites = ArrayUtils.clone(enabledCipherSuites);
+
+        this.secure = secure;
     }
 
     @Override
@@ -59,11 +56,8 @@ public abstract class AbstractSSLAwareChannelPipelineFactory 
extends AbstractCha
         if (isSSLSocket()) {
             // We need to set clientMode to false.
             // See https://issues.apache.org/jira/browse/JAMES-1025
-            SSLEngine engine = getSSLContext().createSSLEngine();
+            SSLEngine engine = secure.createSSLEngine();
             engine.setUseClientMode(false);
-            if (enabledCipherSuites != null && enabledCipherSuites.length > 0) 
{
-                engine.setEnabledCipherSuites(enabledCipherSuites);
-            }
             pipeline.addFirst(HandlerConstants.SSL_HANDLER, new 
SslHandler(engine));
         }
         return pipeline;
@@ -72,10 +66,7 @@ public abstract class AbstractSSLAwareChannelPipelineFactory 
extends AbstractCha
     /**
      * Return if the socket is using SSL/TLS
      */
-    protected abstract boolean isSSLSocket();
-    
-    /**
-     * Return the SSL context
-     */
-    protected abstract SSLContext getSSLContext();
+    protected boolean isSSLSocket() {
+        return secure != null && secure.getContext() != null && 
!secure.isStartTLS();
+    }
 }
diff --git 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelUpstreamHandler.java
 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelUpstreamHandler.java
index 1c8af40..eef4a30 100644
--- 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelUpstreamHandler.java
+++ 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelUpstreamHandler.java
@@ -210,13 +210,9 @@ public class BasicChannelUpstreamHandler extends 
SimpleChannelUpstreamHandler {
     protected ProtocolSession createSession(ChannelHandlerContext ctx) throws 
Exception {
         SSLEngine engine = null;
         if (secure != null) {
-            engine = secure.getContext().createSSLEngine();
-            String[] enabledCipherSuites = secure.getEnabledCipherSuites();
-            if (enabledCipherSuites != null && enabledCipherSuites.length > 0) 
{
-                engine.setEnabledCipherSuites(enabledCipherSuites);
-            }
+            engine = secure.createSSLEngine();
         }
-        
+
         return protocol.newSession(new 
NettyProtocolTransport(ctx.getChannel(), engine));
     }
 
diff --git 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
index 27ca9a8..ab80c96 100644
--- 
a/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
+++ 
b/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
@@ -21,7 +21,6 @@ package org.apache.james.protocols.netty;
 import java.util.Optional;
 
 import javax.inject.Inject;
-import javax.net.ssl.SSLContext;
 
 import org.apache.james.protocols.api.Encryption;
 import org.apache.james.protocols.api.Protocol;
@@ -121,7 +120,7 @@ public class NettyServer extends AbstractAsyncServer {
             maxCurConnections,
             maxCurConnectionsPerIP,
             group,
-            secure != null ? secure.getEnabledCipherSuites() : null,
+            secure,
             eHandler,
             getFrameHandlerFactory(),
             hashedWheelTimer) {
@@ -130,20 +129,6 @@ public class NettyServer extends AbstractAsyncServer {
             protected ChannelUpstreamHandler createHandler() {
                 return coreHandler;
             }
-
-            @Override
-            protected boolean isSSLSocket() {
-                return getSSLContext() != null && secure != null && 
!secure.isStartTLS();
-            }
-
-            @Override
-            protected SSLContext getSSLContext() {
-                if (secure != null) {
-                    return secure.getContext();
-                } else  {
-                    return null;
-                }
-            }
         };
 
     }
diff --git 
a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/ssl.adoc 
b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/ssl.adoc
index 140d5d4..84da1ec 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/ssl.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/ssl.adoc
@@ -84,6 +84,28 @@ Please note `JKS` keystore format is also supported (default 
value if no keystor
 </tls>
 ....
 
+
+=== Client authentication via certificates
+
+When you enable TLS, you may also configure the server to require a client 
certificate for authentication:
+
+....
+<tls socketTLS="false" startTLS="true">
+  <keystore>file://conf/keystore</keystore>
+  <keystoreType>JKS</keystoreType>
+  <secret>yoursecret</secret>
+
+  <clientAuth>
+    <truststore>file://conf/truststore</truststore>
+    <truststoreType>JKS</truststoreType>
+    <truststoreSecret>yoursecret</truststoreSecret>
+  </clientAuth>
+</tls>
+....
+
+James verifies client certificates against the provided truststore. You can 
fill it with trusted peer certificates directly, or an issuer certificate (CA) 
if you trust all certificates created by it. If you omit the truststore 
configuration, James will use the Java default truststore instead, effectively 
trusting any known CA.
+
+
 == Creating your own PEM keys
 
 The following commands can be used to create self signed PEM keys:
diff --git 
a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
 
b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
index 580484f..dd8a4e5 100644
--- 
a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
+++ 
b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
@@ -177,7 +177,7 @@ public class IMAPServer extends 
AbstractConfigurableAsyncServer implements ImapC
                 if (secure != null && !secure.isStartTLS()) {
                     // We need to set clientMode to false.
                     // See https://issues.apache.org/jira/browse/JAMES-1025
-                    SSLEngine engine = secure.getContext().createSSLEngine();
+                    SSLEngine engine = secure.createSSLEngine();
                     engine.setUseClientMode(false);
                     pipeline.addFirst(SSL_HANDLER, new SslHandler(engine));
 
@@ -210,7 +210,7 @@ public class IMAPServer extends 
AbstractConfigurableAsyncServer implements ImapC
         ImapChannelUpstreamHandler coreHandler;
         Encryption secure = getEncryption();
         if (secure != null && secure.isStartTLS()) {
-           coreHandler = new ImapChannelUpstreamHandler(hello, processor, 
encoder, compress, plainAuthDisallowed, secure.getContext(), 
getEnabledCipherSuites(), imapMetrics);
+           coreHandler = new ImapChannelUpstreamHandler(hello, processor, 
encoder, compress, plainAuthDisallowed, secure, imapMetrics);
         } else {
            coreHandler = new ImapChannelUpstreamHandler(hello, processor, 
encoder, compress, plainAuthDisallowed, imapMetrics);
         }
diff --git 
a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
 
b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
index d1b50c5..df9d392 100644
--- 
a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
+++ 
b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/ImapChannelUpstreamHandler.java
@@ -23,8 +23,6 @@ import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.NoSuchElementException;
 
-import javax.net.ssl.SSLContext;
-
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.ImapMessage;
 import org.apache.james.imap.api.ImapSessionState;
@@ -36,6 +34,7 @@ import org.apache.james.imap.encode.ImapResponseComposer;
 import org.apache.james.imap.encode.base.ImapResponseComposerImpl;
 import org.apache.james.imap.main.ResponseEncoder;
 import org.apache.james.metrics.api.Metric;
+import org.apache.james.protocols.api.Encryption;
 import org.apache.james.util.MDCBuilder;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.Channel;
@@ -59,9 +58,7 @@ public class ImapChannelUpstreamHandler extends 
SimpleChannelUpstreamHandler imp
 
     private final String hello;
 
-    private final String[] enabledCipherSuites;
-
-    private final SSLContext context;
+    private final Encryption secure;
 
     private final boolean compress;
 
@@ -78,17 +75,15 @@ public class ImapChannelUpstreamHandler extends 
SimpleChannelUpstreamHandler imp
     
     public ImapChannelUpstreamHandler(String hello, ImapProcessor processor, 
ImapEncoder encoder, boolean compress,
                                       boolean plainAuthDisallowed, ImapMetrics 
imapMetrics) {
-        this(hello, processor, encoder, compress, plainAuthDisallowed, null, 
null, imapMetrics);
+        this(hello, processor, encoder, compress, plainAuthDisallowed, null, 
imapMetrics);
     }
 
     public ImapChannelUpstreamHandler(String hello, ImapProcessor processor, 
ImapEncoder encoder, boolean compress,
-                                      boolean plainAuthDisallowed, SSLContext 
context, String[] enabledCipherSuites,
-                                      ImapMetrics imapMetrics) {
+                                      boolean plainAuthDisallowed, Encryption 
secure, ImapMetrics imapMetrics) {
         this.hello = hello;
         this.processor = processor;
         this.encoder = encoder;
-        this.context = context;
-        this.enabledCipherSuites = enabledCipherSuites;
+        this.secure = secure;
         this.compress = compress;
         this.plainAuthDisallowed = plainAuthDisallowed;
         this.imapConnectionsMetric = imapMetrics.getConnectionsMetric();
@@ -97,7 +92,7 @@ public class ImapChannelUpstreamHandler extends 
SimpleChannelUpstreamHandler imp
 
     @Override
     public void channelBound(final ChannelHandlerContext ctx, 
ChannelStateEvent e) throws Exception {
-        ImapSession imapsession = new NettyImapSession(ctx.getChannel(), 
context, enabledCipherSuites, compress, plainAuthDisallowed,
+        ImapSession imapsession = new NettyImapSession(ctx.getChannel(), 
secure, compress, plainAuthDisallowed,
             SessionId.generate());
         MDCBuilder boundMDC = IMAPMDCContext.boundMDC(ctx);
         imapsession.setAttribute(MDC_KEY, boundMDC);
diff --git 
a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/NettyImapSession.java
 
b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/NettyImapSession.java
index 30718cd..579348c 100644
--- 
a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/NettyImapSession.java
+++ 
b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/NettyImapSession.java
@@ -21,12 +21,11 @@ package org.apache.james.imapserver.netty;
 import java.util.HashMap;
 import java.util.Map;
 
-import javax.net.ssl.SSLContext;
-
 import org.apache.james.imap.api.ImapSessionState;
 import org.apache.james.imap.api.process.ImapLineHandler;
 import org.apache.james.imap.api.process.ImapSession;
 import org.apache.james.imap.api.process.SelectedMailbox;
+import org.apache.james.protocols.api.Encryption;
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.handler.codec.compression.ZlibDecoder;
 import org.jboss.netty.handler.codec.compression.ZlibEncoder;
@@ -37,8 +36,7 @@ public class NettyImapSession implements ImapSession, 
NettyConstants {
     private ImapSessionState state = ImapSessionState.NON_AUTHENTICATED;
     private SelectedMailbox selectedMailbox;
     private final Map<String, Object> attributesByKey = new HashMap<>();
-    private final SSLContext sslContext;
-    private final String[] enabledCipherSuites;
+    private final Encryption secure;
     private final boolean compress;
     private final Channel channel;
     private int handlerCount;
@@ -46,10 +44,9 @@ public class NettyImapSession implements ImapSession, 
NettyConstants {
     private final SessionId sessionId;
     private boolean needsCommandInjectionDetection;
 
-    public NettyImapSession(Channel channel, SSLContext sslContext, String[] 
enabledCipherSuites, boolean compress, boolean plainAuthDisallowed, SessionId 
sessionId) {
+    public NettyImapSession(Channel channel, Encryption secure, boolean 
compress, boolean plainAuthDisallowed, SessionId sessionId) {
         this.channel = channel;
-        this.sslContext = sslContext;
-        this.enabledCipherSuites = enabledCipherSuites;
+        this.secure = secure;
         this.compress = compress;
         this.plainAuthDisallowed = plainAuthDisallowed;
         this.sessionId = sessionId;
@@ -138,11 +135,8 @@ public class NettyImapSession implements ImapSession, 
NettyConstants {
         }
         channel.setReadable(false);
 
-        SslHandler filter = new SslHandler(sslContext.createSSLEngine(), 
false);
+        SslHandler filter = new SslHandler(secure.createSSLEngine(), false);
         filter.getEngine().setUseClientMode(false);
-        if (enabledCipherSuites != null && enabledCipherSuites.length > 0) {
-            filter.getEngine().setEnabledCipherSuites(enabledCipherSuites);
-        }
         channel.getPipeline().addFirst(SSL_HANDLER, filter);
 
         channel.setReadable(true);
@@ -152,7 +146,7 @@ public class NettyImapSession implements ImapSession, 
NettyConstants {
 
     @Override
     public boolean supportStartTLS() {
-        return sslContext != null;
+        return secure != null && secure.getContext() != null;
     }
 
     @Override
diff --git a/server/protocols/protocols-library/pom.xml 
b/server/protocols/protocols-library/pom.xml
index bab78b2..b895819 100644
--- a/server/protocols/protocols-library/pom.xml
+++ b/server/protocols/protocols-library/pom.xml
@@ -75,6 +75,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>io.github.hakky54</groupId>
             <artifactId>sslcontext-kickstart-for-pem</artifactId>
         </dependency>
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 7f1764e..52887da 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
@@ -43,6 +43,7 @@ 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.api.Encryption;
 import org.apache.james.protocols.lib.jmx.ServerMBean;
 import org.apache.james.protocols.netty.AbstractAsyncServer;
@@ -101,6 +102,8 @@ public abstract class AbstractConfigurableAsyncServer 
extends AbstractAsyncServe
     private boolean useStartTLS;
     private boolean useSSL;
 
+    private ClientAuth clientAuth;
+
     protected int connectionLimit;
 
     private String helloName;
@@ -112,6 +115,10 @@ public abstract class AbstractConfigurableAsyncServer 
extends AbstractAsyncServe
 
     private String secret;
 
+    private String truststore;
+    private String truststoreType;
+    private char[] truststoreSecret;
+
     protected Encryption encryption;
 
     protected String jmxName;
@@ -245,6 +252,12 @@ 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");
         }
@@ -260,6 +273,11 @@ public abstract class AbstractConfigurableAsyncServer 
extends AbstractAsyncServe
             }
             secret = config.getString("tls.secret", null);
             x509Algorithm = config.getString("tls.algorithm", 
defaultX509algorithm);
+
+            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);
         }
 
         doConfigure(config);
@@ -421,12 +439,22 @@ public abstract class AbstractConfigurableAsyncServer 
extends AbstractAsyncServe
 
                     sslFactoryBuilder.withIdentityMaterial(keyManager);
                 }
+
+                if (clientAuth != null) {
+                    if (truststore != null) {
+                        sslFactoryBuilder.withTrustMaterial(
+                            fileSystem.getFile(truststore).toPath(),
+                            truststoreSecret,
+                            truststoreType);
+                    }
+                }
+
                 SSLContext context = sslFactoryBuilder.build().getSslContext();
 
                 if (useStartTLS) {
-                    encryption = Encryption.createStartTls(context, 
enabledCipherSuites);
+                    encryption = Encryption.createStartTls(context, 
enabledCipherSuites, clientAuth);
                 } else {
-                    encryption = Encryption.createTls(context, 
enabledCipherSuites);
+                    encryption = Encryption.createTls(context, 
enabledCipherSuites, clientAuth);
                 }
             } finally {
                 if (fis != null) {
@@ -490,10 +518,6 @@ public abstract class AbstractConfigurableAsyncServer 
extends AbstractAsyncServe
      */
     protected abstract String getDefaultJMXName();
 
-    protected String[] getEnabledCipherSuites() {
-        return enabledCipherSuites;
-    }
-
     @Override
     public boolean isStarted() {
         return isBound();
@@ -580,21 +604,7 @@ public abstract class AbstractConfigurableAsyncServer 
extends AbstractAsyncServe
     @Override
     protected ChannelPipelineFactory createPipelineFactory(ChannelGroup group) 
{
         return new AbstractExecutorAwareChannelPipelineFactory(getTimeout(), 
connectionLimit, connPerIP, group,
-            enabledCipherSuites, getExecutionHandler(), 
getFrameHandlerFactory(), timer) {
-            @Override
-            protected SSLContext getSSLContext() {
-                if (getEncryption() == null) {
-                    return null;
-                } else {
-                    return getEncryption().getContext();
-                }
-            }
-
-            @Override
-            protected boolean isSSLSocket() {
-                return getEncryption() != null && 
!getEncryption().isStartTLS();
-            }
-
+            getEncryption(), getExecutionHandler(), getFrameHandlerFactory(), 
timer) {
 
             @Override
             protected ChannelUpstreamHandler createHandler() {
diff --git 
a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractExecutorAwareChannelPipelineFactory.java
 
b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractExecutorAwareChannelPipelineFactory.java
index ee32379..06c6db2 100644
--- 
a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractExecutorAwareChannelPipelineFactory.java
+++ 
b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractExecutorAwareChannelPipelineFactory.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james.protocols.lib.netty;
 
+import org.apache.james.protocols.api.Encryption;
 import org.apache.james.protocols.netty.AbstractSSLAwareChannelPipelineFactory;
 import org.apache.james.protocols.netty.ChannelHandlerFactory;
 import org.apache.james.protocols.netty.HandlerConstants;
@@ -34,10 +35,10 @@ import org.jboss.netty.util.HashedWheelTimer;
 public abstract class AbstractExecutorAwareChannelPipelineFactory extends 
AbstractSSLAwareChannelPipelineFactory {
 
     public AbstractExecutorAwareChannelPipelineFactory(int timeout, int 
maxConnections, int maxConnectsPerIp,
-                                                       ChannelGroup group, 
String[] enabledCipherSuites,
+                                                       ChannelGroup group, 
Encryption encryption,
                                                        ExecutionHandler 
eHandler, ChannelHandlerFactory frameHandlerFactory,
                                                        HashedWheelTimer 
hashedWheelTimer) {
-        super(timeout, maxConnections, maxConnectsPerIp, group, 
enabledCipherSuites, eHandler, frameHandlerFactory, hashedWheelTimer);
+        super(timeout, maxConnections, maxConnectsPerIp, group, encryption, 
eHandler, frameHandlerFactory, hashedWheelTimer);
     }
     
     @Override
diff --git 
a/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java
 
b/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java
new file mode 100644
index 0000000..481c958
--- /dev/null
+++ 
b/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java
@@ -0,0 +1,255 @@
+/****************************************************************
+ * 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.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.protocols.api.ClientAuth;
+import org.apache.james.protocols.api.Encryption;
+import org.apache.james.protocols.lib.mock.ConfigLoader;
+import org.apache.james.protocols.lib.netty.AbstractConfigurableAsyncServer;
+import org.apache.james.protocols.netty.ChannelHandlerFactory;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AbstractConfigurableAsyncServerTest {
+
+    private static class MemoryFileSystem implements FileSystem {
+
+        private final Map<String, String> fileResources = new HashMap<>();
+
+        @Override
+        public InputStream getResource(String url) throws IOException {
+            String resourceName = fileResources.get(url);
+            if (resourceName != null) {
+                InputStream resourceStream = 
ClassLoader.getSystemResourceAsStream(resourceName);
+                if (resourceStream != null) {
+                    return resourceStream;
+                }
+            }
+            throw new FileNotFoundException(url);
+        }
+
+        @Override
+        public File getFile(String fileURL) throws FileNotFoundException {
+            String resourceName = fileResources.get(fileURL);
+            if (resourceName != null) {
+                URL resource = ClassLoader.getSystemResource(resourceName);
+                if (resource != null) {
+                    return new File(resource.getPath());
+                }
+            }
+            throw new FileNotFoundException(fileURL);
+        }
+
+        @Override
+        public File getBasedir() {
+            throw new NotImplementedException("getBasedir");
+        }
+
+        public void put(String fileUrl, String resourceName) {
+            fileResources.put(fileUrl, resourceName);
+        }
+    }
+
+    private static class TestableConfigurableAsyncServer extends 
AbstractConfigurableAsyncServer {
+        @Override
+        public String getServiceType () {
+            return "Test Service";
+        }
+
+        @Override
+        protected int getDefaultPort () {
+            return 12345;
+        }
+
+        @Override
+        protected String getDefaultJMXName () {
+            return "testserver";
+        }
+
+        @Override
+        protected ChannelHandlerFactory createFrameHandlerFactory () {
+            return null;
+        }
+
+        @Override
+        protected ChannelUpstreamHandler createCoreHandler () {
+            return null;
+        }
+
+        // test accessors
+
+        public int getConnPerIp () {
+            return connPerIP;
+        }
+
+        public String getJmxName() {
+            return jmxName;
+        }
+
+        @Override
+        public Encryption getEncryption() {
+            return encryption;
+        }
+
+        @Override
+        public void buildSSLContext() throws Exception {
+            super.buildSSLContext();
+        }
+    }
+
+    private void initTestServer(String configFile) throws Exception {
+        testServer = new TestableConfigurableAsyncServer();
+        testServer.setFileSystem(memoryFileSystem);
+        
testServer.configure(ConfigLoader.getConfig(ClassLoader.getSystemResourceAsStream(configFile)));
+    }
+
+    private MemoryFileSystem memoryFileSystem;
+    private TestableConfigurableAsyncServer testServer;
+
+    @BeforeEach
+    public void setUp() {
+        memoryFileSystem = new MemoryFileSystem();
+    }
+
+    @Test
+    public void testServerDisabled() throws Exception {
+        initTestServer("testServerDisabled.xml");
+        assertThat(testServer.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        initTestServer("testServerDefaults.xml");
+        assertThat(testServer.isEnabled()).isTrue();
+
+        // NOTE: bind address/port etc. not exposed, cannot test without bind()
+
+        
assertThat(testServer.getJmxName()).isEqualTo(testServer.getDefaultJMXName());
+
+        assertThat(testServer.getHelloName()).isEqualTo(getLocalHostName());
+
+        
assertThat(testServer.getTimeout()).isEqualTo(AbstractConfigurableAsyncServer.DEFAULT_TIMEOUT);
+        
assertThat(testServer.getBacklog()).isEqualTo(AbstractConfigurableAsyncServer.DEFAULT_BACKLOG);
+
+        assertThat(testServer.getMaximumConcurrentConnections()).isEqualTo(0); 
// no default limit
+        assertThat(testServer.getConnPerIp()).isEqualTo(0); // no default limit
+
+        testServer.buildSSLContext();
+        assertThat(testServer.getEncryption()).isNull(); // no TLS by default
+    }
+
+    @Test
+    public void testServerPlain() throws Exception {
+        initTestServer("testServerPlain.xml");
+        assertThat(testServer.isEnabled()).isTrue();
+
+        // NOTE: bind address/port etc. not exposed, cannot test without bind()
+
+        assertThat(testServer.getJmxName()).isEqualTo("testserver-custom");
+
+        assertThat(testServer.getHelloName()).isEqualTo("custom-mailer");
+
+        assertThat(testServer.getTimeout()).isEqualTo(360);
+        assertThat(testServer.getBacklog()).isEqualTo(150);
+
+        
assertThat(testServer.getMaximumConcurrentConnections()).isEqualTo(100);
+        assertThat(testServer.getConnPerIp()).isEqualTo(5);
+
+        testServer.buildSSLContext();
+        assertThat(testServer.getEncryption()).isNull(); // no TLS by default
+    }
+
+    @Test
+    public void testServerTLS() throws Exception {
+        memoryFileSystem.put("file://conf/keystore", "keystore");
+
+        initTestServer("testServerTLS.xml");
+        testServer.buildSSLContext();
+
+        assertThat(testServer.getEncryption()).isNotNull();
+        assertThat(testServer.getEncryption().isStartTLS()).isFalse();
+        
assertThat(testServer.getEncryption().getEnabledCipherSuites()).isEmpty(); // 
no default constraints
+        
assertThat(testServer.getEncryption().getClientAuth()).isEqualTo(ClientAuth.NONE);
+        assertThat(testServer.getEncryption().getContext()).isNotNull();
+    }
+
+    @Test
+    public void testServerStartTLS() throws Exception {
+        memoryFileSystem.put("file://conf/keystore", "keystore");
+
+        initTestServer("testServerStartTLS.xml");
+        testServer.buildSSLContext();
+
+        assertThat(testServer.getEncryption()).isNotNull();
+        assertThat(testServer.getEncryption().isStartTLS()).isTrue();
+        
assertThat(testServer.getEncryption().getEnabledCipherSuites()).isEmpty(); // 
no default constraints
+        
assertThat(testServer.getEncryption().getClientAuth()).isEqualTo(ClientAuth.NONE);
+        assertThat(testServer.getEncryption().getContext()).isNotNull();
+    }
+
+    @Test
+    public void testServerTLSNeedClientAuth() throws Exception {
+        memoryFileSystem.put("file://conf/keystore", "keystore");
+        memoryFileSystem.put("file://conf/truststore", "keystore");
+
+        initTestServer("testServerTLSNeedAuth.xml");
+        testServer.buildSSLContext();
+
+        assertThat(testServer.getEncryption()).isNotNull();
+        assertThat(testServer.getEncryption().getClientAuth()).isNotNull();
+        
assertThat(testServer.getEncryption().getClientAuth()).isEqualTo(ClientAuth.NEED);
+    }
+
+    @Test
+    public void testServerTLSDefaultClientAuth() throws Exception {
+        memoryFileSystem.put("file://conf/keystore", "keystore");
+        // memoryFileSystem.put("file://conf/truststore", "keystore");
+
+        initTestServer("testServerTLSDefaultAuth.xml");
+        testServer.buildSSLContext();
+
+        assertThat(testServer.getEncryption()).isNotNull();
+        assertThat(testServer.getEncryption().getClientAuth()).isNotNull();
+        
assertThat(testServer.getEncryption().getClientAuth()).isEqualTo(ClientAuth.NEED);
+    }
+
+    private static String getLocalHostName() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException ue) {
+            return "localhost";
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/protocols-library/src/test/resources/keystore 
b/server/protocols/protocols-library/src/test/resources/keystore
new file mode 100644
index 0000000..536a6c7
Binary files /dev/null and 
b/server/protocols/protocols-library/src/test/resources/keystore differ
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerDefaults.xml 
b/server/protocols/protocols-library/src/test/resources/testServerDefaults.xml
new file mode 100644
index 0000000..91a73bb
--- /dev/null
+++ 
b/server/protocols/protocols-library/src/test/resources/testServerDefaults.xml
@@ -0,0 +1,2 @@
+<testserver enabled="true">
+</testserver>
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerDisabled.xml 
b/server/protocols/protocols-library/src/test/resources/testServerDisabled.xml
new file mode 100644
index 0000000..0f81a30
--- /dev/null
+++ 
b/server/protocols/protocols-library/src/test/resources/testServerDisabled.xml
@@ -0,0 +1,2 @@
+<testserver enabled="false">
+</testserver>
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerPlain.xml 
b/server/protocols/protocols-library/src/test/resources/testServerPlain.xml
new file mode 100644
index 0000000..fe21260
--- /dev/null
+++ b/server/protocols/protocols-library/src/test/resources/testServerPlain.xml
@@ -0,0 +1,9 @@
+<testserver enabled="true">
+    <jmxName>testserver-custom</jmxName>
+    <bind>0.0.0.0:0</bind>
+    <connectionBacklog>150</connectionBacklog>
+    <connectiontimeout>360</connectiontimeout>
+    <connectionLimit>100</connectionLimit>
+    <connectionLimitPerIP>5</connectionLimitPerIP>
+    <helloName autodetect="false">custom-mailer</helloName>
+</testserver>
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerStartTLS.xml 
b/server/protocols/protocols-library/src/test/resources/testServerStartTLS.xml
new file mode 100644
index 0000000..12f1163
--- /dev/null
+++ 
b/server/protocols/protocols-library/src/test/resources/testServerStartTLS.xml
@@ -0,0 +1,9 @@
+<testserver enabled="true">
+    <tls socketTLS="false" startTLS="true">
+        <keystore>file://conf/keystore</keystore>
+        <keystoreType>JKS</keystoreType>
+        <secret>james72laBalle</secret>
+        <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider>
+        <algorithm>SunX509</algorithm>
+    </tls>
+</testserver>
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerTLS.xml 
b/server/protocols/protocols-library/src/test/resources/testServerTLS.xml
new file mode 100644
index 0000000..a616079
--- /dev/null
+++ b/server/protocols/protocols-library/src/test/resources/testServerTLS.xml
@@ -0,0 +1,9 @@
+<testserver enabled="true">
+    <tls socketTLS="true" startTLS="false">
+        <keystore>file://conf/keystore</keystore>
+        <keystoreType>JKS</keystoreType>
+        <secret>james72laBalle</secret>
+        <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider>
+        <algorithm>SunX509</algorithm>
+    </tls>
+</testserver>
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerTLSDefaultAuth.xml
 
b/server/protocols/protocols-library/src/test/resources/testServerTLSDefaultAuth.xml
new file mode 100644
index 0000000..d0fecb3
--- /dev/null
+++ 
b/server/protocols/protocols-library/src/test/resources/testServerTLSDefaultAuth.xml
@@ -0,0 +1,11 @@
+<testserver enabled="true">
+    <tls socketTLS="true" startTLS="false">
+        <keystore>file://conf/keystore</keystore>
+        <keystoreType>JKS</keystoreType>
+        <secret>james72laBalle</secret>
+        <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider>
+        <algorithm>SunX509</algorithm>
+
+        <clientAuth/>
+    </tls>
+</testserver>
diff --git 
a/server/protocols/protocols-library/src/test/resources/testServerTLSNeedAuth.xml
 
b/server/protocols/protocols-library/src/test/resources/testServerTLSNeedAuth.xml
new file mode 100644
index 0000000..8b8d462
--- /dev/null
+++ 
b/server/protocols/protocols-library/src/test/resources/testServerTLSNeedAuth.xml
@@ -0,0 +1,15 @@
+<testserver enabled="true">
+    <tls socketTLS="true" startTLS="false">
+        <keystore>file://conf/keystore</keystore>
+        <keystoreType>JKS</keystoreType>
+        <secret>james72laBalle</secret>
+        <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider>
+        <algorithm>SunX509</algorithm>
+
+        <clientAuth>
+            <truststore>file://conf/truststore</truststore>
+            <truststoreType>JKS</truststoreType>
+            <truststoreSecret>james72laBalle</truststoreSecret>
+        </clientAuth>
+    </tls>
+</testserver>
diff --git 
a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveChannelUpstreamHandler.java
 
b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveChannelUpstreamHandler.java
index 3274b9b..bdee262 100644
--- 
a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveChannelUpstreamHandler.java
+++ 
b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveChannelUpstreamHandler.java
@@ -22,13 +22,12 @@ package org.apache.james.managesieveserver.netty;
 import java.io.Closeable;
 import java.net.InetSocketAddress;
 
-import javax.net.ssl.SSLContext;
-
 import org.apache.james.managesieve.api.Session;
 import org.apache.james.managesieve.api.SessionTerminatedException;
 import org.apache.james.managesieve.transcode.ManageSieveProcessor;
 import org.apache.james.managesieve.transcode.NotEnoughDataException;
 import org.apache.james.managesieve.util.SettableSession;
+import org.apache.james.protocols.api.Encryption;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.channel.ChannelFutureListener;
@@ -49,18 +48,18 @@ public class ManageSieveChannelUpstreamHandler extends 
SimpleChannelUpstreamHand
     private final Logger logger;
     private final ChannelLocal<Session> attributes;
     private final ManageSieveProcessor manageSieveProcessor;
-    private final SSLContext sslContext;
-    private final String[] enabledCipherSuites;
-    private final boolean sslServer;
+    private final Encryption secure;
 
-    public ManageSieveChannelUpstreamHandler(ManageSieveProcessor 
manageSieveProcessor, SSLContext sslContext,
-                                             String[] enabledCipherSuites, 
boolean sslServer, Logger logger) {
+    public ManageSieveChannelUpstreamHandler(
+            ManageSieveProcessor manageSieveProcessor, Encryption secure, 
Logger logger) {
         this.logger = logger;
         this.attributes = new ChannelLocal<>();
         this.manageSieveProcessor = manageSieveProcessor;
-        this.sslContext = sslContext;
-        this.enabledCipherSuites = enabledCipherSuites;
-        this.sslServer = sslServer;
+        this.secure = secure;
+    }
+
+    private boolean isSSL() {
+        return secure != null && !secure.isStartTLS();
     }
 
     @Override
@@ -119,7 +118,7 @@ public class ManageSieveChannelUpstreamHandler extends 
SimpleChannelUpstreamHand
             logger.info("Connection established from {}", 
address.getAddress().getHostAddress());
 
             Session session = new SettableSession();
-            if (sslServer) {
+            if (isSSL()) {
                 session.setSslEnabled(true);
             }
             attributes.set(ctx.getChannel(), session);
@@ -140,13 +139,10 @@ public class ManageSieveChannelUpstreamHandler extends 
SimpleChannelUpstreamHand
     }
 
     private void turnSSLon(Channel channel) {
-        if (sslContext != null) {
+        if (secure != null) {
             channel.setReadable(false);
-            SslHandler filter = new SslHandler(sslContext.createSSLEngine(), 
false);
+            SslHandler filter = new SslHandler(secure.createSSLEngine(), 
false);
             filter.getEngine().setUseClientMode(false);
-            if (enabledCipherSuites != null && enabledCipherSuites.length > 0) 
{
-                filter.getEngine().setEnabledCipherSuites(enabledCipherSuites);
-            }
             channel.getPipeline().addFirst(SSL_HANDLER, filter);
             channel.setReadable(true);
         }
diff --git 
a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
 
b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
index 3343668..b1510b7 100644
--- 
a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
+++ 
b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
@@ -80,17 +80,10 @@ public class ManageSieveServer extends 
AbstractConfigurableAsyncServer implement
     @Override
     protected ChannelUpstreamHandler createCoreHandler() {
         return new ManageSieveChannelUpstreamHandler(manageSieveProcessor,
-            getEncryption() == null ? null : getEncryption().getContext(),
-            getEnabledCipherSuites(),
-            isSSL(),
+            getEncryption(),
             LOGGER);
     }
 
-    private boolean isSSL() {
-        return getEncryption() != null
-            && !getEncryption().isStartTLS();
-    }
-
     @Override
     protected ChannelPipelineFactory createPipelineFactory(final ChannelGroup 
group) {
 
@@ -105,7 +98,7 @@ public class ManageSieveServer extends 
AbstractConfigurableAsyncServer implement
                 if (secure != null && !secure.isStartTLS()) {
                     // We need to set clientMode to false.
                     // See https://issues.apache.org/jira/browse/JAMES-1025
-                    SSLEngine engine = secure.getContext().createSSLEngine();
+                    SSLEngine engine = secure.createSSLEngine();
                     engine.setUseClientMode(false);
                     pipeline.addFirst(SSL_HANDLER, new SslHandler(engine));
 
diff --git a/src/site/xdoc/server/config-ssl-tls.xml 
b/src/site/xdoc/server/config-ssl-tls.xml
index bef6e02..9b5c493 100644
--- a/src/site/xdoc/server/config-ssl-tls.xml
+++ b/src/site/xdoc/server/config-ssl-tls.xml
@@ -111,6 +111,29 @@
 
     </subsection>
 
+    <subsection name="Client authentication via certificates">
+
+      <p>When you enable TLS, you may also configure the server to require a 
client certificate for authentication:</p>
+      <source>
+&lt;tls socketTLS="false" startTLS="false"&gt;
+    &lt;keystore&gt;file://conf/keystore&lt;/keystore&gt;
+    &lt;keystoreType&gt;JKS&lt;/keystoreType&gt;
+    &lt;secret&gt;yoursecret&lt;/secret&gt;
+
+    &lt;clientAuth&gt;
+        &lt;truststore&gt;file://conf/truststore&lt;/truststore&gt;
+        &lt;truststoreType&gt;JKS&lt;/truststoreType&gt;
+        &lt;truststoreSecret&gt;yoursecret&lt;/truststoreSecret&gt;
+    &lt;/clientAuth&gt;
+&lt;/tls&gt;
+</source>
+
+      <p>James verifies client certificates against the provided truststore. 
You can fill it with trusted peer certificates directly,
+         or an issuer certificate (CA) if you trust all certificates created 
by it. If you omit the truststore configuration,
+         James will use the Java default truststore instead, effectively 
trusting any known CA.</p>
+
+    </subsection>
+
     <subsection name="Creating your own PEM keys">
         <p>The following commands can be used to create self signed PEM 
keys:</p>
 

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to