You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2016/07/06 18:45:55 UTC

nifi git commit: NIFI-2119 Refactored CertificateUtils to separate logic for DN extraction from server/client sockets. Added logic to detect server/client mode encapsulated in exposed method. Added unit tests for DN extraction. Corrected typo in Javadoc.

Repository: nifi
Updated Branches:
  refs/heads/0.x 104224343 -> bd69f81af


NIFI-2119 Refactored CertificateUtils to separate logic for DN extraction from server/client sockets. Added logic to detect server/client mode encapsulated in exposed method.
Added unit tests for DN extraction.
Corrected typo in Javadoc.
Switched server/client socket logic for certificate extraction -- when the local socket is in client/server mode, the peer is necessarily the inverse.
Fixed unit tests.
Moved lazy-loading authentication access out of isDebugEnabled() control branch.
This closes #611


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/bd69f81a
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/bd69f81a
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/bd69f81a

Branch: refs/heads/0.x
Commit: bd69f81af5d03d1455b48169725f57caf8d4e883
Parents: 1042243
Author: Andy LoPresto <al...@apache.org>
Authored: Mon Jul 4 21:05:58 2016 -0700
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Jul 6 14:44:37 2016 -0400

----------------------------------------------------------------------
 .../nifi/security/util/CertificateUtils.java    | 72 +++++++++++++++-
 .../security/util/CertificateUtilsTest.groovy   | 87 +++++++++++++++++---
 .../protocol/impl/NodeProtocolSenderImpl.java   |  2 +-
 .../protocol/impl/SocketProtocolListener.java   |  2 +-
 .../web/security/NiFiAuthenticationFilter.java  |  5 +-
 5 files changed, 150 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/bd69f81a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
index b3321f7..3579493 100644
--- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
+++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java
@@ -166,11 +166,51 @@ public final class CertificateUtils {
         return result;
     }
 
