From e1125002b35ac2e10d2873b46a24f1f9523fd07d Mon Sep 17 00:00:00 2001 From: Gordon Sim Date: Fri, 19 Oct 2012 17:15:38 +0000 Subject: QPID-4368: Define SASL server role that is free from the AMQP 0-10 handshake git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1400176 13f79535-47bb-0310-9956-ffa450edef68 --- cpp/src/CMakeLists.txt | 1 + cpp/src/Makefile.am | 3 + cpp/src/qpid/NullSaslServer.cpp | 82 ++++++++++++++++ cpp/src/qpid/NullSaslServer.h | 49 ++++++++++ cpp/src/qpid/Sasl.h | 2 +- cpp/src/qpid/SaslFactory.cpp | 211 +++++++++++++++++++++++++++++++++++++++- cpp/src/qpid/SaslFactory.h | 3 +- cpp/src/qpid/SaslServer.h | 48 +++++++++ 8 files changed, 393 insertions(+), 6 deletions(-) create mode 100644 cpp/src/qpid/NullSaslServer.cpp create mode 100644 cpp/src/qpid/NullSaslServer.h create mode 100644 cpp/src/qpid/SaslServer.h (limited to 'cpp') diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index b845dd99e7..fb0db3e22e 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -902,6 +902,7 @@ set (qpidcommon_SOURCES qpid/StringUtils.cpp qpid/Url.cpp qpid/UrlArray.cpp + qpid/NullSaslServer.cpp qpid/amqp_0_10/SessionHandler.cpp qpid/framing/AccumulatedAck.cpp qpid/framing/AMQBody.cpp diff --git a/cpp/src/Makefile.am b/cpp/src/Makefile.am index 3e3d2a1666..0130c62852 100644 --- a/cpp/src/Makefile.am +++ b/cpp/src/Makefile.am @@ -359,6 +359,9 @@ libqpidcommon_la_SOURCES += \ qpid/Sasl.h \ qpid/SaslFactory.cpp \ qpid/SaslFactory.h \ + qpid/SaslServer.h \ + qpid/NullSaslServer.h \ + qpid/NullSaslServer.cpp \ qpid/Serializer.h \ qpid/SessionId.cpp \ qpid/SessionState.cpp \ diff --git a/cpp/src/qpid/NullSaslServer.cpp b/cpp/src/qpid/NullSaslServer.cpp new file mode 100644 index 0000000000..5139fec42f --- /dev/null +++ b/cpp/src/qpid/NullSaslServer.cpp @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * 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 + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "NullSaslServer.h" +#include "qpid/log/Statement.h" +#include +#include + +namespace qpid { +NullSaslServer::NullSaslServer(const std::string& r) : realm(r) {} +NullSaslServer::Status NullSaslServer::start(const std::string& mechanism, const std::string* response, std::string& /*challenge*/) +{ + if (mechanism == "PLAIN") { + if (response) { + std::string uid; + std::string::size_type i = response->find((char)0); + if (i == 0 && response->size() > 1) { + //no authorization id; use authentication id + i = response->find((char)0, 1); + if (i != std::string::npos) uid = response->substr(1, i-1); + } else if (i != std::string::npos) { + //authorization id is first null delimited field + uid = response->substr(0, i); + }//else not a valid SASL PLAIN response, throw error? + if (!uid.empty()) { + //append realm if it has not already been added + i = uid.find(realm); + if (i == std::string::npos || realm.size() + i < uid.size()) { + uid = boost::str(boost::format("%1%@%2%") % uid % realm); + } + userid = uid; + } + return OK; + } else { + QPID_LOG(error, "Invalid PLAIN request, expected response containing user credentials"); + return FAIL; + } + } else if (mechanism == "ANONYMOUS") { + userid = "anonymous"; + return OK; + } else { + return FAIL; + } +} + +NullSaslServer::Status NullSaslServer::step(const std::string* /*response*/, std::string& /*challenge*/) +{ + assert(false); + return FAIL; +} +std::string NullSaslServer::getMechanisms() +{ + return std::string("ANONYMOUS PLAIN"); +} +std::string NullSaslServer::getUserid() +{ + return userid; +} + +std::auto_ptr NullSaslServer::getSecurityLayer(size_t) +{ + return std::auto_ptr(); +} + +} // namespace qpid diff --git a/cpp/src/qpid/NullSaslServer.h b/cpp/src/qpid/NullSaslServer.h new file mode 100644 index 0000000000..810defe574 --- /dev/null +++ b/cpp/src/qpid/NullSaslServer.h @@ -0,0 +1,49 @@ +#ifndef QPID_NULLSASLSERVER_H +#define QPID_NULLSASLSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * 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 + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/SaslServer.h" + +namespace qpid { + +/** + * Dummy implementation of the SASL server role. This will advertise + * ANONYMOUS and PLAIN, and parse the reponse data for those + * accordingly, but will make no attempt to actually authenticate + * users. + */ +class NullSaslServer : public SaslServer +{ + public: + NullSaslServer(const std::string& realm); + Status start(const std::string& mechanism, const std::string* response, std::string& challenge); + Status step(const std::string* response, std::string& challenge); + std::string getMechanisms(); + std::string getUserid(); + std::auto_ptr getSecurityLayer(size_t); + private: + std::string realm; + std::string userid; +}; +} // namespace qpid + +#endif /*!QPID_NULLSASLSERVER_H*/ diff --git a/cpp/src/qpid/Sasl.h b/cpp/src/qpid/Sasl.h index 4d579fa051..efda92d718 100644 --- a/cpp/src/qpid/Sasl.h +++ b/cpp/src/qpid/Sasl.h @@ -34,7 +34,7 @@ struct SecuritySettings; } /** - * Interface to SASL support. This class is implemented by platform-specific + * Interface to support for the SASL client role. This class is implemented by platform-specific * SASL providers. */ class Sasl diff --git a/cpp/src/qpid/SaslFactory.cpp b/cpp/src/qpid/SaslFactory.cpp index 9fb5d64c38..4d81a793ee 100644 --- a/cpp/src/qpid/SaslFactory.cpp +++ b/cpp/src/qpid/SaslFactory.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 @@ -18,7 +18,9 @@ * under the License. * */ -#include "qpid//SaslFactory.h" +#include "qpid/SaslFactory.h" +#include "qpid/SaslServer.h" +#include "qpid/NullSaslServer.h" #include #include @@ -32,6 +34,7 @@ namespace qpid { //Null implementation + SaslFactory::SaslFactory() {} SaslFactory::~SaslFactory() {} @@ -50,6 +53,12 @@ std::auto_ptr SaslFactory::create( const std::string &, const std::string return std::auto_ptr(); } +std::auto_ptr SaslFactory::createServer(const std::string& realm, bool /*encryptionRequired*/, const qpid::sys::SecuritySettings&) +{ + std::auto_ptr server(new NullSaslServer(realm)); + return server; +} + qpid::sys::Mutex SaslFactory::lock; std::auto_ptr SaslFactory::instance; @@ -140,6 +149,22 @@ typedef int CallbackProc(); qpid::sys::Mutex SaslFactory::lock; std::auto_ptr SaslFactory::instance; +class CyrusSaslServer : public SaslServer +{ + public: + CyrusSaslServer(const std::string& realm, bool encryptionRequired, const qpid::sys::SecuritySettings& external); + ~CyrusSaslServer(); + Status start(const std::string& mechanism, const std::string* response, std::string& challenge); + Status step(const std::string* response, std::string& challenge); + std::string getMechanisms(); + std::string getUserid(); + std::auto_ptr getSecurityLayer(size_t); + private: + std::string realm; + std::string userid; + sasl_conn_t *sasl_conn; +}; + SaslFactory::SaslFactory() { sasl_callback_t* callbacks = 0; @@ -169,6 +194,12 @@ std::auto_ptr SaslFactory::create(const std::string & username, const std: return sasl; } +std::auto_ptr SaslFactory::createServer(const std::string& realm, bool encryptionRequired, const qpid::sys::SecuritySettings& external) +{ + std::auto_ptr server(new CyrusSaslServer(realm, encryptionRequired, external)); + return server; +} + CyrusSasl::CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction) : conn(0), settings(username, password, serviceName, hostName, minSsf, maxSsf), allowInteraction(allowInteraction) { @@ -382,6 +413,179 @@ std::auto_ptr CyrusSasl::getSecurityLayer(uint16_t maxFrameSize) return securityLayer; } +CyrusSaslServer::CyrusSaslServer(const std::string& r, bool encryptionRequired, const qpid::sys::SecuritySettings& external) : realm(r), sasl_conn(0) +{ + int code = sasl_server_new(BROKER_SASL_NAME, /* Service name */ + NULL, /* Server FQDN, gethostname() */ + realm.c_str(), /* Authentication realm */ + NULL, /* Local IP, needed for some mechanism */ + NULL, /* Remote IP, needed for some mechanism */ + NULL, /* Callbacks */ + 0, /* Connection flags */ + &sasl_conn); + + if (SASL_OK != code) { + QPID_LOG(error, "SASL: Connection creation failed: [" << code << "] " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw qpid::framing::ConnectionForcedException("Unable to perform authentication"); + } + + sasl_security_properties_t secprops; + + //TODO: should the actual SSF values be configurable here? + secprops.min_ssf = encryptionRequired ? 10: 0; + secprops.max_ssf = 256; + + // If the transport provides encryption, notify the SASL library of + // the key length and set the ssf range to prevent double encryption. + QPID_LOG(debug, "External ssf=" << external.ssf << " and auth=" << external.authid); + sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf; + if (external_ssf) { + int result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL, &external_ssf); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); + } + + secprops.max_ssf = secprops.min_ssf = 0; + } + + QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << + ", max_ssf: " << secprops.max_ssf << + ", external_ssf: " << external_ssf ); + + if (!external.authid.empty()) { + const char* external_authid = external.authid.c_str(); + int result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, external_authid); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result)); + } + + QPID_LOG(debug, "external auth detected and set to " << external_authid); + } + secprops.maxbufsize = 65535; + secprops.property_names = 0; + secprops.property_values = 0; + secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */ + /* + * The nodict flag restricts SASL authentication mechanisms + * to those that are not susceptible to dictionary attacks. + * They are: + * SRP + * PASSDSS-3DES-1 + * EXTERNAL + */ + if (external.nodict) secprops.security_flags |= SASL_SEC_NODICTIONARY; + int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << result)); + } +} + +CyrusSaslServer::~CyrusSaslServer() +{ + if (sasl_conn) { + sasl_dispose(&sasl_conn); + sasl_conn = 0; + } +} + +CyrusSaslServer::Status CyrusSaslServer::start(const std::string& mechanism, const std::string* response, std::string& chllng) +{ + const char *challenge; + unsigned int challenge_len; + + // This should be at same debug level as mech list in getMechanisms(). + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); + int code = sasl_server_start(sasl_conn, + mechanism.c_str(), + (response ? response->c_str() : 0), (response ? response->size() : 0), + &challenge, &challenge_len); + switch (code) { + case SASL_OK: + return SaslServer::OK; + case SASL_CONTINUE: + chllng = std::string(challenge, challenge_len); + return SaslServer::CHALLENGE; + case SASL_NOMECH: + QPID_LOG(info, "Unsupported mechanism: " << mechanism); + default: + return SaslServer::FAIL; + } +} + +CyrusSaslServer::Status CyrusSaslServer::step(const std::string* response, std::string& chllng) +{ + const char *challenge; + unsigned int challenge_len; + + int code = sasl_server_step(sasl_conn, + (response ? response->c_str() : 0), (response ? response->size() : 0), + &challenge, &challenge_len); + + switch (code) { + case SASL_OK: + return SaslServer::OK; + case SASL_CONTINUE: + chllng = std::string(challenge, challenge_len); + return SaslServer::CHALLENGE; + default: + return SaslServer::FAIL; + } + +} +std::string CyrusSaslServer::getMechanisms() +{ + const char *separator = " "; + const char *list; + unsigned int list_len; + int count; + + int code = sasl_listmech(sasl_conn, NULL, + "", separator, "", + &list, &list_len, + &count); + + if (SASL_OK != code) { + QPID_LOG(info, "SASL: Mechanism listing failed: " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw qpid::framing::ConnectionForcedException("Mechanism listing failed"); + } else { + std::string mechanisms(list, list_len); + QPID_LOG(info, "SASL: Mechanism list: " << mechanisms); + return mechanisms; + } +} +std::string CyrusSaslServer::getUserid() +{ + const void* ptr; + int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr); + if (SASL_OK == code) { + userid = static_cast(ptr); + } else { + QPID_LOG(warning, "Failed to retrieve sasl username"); + } + return userid; +} + +std::auto_ptr CyrusSaslServer::getSecurityLayer(size_t maxFrameSize) +{ + const void* value(0); + int result = sasl_getprop(sasl_conn, SASL_SSF, &value); + if (result != SASL_OK) { + throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(sasl_conn))); + } + uint ssf = *(reinterpret_cast(value)); + std::auto_ptr securityLayer; + if (ssf) { + securityLayer = std::auto_ptr(new CyrusSecurityLayer(sasl_conn, maxFrameSize, ssf)); + } + return securityLayer; +} + int getUserFromSettings(void* context, int /*id*/, const char** result, unsigned* /*len*/) { if (context) { @@ -428,7 +632,6 @@ int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_s return SASL_FAIL; } } - } // namespace qpid #endif diff --git a/cpp/src/qpid/SaslFactory.h b/cpp/src/qpid/SaslFactory.h index 8554597147..da7e892f1d 100644 --- a/cpp/src/qpid/SaslFactory.h +++ b/cpp/src/qpid/SaslFactory.h @@ -26,7 +26,7 @@ #include namespace qpid { - +class SaslServer; /** * Factory for instances of the Sasl interface through which Sasl * support is provided to a ConnectionHandler. @@ -35,6 +35,7 @@ class SaslFactory { public: QPID_COMMON_EXTERN std::auto_ptr create(const std::string & userName, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction=true ); + QPID_COMMON_EXTERN std::auto_ptr createServer(const std::string& realm, bool encryptionRequired, const qpid::sys::SecuritySettings&); QPID_COMMON_EXTERN static SaslFactory& getInstance(); QPID_COMMON_EXTERN ~SaslFactory(); private: diff --git a/cpp/src/qpid/SaslServer.h b/cpp/src/qpid/SaslServer.h new file mode 100644 index 0000000000..a707a468eb --- /dev/null +++ b/cpp/src/qpid/SaslServer.h @@ -0,0 +1,48 @@ +#ifndef QPID_SASLSERVER_H +#define QPID_SASLSERVER_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * 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 + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include +#include + +namespace qpid { +namespace sys { +class SecurityLayer; +} +/** + * + */ +class SaslServer +{ + public: + typedef enum {OK, FAIL, CHALLENGE} Status; + virtual ~SaslServer() {} + virtual Status start(const std::string& mechanism, const std::string* response, std::string& challenge) = 0; + virtual Status step(const std::string* response, std::string& challenge) = 0; + virtual std::string getMechanisms() = 0; + virtual std::string getUserid() = 0; + virtual std::auto_ptr getSecurityLayer(size_t) = 0; + private: +}; +} // namespace qpid + +#endif /*!QPID_SASLSERVER_H*/ -- cgit v1.2.1