You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by gm...@apache.org on 2016/05/16 18:59:05 UTC

qpid-dispatch git commit: DISPATCH-320 - Added a new connector property called verifyHostName which will verify host name on SSL connections

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master ded25e5dc -> 2b17c00f7


DISPATCH-320 - Added a new connector property called verifyHostName which will verify host name on SSL connections

(cherry picked from commit 3629375f5f09e53bb955001fd4152ed831e47795)


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/2b17c00f
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/2b17c00f
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/2b17c00f

Branch: refs/heads/master
Commit: 2b17c00f73044d613f27d437166ddd8ae6705911
Parents: ded25e5
Author: Ganesh Murthy <gm...@redhat.com>
Authored: Wed May 11 10:51:13 2016 -0400
Committer: Ganesh Murthy <gm...@redhat.com>
Committed: Mon May 16 14:57:41 2016 -0400

----------------------------------------------------------------------
 include/qpid/dispatch/server.h                |   6 +
 python/qpid_dispatch/management/qdrouter.json |   6 +
 src/connection_manager.c                      |  33 ++--
 src/server.c                                  |  11 ++
 tests/system_test.py                          |   9 +-
 tests/system_tests_sasl_plain.py              | 181 ++++++++++++++++++++-
 tests/system_tests_two_routers.py             |   5 +-
 7 files changed, 229 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/include/qpid/dispatch/server.h
----------------------------------------------------------------------
diff --git a/include/qpid/dispatch/server.h b/include/qpid/dispatch/server.h
index 2a8e34a..935cb17 100644
--- a/include/qpid/dispatch/server.h
+++ b/include/qpid/dispatch/server.h
@@ -311,6 +311,12 @@ typedef struct qd_server_config_t {
     bool requireEncryption;
 
     /**
+     * Ensures that when initiating a connection (as a client) the host name in the URL to which this connector
+     * connects to matches the host name in the digital certificate that the peer sends back as part of the SSL connection
+     */
+    bool verify_host_name;
+
+    /**
      * If true, strip the inbound qpid dispatch specific message annotations. This only applies to ingress and egress routers.
      * Annotations generated by inter-router messages will be untouched.
      */

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index a215d5c..138942f 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -757,6 +757,12 @@
                     "required": false,
                     "description": "The capacity of links within this connection, in terms of message deliveries.  The capacity is the number of messages that can be in-flight concurrently for each link."
                 },
