You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by al...@apache.org on 2018/08/07 03:02:48 UTC

nifi git commit: NIFI-5400 - Changed the hostname verifier from the custom NiFi verifier to the Apache http-client DefaultHostnameVerifier Removed NiFiHostnameVerifier. Removed NiFi WebUtils usage of NiFiHostnameVerifier. Added unit tests for the Default

Repository: nifi
Updated Branches:
  refs/heads/master a19134f32 -> 8106af699


NIFI-5400 - Changed the hostname verifier from the custom NiFi verifier to the Apache http-client DefaultHostnameVerifier
Removed NiFiHostnameVerifier. Removed NiFi WebUtils usage of NiFiHostnameVerifier.
Added unit tests for the DefaultHostnameVerifier to WebUtils.java
Added groovy-eclipse-compiler definition to nifi-web-utils/pom.xml to execute Groovy unit tests.

This closes #2919.

Co-authored-by: Andy LoPresto <al...@apache.org>
Signed-off-by: Andy LoPresto <al...@apache.org>


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

Branch: refs/heads/master
Commit: 8106af699c4dba21c03b1d8ba6c3e702dc7b1380
Parents: a19134f
Author: thenatog <th...@gmail.com>
Authored: Thu Jul 26 16:33:52 2018 -0400
Committer: Andy LoPresto <al...@apache.org>
Committed: Mon Aug 6 19:57:49 2018 -0700

----------------------------------------------------------------------
 nifi-commons/nifi-web-utils/pom.xml             |  54 ++++++
 .../nifi/web/util/NiFiHostnameVerifier.java     |  61 ------
 .../java/org/apache/nifi/web/util/WebUtils.java |   5 +-
 .../apache/nifi/web/util/WebUtilsTest.groovy    | 188 ++++++++++++++++++-
 .../admin/client/NiFiClientFactory.groovy       |  57 +-----
 .../admin/client/NiFiClientFactorySpec.groovy   |  88 ++++++++-
 6 files changed, 326 insertions(+), 127 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/8106af69/nifi-commons/nifi-web-utils/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-web-utils/pom.xml b/nifi-commons/nifi-web-utils/pom.xml
index b87dce5..2830e58 100644
--- a/nifi-commons/nifi-web-utils/pom.xml
+++ b/nifi-commons/nifi-web-utils/pom.xml
@@ -85,5 +85,59 @@
             <artifactId>javax.servlet-api</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.6</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <executions>
+                    <!-- Only run for tests -->
+                    <execution>
+                        <id>groovy-tests</id>
+                        <goals>
+                            <goal>testCompile</goal>
+                        </goals>
+                        <configuration>
+                            <compilerId>groovy-eclipse-compiler</compilerId>
+                        </configuration>
+                    </execution>
+                </executions>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.codehaus.groovy</groupId>
+                        <artifactId>groovy-eclipse-compiler</artifactId>
+                        <version>2.9.2-01</version>
+                    </dependency>
+                    <dependency>
+                        <groupId>org.codehaus.groovy</groupId>
+                        <artifactId>groovy-eclipse-batch</artifactId>
+                        <version>2.4.3-01</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.groovy</groupId>
+                <artifactId>groovy-eclipse-compiler</artifactId>
+                <version>2.9.2-01</version>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
 </project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/8106af69/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/NiFiHostnameVerifier.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/NiFiHostnameVerifier.java b/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/NiFiHostnameVerifier.java
