summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Robie <jonathan@apache.org>2011-02-02 16:00:51 +0000
committerJonathan Robie <jonathan@apache.org>2011-02-02 16:00:51 +0000
commit5a8c4d1855fdcf7fe2588502d80b767edabae96e (patch)
tree281f03af5c9685ede59e59c241c2e01976a05ca5
parent8cc4082337bd6ea1be0c9f96d3383314f7fc228b (diff)
downloadqpid-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.h5
-rw-r--r--cpp/src/qpid/client/ConnectionSettings.cpp9
-rw-r--r--cpp/src/qpid/client/SslConnector.cpp23
-rw-r--r--cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp14
-rw-r--r--cpp/src/qpid/client/amqp0_10/ConnectionImpl.h4
-rw-r--r--cpp/src/qpid/sys/ssl/SslSocket.cpp47
-rw-r--r--cpp/src/qpid/sys/ssl/SslSocket.h27
-rwxr-xr-xcpp/src/tests/ssl_test40
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