Repository: qpid-proton Updated Branches: refs/heads/master 595a85404 -> d9c0ed5a4
PROTON-1074: C++ ssl_domain: restore removed ref counting; add test Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/d9c0ed5a Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/d9c0ed5a Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/d9c0ed5a Branch: refs/heads/master Commit: d9c0ed5a4087b621af13c2f395afba91f2bd9063 Parents: 595a854 Author: Clifford Jansen <cliffjan...@apache.org> Authored: Fri Dec 11 09:25:46 2015 -0800 Committer: Clifford Jansen <cliffjan...@apache.org> Committed: Fri Dec 11 09:57:04 2015 -0800 ---------------------------------------------------------------------- examples/cpp/example_test.py | 25 +++++++ examples/cpp/ssl.cpp | 17 ++++- examples/cpp/ssl_client_cert.cpp | 86 +++++++++++++---------- proton-c/bindings/cpp/include/proton/ssl.hpp | 13 ++-- proton-c/bindings/cpp/src/ssl_domain.cpp | 72 +++++++++++++------ 5 files changed, 149 insertions(+), 64 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/d9c0ed5a/examples/cpp/example_test.py ---------------------------------------------------------------------- diff --git a/examples/cpp/example_test.py b/examples/cpp/example_test.py index 2f8b08c..3ea0c28 100644 --- a/examples/cpp/example_test.py +++ b/examples/cpp/example_test.py @@ -25,6 +25,7 @@ from random import randrange from subprocess import Popen, PIPE, STDOUT from copy import copy import platform +from os.path import dirname as dirname def cmdline(*args): """Adjust executable name args[0] for windows and/or valgrind""" @@ -82,6 +83,11 @@ def pick_addr(): p = randrange(10000, 20000) return "127.0.0.1:%s" % p +def ssl_certs_dir(): + """Absolute path to the test SSL certificates""" + pn_root = dirname(dirname(dirname(sys.argv[0]))) + return os.path.join(pn_root, "examples/cpp/ssl_certs") + class Broker(object): """Run the test broker""" @@ -237,5 +243,24 @@ Tock... finally: os.environ = env # Restore environment + def test_ssl(self): + # SSL without SASL + expect="""Outgoing client connection connected via SSL. Server certificate identity CN=test_server +"Hello World!" +""" + addr = "amqps://" + pick_addr() + "/examples" + ignore_first_line, ignore_nl, ssl_hw = execute("ssl", addr, ssl_certs_dir()).partition('\n') + self.assertEqual(expect, ssl_hw) + + def test_ssl_client_cert(self): + # SSL with SASL EXTERNAL + expect="""Inbound client certificate identity CN=test_client +Outgoing client connection connected via SSL. Server certificate identity CN=test_server +"Hello World!" +""" + addr = "amqps://" + pick_addr() + "/examples" + ignore_first_line, ignore_nl, ssl_hw = execute("ssl_client_cert", addr, ssl_certs_dir()).partition('\n') + self.assertEqual(expect, ssl_hw) + if __name__ == "__main__": unittest.main() http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/d9c0ed5a/examples/cpp/ssl.cpp ---------------------------------------------------------------------- diff --git a/examples/cpp/ssl.cpp b/examples/cpp/ssl.cpp index 13ad76f..5e670c2 100644 --- a/examples/cpp/ssl.cpp +++ b/examples/cpp/ssl.cpp @@ -38,6 +38,7 @@ bool using_OpenSSL(); std::string platform_CA(const std::string &base_name); ssl_certificate platform_certificate(const std::string &base_name, const std::string &passwd); std::string cert_directory; +std::string find_CN(const std::string &); struct server_handler : public proton::messaging_handler { @@ -83,8 +84,9 @@ class hello_world_direct : public proton::messaging_handler { } void on_connection_opened(proton::event &e) { - std::cout << "Outgoing client connection connected via SSL. Server certificate has subject " << - e.connection().transport().ssl().remote_subject() << std::endl; + std::string subject = e.connection().transport().ssl().remote_subject(); + std::cout << "Outgoing client connection connected via SSL. Server certificate identity " << + find_CN(subject) << std::endl; } void on_sendable(proton::event &e) { @@ -159,3 +161,14 @@ std::string platform_CA(const std::string &base_name) { return cert_directory + base_name + "-certificate.p12"; } } + +std::string find_CN(const std::string &subject) { + // The subject string is returned with different whitespace and component ordering between platforms. + // Here we just return the common name by searching for "CN=...." in the subject, knowing that + // the test certificates do not contain any escaped characters. + size_t pos = subject.find("CN="); + if (pos == std::string::npos) throw std::runtime_error("No common name in certificate subject"); + std::string cn = subject.substr(pos); + pos = cn.find(','); + return pos == std::string::npos ? cn : cn.substr(0, pos); +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/d9c0ed5a/examples/cpp/ssl_client_cert.cpp ---------------------------------------------------------------------- diff --git a/examples/cpp/ssl_client_cert.cpp b/examples/cpp/ssl_client_cert.cpp index 412162a..0be0c32 100644 --- a/examples/cpp/ssl_client_cert.cpp +++ b/examples/cpp/ssl_client_cert.cpp @@ -40,22 +40,24 @@ bool using_OpenSSL(); std::string platform_CA(const std::string &base_name); ssl_certificate platform_certificate(const std::string &base_name, const std::string &passwd); std::string cert_directory; +std::string find_CN(const std::string &); struct server_handler : public proton::messaging_handler { proton::acceptor inbound_listener; void on_connection_opened(proton::event &e) { - std::cout << "Inbound server connection connected via SSL. Protocol: " << + std::cout << "Inbound server connection connected via SSL. Protocol: " << e.connection().transport().ssl().protocol() << std::endl; - if (e.connection().transport().sasl().outcome() == sasl::OK) - std::cout << "Inbound client certificate subject is " << - e.connection().transport().ssl().remote_subject() << std::endl; + if (e.connection().transport().sasl().outcome() == sasl::OK) { + std::string subject = e.connection().transport().ssl().remote_subject(); + std::cout << "Inbound client certificate identity " << find_CN(subject) << std::endl; + } else { std::cout << "Inbound client authentication failed" <<std::endl; e.connection().close(); } - inbound_listener.close(); + inbound_listener.close(); } void on_message(proton::event &e) { @@ -75,18 +77,18 @@ class hello_world_direct : public proton::messaging_handler { void on_start(proton::event &e) { // Configure listener. Details vary by platform. ssl_certificate server_cert = platform_certificate("tserver", "tserverpw"); - std::string client_CA = platform_CA("tclient"); + std::string client_CA = platform_CA("tclient"); // Specify an SSL domain with CA's for client certificate verification. server_domain sdomain(server_cert, client_CA); connection_options server_opts; server_opts.server_domain(sdomain).handler(&s_handler); - server_opts.allowed_mechs("EXTERNAL"); + server_opts.allowed_mechs("EXTERNAL"); e.container().server_connection_options(server_opts); // Configure client. - ssl_certificate client_cert = platform_certificate("tclient", "tclientpw"); - std::string server_CA = platform_CA("tserver"); - client_domain cdomain(client_cert, server_CA); + ssl_certificate client_cert = platform_certificate("tclient", "tclientpw"); + std::string server_CA = platform_CA("tserver"); + client_domain cdomain(client_cert, server_CA); connection_options client_opts; client_opts.client_domain(cdomain).allowed_mechs("EXTERNAL"); // Validate the server certificate against this name: @@ -98,8 +100,9 @@ class hello_world_direct : public proton::messaging_handler { } void on_connection_opened(proton::event &e) { - std::cout << "Outgoing client connection connected via SSL. Server certificate has subject " << - e.connection().transport().ssl().remote_subject() << std::endl; + std::string subject = e.connection().transport().ssl().remote_subject(); + std::cout << "Outgoing client connection connected via SSL. Server certificate identity " << + find_CN(subject) << std::endl; } void on_sendable(proton::event &e) { @@ -110,7 +113,7 @@ class hello_world_direct : public proton::messaging_handler { } void on_accepted(proton::event &e) { - // All done. + // All done. e.connection().close(); } }; @@ -118,16 +121,16 @@ class hello_world_direct : public proton::messaging_handler { int main(int argc, char **argv) { try { // Pick an "unusual" port since we are going to be talking to ourselves, not a broker. - // Note the use of "amqps" as the URL scheme to denote a TLS/SSL connection. + // Note the use of "amqps" as the URL scheme to denote a TLS/SSL connection. std::string url = argc > 1 ? argv[1] : "amqps://127.0.0.1:8888/examples"; - // Location of certificates and private key information: - if (argc > 2) { - cert_directory = argv[2]; - size_t sz = cert_directory.size(); - if (sz && cert_directory[sz -1] != '/') - cert_directory.append("/"); - } - else cert_directory = "ssl_certs/"; + // Location of certificates and private key information: + if (argc > 2) { + cert_directory = argv[2]; + size_t sz = cert_directory.size(); + if (sz && cert_directory[sz -1] != '/') + cert_directory.append("/"); + } + else cert_directory = "ssl_certs/"; hello_world_direct hwd(url); proton::container(hwd).run(); @@ -139,7 +142,7 @@ int main(int argc, char **argv) { } -bool using_OpenSSL() { +bool using_OpenSSL() { // Current defaults. #if defined(WIN32) return false; @@ -150,27 +153,38 @@ bool using_OpenSSL() { ssl_certificate platform_certificate(const std::string &base_name, const std::string &passwd) { if (using_OpenSSL()) { - // The first argument will be the name of the file containing the public certificate, the - // second argument will be the name of the file containing the private key. - return ssl_certificate(cert_directory + base_name + "-certificate.pem", - cert_directory + base_name + "-private-key.pem", passwd); + // The first argument will be the name of the file containing the public certificate, the + // second argument will be the name of the file containing the private key. + return ssl_certificate(cert_directory + base_name + "-certificate.pem", + cert_directory + base_name + "-private-key.pem", passwd); } else { - // Windows SChannel - // The first argument will be the database or store that contains one or more complete certificates - // (public and private data). The second will be an optional name of the certificate in the store - // (not used in this example with one certificate per store). - return ssl_certificate(cert_directory + base_name + "-full.p12", "", passwd); + // Windows SChannel + // The first argument will be the database or store that contains one or more complete certificates + // (public and private data). The second will be an optional name of the certificate in the store + // (not used in this example with one certificate per store). + return ssl_certificate(cert_directory + base_name + "-full.p12", "", passwd); } } std::string platform_CA(const std::string &base_name) { if (using_OpenSSL()) { - // In this simple example with self-signed certificates, the peer's certificate is the CA database. - return cert_directory + base_name + "-certificate.pem"; + // In this simple example with self-signed certificates, the peer's certificate is the CA database. + return cert_directory + base_name + "-certificate.pem"; } else { - // Windows SChannel. Use a pkcs#12 file with just the peer's public certificate information. - return cert_directory + base_name + "-certificate.p12"; + // Windows SChannel. Use a pkcs#12 file with just the peer's public certificate information. + return cert_directory + base_name + "-certificate.p12"; } } + +std::string find_CN(const std::string &subject) { + // The subject string is returned with different whitespace and component ordering between platforms. + // Here we just return the common name by searching for "CN=...." in the subject, knowing that + // the test certificates do not contain any escaped characters. + size_t pos = subject.find("CN="); + if (pos == std::string::npos) throw std::runtime_error("No common name in certificate subject"); + std::string cn = subject.substr(pos); + pos = cn.find(','); + return pos == std::string::npos ? cn : cn.substr(0, pos); +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/d9c0ed5a/proton-c/bindings/cpp/include/proton/ssl.hpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/include/proton/ssl.hpp b/proton-c/bindings/cpp/include/proton/ssl.hpp index f1b5974..bb78ae3 100644 --- a/proton-c/bindings/cpp/include/proton/ssl.hpp +++ b/proton-c/bindings/cpp/include/proton/ssl.hpp @@ -71,18 +71,22 @@ class ssl_certificate { friend class server_domain; }; + +class ssl_domain_impl; + // Base class for SSL configuration class ssl_domain { public: + PN_CPP_EXTERN ssl_domain(const ssl_domain&); + PN_CPP_EXTERN ssl_domain& operator=(const ssl_domain&); ~ssl_domain(); protected: - ssl_domain(); - pn_ssl_domain_t *init(bool is_server); + ssl_domain(bool is_server); pn_ssl_domain_t *pn_domain(); private: - pn_ssl_domain_t *impl_; + ssl_domain_impl *impl_; }; /** SSL/TLS configuration for inbound connections created from a listener */ @@ -122,9 +126,6 @@ class client_domain : private ssl_domain { friend class connection_options; }; - - - } #endif /*!PROTON_CPP_SSL_H*/ http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/d9c0ed5a/proton-c/bindings/cpp/src/ssl_domain.cpp ---------------------------------------------------------------------- diff --git a/proton-c/bindings/cpp/src/ssl_domain.cpp b/proton-c/bindings/cpp/src/ssl_domain.cpp index 2631880..9062713 100644 --- a/proton-c/bindings/cpp/src/ssl_domain.cpp +++ b/proton-c/bindings/cpp/src/ssl_domain.cpp @@ -26,19 +26,50 @@ namespace proton { -ssl_domain::ssl_domain() : impl_(0) {} - -// Create on demand -pn_ssl_domain_t *ssl_domain::init(bool server_type) { - if (impl_) return impl_; - impl_ = pn_ssl_domain(server_type ? PN_SSL_MODE_SERVER : PN_SSL_MODE_CLIENT); - if (!impl_) throw error(MSG("SSL/TLS unavailable")); - return impl_; +class ssl_domain_impl { + public: + ssl_domain_impl(bool is_server) : refcount_(1), server_type_(is_server), pn_domain_(0) {} + void incref() { refcount_++; } + void decref() { + if (--refcount_ == 0) { + if (pn_domain_) pn_ssl_domain_free(pn_domain_); + delete this; + } + } + pn_ssl_domain_t *pn_domain(); + private: + int refcount_; + bool server_type_; + pn_ssl_domain_t *pn_domain_; + ssl_domain_impl(const ssl_domain_impl&); + ssl_domain_impl& operator=(const ssl_domain_impl&); +}; + +pn_ssl_domain_t *ssl_domain_impl::pn_domain() { + if (pn_domain_) return pn_domain_; + // Lazily create in case never actually used or configured. + pn_domain_ = pn_ssl_domain(server_type_ ? PN_SSL_MODE_SERVER : PN_SSL_MODE_CLIENT); + if (!pn_domain_) throw error(MSG("SSL/TLS unavailable")); + return pn_domain_; } -pn_ssl_domain_t *ssl_domain::pn_domain() { return impl_; } +ssl_domain::ssl_domain(bool is_server) : impl_(new ssl_domain_impl(is_server)) {} + +ssl_domain::ssl_domain(const ssl_domain &x) { + impl_ = x.impl_; + impl_->incref(); +} +ssl_domain& ssl_domain::operator=(const ssl_domain&x) { + if (this != &x) { + impl_ = x.impl_; + impl_->incref(); + } + return *this; +} +ssl_domain::~ssl_domain() { impl_->decref(); } + +pn_ssl_domain_t *ssl_domain::pn_domain() { return impl_->pn_domain(); } -ssl_domain::~ssl_domain() { if (impl_) pn_ssl_domain_free(impl_); } namespace { @@ -51,17 +82,17 @@ void set_cred(pn_ssl_domain_t *dom, const std::string &main, const std::string & } } -server_domain::server_domain(ssl_certificate &cert) { - set_cred(init(true), cert.certdb_main_, cert.certdb_extra_, cert.passwd_, cert.pw_set_); +server_domain::server_domain(ssl_certificate &cert) : ssl_domain(true) { + set_cred(pn_domain(), cert.certdb_main_, cert.certdb_extra_, cert.passwd_, cert.pw_set_); } server_domain::server_domain( ssl_certificate &cert, const std::string &trust_db, const std::string &advertise_db, - ssl::verify_mode_t mode) + ssl::verify_mode_t mode) : ssl_domain(true) { - pn_ssl_domain_t* dom = init(true); + pn_ssl_domain_t* dom = pn_domain(); set_cred(dom, cert.certdb_main_, cert.certdb_extra_, cert.passwd_, cert.pw_set_); if (pn_ssl_domain_set_trusted_ca_db(dom, trust_db.c_str())) throw error(MSG("SSL trust store initialization failure for " << trust_db)); @@ -70,9 +101,10 @@ server_domain::server_domain( throw error(MSG("SSL server configuration failure requiring client certificates using " << db)); } -server_domain::server_domain() {} +server_domain::server_domain() : ssl_domain(true) {} server_domain::~server_domain() {} + namespace { void client_setup(pn_ssl_domain_t *dom, const std::string &trust_db, ssl::verify_mode_t mode) { if (pn_ssl_domain_set_trusted_ca_db(dom, trust_db.c_str())) @@ -82,17 +114,17 @@ void client_setup(pn_ssl_domain_t *dom, const std::string &trust_db, ssl::verify } } -client_domain::client_domain(const std::string &trust_db, ssl::verify_mode_t mode) { - client_setup(init(false), trust_db, mode); +client_domain::client_domain(const std::string &trust_db, ssl::verify_mode_t mode) : ssl_domain(false) { + client_setup(pn_domain(), trust_db, mode); } -client_domain::client_domain(ssl_certificate &cert, const std::string &trust_db, ssl::verify_mode_t mode) { - pn_ssl_domain_t *dom = init(false); +client_domain::client_domain(ssl_certificate &cert, const std::string &trust_db, ssl::verify_mode_t mode) : ssl_domain(false) { + pn_ssl_domain_t *dom = pn_domain(); set_cred(dom, cert.certdb_main_, cert.certdb_extra_, cert.passwd_, cert.pw_set_); client_setup(dom, trust_db, mode); } -client_domain::client_domain() {} +client_domain::client_domain() : ssl_domain(false) {} client_domain::~client_domain() {} ssl_certificate::ssl_certificate(const std::string &main, const std::string &extra) --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org