Author: kgiusti
Date: Fri Dec 14 19:16:51 2012
New Revision: 1422044

URL: http://svn.apache.org/viewvc?rev=1422044&view=rev
Log:
PROTON-161: checkpoint - peer common name retrieval and check works.

Modified:
    qpid/proton/trunk/proton-c/bindings/python/proton.py
    qpid/proton/trunk/proton-c/include/proton/ssl.h
    qpid/proton/trunk/proton-c/src/ssl/openssl.c
    qpid/proton/trunk/tests/proton_tests/ssl.py

Modified: qpid/proton/trunk/proton-c/bindings/python/proton.py
URL: 
http://svn.apache.org/viewvc/qpid/proton/trunk/proton-c/bindings/python/proton.py?rev=1422044&r1=1422043&r2=1422044&view=diff
==============================================================================
--- qpid/proton/trunk/proton-c/bindings/python/proton.py (original)
+++ qpid/proton/trunk/proton-c/bindings/python/proton.py Fri Dec 14 19:16:51 
2012
@@ -2364,6 +2364,9 @@ class SSL(object):
       return name
     return None
 
+  def set_peer_hostname(self, hostname):
+    pn_ssl_set_peer_hostname( self._ssl, hostname )
+
 __all__ = ["Messenger", "Message", "ProtonException", "MessengerException",
            "MessageException", "Timeout", "Condition", "Data", "Endpoint",
            "Connection", "Session", "Link", "Terminus", "Sender", "Receiver",

Modified: qpid/proton/trunk/proton-c/include/proton/ssl.h
URL: 
http://svn.apache.org/viewvc/qpid/proton/trunk/proton-c/include/proton/ssl.h?rev=1422044&r1=1422043&r2=1422044&view=diff
==============================================================================
--- qpid/proton/trunk/proton-c/include/proton/ssl.h (original)
+++ qpid/proton/trunk/proton-c/include/proton/ssl.h Fri Dec 14 19:16:51 2012
@@ -210,6 +210,16 @@ bool pn_ssl_get_cipher_name(pn_ssl_t *ss
  * @return True if the version information was to buffer, False if SSL 
connection not ready.
  */
 bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size);
+
+
+void pn_ssl_set_peer_hostname( pn_ssl_t *ssl, const char *hostname);
+
+typedef enum {
+  PN_SSL_MATCH_EXACT,
+  PN_SSL_MATCH_WILDCARD
+} pn_ssl_match_flag;
+int pn_ssl_set_peer_hostname_match( pn_ssl_t *ssl, const char *pattern, 
pn_ssl_match_flag flag);
+
 #ifdef __cplusplus
 }
 #endif

Modified: qpid/proton/trunk/proton-c/src/ssl/openssl.c
URL: 
http://svn.apache.org/viewvc/qpid/proton/trunk/proton-c/src/ssl/openssl.c?rev=1422044&r1=1422043&r2=1422044&view=diff
==============================================================================
--- qpid/proton/trunk/proton-c/src/ssl/openssl.c (original)
+++ qpid/proton/trunk/proton-c/src/ssl/openssl.c Fri Dec 14 19:16:51 2012
@@ -41,6 +41,7 @@
  */
 
 static int ssl_initialized;
+static int ssl_ex_data_index;
 
 typedef enum { UNKNOWN_CONNECTION, SSL_CONNECTION, CLEAR_CONNECTION } 
connection_mode_t;
 
@@ -55,6 +56,10 @@ struct pn_ssl_t {
   pn_ssl_verify_mode_t verify_mode;
   char *trusted_CAs;
 
+  char *peer_hostname;
+  char *peer_match_pattern;
+  pn_ssl_match_flag     match_type;
+
   pn_transport_t *transport;
 
   BIO *bio_ssl;         // i/o from/to SSL socket layer
@@ -163,55 +168,69 @@ static int ssl_failed(pn_ssl_t *ssl)
   return pn_error_format( ssl->transport->error, PN_ERR, "SSL Failure: %s", 
buf );
 }
 