+                "verifyHostName": {
+                    "type": "boolean",
+                    "default": true,
+                    "description": "yes: Ensures that when initiating a connection (as a client) the host name in the URL to which this connector connects to matches the host name in the digital certificate that the peer sends back as part of the SSL connection; no: Does not perform host name verification",
+                    "create": true
+                },                
                 "saslUsername": {
                     "type": "string",
                     "required": false,

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/src/connection_manager.c
----------------------------------------------------------------------
diff --git a/src/connection_manager.c b/src/connection_manager.c
index 1e09683..459ea7f 100644
--- a/src/connection_manager.c
+++ b/src/connection_manager.c
@@ -127,29 +127,31 @@ static qd_error_t load_server_config(qd_dispatch_t *qd, qd_server_config_t *conf
 {
     qd_error_clear();
 
-    bool authenticatePeer   = qd_entity_opt_bool(entity, "authenticatePeer",  false); CHECK();
-    char *stripAnnotations  = qd_entity_opt_string(entity, "stripAnnotations", 0);    CHECK();
-    bool requireEncryption  = qd_entity_opt_bool(entity, "requireEncryption", false); CHECK();
-    bool requireSsl         = qd_entity_opt_bool(entity, "requireSsl",        false); CHECK();
-    bool depRequirePeerAuth = qd_entity_opt_bool(entity, "requirePeerAuth",   false); CHECK();
+    bool authenticatePeer   = qd_entity_opt_bool(entity, "authenticatePeer",  false);    CHECK();
+    bool verifyHostName     = qd_entity_opt_bool(entity, "verifyHostName",    true);     CHECK();
+    char *stripAnnotations  = qd_entity_opt_string(entity, "stripAnnotations", 0);       CHECK();
+    bool requireEncryption  = qd_entity_opt_bool(entity, "requireEncryption", false);    CHECK();
+    bool requireSsl         = qd_entity_opt_bool(entity, "requireSsl",        false);    CHECK();
+    bool depRequirePeerAuth = qd_entity_opt_bool(entity, "requirePeerAuth",   false);    CHECK();
     bool depAllowUnsecured  = qd_entity_opt_bool(entity, "allowUnsecured", !requireSsl); CHECK();
 
     memset(config, 0, sizeof(*config));
-    config->port                 = qd_entity_get_string(entity, "port"); CHECK();
-    config->name                 = qd_entity_opt_string(entity, "name", 0); CHECK();
-    config->role                 = qd_entity_get_string(entity, "role"); CHECK();
-    config->inter_router_cost    = qd_entity_opt_long(entity, "cost", 1); CHECK();
+    config->port                 = qd_entity_get_string(entity, "port");              CHECK();
+    config->name                 = qd_entity_opt_string(entity, "name", 0);           CHECK();
+    config->role                 = qd_entity_get_string(entity, "role");              CHECK();
+    config->inter_router_cost    = qd_entity_opt_long(entity, "cost", 1);             CHECK();
     config->protocol_family      = qd_entity_opt_string(entity, "protocolFamily", 0); CHECK();
-    config->max_frame_size       = qd_entity_get_long(entity, "maxFrameSize"); CHECK();
-    config->idle_timeout_seconds = qd_entity_get_long(entity, "idleTimeoutSeconds"); CHECK();
-    config->sasl_username        = qd_entity_opt_string(entity, "saslUsername", 0); CHECK();
-    config->sasl_password        = qd_entity_opt_string(entity, "saslPassword", 0); CHECK();
+    config->max_frame_size       = qd_entity_get_long(entity, "maxFrameSize");        CHECK();
+    config->idle_timeout_seconds = qd_entity_get_long(entity, "idleTimeoutSeconds");  CHECK();
+    config->sasl_username        = qd_entity_opt_string(entity, "saslUsername", 0);   CHECK();
+    config->sasl_password        = qd_entity_opt_string(entity, "saslPassword", 0);   CHECK();
     config->sasl_mechanisms      = qd_entity_opt_string(entity, "saslMechanisms", 0); CHECK();
+    config->link_capacity        = qd_entity_opt_long(entity, "linkCapacity", 0);     CHECK();
     config->ssl_enabled          = has_attrs(entity, ssl_attributes, ssl_attributes_count);
-    config->link_capacity        = qd_entity_opt_long(entity, "linkCapacity", 0); CHECK();
+    config->link_capacity        = qd_entity_opt_long(entity, "linkCapacity", 0);     CHECK();
     config->host                 = qd_entity_opt_string(entity, "host", 0); QD_ERROR_RET();
     if (! config->host)
-        config->host             = qd_entity_opt_string(entity, "addr", 0); CHECK();
+        config->host             = qd_entity_opt_string(entity, "addr", 0);           CHECK();
     assert(config->host);
 
     //
@@ -163,6 +165,7 @@ static qd_error_t load_server_config(qd_dispatch_t *qd, qd_server_config_t *conf
     // user community, we can revisit this later.
     //
     config->allowInsecureAuthentication = true;
+    config->verify_host_name = verifyHostName;
 
     load_strip_annotations(config, stripAnnotations);
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/src/server.c
----------------------------------------------------------------------
diff --git a/src/server.c b/src/server.c
index 3ddef99..9e7a0d8 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1163,12 +1163,14 @@ static void cxtr_try_open(void *context)
     //
     if (config->ssl_enabled) {
         pn_ssl_domain_t *domain = pn_ssl_domain(PN_SSL_MODE_CLIENT);
+
         if (!domain) {
             qd_error(QD_ERROR_RUNTIME, "SSL domain failed for connection to %s:%s",
                      ct->config->host, ct->config->port);
             /* TODO aconway 2014-07-15: Close the connection, clean up. */
             return;
         }
+
         /* TODO aconway 2014-07-15: error handling on all SSL calls. */
 
         // set our trusted database for checking the peer's cert:
@@ -1205,6 +1207,15 @@ static void cxtr_try_open(void *context)
             }
         }
 
+        //If ssl is enabled and verify_host_name is true, instruct proton to verify peer name
+        if (config->verify_host_name) {
+            if (pn_ssl_domain_set_peer_authentication(domain, PN_SSL_VERIFY_PEER_NAME, NULL)) {
+                    qd_log(ct->server->log_source, QD_LOG_ERROR,
+                           "SSL peer host name verification failed for %s:%s",
+                           ct->config->host, ct->config->port);
+            }
+        }
+
         ctx->ssl = pn_ssl(tport);
         pn_ssl_init(ctx->ssl, domain, 0);
         pn_ssl_domain_free(domain);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/tests/system_test.py
----------------------------------------------------------------------
diff --git a/tests/system_test.py b/tests/system_test.py
index 9bbcc00..f97b2bc 100755
--- a/tests/system_test.py
+++ b/tests/system_test.py
@@ -472,19 +472,23 @@ class Qdrouterd(Process):
         else:
             return '127.0.0.1'
 
+    def wait_ports(self, **retry_kwargs):
+        wait_ports(self.ports_family, **retry_kwargs)
+
     def wait_connectors(self, **retry_kwargs):
         """
         Wait for all connectors to be connected
         @param retry_kwargs: keyword args for L{retry}
         """
         for c in self.config.sections('connector'):
-            assert retry(lambda: self.is_connected(port=c['port'], host=self.get_host(c.get('protocolFamily'))), **retry_kwargs), "Port not connected %s" % c['port']
+            assert retry(lambda: self.is_connected(port=c['port'], host=self.get_host(c.get('protocolFamily'))),
+                         **retry_kwargs), "Port not connected %s" % c['port']
 
     def wait_ready(self, **retry_kwargs):
         """Wait for ports and connectors to be ready"""
         if not self._wait_ready:
             self._wait_ready = True
-            wait_ports(self.ports_family, **retry_kwargs)
+            self.wait_ports(**retry_kwargs)
             self.wait_connectors(**retry_kwargs)
         return self
 
@@ -501,7 +505,6 @@ class Qdrouterd(Process):
         except:
             return False
 
-
     def wait_router_connected(self, router_id, **retry_kwargs):
         retry(lambda: self.is_router_connected(router_id), **retry_kwargs)
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/tests/system_tests_sasl_plain.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_sasl_plain.py b/tests/system_tests_sasl_plain.py
index 40d8cd9..48a2b0a 100644
--- a/tests/system_tests_sasl_plain.py
+++ b/tests/system_tests_sasl_plain.py
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-import unittest, os
+import unittest, os, time
 from subprocess import PIPE, Popen
 from system_test import TestCase, Qdrouterd, main_module, DIR, TIMEOUT
 
@@ -96,7 +96,9 @@ class RouterTestPlainSasl(RouterTestPlainSaslCommon):
         super(RouterTestPlainSasl, cls).router('Y', [
                      ('connector', {'host': '0.0.0.0', 'role': 'inter-router', 'port': x_listener_port,
                                     # Provide a sasl user name and password to connect to QDR.X
-                                   'saslMechanisms': 'PLAIN', 'saslUsername': 'test@domain.com', 'saslPassword': 'password'}),
+                                   'saslMechanisms': 'PLAIN',
+                                    'saslUsername': 'test@domain.com',
+                                    'saslPassword': 'password'}),
                      ('router', {'workerThreads': 1,
                                  'mode': 'interior',
                                  'id': 'QDR.Y'}),
@@ -174,9 +176,11 @@ class RouterTestPlainSaslOverSsl(RouterTestPlainSaslCommon):
                      # we will have SASL plain authentication over SSL.
                      ('connector', {'host': '0.0.0.0', 'role': 'inter-router', 'port': x_listener_port,
                                     'ssl-profile': 'client-ssl-profile',
+                                    'verifyHostName': 'no',
                                     # Provide a sasl user name and password to connect to QDR.X
                                     'saslMechanisms': 'PLAIN',
-                                    'saslUsername': 'test@domain.com', 'saslPassword': 'password'}),
+                                    'saslUsername': 'test@domain.com',
+                                    'saslPassword': 'password'}),
                      ('router', {'workerThreads': 1,
                                  'mode': 'interior',
                                  'id': 'QDR.Y'}),
@@ -215,6 +219,177 @@ class RouterTestPlainSaslOverSsl(RouterTestPlainSaslCommon):
         # user must be test@domain.com
         self.assertEqual(u'test@domain.com', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][16])
 
