You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by il...@apache.org on 2020/03/16 10:07:34 UTC

[ignite] branch master updated: IGNITE-12752 Pass client certificates to security - Fixes #7508.

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

ilyak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 19ffc88  IGNITE-12752 Pass client certificates to security - Fixes #7508.
19ffc88 is described below

commit 19ffc883d7cb25c0b998f9437cd6a927ff477b1f
Author: Ilya Kasnacheev <il...@gmail.com>
AuthorDate: Mon Mar 16 13:06:48 2020 +0300

    IGNITE-12752 Pass client certificates to security - Fixes #7508.
    
    Signed-off-by: Ilya Kasnacheev <il...@gmail.com>
---
 .../rest/JettyRestProcessorCommonSelfTest.java     |  25 ++-
 .../rest/protocols/tcp/TcpRestParserSelfTest.java  |   1 +
 modules/clients/src/test/keystore/node0102.jks     | Bin 0 -> 4618 bytes
 .../resources/jetty/rest-jetty-ssl-client-auth.xml |  92 ++++++++
 .../ClientListenerAbstractConnectionContext.java   |   8 +-
 .../odbc/ClientListenerConnectionContext.java      |   5 +-
 .../processors/odbc/ClientListenerNioListener.java |  10 +-
 .../odbc/jdbc/JdbcConnectionContext.java           |  14 +-
 .../odbc/odbc/OdbcConnectionContext.java           |  12 +-
 .../platform/client/ClientConnectionContext.java   |   6 +-
 .../processors/rest/GridRestProcessor.java         |   1 +
 .../rest/protocols/tcp/GridTcpRestNioListener.java |   1 +
 .../processors/rest/request/GridRestRequest.java   |  18 ++
 .../ignite/internal/util/nio/GridNioSession.java   |   6 +
 .../internal/util/nio/GridNioSessionImpl.java      |  20 ++
 .../internal/util/nio/ssl/GridNioSslFilter.java    |   2 +
 .../plugin/security/AuthenticationContext.java     |  21 ++
 .../ignite/plugin/security/SecuritySubject.java    |  10 +
 modules/core/src/test/config/tests.properties      |   1 +
 .../client/ThinClientSslPermissionCheckTest.java   | 237 +++++++++++++++++++++
 .../TestCertificateSecurityPluginProvider.java     |  40 ++++
 ....java => TestCertificateSecurityProcessor.java} |  91 +++-----
 .../security/impl/TestSecurityProcessor.java       |   1 +
 .../security/impl/TestSecuritySubject.java         |  19 ++
 .../util/nio/impl/GridNioFilterChainSelfTest.java  | 139 ------------
 .../internal/util/nio/impl}/MockNioSession.java    |   8 +-
 .../tcp/TcpDiscoverySslTrustedUntrustedTest.java   |  16 ++
 .../ignite/testsuites/SecurityTestSuite.java       |   2 +
 .../util/GridCommandHandlerClusterByClassTest.java |   4 +-
 ...andHandlerClusterByClassTest_cache_help.output} |   0
 ...idCommandHandlerClusterByClassTest_help.output} |   0
 ...lerClusterByClassWithSSLTest_cache_help.output} |   0
 ...ndHandlerClusterByClassWithSSLTest_help.output} |   4 +-
 .../protocols/http/jetty/GridJettyRestHandler.java |   6 +
 parent/pom.xml                                     |   3 +-
 35 files changed, 586 insertions(+), 237 deletions(-)

diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java
index 1b93284..5f14e2c 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java
@@ -108,12 +108,7 @@ public abstract class JettyRestProcessorCommonSelfTest extends AbstractRestProce
 
         URL url = new URL(sb.toString());
 
-        URLConnection conn = url.openConnection();
-
-        String signature = signature();
-
-        if (signature != null)
-            conn.setRequestProperty("X-Signature", signature);
+        URLConnection conn = openConnection(url);
 
         InputStream in = conn.getInputStream();
 
@@ -128,6 +123,24 @@ public abstract class JettyRestProcessorCommonSelfTest extends AbstractRestProce
     }
 
     /**
+     * Open REST connection, set signature header if needed.
+     *
+     * @param url URL to open.
+     * @return URL connection.
+     * @throws Exception If failed.
+     */
+    protected URLConnection openConnection(URL url) throws Exception {
+        URLConnection conn = url.openConnection();
+
+        String signature = signature();
+
+        if (signature != null)
+            conn.setRequestProperty("X-Signature", signature);
+
+        return conn;
+    }
+
+    /**
      * @param cacheName Optional cache name.
      * @param cmd REST command.
      * @param params Command parameters.
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java
index 182149e..6780fa1 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java
@@ -29,6 +29,7 @@ import org.apache.ignite.internal.processors.rest.client.message.GridClientCache
 import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeRequest;
 import org.apache.ignite.internal.processors.rest.client.message.GridClientMessage;
 import org.apache.ignite.internal.util.nio.GridNioSession;
+import org.apache.ignite.internal.util.nio.impl.MockNioSession;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.testframework.GridTestUtils;
diff --git a/modules/clients/src/test/keystore/node0102.jks b/modules/clients/src/test/keystore/node0102.jks
new file mode 100644
index 0000000..3e7cfd7
Binary files /dev/null and b/modules/clients/src/test/keystore/node0102.jks differ
diff --git a/modules/clients/src/test/resources/jetty/rest-jetty-ssl-client-auth.xml b/modules/clients/src/test/resources/jetty/rest-jetty-ssl-client-auth.xml
new file mode 100644
index 0000000..a1bd0dd
--- /dev/null
+++ b/modules/clients/src/test/resources/jetty/rest-jetty-ssl-client-auth.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Arg name="threadPool">
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">5</Set>
+            <Set name="maxThreads">10</Set>
+        </New>
+    </Arg>
+
+    <New id="httpsCfg" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Set name="secureScheme">https</Set>
+        <Set name="securePort"><SystemProperty name="IGNITE_JETTY_PORT" default="8091"/></Set>
+        <Set name="sendServerVersion">true</Set>
+        <Set name="sendDateHeader">true</Set>
+        <Call name="addCustomizer">
+            <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+        </Call>
+    </New>
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory$Server">
+        <Set name="keyStorePath"><SystemProperty
+                name="IGNITE_HOME" default="${IGNITE_HOME}"/>/modules/clients/src/test/keystore/server.jks</Set>
+        <Set name="keyStorePassword">123456</Set>
+        <Set name="keyManagerPassword">123456</Set>
+        <Set name="trustStorePath"><SystemProperty
+                name="IGNITE_HOME" default="${IGNITE_HOME}"/>/modules/clients/src/test/keystore/trust-both.jks</Set>
+        <Set name="trustStorePassword">123456</Set>
+        <Set name="needClientAuth">true</Set>
+    </New>
+
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg><Ref refid="sslContextFactory"/></Arg>
+                                <Arg>http/1.1</Arg>
+                            </New>
+                        </Item>
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Arg><Ref refid="httpsCfg"/></Arg>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="host"><SystemProperty name="IGNITE_JETTY_HOST" default="localhost"/></Set>
+                <Set name="port"><SystemProperty name="IGNITE_JETTY_PORT" default="8091"/></Set>
+                <Set name="idleTimeout">30000</Set>
+                <Set name="reuseAddress">true</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+            <Set name="handlers">
+                <Array type="org.eclipse.jetty.server.Handler">
+                    <Item>
+                        <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                    </Item>
+                </Array>
+            </Set>
+        </New>
+    </Set>
+
+    <Set name="stopAtShutdown">false</Set>
+</Configure>
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java
index 7220555..380c76b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.odbc;
 import java.util.Collections;
 import java.util.Map;
 import java.util.UUID;
+import java.security.cert.Certificate;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.processors.authentication.AuthorizationContext;
@@ -90,10 +91,10 @@ public abstract class ClientListenerAbstractConnectionContext implements ClientL
      * @return Auth context.
      * @throws IgniteCheckedException If failed.
      */