-// @todo replace with a "reasonable" default (?), allow application to 
register its own
-// callback.
-#if 0
+// Certificate chain verification callback: return 1 if verified,
+// 0 if remote cannot be verified (fail handshake).
+//
 static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
 {
-    fprintf(stderr, "VERIFY_CALLBACK: pre-verify-ok=%d\n", preverify_ok);
-
-           char    buf[256];
-           X509   *err_cert;
-           int     err, depth;
-
-           err_cert = X509_STORE_CTX_get_current_cert(ctx);
-           err = X509_STORE_CTX_get_error(ctx);
-           depth = X509_STORE_CTX_get_error_depth(ctx);
-
-           /*
-            * Retrieve the pointer to the SSL of the connection currently 
treated
-            * and the application specific data stored into the SSL object.
-            */
-
-           X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
-
-           /*
-            * Catch a too long certificate chain. The depth limit set using
-            * SSL_CTX_set_verify_depth() is by purpose set to "limit+1" so
-            * that whenever the "depth>verify_depth" condition is met, we
-            * have violated the limit and want to log this error condition.
-            * We must do it here, because the CHAIN_TOO_LONG error would not
-            * be found explicitly; only errors introduced by cutting off the
-            * additional certificates would be logged.
-            */
-           if (!preverify_ok) {
-               printf("verify error:num=%d:%s:depth=%d:%s\n", err,
-                        X509_verify_cert_error_string(err), depth, buf);
-           }
-
-           /*
-            * At this point, err contains the last verification error. We can 
use
-            * it for something special
-            */
-           if (!preverify_ok && (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT))
-           {
-             X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 
256);
-             printf("issuer= %s\n", buf);
-           }
+  if (!preverify_ok || X509_STORE_CTX_get_error_depth(ctx) != 0)
+    // already failed, or not at peer cert in chain
+    return preverify_ok;
+
+  X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
+  SSL *ssn = X509_STORE_CTX_get_ex_data(ctx, 
SSL_get_ex_data_X509_STORE_CTX_idx());
+  if (!ssn) {
+    _log_error("Error: unexpected error - SSL session info not available for 
peer verify!\n");
+    return 0;  // fail connection
+  }
+
+  pn_ssl_t *ssl = (pn_ssl_t *)SSL_get_ex_data(ssn, ssl_ex_data_index);
+  if (!ssl) {
+    _log_error("Error: unexpected error - SSL context info not available for 
peer verify!\n");
+    return 0;  // fail connection
+  }
+
+  if (!ssl->peer_match_pattern) return preverify_ok;
+
+  _log( ssl, "Checking commonName in peer cert against host pattern (%s)\n", 
ssl->peer_match_pattern);
+
+  X509_NAME *name = X509_get_subject_name(cert);
+  int i = -1;
+  bool matched = false;
+  while (!matched && (i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) 
>= 0) {
+    X509_NAME_ENTRY *ne = X509_NAME_get_entry(name, i);
+    ASN1_STRING *name_asn1 = X509_NAME_ENTRY_get_data(ne);
+    if (name_asn1) {
+      unsigned char *str;
+      int len = ASN1_STRING_to_UTF8( &str, name_asn1);
+      if (len >= 0) {
+        _log( ssl, "commonName from peer cert = '%.*s'\n", len, str );
+        switch (ssl->match_type) {
+        case PN_SSL_MATCH_EXACT:
+          matched = (len == strlen(ssl->peer_match_pattern) &&
+                     strncasecmp( (const char *)str, ssl->peer_match_pattern, 
len ) == 0);
+          break;
+        case PN_SSL_MATCH_WILDCARD:
+          // TBD
+          break;
+        }
+        OPENSSL_free(str);
+      }
+    }
+  }
 
-    return 1;
-}
+  if (!matched) {
+    _log( ssl, "Error: no commonName matching %s found - peer is invalid.\n",
+          ssl->peer_match_pattern);
+    preverify_ok = 0;
+#ifdef X509_V_ERR_APPLICATION_VERIFICATION
+    X509_STORE_CTX_set_error( ctx, X509_V_ERR_APPLICATION_VERIFICATION );
 #endif
+  } else {
+    _log( ssl, "commonName matched - peer is valid.\n" );
+  }
+  return preverify_ok;
+}
 
 
 // this code was generated using the command:
@@ -404,8 +423,8 @@ int pn_ssl_set_peer_authentication(pn_ss
       }
     }
 
-    SSL_CTX_set_verify( ssl->ctx, SSL_VERIFY_PEER | 
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
-    // verify_callback /*?verify callback?*/ );
+    SSL_CTX_set_verify( ssl->ctx, SSL_VERIFY_PEER | 
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                        verify_callback);
 #if (OPENSSL_VERSION_NUMBER < 0x00905100L)
     SSL_CTX_set_verify_depth(ssl->ctx, 1);
 #endif
@@ -550,6 +569,8 @@ pn_ssl_t *pn_ssl(pn_transport_t *transpo
     SSL_library_init();
     SSL_load_error_strings();
     OpenSSL_add_all_algorithms();
+    ssl_ex_data_index = SSL_get_ex_new_index( 0, "org.apache.qpid.proton.ssl",
+                                              NULL, NULL, NULL);
   }
 
   pn_ssl_t *ssl = calloc(1, sizeof(pn_ssl_t));
@@ -585,6 +606,8 @@ void pn_ssl_free( pn_ssl_t *ssl)
 
   if (ssl->keyfile_pw) free(ssl->keyfile_pw);
   if (ssl->trusted_CAs) free(ssl->trusted_CAs);
+  if (ssl->peer_hostname) free(ssl->peer_hostname);
+  if (ssl->peer_match_pattern) free(ssl->peer_match_pattern);
 
   free(ssl);
 }