deleted file mode 100644
index 960af58..0000000
--- a/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/NiFiHostnameVerifier.java
+++ /dev/null
@@ -1,61 +0,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.
- */
-
-package org.apache.nifi.web.util;
-
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.List;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-
-import org.apache.nifi.security.util.CertificateUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class NiFiHostnameVerifier implements HostnameVerifier {
-    private static final Logger logger = LoggerFactory.getLogger(NiFiHostnameVerifier.class);
-
-    @Override
-    public boolean verify(final String hostname, final SSLSession ssls) {
-        try {
-            for (final Certificate peerCertificate : ssls.getPeerCertificates()) {
-                if (peerCertificate instanceof X509Certificate) {
-                    final X509Certificate x509Cert = (X509Certificate) peerCertificate;
-                    final String dn = x509Cert.getSubjectDN().getName();
-                    final String commonName = CertificateUtils.extractUsername(dn);
-                    if (commonName.equals(hostname)) {
-                        return true;
-                    }
-
-                    final List<String> subjectAltNames = CertificateUtils.getSubjectAlternativeNames(x509Cert);
-                    if (subjectAltNames.contains(hostname.toLowerCase())) {
-                        return true;
-                    }
-                }
-            }
-        } catch (final SSLPeerUnverifiedException | CertificateParsingException ex) {
-            logger.warn("Hostname Verification encountered exception verifying hostname due to: " + ex, ex);
-        }
-
-        return false;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/8106af69/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java b/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
index cbf64e6..90a83a9 100644
--- a/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
+++ b/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
@@ -32,6 +32,7 @@ import org.glassfish.jersey.client.ClientConfig;
 import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
 
 /**
  * Common utilities related to web development.
@@ -93,8 +94,8 @@ public final class WebUtils {
 
         if (ctx != null) {
 
-            // custom hostname verifier that checks subject alternative names against the hostname of the URI
-            clientBuilder = clientBuilder.sslContext(ctx).hostnameVerifier(new NiFiHostnameVerifier());
+            // Apache http DefaultHostnameVerifier that checks subject alternative names against the hostname of the URI
+            clientBuilder = clientBuilder.sslContext(ctx).hostnameVerifier(new DefaultHostnameVerifier());
         }
 
         clientBuilder = clientBuilder.register(ObjectMapperResolver.class).register(JacksonJaxbJsonProvider.class);

http://git-wip-us.apache.org/repos/asf/nifi/blob/8106af69/nifi-commons/nifi-web-utils/src/test/groovy/org/apache/nifi/web/util/WebUtilsTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-commons/nifi-web-utils/src/test/groovy/org/apache/nifi/web/util/WebUtilsTest.groovy b/nifi-commons/nifi-web-utils/src/test/groovy/org/apache/nifi/web/util/WebUtilsTest.groovy
index 62755a1..b0a1191 100644
--- a/nifi-commons/nifi-web-utils/src/test/groovy/org/apache/nifi/web/util/WebUtilsTest.groovy
+++ b/nifi-commons/nifi-web-utils/src/test/groovy/org/apache/nifi/web/util/WebUtilsTest.groovy
@@ -16,17 +16,27 @@
  */
 package org.apache.nifi.web.util
 
+import org.glassfish.jersey.client.ClientConfig
 import org.junit.After
 import org.junit.Before
 import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mockito
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-
+import org.apache.http.conn.ssl.DefaultHostnameVerifier
+import sun.security.tools.keytool.CertAndKeyGen
+import sun.security.x509.X500Name
+import javax.net.ssl.SSLPeerUnverifiedException
 import javax.servlet.http.HttpServletRequest
 import javax.ws.rs.core.UriBuilderException
+import javax.ws.rs.client.Client;
+import javax.net.ssl.SSLContext
+import javax.net.ssl.HostnameVerifier;
+import java.security.cert.X509Certificate;
+
 
 @RunWith(JUnit4.class)
 class WebUtilsTest extends GroovyTestCase {
@@ -36,6 +46,7 @@ class WebUtilsTest extends GroovyTestCase {
     static final String FC_HEADER = "X-Forwarded-Context"
 
     static final String WHITELISTED_PATH = "/some/context/path"
+    private static final String OCSP_REQUEST_CONTENT_TYPE = "application/ocsp-request";
 
     @BeforeClass
     static void setUpOnce() throws Exception {
@@ -272,4 +283,179 @@ class WebUtilsTest extends GroovyTestCase {
             assert msg =~ " was not whitelisted "
         }
     }
+
+    @Test
+    void testHostnameVerifierType() {
+
+        // Arrange
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        HostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Assert
+        assertTrue(hostnameVerifier instanceof DefaultHostnameVerifier)
+    }
+
+    @Test
+    void testHostnameVerifierWildcard() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=*.apache.com,OU=Security,O=Apache,ST=CA,C=US"
+        final String hostname = "nifi.apache.com"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = (DefaultHostnameVerifier) client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(hostname, cert)
+    }
+
+    @Test
+    void testHostnameVerifierDNWildcardFourthLevelDomain() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=*.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        final String clientHostname = "client.nifi.apache.org"
+        final String serverHostname = "server.nifi.apache.org"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(clientHostname, cert)
+        hostnameVerifier.verify(serverHostname, cert)
+    }
+
+    @Test(expected = SSLPeerUnverifiedException)
+    void testHostnameVerifierDomainLevelMismatch() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=*.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        final String hostname = "nifi.apache.org"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(hostname, cert)
+    }
+
+    @Test(expected = SSLPeerUnverifiedException)
+    void testHostnameVerifierEmptyHostname() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        final String hostname = ""
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(hostname, cert)
+    }
+
+    @Test(expected = SSLPeerUnverifiedException)
+    void testHostnameVerifierDifferentSubdomain() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        final String hostname = "egg.apache.org"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(hostname, cert)
+    }
+
+    @Test(expected = SSLPeerUnverifiedException)
+    void testHostnameVerifierDifferentTLD() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        final String hostname = "nifi.apache.com"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(hostname, cert)
+    }
+
+    @Test
+    void testHostnameVerifierWildcardTLD() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=nifi.apache.*,OU=Security,O=Apache,ST=CA,C=US"
+        final String comTLDhostname = "nifi.apache.com"
+        final String orgTLDHostname = "nifi.apache.org"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(comTLDhostname, cert)
+        hostnameVerifier.verify(orgTLDHostname, cert)
+    }
+
+    @Test
+    void testHostnameVerifierWildcardDomain() {
+
+        // Arrange
+        final String EXPECTED_DN = "CN=nifi.*.com,OU=Security,O=Apache,ST=CA,C=US"
+        final String hostname = "nifi.apache.com"
+        X509Certificate cert = generateCertificate(EXPECTED_DN)
+        SSLContext sslContext = Mockito.mock(SSLContext.class)
+        final ClientConfig clientConfig = new ClientConfig()
+
+        // Act
+        Client client = WebUtils.createClient(clientConfig, sslContext)
+        DefaultHostnameVerifier hostnameVerifier = client.getHostnameVerifier()
+
+        // Verify
+        hostnameVerifier.verify(hostname, cert)
+    }
+
+
+    X509Certificate generateCertificate(String DN) {
+         CertAndKeyGen certGenerator = new CertAndKeyGen("RSA", "SHA256WithRSA", null)
+         certGenerator.generate(2048)
+
+
+         long validityPeriod = (long) 365 * 24 * 60 * 60 // 1 YEAR
+         X509Certificate cert = certGenerator.getSelfCertificate(new X500Name(DN), validityPeriod)
+         return cert
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/8106af69/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactory.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactory.groovy b/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactory.groovy
index abafbf0..27f75c2 100644
--- a/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactory.groovy
+++ b/nifi-toolkit/nifi-toolkit-admin/src/main/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactory.groovy
@@ -17,18 +17,13 @@
 package org.apache.nifi.toolkit.admin.client
 
 import org.apache.commons.lang3.StringUtils
-import org.apache.nifi.security.util.CertificateUtils
+import org.apache.http.conn.ssl.DefaultHostnameVerifier
 import org.apache.nifi.util.NiFiProperties
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
-import javax.naming.ldap.LdapName
-import javax.naming.ldap.Rdn
-import javax.net.ssl.HostnameVerifier
 import javax.net.ssl.KeyManagerFactory
 import javax.net.ssl.SSLContext
-import javax.net.ssl.SSLPeerUnverifiedException
-import javax.net.ssl.SSLSession
 import javax.net.ssl.TrustManagerFactory
 import javax.ws.rs.client.Client
 import javax.ws.rs.client.ClientBuilder
@@ -38,10 +33,7 @@ import java.security.KeyStoreException
 import java.security.NoSuchAlgorithmException
 import java.security.SecureRandom
 import java.security.UnrecoverableKeyException
-import java.security.cert.Certificate
 import java.security.cert.CertificateException
-import java.security.cert.CertificateParsingException
-import java.security.cert.X509Certificate
 
 class NiFiClientFactory implements ClientFactory{
 
@@ -83,14 +75,13 @@ class NiFiClientFactory implements ClientFactory{
         final ClientBuilder clientBuilder = ClientBuilder.newBuilder();
 
         if (sslContext != null) {
-            clientBuilder.sslContext(sslContext).hostnameVerifier(new NiFiHostnameVerifier());
+            clientBuilder.sslContext(sslContext).hostnameVerifier(new DefaultHostnameVerifier());
         }
 
         return clientBuilder.build();
 
     }
 
-
     static SSLContext createSslContext(
             final String keystore, final char[] keystorePasswd, final String keystoreType,
             final String truststore, final char[] truststorePasswd, final String truststoreType,
@@ -121,50 +112,6 @@ class NiFiClientFactory implements ClientFactory{
         return sslContext;
     }
 
-    static class NiFiHostnameVerifier implements HostnameVerifier {
-
-        @Override
-        public boolean verify(final String hostname, final SSLSession ssls) {
-
-            if (ssls.getPeerCertificates() != null && ssls.getPeerCertificates().length > 0) {
-
-                try {
-                    final Certificate peerCertificate = ssls.getPeerCertificates()[0]
-                    final X509Certificate x509Cert = CertificateUtils.convertAbstractX509Certificate(peerCertificate)
-                    final String dn = x509Cert.getSubjectDN().getName().trim()
-
-                    final LdapName ln = new LdapName(dn)
-                    final boolean match = ln.getRdns().any { Rdn rdn -> rdn.getType().equalsIgnoreCase("CN") && rdn.getValue().toString().equalsIgnoreCase(hostname)}
-                    return match || getSubjectAlternativeNames(x509Cert).any { String san -> san.equalsIgnoreCase(hostname) }
 
-                } catch (final SSLPeerUnverifiedException | CertificateParsingException ex ) {
-                    logger.warn("Hostname Verification encountered exception verifying hostname due to: " + ex, ex);
-                }
-
-            }else{
-                logger.warn("Peer certificates not found on ssl session ");
-            }
-
-            return false
-        }
-
-        private List<String> getSubjectAlternativeNames(final X509Certificate certificate) throws CertificateParsingException {
-            final Collection<List<?>> altNames = certificate.getSubjectAlternativeNames()
-
-            if (altNames == null) {
-                return new ArrayList<>()
-            }
-
-            final List<String> result = new ArrayList<>()
-            for (final List<?> generalName : altNames) {
-                final Object value = generalName.get(1)
-                if (value instanceof String) {
-                    result.add(((String) value).toLowerCase())
-                }
-            }
-
-            return result
-        }
-    }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/8106af69/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy b/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
index a37b1c1..ddb445f 100644
--- a/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
+++ b/nifi-toolkit/nifi-toolkit-admin/src/test/groovy/org/apache/nifi/toolkit/admin/client/NiFiClientFactorySpec.groovy
@@ -18,6 +18,7 @@
 package org.apache.nifi.toolkit.admin.client
 
 import org.apache.commons.lang3.SystemUtils
+import org.apache.http.conn.ssl.DefaultHostnameVerifier
 import org.apache.nifi.properties.NiFiPropertiesLoader
 import org.apache.nifi.security.util.CertificateUtils
 import org.apache.nifi.toolkit.tls.standalone.TlsToolkitStandalone
@@ -110,7 +111,7 @@ class NiFiClientFactorySpec extends Specification {
         final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
         Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN,ISSUER_DN)
         def mockSession = Mock(SSLSession)
-        NiFiClientFactory.NiFiHostnameVerifier verifier = new NiFiClientFactory.NiFiHostnameVerifier()
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
         mockSession.getPeerCertificates() >> certificateChain
 
         when:
@@ -121,21 +122,88 @@ class NiFiClientFactorySpec extends Specification {
 
     }
 
+    def "should verify wildcard in CN in certificate based on subjectDN"(){
+
+        given:
+        final String EXPECTED_DN = "CN=*.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN,ISSUER_DN)
+        def mockSession = Mock(SSLSession)
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
+        mockSession.getPeerCertificates() >> certificateChain
+
+        when:
+        def validSubdomainIsVerified = verifier.verify("client.nifi.apache.org",mockSession)
+        def validSubdomainIsVerified2 = verifier.verify("server.nifi.apache.org",mockSession)
+        def invalidSubdomainIsNotVerified = !verifier.verify("client.hive.apache.org",mockSession)
+
+        then:
+        validSubdomainIsVerified
+        validSubdomainIsVerified2
+        invalidSubdomainIsNotVerified
+    }
+
+    def "should verify appropriately CN in certificate based on TLD wildcard in SAN"(){
+
+        given:
+        final String EXPECTED_DN = "CN=client.nifi.apache.*,OU=Security,O=Apache,ST=CA,C=US"
+        final String wildcardHostname = "client.nifi.apache.com"
+        byte[] subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, wildcardHostname)).getEncoded()
+        Extensions extensions = new Extensions(new Extension(Extension.subjectAlternativeName, false, subjectAltName))
+        Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN, ISSUER_DN, extensions)
+        def mockSession = Mock(SSLSession)
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
+        mockSession.getPeerCertificates() >> certificateChain
+
+        when:
+        def validTLDIsVerified = verifier.verify("client.nifi.apache.org",mockSession)
+        def validTLDIsVerified2 = verifier.verify("client.nifi.apache.com",mockSession)
+        def validTLDIsNotVerified = !verifier.verify("client.hive.apache.org",mockSession)
+
+        then:
+        //validTLDIsVerified
+        validTLDIsVerified2
+        validTLDIsNotVerified
+    }
+
+    def "should verify appropriately CN in certificate based on subdomain wildcard in SAN"(){
+
+        given:
+        final String EXPECTED_DN = "CN=client.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
+        final String wildcardHostname = "*.nifi.apache.org"
+        byte[] subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, wildcardHostname)).getEncoded()
+        Extensions extensions = new Extensions(new Extension(Extension.subjectAlternativeName, false, subjectAltName))
+        Certificate[] certificateChain = generateCertificateChain(EXPECTED_DN, ISSUER_DN, extensions)
+        def mockSession = Mock(SSLSession)
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
+        mockSession.getPeerCertificates() >> certificateChain
+
+        when:
+        def validSubdomainIsVerified = verifier.verify("client.nifi.apache.org", mockSession)
+        def validSubdomainIsVerified1 = verifier.verify("egg.nifi.apache.org", mockSession)
+        def invalidSubdomainIsNotVerified = !verifier.verify("client.hive.apache.org", mockSession)
+        def invalidDomainIsNotVerified = !verifier.verify("egg.com", mockSession)
+
+        then:
+        validSubdomainIsVerified
+        validSubdomainIsVerified1
+        invalidSubdomainIsNotVerified
+        invalidDomainIsNotVerified
+    }
+
     def "should not verify based on no certificate chain"(){
 
         given:
         final String EXPECTED_DN = "CN=client.nifi.apache.org, OU=Security, O=Apache, ST=CA, C=US"
         Certificate[] certificateChain = [] as Certificate[]
         def mockSession = Mock(SSLSession)
-        NiFiClientFactory.NiFiHostnameVerifier verifier = new NiFiClientFactory.NiFiHostnameVerifier()
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
         mockSession.getPeerCertificates() >> certificateChain
 
         when:
         def notVerified = !verifier.verify("client.nifi.apache.org",mockSession)
 
         then:
-        notVerified
-
+        final ArrayIndexOutOfBoundsException exception = thrown()
     }
 
     def "should not verify based on multiple CN values"(){
@@ -163,7 +231,7 @@ class NiFiClientFactorySpec extends Specification {
 
         Certificate[] certificateChain = [certificate,issuerCertificate] as Certificate[]
         def mockSession = Mock(SSLSession)
-        NiFiClientFactory.NiFiHostnameVerifier verifier = new NiFiClientFactory.NiFiHostnameVerifier()
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
         mockSession.getPeerCertificates() >> certificateChain
 
 
@@ -175,7 +243,7 @@ class NiFiClientFactorySpec extends Specification {
 
     }
 
-    def "should verify appropriately CN in certificate based on SAN"(){
+    def "should verify appropriately CN in certificate based on SAN"() {
 
         given:
 
@@ -194,7 +262,7 @@ class NiFiClientFactorySpec extends Specification {
         final X509Certificate certificate = generateIssuedCertificate(EXPECTED_DN, issuerCertificate,extensions, issuerKeyPair)
         Certificate[] certificateChain = [certificate, issuerCertificate] as X509Certificate[]
         def mockSession = Mock(SSLSession)
-        NiFiClientFactory.NiFiHostnameVerifier verifier = new NiFiClientFactory.NiFiHostnameVerifier()
+        DefaultHostnameVerifier verifier = new DefaultHostnameVerifier()
         mockSession.getPeerCertificates() >> certificateChain
 
         when:
@@ -220,9 +288,13 @@ class NiFiClientFactorySpec extends Specification {
     }
 
     def X509Certificate[] generateCertificateChain(String dn,String issuerDn) {
+        generateCertificateChain(dn, issuerDn, null)
+    }
+
+    def X509Certificate[] generateCertificateChain(String dn,String issuerDn, Extensions extensions) {
         final KeyPair issuerKeyPair = generateKeyPair()
         final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
-        final X509Certificate certificate = generateIssuedCertificate(dn, issuerCertificate,null, issuerKeyPair)
+        final X509Certificate certificate = generateIssuedCertificate(dn, issuerCertificate, extensions, issuerKeyPair)
         [certificate, issuerCertificate] as X509Certificate[]
     }