diff options
author | Jonathan Robie <jonathan@apache.org> | 2011-02-02 16:00:51 +0000 |
---|---|---|
committer | Jonathan Robie <jonathan@apache.org> | 2011-02-02 16:00:51 +0000 |
commit | 5a8c4d1855fdcf7fe2588502d80b767edabae96e (patch) | |
tree | 281f03af5c9685ede59e59c241c2e01976a05ca5 | |
parent | 8cc4082337bd6ea1be0c9f96d3383314f7fc228b (diff) | |
download | qpid-python-5a8c4d1855fdcf7fe2588502d80b767edabae96e.tar.gz |
Resolves QPID-3031. Allows client connection options to specify an SSL cert-name.
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1066508 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | cpp/include/qpid/client/ConnectionSettings.h | 5 | ||||
-rw-r--r-- | cpp/src/qpid/client/ConnectionSettings.cpp | 9 | ||||
-rw-r--r-- | cpp/src/qpid/client/SslConnector.cpp | 23 | ||||
-rw-r--r-- | cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp | 14 | ||||
-rw-r--r-- | cpp/src/qpid/client/amqp0_10/ConnectionImpl.h | 4 | ||||
-rw-r--r-- | cpp/src/qpid/sys/ssl/SslSocket.cpp | 47 | ||||
-rw-r--r-- | cpp/src/qpid/sys/ssl/SslSocket.h | 27 | ||||
-rwxr-xr-x | cpp/src/tests/ssl_test | 40 |
8 files changed, 114 insertions, 55 deletions
diff --git a/cpp/include/qpid/client/ConnectionSettings.h b/cpp/include/qpid/client/ConnectionSettings.h index bf060e73bb..1c2ee28b1b 100644 --- a/cpp/include/qpid/client/ConnectionSettings.h +++ b/cpp/include/qpid/client/ConnectionSettings.h @@ -122,6 +122,11 @@ struct ConnectionSettings { * layer. 0 means no security layer allowed. */ unsigned int maxSsf; + /** + * SSL cert-name for the connection. Overrides global SSL + * settings. Used only when a client connects to the broker. + */ + std::string sslCertName; }; }} // namespace qpid::client diff --git a/cpp/src/qpid/client/ConnectionSettings.cpp b/cpp/src/qpid/client/ConnectionSettings.cpp index 60b2eac2e8..822e4af269 100644 --- a/cpp/src/qpid/client/ConnectionSettings.cpp +++ b/cpp/src/qpid/client/ConnectionSettings.cpp @@ -7,9 +7,9 @@ * 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 @@ -30,7 +30,7 @@ namespace client { ConnectionSettings::ConnectionSettings() : protocol("tcp"), - host("localhost"), + host("localhost"), port(5672), locale("en_US"), heartbeat(0), @@ -40,7 +40,8 @@ ConnectionSettings::ConnectionSettings() : tcpNoDelay(false), service(qpid::saslName), minSsf(0), - maxSsf(256) + maxSsf(256), + sslCertName("") {} ConnectionSettings::~ConnectionSettings() {} diff --git a/cpp/src/qpid/client/SslConnector.cpp b/cpp/src/qpid/client/SslConnector.cpp index e82fc2f8da..35c7e6bdf6 100644 --- a/cpp/src/qpid/client/SslConnector.cpp +++ b/cpp/src/qpid/client/SslConnector.cpp @@ -7,9 +7,9 @@ * 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 @@ -130,7 +130,7 @@ class SslConnector : public Connector public: SslConnector(Poller::shared_ptr p, framing::ProtocolVersion pVersion, - const ConnectionSettings&, + const ConnectionSettings&, ConnectionImpl*); }; @@ -170,7 +170,7 @@ SslConnector::SslConnector(Poller::shared_ptr p, const ConnectionSettings& settings, ConnectionImpl* cimpl) : maxFrameSize(settings.maxFrameSize), - version(ver), + version(ver), initiated(false), closed(true), shutdownHandler(0), @@ -179,8 +179,11 @@ SslConnector::SslConnector(Poller::shared_ptr p, poller(p) { QPID_LOG(debug, "SslConnector created for " << version.toString()); - //TODO: how do we want to handle socket configuration with ssl? - //settings.configureSocket(socket); + + if (settings.sslCertName != "") { + QPID_LOG(debug, "ssl-cert-name = " << settings.sslCertName); + socket.setCertName(settings.sslCertName); + } } SslConnector::~SslConnector() { @@ -244,14 +247,14 @@ void SslConnector::setShutdownHandler(ShutdownHandler* handler){ } OutputHandler* SslConnector::getOutputHandler() { - return this; + return this; } sys::ShutdownHandler* SslConnector::getShutdownHandler() const { return shutdownHandler; } -const std::string& SslConnector::getIdentifier() const { +const std::string& SslConnector::getIdentifier() const { return identifier; } @@ -271,7 +274,7 @@ void SslConnector::Writer::init(std::string id, sys::ssl::SslIO* a) { aio = a; newBuffer(); } -void SslConnector::Writer::handle(framing::AMQFrame& frame) { +void SslConnector::Writer::handle(framing::AMQFrame& frame) { Mutex::ScopedLock l(lock); frames.push_back(frame); if (frame.getEof() || (bounds && bounds->getCurrentSize() >= maxFrameSize)) { @@ -372,7 +375,7 @@ const SecuritySettings* SslConnector::getSecuritySettings() { securitySettings.ssf = socket.getKeyLen(); securitySettings.authid = "dummy";//set to non-empty string to enable external authentication - return &securitySettings; + return &securitySettings; } }} // namespace qpid::client diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp index a60177a5d8..5a545c1f6a 100644 --- a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp @@ -7,9 +7,9 @@ * 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 @@ -42,7 +42,7 @@ using qpid::framing::Uuid; void convert(const Variant::List& from, std::vector<std::string>& to) { for (Variant::List::const_iterator i = from.begin(); i != from.end(); ++i) { - to.push_back(i->asString()); + to.push_back(i->asString()); } } @@ -108,9 +108,11 @@ void convert(const Variant::Map& from, ConnectionSettings& to) setIfFound(from, "bounds", to.bounds); setIfFound(from, "transport", to.protocol); + + setIfFound(from, "ssl-cert-name", to.sslCertName); } -ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) : +ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) : reconnect(false), timeout(-1), limit(-1), minReconnectInterval(3), maxReconnectInterval(60), retries(0), reconnectOnLimitExceeded(true) @@ -135,7 +137,7 @@ void ConnectionImpl::setOptions(const Variant::Map& options) setIfFound(options, "reconnect-interval-max", maxReconnectInterval); } setIfFound(options, "reconnect-urls", urls); - setIfFound(options, "x-reconnect-on-limit-exceeded", reconnectOnLimitExceeded); + setIfFound(options, "x-reconnect-on-limit-exceeded", reconnectOnLimitExceeded); } void ConnectionImpl::setOption(const std::string& name, const Variant& value) @@ -216,7 +218,7 @@ qpid::messaging::Session ConnectionImpl::newSession(bool transactional, const st } catch (const qpid::SessionException& e) { throw qpid::messaging::SessionError(e.what()); } catch (const std::exception& e) { - throw qpid::messaging::MessagingException(e.what()); + throw qpid::messaging::MessagingException(e.what()); } } return impl; diff --git a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h index 8562add6b1..09f2038312 100644 --- a/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h +++ b/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h @@ -10,9 +10,9 @@ * 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 diff --git a/cpp/src/qpid/sys/ssl/SslSocket.cpp b/cpp/src/qpid/sys/ssl/SslSocket.cpp index 8ebc5937d2..01e2658877 100644 --- a/cpp/src/qpid/sys/ssl/SslSocket.cpp +++ b/cpp/src/qpid/sys/ssl/SslSocket.cpp @@ -7,9 +7,9 @@ * 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 @@ -52,9 +52,9 @@ namespace ssl { namespace { std::string getName(int fd, bool local, bool includeService = false) { - ::sockaddr_storage name; // big enough for any socket address + ::sockaddr_storage name; // big enough for any socket address ::socklen_t namelen = sizeof(name); - + int result = -1; if (local) { result = ::getsockname(fd, (::sockaddr*)&name, &namelen); @@ -67,8 +67,8 @@ std::string getName(int fd, bool local, bool includeService = false) char servName[NI_MAXSERV]; char dispName[NI_MAXHOST]; if (includeService) { - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), - servName, sizeof(servName), + if (int rc=::getnameinfo((::sockaddr*)&name, namelen, dispName, sizeof(dispName), + servName, sizeof(servName), NI_NUMERICHOST | NI_NUMERICSERV) != 0) throw QPID_POSIX_ERROR(rc); return std::string(dispName) + ":" + std::string(servName); @@ -82,9 +82,9 @@ std::string getName(int fd, bool local, bool includeService = false) std::string getService(int fd, bool local) { - ::sockaddr_storage name; // big enough for any socket address + ::sockaddr_storage name; // big enough for any socket address ::socklen_t namelen = sizeof(name); - + int result = -1; if (local) { result = ::getsockname(fd, (::sockaddr*)&name, &namelen); @@ -95,8 +95,8 @@ std::string getService(int fd, bool local) QPID_POSIX_CHECK(result); char servName[NI_MAXSERV]; - if (int rc=::getnameinfo((::sockaddr*)&name, namelen, 0, 0, - servName, sizeof(servName), + if (int rc=::getnameinfo((::sockaddr*)&name, namelen, 0, 0, + servName, sizeof(servName), NI_NUMERICHOST | NI_NUMERICSERV) != 0) throw QPID_POSIX_ERROR(rc); return servName; @@ -132,8 +132,8 @@ std::string getDomainFromSubject(std::string subject) } -SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), prototype(0) -{ +SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), prototype(0) +{ impl->fd = ::socket (PF_INET, SOCK_STREAM, 0); if (impl->fd < 0) throw QPID_POSIX_ERROR(errno); socket = SSL_ImportFD(0, PR_ImportTCPSocket(impl->fd)); @@ -145,12 +145,12 @@ SslSocket::SslSocket() : IOHandle(new IOHandlePrivate()), socket(0), prototype(0 * PR_Accept, we have to reset the handshake. */ SslSocket::SslSocket(IOHandlePrivate* ioph, PRFileDesc* model) : IOHandle(ioph), socket(0), prototype(0) -{ +{ socket = SSL_ImportFD(model, PR_ImportTCPSocket(impl->fd)); NSS_CHECK(SSL_ResetHandshake(socket, true)); } -void SslSocket::setNonblocking() const +void SslSocket::setNonblocking() const { PRSocketOptionData option; option.option = PR_SockOpt_Nonblocking; @@ -164,7 +164,15 @@ void SslSocket::connect(const std::string& host, uint16_t port) const namestream << host << ":" << port; connectname = namestream.str(); - void* arg = SslOptions::global.certName.empty() ? 0 : const_cast<char*>(SslOptions::global.certName.c_str()); + void* arg; + // Use the connection's cert-name if it has one; else use global cert-name + if (certname != "") { + arg = const_cast<char*>(certname.c_str()); + } else if (SslOptions::global.certName.empty()) { + arg = 0; + } else { + arg = const_cast<char*>(SslOptions::global.certName.c_str()); + } NSS_CHECK(SSL_GetClientAuthDataHook(socket, NSS_GetClientAuthData, arg)); NSS_CHECK(SSL_SetURL(socket, host.data())); @@ -220,7 +228,7 @@ int SslSocket::listen(uint16_t port, int backlog, const std::string& certName, b throw Exception(QPID_MSG("Can't bind to port " << port << ": " << strError(errno))); if (::listen(socket, backlog) < 0) throw Exception(QPID_MSG("Can't listen on port " << port << ": " << strError(errno))); - + socklen_t namelen = sizeof(name); if (::getsockname(socket, (struct sockaddr*)&name, &namelen) < 0) throw QPID_POSIX_ERROR(errno); @@ -235,7 +243,7 @@ SslSocket* SslSocket::accept() const return new SslSocket(new IOHandlePrivate(afd), prototype); } else if (errno == EAGAIN) { return 0; - } else { + } else { throw QPID_POSIX_ERROR(errno); } } @@ -303,6 +311,11 @@ void SslSocket::setTcpNoDelay(bool nodelay) const } } +void SslSocket::setCertName(const std::string& name) +{ + certname = name; +} + /** get the bit length of the current cipher's key */ int SslSocket::getKeyLen() const diff --git a/cpp/src/qpid/sys/ssl/SslSocket.h b/cpp/src/qpid/sys/ssl/SslSocket.h index 102e56986f..25712c98d5 100644 --- a/cpp/src/qpid/sys/ssl/SslSocket.h +++ b/cpp/src/qpid/sys/ssl/SslSocket.h @@ -10,9 +10,9 @@ * 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 @@ -41,13 +41,18 @@ class SslSocket : public qpid::sys::IOHandle public: /** Create a socket wrapper for descriptor. */ SslSocket(); - + /** Set socket non blocking */ void setNonblocking() const; /** Set tcp-nodelay */ void setTcpNoDelay(bool nodelay) const; + /** Set SSL cert-name. Allows the cert-name to be set per + * connection, overriding global cert-name settings from + * NSSInit().*/ + void setCertName(const std::string& certName); + void connect(const std::string& host, uint16_t port) const; void close() const; @@ -59,33 +64,33 @@ public: *@return The bound port. */ int listen(uint16_t port = 0, int backlog = 10, const std::string& certName = "localhost.localdomain", bool clientAuth = false) const; - - /** + + /** * Accept a connection from a socket that is already listening * and has an incoming connection */ SslSocket* accept() const; - // TODO The following are raw operations, maybe they need better wrapping? + // TODO The following are raw operations, maybe they need better wrapping? int read(void *buf, size_t count) const; int write(const void *buf, size_t count) const; - /** Returns the "socket name" ie the address bound to + /** Returns the "socket name" ie the address bound to * the near end of the socket */ std::string getSockname() const; - /** Returns the "peer name" ie the address bound to + /** Returns the "peer name" ie the address bound to * the remote end of the socket */ std::string getPeername() const; - /** + /** * Returns an address (host and port) for the remote end of the * socket */ std::string getPeerAddress() const; - /** + /** * Returns an address (host and port) for the local end of the * socket */ @@ -111,6 +116,8 @@ public: private: mutable std::string connectname; mutable PRFileDesc* socket; + std::string certname; + /** * 'model' socket, with configuration to use when importing * accepted sockets for use as ssl sockets. Set on listen(), used diff --git a/cpp/src/tests/ssl_test b/cpp/src/tests/ssl_test index 2e4add558e..04584f169d 100755 --- a/cpp/src/tests/ssl_test +++ b/cpp/src/tests/ssl_test @@ -8,9 +8,9 @@ # 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 @@ -26,6 +26,7 @@ CONFIG=$(dirname $0)/config.null CERT_DIR=`pwd`/test_cert_db CERT_PW_FILE=`pwd`/cert.password TEST_HOSTNAME=127.0.0.1 +TEST_CLIENT_CERT=rumplestiltskin COUNT=10 trap cleanup EXIT @@ -36,7 +37,8 @@ create_certs() { #create certificate and key databases with single, simple, self-signed certificate in it mkdir ${CERT_DIR} certutil -N -d ${CERT_DIR} -f ${CERT_PW_FILE} - certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + certutil -S -d ${CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil } delete_certs() { @@ -46,11 +48,19 @@ delete_certs() { } COMMON_OPTS="--daemon --no-data-dir --no-module-dir --auth no --config $CONFIG --load-module $SSL_LIB --ssl-cert-db $CERT_DIR --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME --require-encryption" -start_broker() { ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS; } +start_broker() { # $1 = extra opts + ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS $1; +} -cleanup() { +stop_brokers() { test -n "$PORT" && ../qpidd --no-module-dir -qp $PORT test -n "$PORT2" && ../qpidd --no-module-dir -qp $PORT2 + PORT="" + PORT2="" +} + +cleanup() { + stop_brokers delete_certs } @@ -76,11 +86,29 @@ export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE} ./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary ## Test connection with a URL -URL=amqp:ssl:$TEST_HOSTNAME:$PORT +URL=amqp:ssl:$TEST_HOSTNAME:$PORT ./qpid-send -b $URL --content-string=hello -a "foo;{create:always}" MSG=`./qpid-receive -b $URL -a "foo;{create:always}" --messages 1` test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; } +#### Client Authentication tests + +PORT2=`start_broker --ssl-require-client-authentication` || error "Could not start broker" +echo "Running SSL client authentication test on port $PORT2" +URL=amqp:ssl:$TEST_HOSTNAME:$PORT2 + +## See if you can set the SSL cert-name for the connection +./qpid-send -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" --content-string=hello -a "bar;{create:always}" +MSG2=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" -a "bar;{create:always}" --messages 1` +test "$MSG2" = "hello" || { echo "receive failed '$MSG2' != 'hello'"; exit 1; } + +## Make sure that connect fails with an invalid SSL cert-name +./qpid-send -b $URL --connection-options "{ssl-cert-name: pignose }" --content-string=hello -a "baz;{create:always}" 2>/dev/null 1>/dev/null +MSG3=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: pignose }" -a "baz;{create:always}" --messages 1 2>/dev/null` +test "$MSG3" = "" || { echo "receive succeeded without valid ssl cert '$MSG3' != ''"; exit 1; } + +stop_brokers + test -z $CLUSTER_LIB && exit 0 # Exit if cluster not supported. ## Test failover in a cluster using SSL only |