@@ -879,6 +902,15 @@ static int init_ssl_socket( pn_ssl_t *ss
     return -1;
   }
 
+  // store backpointer to pn_ssl_t in SSL object:
+  SSL_set_ex_data(ssl->ssl, ssl_ex_data_index, ssl);
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+  if (ssl->peer_hostname) {
+    SSL_set_tlsext_host_name(ssl->ssl, ssl->peer_hostname);
+  }
+#endif
+
   // now layer a BIO over the SSL socket
   ssl->bio_ssl = BIO_new(BIO_f_ssl());
   if (!ssl->bio_ssl) {
@@ -1010,3 +1042,35 @@ void pn_ssl_trace(pn_ssl_t *ssl, pn_trac
 {
   ssl->trace = trace;
 }
+
+void pn_ssl_set_peer_hostname( pn_ssl_t *ssl, const char *hostname)
+{
+  if (!ssl) return;
+
+  if (ssl->peer_hostname) free(ssl->peer_hostname);
+  ssl->peer_hostname = NULL;
+  if (hostname) {
+    ssl->peer_hostname = pn_strdup(hostname);
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+    if (ssl->ssl) {
+      SSL_set_tlsext_host_name(ssl->ssl, ssl->peer_hostname);
+    }
+#endif
+    if (!ssl->peer_match_pattern)
+      pn_ssl_set_peer_hostname_match( ssl, hostname, PN_SSL_MATCH_EXACT );
+  }
+}
+
+// uses RFC1034, Sec 3.5 host name syntax
+int pn_ssl_set_peer_hostname_match( pn_ssl_t *ssl, const char *pattern, 
pn_ssl_match_flag flag)
+{
+  if (!ssl) return -1;
+
+  if (ssl->peer_match_pattern) free(ssl->peer_match_pattern);
+  ssl->peer_match_pattern = NULL;
+  if (pattern) {
+    ssl->peer_match_pattern = strdup(pattern);
+    ssl->match_type = flag;
+  }
+  return 0;
+}

Modified: qpid/proton/trunk/tests/proton_tests/ssl.py
URL: 
http://svn.apache.org/viewvc/qpid/proton/trunk/tests/proton_tests/ssl.py?rev=1422044&r1=1422043&r2=1422044&view=diff
==============================================================================
--- qpid/proton/trunk/tests/proton_tests/ssl.py (original)
+++ qpid/proton/trunk/tests/proton_tests/ssl.py Fri Dec 14 19:16:51 2012
@@ -277,3 +277,52 @@ class SslTest(common.Test):
         except TransportException:
             assert True
 
+    def test_server_hostname_authentication(self):
+        """ Simple SSL connection with authentication of the server and check
+        of its hostname.
+        """
+        self.server.set_credentials(self._testpath("server-certificate.pem"),
+                                    self._testpath("server-private-key.pem"),
+                                    "server-password")
+
+        #self.t_client.trace( Transport.TRACE_DRV )
+        self.client.set_trusted_ca_db(self._testpath("ca-certificate.pem"))
+        self.client.set_peer_authentication( SSL.VERIFY_PEER )
+        self.client.set_peer_hostname( "127.0.0.1" )
+
+        client_conn = Connection()
+        self.t_client.bind(client_conn)
+        server_conn = Connection()
+        self.t_server.bind(server_conn)
+        client_conn.open()
+        server_conn.open()
+        self._pump()
+        assert self.client.protocol_name() is not None
+        client_conn.close()
+        server_conn.close()
+        self._pump()
+
+
+    def test_server_hostname_authentication_fail(self):
+        """ Should fail due to mismatched peer hostname
+        """
+        self.server.set_credentials(self._testpath("server-certificate.pem"),
+                                    self._testpath("server-private-key.pem"),
+                                    "server-password")
+
+        #self.t_client.trace( Transport.TRACE_DRV )
+        self.client.set_trusted_ca_db(self._testpath("ca-certificate.pem"))
+        self.client.set_peer_authentication( SSL.VERIFY_PEER )
+        self.client.set_peer_hostname( "127.0.0.1x" )
+
+        client_conn = Connection()
+        self.t_client.bind(client_conn)
+        server_conn = Connection()
+        self.t_server.bind(server_conn)
+        client_conn.open()
+        server_conn.open()
+        try:
+            self._pump()
+            assert False, "Expected connection to fail due to hostname 
mismatch"
+        except TransportException:
+            pass



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

Reply via email to