+class RouterTestVerifyHostNameYes(RouterTestPlainSaslCommon):
+
+    @staticmethod
+    def ssl_file(name):
+        return os.path.join(DIR, 'ssl_certs', name)
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Tests the verifyHostName property of the connector. The hostname on the server certificate we use is
+        A1.Good.Server.domain.com and the host is 0.0.0.0 on the client router initiating the SSL connection.
+        Since the host names do not match and the verifyHostName is set to true, the client router
+        will NOT be able make a successful SSL connection the server router.
+        """
+        super(RouterTestVerifyHostNameYes, cls).setUpClass()
+
+        super(RouterTestVerifyHostNameYes, cls).createSaslFiles()
+
+        cls.routers = []
+
+        x_listener_port = cls.tester.get_port()
+        y_listener_port = cls.tester.get_port()
+
+        super(RouterTestVerifyHostNameYes, cls).router('X', [
+                     ('listener', {'addr': '0.0.0.0', 'role': 'inter-router', 'port': x_listener_port,
+                                   'sslProfile':'server-ssl-profile',
+                                   'saslMechanisms':'PLAIN', 'authenticatePeer': 'yes'}),
+                     # This unauthenticated listener is for qdstat to connect to it.
+                     ('listener', {'addr': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port(),
+                                   'authenticatePeer': 'no'}),
+                     ('sslProfile', {'name': 'server-ssl-profile',
+                                     'cert-db': cls.ssl_file('ca-certificate.pem'),
+                                     'cert-file': cls.ssl_file('server-certificate.pem'),
+                                     'key-file': cls.ssl_file('server-private-key.pem'),
+                                     'password': 'server-password'}),
+                     ('router', {'workerThreads': 1,
+                                 'routerId': 'QDR.X',
+                                 'mode': 'interior',
+                                 'saslConfigName': 'tests-mech-PLAIN',
+                                 'saslConfigPath': os.getcwd()}),
+        ])
+
+        super(RouterTestVerifyHostNameYes, cls).router('Y', [
+                     ('connector', {'addr': '127.0.0.1', 'role': 'inter-router', 'port': x_listener_port,
+                                    'ssl-profile': 'client-ssl-profile',
+                                    'verifyHostName': 'yes',
+                                    'saslMechanisms': 'PLAIN',
+                                    'saslUsername': 'test@domain.com', 'saslPassword': 'password'}),
+                     ('router', {'workerThreads': 1,
+                                 'mode': 'interior',
+                                 'routerId': 'QDR.Y'}),
+                     ('listener', {'addr': '0.0.0.0', 'role': 'normal', 'port': y_listener_port}),
+                     ('sslProfile', {'name': 'client-ssl-profile',
+                                     'cert-db': cls.ssl_file('ca-certificate.pem'),
+                                     'cert-file': cls.ssl_file('client-certificate.pem'),
+                                     'key-file': cls.ssl_file('client-private-key.pem'),
+                                     'password': 'client-password'}),
+        ])
+
+        cls.routers[0].wait_ports()
+        cls.routers[1].wait_ports()
+        try:
+            # This will time out because there is no inter-router connection
+            cls.routers[1].wait_connectors(timeout=3)
+        except:
+            pass
+
+    def test_no_inter_router_connection(self):
+        """
+        Tests to make sure that there are no 'inter-router' connections.
+        The connection to the other router will not happen because the connection failed
+        due to setting 'verifyHostName': 'yes'
+        """
+        local_node = Node.connect(self.routers[1].addresses[0], timeout=TIMEOUT)
+
+        # There should be only two connections.
+        # There will be no inter-router connection
+        self.assertEqual(2, len(local_node.query(type='org.apache.qpid.dispatch.connection').results))
+        self.assertEqual('in', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][15])
+        self.assertEqual('normal', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][9])
+        self.assertEqual('anonymous', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][16])
+
+        self.assertEqual('normal', local_node.query(type='org.apache.qpid.dispatch.connection').results[1][9])
+        self.assertEqual('anonymous', local_node.query(type='org.apache.qpid.dispatch.connection').results[1][16])
+
+class RouterTestVerifyHostNameNo(RouterTestPlainSaslCommon):
+
+    @staticmethod
+    def ssl_file(name):
+        return os.path.join(DIR, 'ssl_certs', name)
+
+    @classmethod
+    def setUpClass(cls):
+        """
+        Tests the verifyHostName property of the connector. The hostname on the server certificate we use is
+        A1.Good.Server.domain.com and the host is 0.0.0.0 on the client router initiating the SSL connection.
+        Since the host names do not match but verifyHostName is set to false, the client router
+        will be successfully able to make an SSL connection the server router.
+        """
+        super(RouterTestVerifyHostNameNo, cls).setUpClass()
+
+        super(RouterTestVerifyHostNameNo, cls).createSaslFiles()
+
+        cls.routers = []
+
+        x_listener_port = cls.tester.get_port()
+        y_listener_port = cls.tester.get_port()
+
+        super(RouterTestVerifyHostNameNo, cls).router('X', [
+                     ('listener', {'addr': '0.0.0.0', 'role': 'inter-router', 'port': x_listener_port,
+                                   'sslProfile':'server-ssl-profile',
+                                   'saslMechanisms':'PLAIN', 'authenticatePeer': 'yes'}),
+                     # This unauthenticated listener is for qdstat to connect to it.
+                     ('listener', {'addr': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port(),
+                                   'authenticatePeer': 'no'}),
+                     ('sslProfile', {'name': 'server-ssl-profile',
+                                     'cert-db': cls.ssl_file('ca-certificate.pem'),
+                                     'cert-file': cls.ssl_file('server-certificate.pem'),
+                                     'key-file': cls.ssl_file('server-private-key.pem'),
+                                     'password': 'server-password'}),
+                     ('router', {'workerThreads': 1,
+                                 'routerId': 'QDR.X',
+                                 'mode': 'interior',
+                                 'saslConfigName': 'tests-mech-PLAIN',
+                                 'saslConfigPath': os.getcwd()}),
+        ])
+
+        super(RouterTestVerifyHostNameNo, cls).router('Y', [
+                     # This router will act like a client. First an SSL connection will be established and then
+                     # we will have SASL plain authentication over SSL.
+                     ('connector', {'addr': '127.0.0.1', 'role': 'inter-router', 'port': x_listener_port,
+                                    'ssl-profile': 'client-ssl-profile',
+                                    # Provide a sasl user name and password to connect to QDR.X
+                                    'saslMechanisms': 'PLAIN',
+                                    'verifyHostName': 'no',
+                                    'saslUsername': 'test@domain.com', 'saslPassword': 'password'}),
+                     ('router', {'workerThreads': 1,
+                                 'mode': 'interior',
+                                 'routerId': 'QDR.Y'}),
+                     ('listener', {'addr': '0.0.0.0', 'role': 'normal', 'port': y_listener_port}),
+                     ('sslProfile', {'name': 'client-ssl-profile',
+                                     'cert-db': cls.ssl_file('ca-certificate.pem'),
+                                     'cert-file': cls.ssl_file('client-certificate.pem'),
+                                     'key-file': cls.ssl_file('client-private-key.pem'),
+                                     'password': 'client-password'}),
+        ])
+
+        cls.routers[0].wait_ports()
+        cls.routers[1].wait_ports()
+        cls.routers[1].wait_connectors(timeout=3)
+
+    def test_inter_router_plain_over_ssl_exists(self):
+        """
+        Tests to make sure that an inter-router connection exists between the routers since verifyHostName is 'no'.
+        """
+        local_node = Node.connect(self.routers[1].addresses[0], timeout=TIMEOUT)
+
+        self.assertEqual(3, len(local_node.query(type='org.apache.qpid.dispatch.connection').results))
+
+        # sslProto should be TLSv1/SSLv3
+        self.assertEqual(u'TLSv1/SSLv3', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][4])
+
+        # role should be inter-router
+        self.assertEqual(u'inter-router', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][9])
+
+        # sasl must be plain
+        self.assertEqual(u'PLAIN', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][12])
+
+        # user must be test@domain.com
+        self.assertEqual(u'test@domain.com', local_node.query(type='org.apache.qpid.dispatch.connection').results[0][16])
+
 if __name__ == '__main__':
     unittest.main(main_module())
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/2b17c00f/tests/system_tests_two_routers.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_two_routers.py b/tests/system_tests_two_routers.py
index 99cc3ac..32a2ee4 100644
--- a/tests/system_tests_two_routers.py
+++ b/tests/system_tests_two_routers.py
@@ -72,7 +72,10 @@ class RouterTest(TestCase):
         router('A', 'server',
                ('listener', {'role': 'inter-router', 'port': inter_router_port}))
         router('B', 'client',
-               ('connector', {'role': 'inter-router', 'port': inter_router_port}))
+               ('connector',
+                {'role': 'inter-router',
+                 'port': inter_router_port,
+                 'verifyHostName': 'no'}))
 
         cls.routers[0].wait_router_connected('QDR.B')
         cls.routers[1].wait_router_connected('QDR.A')


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org