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]