-    public static String extractClientDNFromSSLSocket(Socket socket) throws CertificateException {
+    /**
+     * Returns the DN extracted from the peer certificate (the server DN if run on the client; the client DN (if available) if run on the server).
+     *
+     * If the client auth setting is WANT or NONE and a client certificate is not present, this method will return {@code null}.
+     * If the client auth is NEED, it will throw a {@link CertificateException}.
+     *
+     * @param socket the SSL Socket
+     * @return the extracted DN
+     * @throws CertificateException if there is a problem parsing the certificate
+     */
+    public static String extractPeerDNFromSSLSocket(Socket socket) throws CertificateException {
         String dn = null;
         if (socket instanceof SSLSocket) {
             final SSLSocket sslSocket = (SSLSocket) socket;
 
+            boolean clientMode = sslSocket.getUseClientMode();
+            logger.debug("SSL Socket in {} mode", clientMode ? "client" : "server");
+            ClientAuth clientAuth = getClientAuthStatus(sslSocket);
+            logger.debug("SSL Socket client auth status: {}", clientAuth);
+
+            if (clientMode) {
+                logger.debug("This socket is in client mode, so attempting to extract certificate from remote 'server' socket");
+               dn = extractPeerDNFromServerSSLSocket(sslSocket);
+            } else {
+                logger.debug("This socket is in server mode, so attempting to extract certificate from remote 'client' socket");
+               dn = extractPeerDNFromClientSSLSocket(sslSocket);
+            }
+        }
+
+        return dn;
+    }
+
+    /**
+     * Returns the DN extracted from the client certificate.
+     *
+     * If the client auth setting is WANT or NONE and a certificate is not present (and {@code respectClientAuth} is {@code true}), this method will return {@code null}.
+     * If the client auth is NEED, it will throw a {@link CertificateException}.
+     *
+     * @param sslSocket the SSL Socket
+     * @return the extracted DN
+     * @throws CertificateException if there is a problem parsing the certificate
+     */
+    private static String extractPeerDNFromClientSSLSocket(SSLSocket sslSocket) throws CertificateException {
+        String dn = null;
+
             /** The clientAuth value can be "need", "want", or "none"
              * A client must send client certificates for need, should for want, and will not for none.
              * This method should throw an exception if none are provided for need, return null if none are provided for want, and return null (without checking) for none.
@@ -185,6 +225,7 @@ public final class CertificateUtils {
                     if (certChains != null && certChains.length > 0) {
                         X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
                         dn = x509Certificate.getSubjectDN().getName().trim();
+                        logger.debug("Extracted DN={} from client certificate", dn);
                     }
                 } catch (SSLPeerUnverifiedException e) {
                     if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
@@ -198,8 +239,35 @@ public final class CertificateUtils {
                     throw new CertificateException(e);
                 }
             }
-        }
+        return dn;
+    }
 
+    /**
+     * Returns the DN extracted from the server certificate.
+     *
+     * @param socket the SSL Socket
+     * @return the extracted DN
+     * @throws CertificateException if there is a problem parsing the certificate
+     */
+    private static String extractPeerDNFromServerSSLSocket(Socket socket) throws CertificateException {
+        String dn = null;
+        if (socket instanceof SSLSocket) {
+            final SSLSocket sslSocket = (SSLSocket) socket;
+                try {
+                    final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
+                    if (certChains != null && certChains.length > 0) {
+                        X509Certificate x509Certificate = convertAbstractX509Certificate(certChains[0]);
+                        dn = x509Certificate.getSubjectDN().getName().trim();
+                        logger.debug("Extracted DN={} from server certificate", dn);
+                    }
+                } catch (SSLPeerUnverifiedException e) {
+                    if (e.getMessage().equals(PEER_NOT_AUTHENTICATED_MSG)) {
+                        logger.error("The server did not present a certificate and thus the DN cannot" +
+                                " be extracted. Check that the other endpoint is providing a complete certificate chain");
+                    }
+                    throw new CertificateException(e);
+                }
+        }
         return dn;
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/bd69f81a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
index 2d00a25..22fc628 100644
--- a/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
+++ b/nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy
@@ -297,16 +297,67 @@ class CertificateUtilsTest extends GroovyTestCase {
         assert noneClientAuthStatus == CertificateUtils.ClientAuth.NONE
     }
 
+
+    @Test
+    void testShouldExtractClientCertificatesFromSSLServerSocketWithAnyClientAuth() {
+        final String EXPECTED_DN = "CN=ncm.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN)
+        logger.info("Expected DN: ${EXPECTED_DN}")
+        logger.info("Expected certificate chain: ${certificateChain.collect { (it as X509Certificate).getSubjectDN().name }.join(" issued by ")}")
+
+        SSLSession mockSession = [getPeerCertificates: { -> certificateChain }] as SSLSession
+
+        // This socket is in client mode, so the peer ("target") is a server
+
+        // Create mock sockets for each possible value of ClientAuth
+        SSLSocket mockNoneSocket = [
+                getUseClientMode : { -> true },
+                getNeedClientAuth: { -> false },
+                getWantClientAuth: { -> false },
+                getSession       : { -> mockSession }
+        ] as SSLSocket
+
+        SSLSocket mockNeedSocket = [
+                getUseClientMode : { -> true },
+                getNeedClientAuth: { -> true },
+                getWantClientAuth: { -> false },
+                getSession       : { -> mockSession }
+        ] as SSLSocket
+
+        SSLSocket mockWantSocket = [
+                getUseClientMode : { -> true },
+                getNeedClientAuth: { -> false },
+                getWantClientAuth: { -> true },
+                getSession       : { -> mockSession }
+        ] as SSLSocket
+
+        // Act
+        def resolvedServerDNs = [mockNeedSocket, mockWantSocket, mockNoneSocket].collect { SSLSocket mockSocket ->
+            logger.info("Running test with socket ClientAuth setting: ${CertificateUtils.getClientAuthStatus(mockSocket)}")
+            String serverDN = CertificateUtils.extractPeerDNFromSSLSocket(mockNoneSocket)
+            logger.info("Extracted server DN: ${serverDN}")
+            serverDN
+        }
+
+        // Assert
+        assert resolvedServerDNs.every { String serverDN ->
+            CertificateUtils.compareDNs(serverDN, EXPECTED_DN)
+        }
+    }
+
     @Test
-    void testShouldNotExtractClientCertificatesFromSSLSocketWithClientAuthNone() {
+    void testShouldNotExtractClientCertificatesFromSSLClientSocketWithClientAuthNone() {
         // Arrange
+
+        // This socket is in server mode, so the peer ("target") is a client
         SSLSocket mockSocket = [
+                getUseClientMode : { -> false },
                 getNeedClientAuth: { -> false },
                 getWantClientAuth: { -> false }
         ] as SSLSocket
 
         // Act
-        String clientDN = CertificateUtils.extractClientDNFromSSLSocket(mockSocket)
+        String clientDN = CertificateUtils.extractPeerDNFromSSLSocket(mockSocket)
         logger.info("Extracted client DN: ${clientDN}")
 
         // Assert
@@ -314,7 +365,7 @@ class CertificateUtilsTest extends GroovyTestCase {
     }
 
     @Test
-    void testShouldExtractClientCertificatesFromSSLSocketWithClientAuthWant() {
+    void testShouldExtractClientCertificatesFromSSLClientSocketWithClientAuthWant() {
         // Arrange
         final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
         Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN)
@@ -323,14 +374,16 @@ class CertificateUtilsTest extends GroovyTestCase {
 
         SSLSession mockSession = [getPeerCertificates: { -> certificateChain }] as SSLSession
 
+        // This socket is in server mode, so the peer ("target") is a client
         SSLSocket mockSocket = [
+                getUseClientMode : { -> false },
                 getNeedClientAuth: { -> false },
                 getWantClientAuth: { -> true },
                 getSession       : { -> mockSession }
         ] as SSLSocket
 
         // Act
-        String clientDN = CertificateUtils.extractClientDNFromSSLSocket(mockSocket)
+        String clientDN = CertificateUtils.extractPeerDNFromSSLSocket(mockSocket)
         logger.info("Extracted client DN: ${clientDN}")
 
         // Assert
@@ -338,18 +391,22 @@ class CertificateUtilsTest extends GroovyTestCase {
     }
 
     @Test
-    void testShouldHandleFailureToExtractClientCertificatesFromSSLSocketWithClientAuthWant() {
+    void testShouldHandleFailureToExtractClientCertificatesFromSSLClientSocketWithClientAuthWant() {
         // Arrange
-        SSLSession mockSession = [getPeerCertificates: { -> throw new SSLPeerUnverifiedException("peer not authenticated") }] as SSLSession
+        SSLSession mockSession = [getPeerCertificates: { ->
+            throw new SSLPeerUnverifiedException("peer not authenticated")
+        }] as SSLSession
 
+        // This socket is in server mode, so the peer ("target") is a client
         SSLSocket mockSocket = [
+                getUseClientMode : { -> false },
                 getNeedClientAuth: { -> false },
                 getWantClientAuth: { -> true },
                 getSession       : { -> mockSession }
         ] as SSLSocket
 
         // Act
-        String clientDN = CertificateUtils.extractClientDNFromSSLSocket(mockSocket)
+        String clientDN = CertificateUtils.extractPeerDNFromSSLSocket(mockSocket)
         logger.info("Extracted client DN: ${clientDN}")
 
         // Assert
@@ -358,7 +415,7 @@ class CertificateUtilsTest extends GroovyTestCase {
 
 
     @Test
-    void testShouldExtractClientCertificatesFromSSLSocketWithClientAuthNeed() {
+    void testShouldExtractClientCertificatesFromSSLClientSocketWithClientAuthNeed() {
         // Arrange
         final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
         Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN)
@@ -367,14 +424,16 @@ class CertificateUtilsTest extends GroovyTestCase {
 
         SSLSession mockSession = [getPeerCertificates: { -> certificateChain }] as SSLSession
 
+        // This socket is in server mode, so the peer ("target") is a client
         SSLSocket mockSocket = [
+                getUseClientMode : { -> false },
                 getNeedClientAuth: { -> true },
                 getWantClientAuth: { -> false },
                 getSession       : { -> mockSession }
         ] as SSLSocket
 
         // Act
-        String clientDN = CertificateUtils.extractClientDNFromSSLSocket(mockSocket)
+        String clientDN = CertificateUtils.extractPeerDNFromSSLSocket(mockSocket)
         logger.info("Extracted client DN: ${clientDN}")
 
         // Assert
@@ -382,11 +441,15 @@ class CertificateUtilsTest extends GroovyTestCase {
     }
 
     @Test
-    void testShouldHandleFailureToExtractClientCertificatesFromSSLSocketWithClientAuthNeed() {
+    void testShouldHandleFailureToExtractClientCertificatesFromSSLClientSocketWithClientAuthNeed() {
         // Arrange
-        SSLSession mockSession = [getPeerCertificates: { -> throw new SSLPeerUnverifiedException("peer not authenticated") }] as SSLSession
+        SSLSession mockSession = [getPeerCertificates: { ->
+            throw new SSLPeerUnverifiedException("peer not authenticated")
+        }] as SSLSession
 
+        // This socket is in server mode, so the peer ("target") is a client
         SSLSocket mockSocket = [
+                getUseClientMode : { -> false },
                 getNeedClientAuth: { -> true },
                 getWantClientAuth: { -> false },
                 getSession       : { -> mockSession }
@@ -394,7 +457,7 @@ class CertificateUtilsTest extends GroovyTestCase {
 
         // Act
         def msg = shouldFail(CertificateException) {
-            String clientDN = CertificateUtils.extractClientDNFromSSLSocket(mockSocket)
+            String clientDN = CertificateUtils.extractPeerDNFromSSLSocket(mockSocket)
             logger.info("Extracted client DN: ${clientDN}")
         }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/bd69f81a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/NodeProtocolSenderImpl.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/NodeProtocolSenderImpl.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/NodeProtocolSenderImpl.java
index 0457d7a..d747495 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/NodeProtocolSenderImpl.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/NodeProtocolSenderImpl.java
@@ -98,7 +98,7 @@ public class NodeProtocolSenderImpl implements NodeProtocolSender {
 
     private String getNCMDN(Socket socket) {
         try {
-            return CertificateUtils.extractClientDNFromSSLSocket(socket);
+            return CertificateUtils.extractPeerDNFromSSLSocket(socket);
         } catch (CertificateException e) {
             throw new ProtocolException(e);
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/bd69f81a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
index 1345df3..15161d9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java
@@ -182,7 +182,7 @@ public class SocketProtocolListener extends SocketListener implements ProtocolLi
 
     private String getRequestorDN(Socket socket) {
         try {
-            return CertificateUtils.extractClientDNFromSSLSocket(socket);
+            return CertificateUtils.extractPeerDNFromSSLSocket(socket);
         } catch (CertificateException e) {
             throw new ProtocolException(e);
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/bd69f81a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
index 0520ac8..4b4c66a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java
@@ -52,8 +52,9 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
 
     @Override
     public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
         if (log.isDebugEnabled()) {
-            log.debug("Checking secure context token: " + SecurityContextHolder.getContext().getAuthentication());
+            log.debug("Checking secure context token: " + authentication);
         }
 
         if (requiresAuthentication((HttpServletRequest) request)) {
@@ -122,7 +123,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
      * the request contains an authentication request but it could not be authenticated.
      *
      * @param request The request
-     * @return The NiFiAutorizationRequestToken used to later authorized the client
+     * @return The NiFiAuthorizationRequestToken used to later authorized the client
      * @throws InvalidAuthenticationException If the request contained an authentication attempt, but could not authenticate
      */
     public abstract NiFiAuthorizationRequestToken attemptAuthentication(HttpServletRequest request);