-    protected AuthorizationContext authenticate(String user, String pwd)
+    protected AuthorizationContext authenticate(Certificate[] certificates, String user, String pwd)
         throws IgniteCheckedException {
         if (ctx.security().enabled())
-            authCtx = authenticateExternal(user, pwd).authorizationContext();
+            authCtx = authenticateExternal(certificates, user, pwd).authorizationContext();
         else if (ctx.authentication().enabled()) {
             if (F.isEmpty(user))
                 throw new IgniteAccessControlException("Unauthenticated sessions are prohibited.");
@@ -112,7 +113,7 @@ public abstract class ClientListenerAbstractConnectionContext implements ClientL
     /**
      * Do 3-rd party authentication.
      */
-    private AuthenticationContext authenticateExternal(String user, String pwd)
+    private AuthenticationContext authenticateExternal(Certificate[] certificates, String user, String pwd)
         throws IgniteCheckedException {
         SecurityCredentials cred = new SecurityCredentials(user, pwd);
 
@@ -122,6 +123,7 @@ public abstract class ClientListenerAbstractConnectionContext implements ClientL
         authCtx.subjectId(UUID.randomUUID());
         authCtx.nodeAttributes(F.isEmpty(userAttrs) ? Collections.emptyMap() : userAttrs);
         authCtx.credentials(cred);
+        authCtx.certificates(certificates);
 
         secCtx = ctx.security().authenticate(authCtx);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
index 2a27986..d0cd6ea 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
@@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.binary.BinaryReaderExImpl;
 import org.apache.ignite.internal.processors.authentication.AuthorizationContext;
 import org.apache.ignite.internal.processors.security.SecurityContext;
+import org.apache.ignite.internal.util.nio.GridNioSession;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -46,11 +47,13 @@ public interface ClientListenerConnectionContext {
     /**
      * Initialize from handshake message.
      *
+     *
+     * @param ses NIO session.
      * @param ver Protocol version.
      * @param reader Reader set to the configuration part of the handshake message.
      * @throws IgniteCheckedException On error.
      */
-    void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
+    void initializeFromHandshake(GridNioSession ses, ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
         throws IgniteCheckedException;
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
index fb4dfda..1bb4ffa 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
@@ -306,12 +306,12 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte
         ClientListenerConnectionContext connCtx = null;
 
         try {
-            connCtx = prepareContext(ses, clientType);
+            connCtx = prepareContext(clientType);
 
             ensureClientPermissions(clientType);
 
             if (connCtx.isVersionSupported(ver)) {
-                connCtx.initializeFromHandshake(ver, reader);
+                connCtx.initializeFromHandshake(ses, ver, reader);
 
                 ses.addMeta(CONN_CTX_META_KEY, connCtx);
             }
@@ -367,15 +367,15 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte
      * @return Context.
      * @throws IgniteCheckedException If failed.
      */
-    private ClientListenerConnectionContext prepareContext(GridNioSession ses, byte clientType) throws IgniteCheckedException {
+    private ClientListenerConnectionContext prepareContext(byte clientType) throws IgniteCheckedException {
         long connId = nextConnectionId();
 
         switch (clientType) {
             case ODBC_CLIENT:
-                return new OdbcConnectionContext(ctx, ses, busyLock, connId, maxCursors);
+                return new OdbcConnectionContext(ctx, busyLock, connId, maxCursors);
 
             case JDBC_CLIENT:
-                return new JdbcConnectionContext(ctx, ses, busyLock, connId, maxCursors);
+                return new JdbcConnectionContext(ctx, busyLock, connId, maxCursors);
 
             case THIN_CLIENT:
                 return new ClientConnectionContext(ctx, connId, maxCursors, thinCfg);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
index 329af01..214ae11 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java
@@ -70,9 +70,6 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
     /** Supported versions. */
     private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>();
 
-    /** Session. */
-    private final GridNioSession ses;
-
     /** Shutdown busy lock. */
     private final GridSpinBusyLock busyLock;
 
@@ -104,17 +101,15 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
 
     /**
      * Constructor.
-     *  @param ctx Kernal Context.
-     * @param ses Session.
+     * @param ctx Kernal Context.
      * @param busyLock Shutdown busy lock.
      * @param connId Connection ID.
      * @param maxCursors Maximum allowed cursors.
      */
-    public JdbcConnectionContext(GridKernalContext ctx, GridNioSession ses, GridSpinBusyLock busyLock, long connId,
+    public JdbcConnectionContext(GridKernalContext ctx, GridSpinBusyLock busyLock, long connId,
         int maxCursors) {
         super(ctx, connId);
 
-        this.ses = ses;
         this.busyLock = busyLock;
         this.maxCursors = maxCursors;
 
@@ -132,7 +127,8 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
     }
 
     /** {@inheritDoc} */
-    @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
+    @Override public void initializeFromHandshake(GridNioSession ses,
+        ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
         throws IgniteCheckedException {
         assert SUPPORTED_VERS.contains(ver): "Unsupported JDBC protocol version.";
 
@@ -192,7 +188,7 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte
                 throw new IgniteCheckedException("Handshake error: " + e.getMessage(), e);
             }
 
-            actx = authenticate(user, passwd);
+            actx = authenticate(ses.certificates(), user, passwd);
         }
 
         parser = new JdbcMessageParser(ctx, ver);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
index 72a4a80..ccab26e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java
@@ -62,9 +62,6 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte
     /** Supported versions. */
     private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>();
 
-    /** Session. */
-    private final GridNioSession ses;
-
     /** Shutdown busy lock. */
     private final GridSpinBusyLock busyLock;
 
@@ -92,15 +89,13 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte
     /**
      * Constructor.
      * @param ctx Kernal Context.
-     * @param ses Session.
      * @param busyLock Shutdown busy lock.
      * @param connId Connection ID.
      * @param maxCursors Maximum allowed cursors.
      */
-    public OdbcConnectionContext(GridKernalContext ctx, GridNioSession ses, GridSpinBusyLock busyLock, long connId, int maxCursors) {
+    public OdbcConnectionContext(GridKernalContext ctx, GridSpinBusyLock busyLock, long connId, int maxCursors) {
         super(ctx, connId);
 
-        this.ses = ses;
         this.busyLock = busyLock;
         this.maxCursors = maxCursors;
 
@@ -118,7 +113,8 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte
     }
 
     /** {@inheritDoc} */
-    @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
+    @Override public void initializeFromHandshake(GridNioSession ses,
+        ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
         throws IgniteCheckedException {
         assert SUPPORTED_VERS.contains(ver): "Unsupported ODBC protocol version.";
 
@@ -153,7 +149,7 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte
             nestedTxMode = NestedTxMode.fromByte(nestedTxModeVal);
         }
 
-        AuthorizationContext actx = authenticate(user, passwd);
+        AuthorizationContext actx = authenticate(ses.certificates(), user, passwd);
 
         ClientListenerResponseSender sender = new ClientListenerResponseSender() {
             @Override public void send(ClientListenerResponse resp) {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java
index 7fc9eb7..6dd0cdd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java
@@ -36,6 +36,7 @@ import org.apache.ignite.internal.processors.odbc.ClientListenerMessageParser;
 import org.apache.ignite.internal.processors.odbc.ClientListenerProtocolVersion;
 import org.apache.ignite.internal.processors.odbc.ClientListenerRequestHandler;
 import org.apache.ignite.internal.processors.platform.client.tx.ClientTxContext;
+import org.apache.ignite.internal.util.nio.GridNioSession;
 
 /**
  * Thin Client connection context.
@@ -155,7 +156,8 @@ public class ClientConnectionContext extends ClientListenerAbstractConnectionCon
     }
 
     /** {@inheritDoc} */
-    @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
+    @Override public void initializeFromHandshake(GridNioSession ses,
+        ClientListenerProtocolVersion ver, BinaryReaderExImpl reader)
         throws IgniteCheckedException {
         boolean hasMore;
 
@@ -179,7 +181,7 @@ public class ClientConnectionContext extends ClientListenerAbstractConnectionCon
             }
         }
 
-        AuthorizationContext authCtx = authenticate(user, pwd);
+        AuthorizationContext authCtx = authenticate(ses.certificates(), user, pwd);
 
         currentVer = ver;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
index 8b97405..9f8bdb5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java
@@ -805,6 +805,7 @@ public class GridRestProcessor extends GridProcessorAdapter implements IgniteRes
         authCtx.subjectId(req.clientId());
         authCtx.nodeAttributes(req.userAttributes());
         authCtx.address(req.address());
+        authCtx.certificates(req.certificates());
 
         SecurityCredentials creds = credentials(req);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
index 6ad9431..10f895c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java
@@ -416,6 +416,7 @@ public class GridTcpRestNioListener extends GridNioServerListenerAdapter<GridCli
             restReq.clientId(msg.clientId());
             restReq.sessionToken(msg.sessionToken());
             restReq.address(ses.remoteAddress());
+            restReq.certificates(ses.certificates());
 
             if (restReq.credentials() == null) {
                 GridClientAbstractMessage msg0 = (GridClientAbstractMessage) msg;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
index 42a687b..e5c9dc8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.processors.rest.request;
 
 import java.net.InetSocketAddress;
+import java.security.cert.Certificate;
 import java.util.Map;
 import java.util.UUID;
 import org.apache.ignite.internal.processors.authentication.AuthorizationContext;
@@ -55,6 +56,9 @@ public class GridRestRequest {
     /** User attributes. */
     Map<String, String> userAttrs;
 
+    /** */
+    private Certificate[] certs;
+
     /**
      * @return Destination ID.
      */
@@ -183,6 +187,20 @@ public class GridRestRequest {
         this.userAttrs = userAttrs;
     }
 
+    /**
+     * @return Client SSL certificates.
+     */
+    public Certificate[] certificates() {
+        return certs;
+    }
+
+    /**
+     * @param certs Client SSL certificates.
+     */
+    public void certificates(Certificate[] certs) {
+        this.certs = certs;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(GridRestRequest.class, this);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java
index 12b9b40b..979bc9f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.util.nio;
 
 import java.net.InetSocketAddress;
+import java.security.cert.Certificate;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.lang.IgniteInClosure;
@@ -146,6 +147,11 @@ public interface GridNioSession {
     public boolean accepted();
 
     /**
+     * @return Client SSL certificates
+     */
+    @Nullable public Certificate[] certificates();
+
+    /**
      * Resumes session reads.
      *
      * @return Future representing result.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java
index 51cb558..d1574b6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java
@@ -18,15 +18,19 @@
 package org.apache.ignite.internal.util.nio;
 
 import java.net.InetSocketAddress;
+import java.security.cert.Certificate;
 import java.util.concurrent.atomic.AtomicLong;
+import javax.net.ssl.SSLPeerUnverifiedException;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.util.nio.ssl.GridSslMeta;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteInClosure;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.MAX_KEYS_CNT;
+import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.SSL_META;
 
 /**
  *
@@ -270,6 +274,22 @@ public class GridNioSessionImpl implements GridNioSession {
         return accepted;
     }
 
+    /** */
+    @Override public Certificate[] certificates() {
+        GridSslMeta meta = meta(SSL_META.ordinal());
+
+        if (meta != null) {
+            try {
+                return meta.sslEngine().getSession().getPeerCertificates();
+            }
+            catch (SSLPeerUnverifiedException e) {
+                // Nothing to do.
+            }
+        }
+
+        return null;
+    }
+
     /**
      * @param <T> Chain type.
      * @return Filter chain.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java
index 9b0c91c..7167c40 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java
@@ -175,6 +175,8 @@ public class GridNioSslFilter extends GridNioFilterAdapter {
 
             sslMeta = new GridSslMeta();
 
+            sslMeta.sslEngine(engine);
+
             ses.addMeta(SSL_META.ordinal(), sslMeta);
 
             handshake = true;
diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java b/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java
index f48b697..3010c19 100644
--- a/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.plugin.security;
 
 import java.net.InetSocketAddress;
+import java.security.cert.Certificate;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -50,6 +51,9 @@ public class AuthenticationContext {
     /** True if this is a client node context. */
     private boolean client;
 
+    /** Client SSL certificates. */
+    private Certificate[] certs;
+
     /**
      * Gets subject type.
      *
@@ -158,6 +162,23 @@ public class AuthenticationContext {
     }
 
     /**
+     * @return Client SSL certificates.
+     */
+    public Certificate[] certificates() {
+        return certs;
+    }
+
+    /**
+     * Set client SSL certificates.
+     * @param certs Client SSL certificates.
+     */
+    public AuthenticationContext certificates(Certificate[] certs) {
+        this.certs = certs;
+
+        return this;
+    }
+
+    /**
      * @return {@code true} if this is a client node context.
      */
     public boolean isClient() {
diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java
index 6b3e6f6..5d4e624 100644
--- a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java
+++ b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java
@@ -21,6 +21,7 @@ import java.io.Serializable;
 import java.net.InetSocketAddress;
 import java.security.PermissionCollection;
 import java.security.ProtectionDomain;
+import java.security.cert.Certificate;
 import java.util.UUID;
 import org.apache.ignite.internal.processors.security.SecurityUtils;
 
@@ -57,6 +58,15 @@ public interface SecuritySubject extends Serializable {
     public InetSocketAddress address();
 
     /**
+     * Gets subject client certificates, or {@code null} if SSL were not used or client certificate checking not enabled.
+     *
+     * @return Subject client certificates.
+     */
+    public default Certificate[] certificates() {
+        return null;
+    }
+
+    /**
      * Authorized permission set for the subject.
      *
      * @return Authorized permission set for the subject.
diff --git a/modules/core/src/test/config/tests.properties b/modules/core/src/test/config/tests.properties
index d659224..d379203 100644
--- a/modules/core/src/test/config/tests.properties
+++ b/modules/core/src/test/config/tests.properties
@@ -148,6 +148,7 @@ ssl.keystore.node01.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node01
 ssl.keystore.node02.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node02.jks
 ssl.keystore.node02old.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node02old.jks
 ssl.keystore.node03.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node03.jks
+ssl.keystore.node0102.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node0102.jks
 
 # Cluster certificate is signed by trust-one, thinServer and thinClient – by trust-two,
 # connectorServer and connectorClient – by trust-three.
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientSslPermissionCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientSslPermissionCheckTest.java
new file mode 100644
index 0000000..d65f2e4
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientSslPermissionCheckTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.ignite.internal.processors.security.client;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientAuthorizationException;
+import org.apache.ignite.client.Config;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.SslMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.ClientConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.security.AbstractSecurityTest;
+import org.apache.ignite.internal.processors.security.impl.TestCertificateSecurityPluginProvider;
+import org.apache.ignite.internal.processors.security.impl.TestSecurityData;
+import org.apache.ignite.internal.util.typedef.G;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.plugin.security.SecurityPermissionSetBuilder;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static java.util.Collections.singletonMap;
+import static org.apache.ignite.internal.util.lang.GridFunc.t;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_CREATE;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_DESTROY;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_PUT;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_READ;
+import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_REMOVE;
+import static org.apache.ignite.plugin.security.SecurityPermission.TASK_EXECUTE;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+
+/**
+ * Security tests for thin client.
+ */
+@RunWith(JUnit4.class)
+public class ThinClientSslPermissionCheckTest extends AbstractSecurityTest {
+    /** Client. */
+    private static final String CLIENT = "node01";
+
+    /** Client that has system permissions. */
+    private static final String CLIENT_SYS_PERM = "node02";
+
+    /** Client that has system permissions. */
+    private static final String CLIENT_CACHE_TASK_OPER = "node03";
+
+    /** Cache. */
+    private static final String CACHE = "TEST_CACHE";
+
+    /** Forbidden cache. */
+    private static final String FORBIDDEN_CACHE = "FORBIDDEN_TEST_CACHE";
+
+    /** Cache to test system oper permissions. */
+    private static final String DYNAMIC_CACHE = "DYNAMIC_TEST_CACHE";
+
+    /** Remove all task name. */
+    public static final String REMOVE_ALL_TASK =
+        "org.apache.ignite.internal.processors.cache.distributed.GridDistributedCacheAdapter$RemoveAllTask";
+
+    /** Clear task name. */
+    public static final String CLEAR_TASK =
+        "org.apache.ignite.internal.processors.cache.GridCacheAdapter$ClearTask";
+
+    /**
+     * @param clientData Array of client security data.
+     */
+    private IgniteConfiguration getConfiguration(TestSecurityData... clientData) throws Exception {
+        return getConfiguration(G.allGrids().size(), clientData);
+    }
+
+    /**
+     * @param idx Index.
+     * @param clientData Array of client security data.
+     */
+    private IgniteConfiguration getConfiguration(int idx, TestSecurityData... clientData) throws Exception {
+        String instanceName = getTestIgniteInstanceName(idx);
+
+        return getConfiguration(
+            instanceName,
+            new TestCertificateSecurityPluginProvider(clientData)
+        ).setCacheConfiguration(
+            new CacheConfiguration().setName(CACHE),
+            new CacheConfiguration().setName(FORBIDDEN_CACHE)
+        ).setClientConnectorConfiguration(
+            new ClientConnectorConfiguration().setSslEnabled(true).setSslClientAuth(true)
+            .setUseIgniteSslContextFactory(false)
+            .setSslContextFactory(GridTestUtils.sslTrustedFactory("node01", "trustboth"))
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        IgniteEx ignite = startGrid(
+            getConfiguration(
+                new TestSecurityData(CLIENT,
+                    SecurityPermissionSetBuilder.create().defaultAllowAll(false)
+                        .appendCachePermissions(CACHE, CACHE_READ, CACHE_PUT, CACHE_REMOVE)
+                        .appendCachePermissions(FORBIDDEN_CACHE, EMPTY_PERMS)
+                        .build()
+                ),
+                new TestSecurityData(CLIENT_SYS_PERM,
+                    SecurityPermissionSetBuilder.create().defaultAllowAll(false)
+                        .appendSystemPermissions(CACHE_CREATE, CACHE_DESTROY)
+                        .build()
+                ),
+                new TestSecurityData(CLIENT_CACHE_TASK_OPER,
+                    SecurityPermissionSetBuilder.create().defaultAllowAll(false)
+                        .appendCachePermissions(CACHE, CACHE_REMOVE)
+                        .appendTaskPermissions(REMOVE_ALL_TASK, TASK_EXECUTE)
+                        .appendTaskPermissions(CLEAR_TASK, TASK_EXECUTE)
+                        .build()
+                )
+            )
+        );
+
+        ignite.cluster().active(true);
+    }
+
+    /** */
+    @Test
+    public void testCacheSinglePermOperations() throws Exception {
+        for (IgniteBiTuple<Consumer<IgniteClient>, String> t : operations(CACHE))
+            runOperation(CLIENT, t);
+
+        for (IgniteBiTuple<Consumer<IgniteClient>, String> t : operations(FORBIDDEN_CACHE))
+            assertThrowsWithCause(() -> runOperation(CLIENT, t), ClientAuthorizationException.class);
+    }
+
+    /**
+     * That test shows the wrong case when a client has permission for a remove operation
+     * but a removeAll operation is forbidden for it. To have permission for the removeAll (clear) operation
+     * a client need to have the permission to execute {@link #REMOVE_ALL_TASK} ({@link #CLEAR_TASK}) task.
+     *
+     * @throws Exception If error occurs.
+     */
+    @Test
+    public void testCacheTaskPermOperations() throws Exception {
+        List<IgniteBiTuple<Consumer<IgniteClient>, String>> ops = Arrays.asList(
+            t(c -> c.cache(CACHE).removeAll(), "removeAll"),
+            t(c -> c.cache(CACHE).clear(), "clear")
+        );
+
+        for (IgniteBiTuple<Consumer<IgniteClient>, String> op : ops) {
+            runOperation(CLIENT_CACHE_TASK_OPER, op);
+
+            assertThrowsWithCause(() -> runOperation(CLIENT, op), ClientAuthorizationException.class);
+        }
+    }
+
+    /** */
+    @Test
+    public void testSysOperation() throws Exception {
+        try (IgniteClient sysPrmClnt = startClient(CLIENT_SYS_PERM)) {
+            sysPrmClnt.createCache(DYNAMIC_CACHE);
+
+            assertTrue(sysPrmClnt.cacheNames().contains(DYNAMIC_CACHE));
+
+            sysPrmClnt.destroyCache(DYNAMIC_CACHE);
+
+            assertFalse(sysPrmClnt.cacheNames().contains(DYNAMIC_CACHE));
+        }
+
+        List<IgniteBiTuple<Consumer<IgniteClient>, String>> ops = Arrays.asList(
+            t(c -> c.createCache(DYNAMIC_CACHE), "createCache"),
+            t(c -> c.destroyCache(CACHE), "destroyCache")
+        );
+
+        for (IgniteBiTuple<Consumer<IgniteClient>, String> op : ops)
+            assertThrowsWithCause(() -> runOperation(CLIENT, op), ClientAuthorizationException.class);
+    }
+
+    /**
+     * @param cacheName Cache name.
+     */
+    private Collection<IgniteBiTuple<Consumer<IgniteClient>, String>> operations(final String cacheName) {
+        return Arrays.asList(
+            t(c -> c.cache(cacheName).put("key", "value"), "put"),
+            t(c -> c.cache(cacheName).putAll(singletonMap("key", "value")), "putAll"),
+            t(c -> c.cache(cacheName).get("key"), "get)"),
+            t(c -> c.cache(cacheName).getAll(Collections.singleton("key")), "getAll"),
+            t(c -> c.cache(cacheName).containsKey("key"), "containsKey"),
+            t(c -> c.cache(cacheName).remove("key"), "remove"),
+            t(c -> c.cache(cacheName).replace("key", "value"), "replace"),
+            t(c -> c.cache(cacheName).putIfAbsent("key", "value"), "putIfAbsent"),
+            t(c -> c.cache(cacheName).getAndPut("key", "value"), "getAndPut"),
+            t(c -> c.cache(cacheName).getAndRemove("key"), "getAndRemove"),
+            t(c -> c.cache(cacheName).getAndReplace("key", "value"), "getAndReplace")
+        );
+    }
+
+    /** */
+    private void runOperation(String clientName, IgniteBiTuple<Consumer<IgniteClient>, String> op) {
+        try (IgniteClient client = startClient(clientName)) {
+            op.get1().accept(client);
+        }
+        catch (Exception e) {
+            throw new IgniteException(op.get2(), e);
+        }
+    }
+
+    /**
+     * @param userName User name.
+     */
+    private IgniteClient startClient(String userName) {
+        return Ignition.startClient(
+            new ClientConfiguration().setAddresses(Config.SERVER).setSslMode(SslMode.REQUIRED)
+            .setSslClientCertificateKeyStorePath(GridTestUtils.keyStorePath(userName))
+            .setSslClientCertificateKeyStorePassword(GridTestUtils.keyStorePassword())
+            .setSslTrustCertificateKeyStorePath(GridTestUtils.keyStorePath("trustone"))
+            .setSslTrustCertificateKeyStorePassword(GridTestUtils.keyStorePassword())
+        );
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityPluginProvider.java
new file mode 100644
index 0000000..818f306
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityPluginProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ignite.internal.processors.security.impl;
+
+import java.util.List;
+import java.util.Arrays;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.security.AbstractTestSecurityPluginProvider;
+import org.apache.ignite.internal.processors.security.GridSecurityProcessor;
+
+/** */
+public class TestCertificateSecurityPluginProvider extends AbstractTestSecurityPluginProvider {
+    /** Users security data. */
+    private final List<TestSecurityData> clientData;
+
+    /** */
+    public TestCertificateSecurityPluginProvider(TestSecurityData... clientData) {
+        this.clientData = Arrays.asList(clientData);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected GridSecurityProcessor securityProcessor(GridKernalContext ctx) {
+        return new TestCertificateSecurityProcessor(ctx, clientData);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityProcessor.java
similarity index 64%
copy from modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
copy to modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityProcessor.java
index dc212cd..a59f644 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityProcessor.java
@@ -18,7 +18,8 @@
 package org.apache.ignite.internal.processors.security.impl;
 
 import java.net.InetSocketAddress;
-import java.security.Permissions;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -34,90 +35,80 @@ import org.apache.ignite.internal.processors.security.GridSecurityProcessor;
 import org.apache.ignite.internal.processors.security.SecurityContext;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.plugin.security.AuthenticationContext;
-import org.apache.ignite.plugin.security.SecurityBasicPermissionSet;
 import org.apache.ignite.plugin.security.SecurityCredentials;
 import org.apache.ignite.plugin.security.SecurityException;
 import org.apache.ignite.plugin.security.SecurityPermission;
 import org.apache.ignite.plugin.security.SecurityPermissionSet;
 import org.apache.ignite.plugin.security.SecuritySubject;
 
+import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL;
 import static org.apache.ignite.plugin.security.SecuritySubjectType.REMOTE_NODE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Security processor for test.
  */
-public class TestSecurityProcessor extends GridProcessorAdapter implements GridSecurityProcessor {
+public class TestCertificateSecurityProcessor extends GridProcessorAdapter implements GridSecurityProcessor {
     /** Permissions. */
-    public static final Map<SecurityCredentials, SecurityPermissionSet> PERMS = new ConcurrentHashMap<>();
-
-    /** Sandbox permissions. */
-    private static final Map<SecurityCredentials, Permissions> SANDBOX_PERMS = new ConcurrentHashMap<>();
-
-    /** Node security data. */
-    private final TestSecurityData nodeSecData;
+    public static final Map<String, SecurityPermissionSet> PERMS = new ConcurrentHashMap<>();
 
     /** Users security data. */
     private final Collection<TestSecurityData> predefinedAuthData;
 
-    /** Global authentication. */
-    private final boolean globalAuth;
-
     /**
      * Constructor.
      */
-    public TestSecurityProcessor(GridKernalContext ctx, TestSecurityData nodeSecData,
-        Collection<TestSecurityData> predefinedAuthData, boolean globalAuth) {
+    public TestCertificateSecurityProcessor(GridKernalContext ctx, Collection<TestSecurityData> predefinedAuthData) {
         super(ctx);
 
-        this.nodeSecData = nodeSecData;
         this.predefinedAuthData = predefinedAuthData.isEmpty()
             ? Collections.emptyList()
             : new ArrayList<>(predefinedAuthData);
-        this.globalAuth = globalAuth;
     }
 
     /** {@inheritDoc} */
-    @Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred)
-        throws IgniteCheckedException {
-        if (!PERMS.containsKey(cred))
-            return null;
-
+    @Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) {
         return new TestSecurityContext(
             new TestSecuritySubject()
                 .setType(REMOTE_NODE)
                 .setId(node.id())
                 .setAddr(new InetSocketAddress(F.first(node.addresses()), 0))
-                .setLogin(cred.getLogin())
-                .setPerms(PERMS.get(cred))
-                .sandboxPermissions(SANDBOX_PERMS.get(cred))
+                .setLogin("")
+                .setPerms(ALLOW_ALL)
         );
     }
 
     /** {@inheritDoc} */
     @Override public boolean isGlobalNodeAuthentication() {
-        return globalAuth;
+        return true;
     }
 
     /** {@inheritDoc} */
-    @Override public SecurityContext authenticate(AuthenticationContext ctx) throws IgniteCheckedException {
-        if (ctx.credentials() == null || ctx.credentials().getLogin() == null)
-            return null;
+    @Override public SecurityContext authenticate(AuthenticationContext ctx) {
+        Certificate[] certs = ctx.certificates();
+
+        assertNotNull(certs);
+
+        assertEquals(2, certs.length);
 
-        SecurityPermissionSet perms = PERMS.get(ctx.credentials());
+        assertTrue(((X509Certificate)certs[0]).getSubjectDN().getName().matches("^CN=[a-z0-9]+$"));
+        assertTrue(((X509Certificate)certs[0]).getIssuerDN().getName().startsWith("C=RU, ST=SPb, L=SPb, O=Ignite, OU=Dev"));
 
-        if (perms == null) {
-            perms = new SecurityBasicPermissionSet();
-            ((SecurityBasicPermissionSet) perms).setDefaultAllowAll(true);
-        }
+        String cn = ((X509Certificate)certs[0]).getSubjectDN().getName().substring(3);
+
+        if (!PERMS.containsKey(cn))
+            return null;
 
         return new TestSecurityContext(
             new TestSecuritySubject()
                 .setType(ctx.subjectType())
                 .setId(ctx.subjectId())
                 .setAddr(ctx.address())
-                .setLogin(ctx.credentials().getLogin())
-                .setPerms(perms)
-                .sandboxPermissions(SANDBOX_PERMS.get(ctx.credentials()))
+                .setLogin(cn)
+                .setPerms(PERMS.get(cn))
+                .setCerts(ctx.certificates())
         );
     }
 
@@ -134,6 +125,7 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
     /** {@inheritDoc} */
     @Override public void authorize(String name, SecurityPermission perm, SecurityContext securityCtx)
         throws SecurityException {
+
         if (!((TestSecurityContext)securityCtx).operationAllowed(name, perm))
             throw new SecurityException("Authorization failed [perm=" + perm +
                 ", name=" + name +
@@ -154,32 +146,17 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
     @Override public void start() throws IgniteCheckedException {
         super.start();
 
-        PERMS.put(nodeSecData.credentials(), nodeSecData.getPermissions());
-        SANDBOX_PERMS.put(nodeSecData.credentials(), nodeSecData.sandboxPermissions());
+        ctx.addNodeAttribute(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, new SecurityCredentials("", ""));
 
-        ctx.addNodeAttribute(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, nodeSecData.credentials());
-
-        for (TestSecurityData data : predefinedAuthData) {
-            PERMS.put(data.credentials(), data.getPermissions());
-            SANDBOX_PERMS.put(nodeSecData.credentials(), data.sandboxPermissions());
-        }
+        for (TestSecurityData data : predefinedAuthData)
+            PERMS.put(data.credentials().getLogin().toString(), data.getPermissions());
     }
 
     /** {@inheritDoc} */
     @Override public void stop(boolean cancel) throws IgniteCheckedException {
         super.stop(cancel);
 
-        PERMS.remove(nodeSecData.credentials());
-        SANDBOX_PERMS.remove(nodeSecData.credentials());
-
-        for (TestSecurityData data : predefinedAuthData) {
-            PERMS.remove(data.credentials());
-            SANDBOX_PERMS.remove(data.credentials());
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override public boolean sandboxEnabled() {
-        return true;
+        for (TestSecurityData data : predefinedAuthData)
+            PERMS.remove(data.credentials().getLogin().toString());
     }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
index dc212cd..d4e46c0 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java
@@ -117,6 +117,7 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS
                 .setAddr(ctx.address())
                 .setLogin(ctx.credentials().getLogin())
                 .setPerms(perms)
+                .setCerts(ctx.certificates())
                 .sandboxPermissions(SANDBOX_PERMS.get(ctx.credentials()))
         );
     }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java
index c41026e..2e27355 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.security.impl;
 
 import java.net.InetSocketAddress;
 import java.security.PermissionCollection;
+import java.security.cert.Certificate;
 import java.util.UUID;
 import org.apache.ignite.plugin.security.SecurityPermissionSet;
 import org.apache.ignite.plugin.security.SecuritySubject;
@@ -46,6 +47,9 @@ public class TestSecuritySubject implements SecuritySubject {
     /** Permissions for Sandbox checks. */
     private PermissionCollection sandboxPerms;
 
+    /** Client certificates. */
+    private Certificate[] certs;
+
     /**
      * Default constructor.
      */
@@ -152,6 +156,21 @@ public class TestSecuritySubject implements SecuritySubject {
     }
 
     /** {@inheritDoc} */
+    @Override public Certificate[] certificates() {
+        return certs;
+    }
+
+    /**
+     * @param perms Permissions.
+     */
+    public TestSecuritySubject setCerts(Certificate[] certs) {
+        this.certs = certs;
+
+        return this;
+    }
+
+
+    /** {@inheritDoc} */
     @Override public String toString() {
         return "TestSecuritySubject{" +
             "id=" + id +
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java
index 3dd3565..2ed6711 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java
@@ -17,17 +17,13 @@
 
 package org.apache.ignite.internal.util.nio.impl;
 
-import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
-import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter;
 import org.apache.ignite.internal.util.nio.GridNioFilterAdapter;
 import org.apache.ignite.internal.util.nio.GridNioFilterChain;
-import org.apache.ignite.internal.util.nio.GridNioFinishedFuture;
 import org.apache.ignite.internal.util.nio.GridNioFuture;
-import org.apache.ignite.internal.util.nio.GridNioRecoveryDescriptor;
 import org.apache.ignite.internal.util.nio.GridNioServerListener;
 import org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter;
 import org.apache.ignite.internal.util.nio.GridNioSession;
@@ -267,139 +263,4 @@ public class GridNioFilterChainSelfTest extends GridCommonAbstractTest {
             ses.addMeta(metaKey, att);
         }
     }
-
-    /**
-     */
-    public class MockNioSession extends GridMetadataAwareAdapter implements GridNioSession {
-        /** Local address */
-        private InetSocketAddress locAddr = new InetSocketAddress(0);
-
-        /** Remote address. */
-        private InetSocketAddress rmtAddr = new InetSocketAddress(0);
-
-        /**
-         * Creates empty mock session.
-         */
-        public MockNioSession() {
-            // No-op.
-        }
-
-        /**
-         * Creates new mock session with given addresses.
-         *
-         * @param locAddr Local address.
-         * @param rmtAddr Remote address.
-         */
-        public MockNioSession(InetSocketAddress locAddr, InetSocketAddress rmtAddr) {
-            this();
-
-            this.locAddr = locAddr;
-            this.rmtAddr = rmtAddr;
-        }
-
-        /** {@inheritDoc} */
-        @Override public InetSocketAddress localAddress() {
-            return locAddr;
-        }
-
-        /** {@inheritDoc} */
-        @Override public InetSocketAddress remoteAddress() {
-            return rmtAddr;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long bytesSent() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long bytesReceived() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long createTime() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long closeTime() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long lastReceiveTime() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long lastSendTime() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public long lastSendScheduleTime() {
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        @Override public GridNioFuture<Boolean> close() {
-            return new GridNioFinishedFuture<>(true);
-        }
-
-        /** {@inheritDoc} */
-        @Override public GridNioFuture<?> send(Object msg) {
-            return new GridNioFinishedFuture<>(true);
-        }
-
-        /** {@inheritDoc} */
-        @Override public void sendNoFuture(Object msg, IgniteInClosure<IgniteException> ackC) {
-            // No-op.
-        }
-
-        /** {@inheritDoc} */
-        @Override public GridNioFuture<Object> resumeReads() {
-            return null;
-        }
-
-        /** {@inheritDoc} */
-        @Override public GridNioFuture<Object> pauseReads() {
-            return null;
-        }
-
-        /** {@inheritDoc} */
-        @Override public boolean accepted() {
-            return false;
-        }
-
-        /** {@inheritDoc} */
-        @Override public boolean readsPaused() {
-            return false;
-        }
-
-        /** {@inheritDoc} */
-        @Override public void outRecoveryDescriptor(GridNioRecoveryDescriptor recoveryDesc) {
-            // No-op.
-        }
-
-        /** {@inheritDoc} */
-        @Nullable @Override public GridNioRecoveryDescriptor outRecoveryDescriptor() {
-            return null;
-        }
-
-        /** {@inheritDoc} */
-        @Override public void inRecoveryDescriptor(GridNioRecoveryDescriptor recoveryDesc) {
-            // No-op.
-        }
-
-        /** {@inheritDoc} */
-        @Nullable @Override public GridNioRecoveryDescriptor inRecoveryDescriptor() {
-            return null;
-        }
-
-        /** {@inheritDoc} */
-        @Override public void systemMessage(Object msg) {
-            // No-op.
-        }
-    }
 }
diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/MockNioSession.java b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/MockNioSession.java
similarity index 95%
rename from modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/MockNioSession.java
rename to modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/MockNioSession.java
index a436ec4..0994ffc 100644
--- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/MockNioSession.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/MockNioSession.java
@@ -15,9 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.processors.rest.protocols.tcp;
+package org.apache.ignite.internal.util.nio.impl;
 
 import java.net.InetSocketAddress;
+import java.security.cert.Certificate;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter;
@@ -135,6 +136,11 @@ public class MockNioSession extends GridMetadataAwareAdapter implements GridNioS
     }
 
     /** {@inheritDoc} */
+    @Override public Certificate[] certificates() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
     @Override public boolean readsPaused() {
         return false;
     }
diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java
index d0188da..e44db54 100644
--- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java
@@ -77,6 +77,22 @@ public class TcpDiscoverySslTrustedUntrustedTest extends GridCommonAbstractTest
      * @throws Exception If failed.
      */
     @Test
+    public void testTrustOneMultiCert() throws Exception {
+        checkDiscoverySuccess("node01", "trustone", "node0102", "trustone");
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testTrustBothMultiCert() throws Exception {
+        checkDiscoverySuccess("node03", "trustboth", "node0102", "trusttwo");
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
     public void testDifferentCa() throws Exception {
         checkDiscoveryFailure("node01", "trustone", "node02", "trusttwo");
     }
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
index b38f491..e466e14 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.security.client.AdditionalSecurityC
 import org.apache.ignite.internal.processors.security.client.AdditionalSecurityCheckWithGlobalAuthTest;
 import org.apache.ignite.internal.processors.security.client.ThinClientPermissionCheckSecurityTest;
 import org.apache.ignite.internal.processors.security.client.ThinClientPermissionCheckTest;
+import org.apache.ignite.internal.processors.security.client.ThinClientSslPermissionCheckTest;
 import org.apache.ignite.internal.processors.security.compute.ComputePermissionCheckTest;
 import org.apache.ignite.internal.processors.security.compute.closure.ComputeTaskCancelRemoteSecurityContextCheckTest;
 import org.apache.ignite.internal.processors.security.compute.closure.ComputeTaskRemoteSecurityContextCheckTest;
@@ -75,6 +76,7 @@ import org.junit.runners.Suite;
     CacheLoadRemoteSecurityContextCheckTest.class,
     ContinuousQueryRemoteSecurityContextCheckTest.class,
     ContinuousQueryWithTransformerRemoteSecurityContextCheckTest.class,
+    ThinClientSslPermissionCheckTest.class,
 
     InvalidServerTest.class,
     AdditionalSecurityCheckTest.class,
diff --git a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
index 6b5f791..cfdd442 100644
--- a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
@@ -318,7 +318,7 @@ public class GridCommandHandlerClusterByClassTest extends GridCommandHandlerClus
                 assertContains(log, output, CommandHandler.UTILITY_NAME);
         }
 
-        checkHelp(output, "org.apache.ignite.util/control.sh_cache_help.output");
+        checkHelp(output, "org.apache.ignite.util/" + getClass().getSimpleName() + "_cache_help.output");
     }
 
     /** */
@@ -349,7 +349,7 @@ public class GridCommandHandlerClusterByClassTest extends GridCommandHandlerClus
 
         assertNotContains(log, testOutStr, "Control.sh");
 
-        checkHelp(testOutStr, "org.apache.ignite.util/control.sh_help.output");
+        checkHelp(testOutStr, "org.apache.ignite.util/" + getClass().getSimpleName() + "_help.output");
     }
 
     /**
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
similarity index 100%
copy from modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output
copy to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
similarity index 100%
copy from modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output
copy to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
similarity index 100%
rename from modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output
rename to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
similarity index 98%
rename from modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output
rename to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
index 1775583..c3c825f 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
@@ -13,13 +13,13 @@ This utility can do the following commands:
     control.(sh|bat) --activate
 
   Deactivate cluster (deprecated. Use --set-state instead):
-    control.(sh|bat) --deactivate [--force] [--yes]
+    control.(sh|bat) --deactivate [--yes]
 
   Print current cluster state:
     control.(sh|bat) --state
 
   Change cluster state:
-    control.(sh|bat) --set-state INACTIVE|ACTIVE|ACTIVE_READ_ONLY [--force] [--yes]
+    control.(sh|bat) --set-state INACTIVE|ACTIVE|ACTIVE_READ_ONLY [--yes]
 
     Parameters:
       ACTIVE            - Activate cluster. Cache updates are allowed.
diff --git a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
index 20ada42..35cd4e0 100644
--- a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
+++ b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java
@@ -28,6 +28,7 @@ import java.io.LineNumberReader;
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
 import java.sql.Date;
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -967,6 +968,11 @@ public class GridJettyRestHandler extends AbstractHandler {
 
         restReq.command(cmd);
 
+        Object certs = req.getAttribute("javax.servlet.request.X509Certificate");
+
+        if (certs instanceof X509Certificate[])
+            restReq.certificates((X509Certificate[])certs);
+
         // TODO: In IGNITE 3.0 we should check credentials only for AUTHENTICATE command.
         if (!credentials(params, IGNITE_LOGIN, IGNITE_PASSWORD, restReq))
             credentials(params, USER_PARAM, PWD_PARAM, restReq);
diff --git a/parent/pom.xml b/parent/pom.xml
index 80f12ef..d08a7ea 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -872,8 +872,7 @@
                                         <exclude>src/main/java/org/jsr166/*.java</exclude>
                                         <exclude>src/main/java/org/mindrot/*.java</exclude>
                                         <exclude>src/test/java/org/apache/ignite/p2p/p2p.properties</exclude><!--test depends on file content-->
-                                        <exclude>src/test/resources/org.apache.ignite.util/control.sh_cache_help.output</exclude><!--test depends on file content-->
-                                        <exclude>src/test/resources/org.apache.ignite.util/control.sh_help.output</exclude><!--test depends on file content-->
+                                        <exclude>src/test/resources/org.apache.ignite.util/*.output</exclude><!--test depends on file content-->
                                         <exclude>src/test/resources/log/ignite.log.tst</exclude><!--test resource-->
                                         <exclude>src/test/java/org/apache/ignite/spi/deployment/uri/META-INF/ignite.incorrefs</exclude><!--test resource-->
                                         <exclude>src/test/java/org/apache/ignite/spi/deployment/uri/META-INF/ignite.empty</exclude><!--should be empty-->