/* * * 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/sys/TransportFactory.h" #include "qpid/sys/SocketTransport.h" #include "qpid/Plugin.h" #include "qpid/broker/Broker.h" #include "qpid/log/Statement.h" #include "qpid/sys/AsynchIOHandler.h" #include "qpid/sys/ConnectionCodec.h" #include "qpid/sys/Socket.h" #include "qpid/sys/SocketAddress.h" #include "qpid/sys/SystemInfo.h" #include "qpid/sys/windows/SslAsynchIO.h" #include #include #include // security.h needs to see this to distinguish from kernel use. #define SECURITY_WIN32 #include #include #undef SECURITY_WIN32 namespace qpid { namespace sys { class Timer; namespace windows { struct SslServerOptions : qpid::Options { std::string certStore; std::string certStoreLocation; std::string certName; uint16_t port; bool clientAuth; SslServerOptions() : qpid::Options("SSL Options"), certStore("My"), certStoreLocation("CurrentUser"), certName("localhost"), port(5671), clientAuth(false) { qpid::Address me; if (qpid::sys::SystemInfo::getLocalHostname(me)) certName = me.host; addOptions() ("ssl-cert-store", optValue(certStore, "NAME"), "Local store name from which to obtain certificate") ("ssl-cert-store-location", optValue(certStoreLocation, "NAME"), "Local store name location for certificates ( CurrentUser | LocalMachine | CurrentService )") ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use") ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections") ("ssl-require-client-authentication", optValue(clientAuth), "Forces clients to authenticate in order to establish an SSL connection"); } }; class SslProtocolFactory : public qpid::sys::SocketAcceptor, public qpid::sys::TransportConnector { Timer& brokerTimer; uint32_t maxNegotiateTime; const bool tcpNoDelay; std::string brokerHost; const bool clientAuthSelected; std::auto_ptr acceptor; ConnectFailedCallback connectFailedCallback; CredHandle credHandle; public: SslProtocolFactory(const qpid::broker::Broker& broker, const SslServerOptions&, Timer& timer); ~SslProtocolFactory(); void connect(sys::Poller::shared_ptr, const std::string& name, const std::string& host, const std::string& port, sys::ConnectionCodec::Factory*, ConnectFailedCallback failed); private: void connectFailed(const qpid::sys::Socket&, int err, const std::string& msg); void establishedIncoming(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*); void establishedOutgoing(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*, std::string& ); void establishedCommon(sys::Poller::shared_ptr, sys::AsynchIOHandler*, sys::AsynchIO*, const qpid::sys::Socket&); }; // Static instance to initialise plugin static struct SslPlugin : public Plugin { SslServerOptions options; Options* getOptions() { return &options; } void earlyInitialize(Target&) { } void initialize(Target& target) { broker::Broker* broker = dynamic_cast(&target); // Only provide to a Broker if (broker) { try { boost::shared_ptr protocol(new SslProtocolFactory(*broker, options, broker->getTimer())); uint16_t port = protocol->listen(broker->getListenInterfaces(), options.port, broker->getConnectionBacklog(), &createSocket); QPID_LOG(notice, "Listening for SSL connections on TCP port " << port); broker->registerTransport("ssl", protocol, protocol, port); } catch (const std::exception& e) { QPID_LOG(error, "Failed to initialise SSL listener: " << e.what()); } } } } sslPlugin; SslProtocolFactory::SslProtocolFactory(const qpid::broker::Broker& broker, const SslServerOptions& options, Timer& timer) : SocketAcceptor(broker.getTcpNoDelay(), false, broker.getMaxNegotiateTime(), timer, boost::bind(&SslProtocolFactory::establishedIncoming, this, _1, _2, _3)), brokerTimer(timer), maxNegotiateTime(broker.getMaxNegotiateTime()), tcpNoDelay(broker.getTcpNoDelay()), clientAuthSelected(options.clientAuth) { // Make sure that certificate store is good before listening to sockets // to avoid having open and listening sockets when there is no cert store SecInvalidateHandle(&credHandle); // Get the certificate for this server. DWORD flags = 0; std::string certStoreLocation = options.certStoreLocation; std::transform(certStoreLocation.begin(), certStoreLocation.end(), certStoreLocation.begin(), ::tolower); if (certStoreLocation == "currentuser") { flags = CERT_SYSTEM_STORE_CURRENT_USER; } else if (certStoreLocation == "localmachine") { flags = CERT_SYSTEM_STORE_LOCAL_MACHINE; } else if (certStoreLocation == "currentservice") { flags = CERT_SYSTEM_STORE_CURRENT_SERVICE; } else { QPID_LOG(error, "Unrecognised SSL certificate store location: " << options.certStoreLocation << " - Using default location"); } HCERTSTORE certStoreHandle; certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, X509_ASN_ENCODING, 0, flags | CERT_STORE_READONLY_FLAG, options.certStore.c_str()); if (!certStoreHandle) throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); PCCERT_CONTEXT certContext; certContext = ::CertFindCertificateInStore(certStoreHandle, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR_A, options.certName.c_str(), NULL); if (certContext == NULL) { int err = ::GetLastError(); ::CertCloseStore(certStoreHandle, 0); throw qpid::Exception(QPID_MSG("Locating certificate " << options.certName << " in store " << options.certStore << " " << qpid::sys::strError(GetLastError()))); throw QPID_WINDOWS_ERROR(err); } SCHANNEL_CRED cred; memset(&cred, 0, sizeof(cred)); cred.dwVersion = SCHANNEL_CRED_VERSION; cred.cCreds = 1; cred.paCred = &certContext; SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_INBOUND, NULL, &cred, NULL, NULL, &credHandle, NULL); if (status != SEC_E_OK) throw QPID_WINDOWS_ERROR(status); ::CertFreeCertificateContext(certContext); ::CertCloseStore(certStoreHandle, 0); } SslProtocolFactory::~SslProtocolFactory() { ::FreeCredentialsHandle(&credHandle); } void SslProtocolFactory::connectFailed(const qpid::sys::Socket&, int err, const std::string& msg) { if (connectFailedCallback) connectFailedCallback(err, msg); } void SslProtocolFactory::establishedIncoming(sys::Poller::shared_ptr poller, const qpid::sys::Socket& s, sys::ConnectionCodec::Factory* f) { sys::AsynchIOHandler* async = new sys::AsynchIOHandler(s.getFullAddress(), f, false, false); sys::AsynchIO *aio = new qpid::sys::windows::ServerSslAsynchIO( clientAuthSelected, s, credHandle, boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), boost::bind(&AsynchIOHandler::eof, async, _1), boost::bind(&AsynchIOHandler::disconnect, async, _1), boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), boost::bind(&AsynchIOHandler::nobuffs, async, _1), boost::bind(&AsynchIOHandler::idle, async, _1)); establishedCommon(poller, async, aio, s); } void SslProtocolFactory::establishedOutgoing(sys::Poller::shared_ptr poller, const qpid::sys::Socket& s, sys::ConnectionCodec::Factory* f, std::string& name) { sys::AsynchIOHandler* async = new sys::AsynchIOHandler(name, f, true, false); sys::AsynchIO *aio = new qpid::sys::windows::ClientSslAsynchIO( brokerHost, s, credHandle, boost::bind(&AsynchIOHandler::readbuff, async, _1, _2), boost::bind(&AsynchIOHandler::eof, async, _1), boost::bind(&AsynchIOHandler::disconnect, async, _1), boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2), boost::bind(&AsynchIOHandler::nobuffs, async, _1), boost::bind(&AsynchIOHandler::idle, async, _1)); establishedCommon(poller, async, aio, s); } void SslProtocolFactory::establishedCommon(sys::Poller::shared_ptr poller, sys::AsynchIOHandler* async, sys::AsynchIO* aio, const qpid::sys::Socket& s) { if (tcpNoDelay) { s.setTcpNoDelay(); QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress()); } async->init(aio, brokerTimer, maxNegotiateTime); aio->start(poller); } void SslProtocolFactory::connect(sys::Poller::shared_ptr poller, const std::string& name, const std::string& host, const std::string& port, sys::ConnectionCodec::Factory* fact, ConnectFailedCallback failed) { SCHANNEL_CRED cred; memset(&cred, 0, sizeof(cred)); cred.dwVersion = SCHANNEL_CRED_VERSION; SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL, &credHandle, NULL); if (status != SEC_E_OK) throw QPID_WINDOWS_ERROR(status); brokerHost = host; // Note that the following logic does not cause a memory leak. // The allocated Socket is freed either by the AsynchConnector // upon connection failure or by the AsynchIO upon connection // shutdown. The allocated AsynchConnector frees itself when it // is no longer needed. qpid::sys::Socket* socket = createSocket(); connectFailedCallback = failed; AsynchConnector::create(*socket, host, port, boost::bind(&SslProtocolFactory::establishedOutgoing, this, poller, _1, fact, name), boost::bind(&SslProtocolFactory::connectFailed, this, _1, _2, _3)); } }}}