/* * * 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/client/TCPConnector.h" #include "config.h" #include "qpid/Msg.h" #include "qpid/client/ConnectionImpl.h" #include "qpid/client/ConnectionSettings.h" #include "qpid/log/Statement.h" #include "qpid/sys/Dispatcher.h" #include "qpid/sys/Poller.h" #include "qpid/sys/Time.h" #include "qpid/sys/windows/check.h" #include "qpid/sys/windows/util.h" #include "qpid/sys/windows/SslAsynchIO.h" #include #include // security.h needs to see this to distinguish from kernel use. #define SECURITY_WIN32 #include #include #undef SECURITY_WIN32 #include /* * Note on client certificates: The Posix/NSS implementation performs a lazy * client certificate search part way through the ssl handshake if the server * requests one. Here, it is not known in advance if the server will * request the certificate so the certificate is pre-loaded (even if never * used). To match the Linux behavior, client certificate load problems are * remembered and reported later if appropriate, but do not prevent the * connection attempt. */ namespace qpid { namespace client { namespace windows { using qpid::sys::Socket; class SslConnector : public qpid::client::TCPConnector { struct SavedError { std::string logMessage; std::string error; void set(const std::string &lm, const std::string es); void set(const std::string &lm, int status); void clear(); bool pending(); }; qpid::sys::windows::ClientSslAsynchIO *shim; boost::shared_ptr poller; std::string brokerHost; HCERTSTORE certStore; PCCERT_CONTEXT cert; SCHANNEL_CRED cred; CredHandle credHandle; TimeStamp credExpiry; SavedError clientCertError; virtual ~SslConnector(); void negotiationDone(SECURITY_STATUS status); void connect(const std::string& host, const std::string& port); void connected(const Socket&); PCCERT_CONTEXT findCertificate(const std::string& name); void loadPrivCertStore(); std::string getPasswd(const std::string& filename); void importHostCert(const ConnectionSettings&); public: SslConnector(boost::shared_ptr, framing::ProtocolVersion pVersion, const ConnectionSettings&, ConnectionImpl*); }; // Static constructor which registers connector here namespace { Connector* create(boost::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) { return new SslConnector(p, v, s, c); } struct StaticInit { StaticInit() { try { CommonOptions common("", "", QPIDC_CONF_FILE); qpid::sys::ssl::SslOptions options; common.parse(0, 0, common.clientConfig, true); options.parse (0, 0, common.clientConfig, true); Connector::registerFactory("ssl", &create); initWinSsl(options); } catch (const std::exception& e) { QPID_LOG(error, "Failed to initialise SSL connector: " << e.what()); } }; ~StaticInit() { } } init; } void SslConnector::negotiationDone(SECURITY_STATUS status) { if (status == SEC_E_OK) { clientCertError.clear(); initAmqp(); } else { if (status == SEC_E_INCOMPLETE_CREDENTIALS && clientCertError.pending()) { // Server requested a client cert but we supplied none for the following reason: if (!clientCertError.logMessage.empty()) QPID_LOG(warning, clientCertError.logMessage); connectFailed(QPID_MSG(clientCertError.error)); } else connectFailed(QPID_MSG(qpid::sys::strError(status))); } } SslConnector::SslConnector(boost::shared_ptr p, framing::ProtocolVersion ver, const ConnectionSettings& settings, ConnectionImpl* cimpl) : TCPConnector(p, ver, settings, cimpl), shim(0), poller(p), certStore(0), cert(0) { SecInvalidateHandle(&credHandle); memset(&cred, 0, sizeof(cred)); cred.dwVersion = SCHANNEL_CRED_VERSION; cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS; const std::string& name = (settings.sslCertName != "") ? settings.sslCertName : qpid::sys::ssl::SslOptions::global.certName; cert = findCertificate(name); if (cert != NULL) { // assign the certificate into the credentials cred.paCred = &cert; cred.cCreds = 1; } SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, NULL, &credHandle, &credExpiry); if (status != SEC_E_OK) throw QPID_WINDOWS_ERROR(status); QPID_LOG(debug, "SslConnector created for " << ver.toString()); } SslConnector::~SslConnector() { if (SecIsValidHandle(&credHandle)) ::FreeCredentialsHandle(&credHandle); if (cert) ::CertFreeCertificateContext(cert); if (certStore) ::CertCloseStore(certStore, CERT_CLOSE_STORE_FORCE_FLAG); } void SslConnector::connect(const std::string& host, const std::string& port) { brokerHost = host; TCPConnector::connect(host, port); } void SslConnector::connected(const Socket& s) { shim = new qpid::sys::windows::ClientSslAsynchIO(brokerHost, s, credHandle, boost::bind(&SslConnector::readbuff, this, _1, _2), boost::bind(&SslConnector::eof, this, _1), boost::bind(&SslConnector::disconnected, this, _1), boost::bind(&SslConnector::socketClosed, this, _1, _2), 0, // nobuffs boost::bind(&SslConnector::writebuff, this, _1), boost::bind(&SslConnector::negotiationDone, this, _1)); start(shim); shim->start(poller); } void SslConnector::loadPrivCertStore() { // Get a handle to the system store or pkcs#12 file qpid::sys::ssl::SslOptions& opts = qpid::sys::ssl::SslOptions::global; if (opts.certFilename.empty()) { // opening a system store, names are not case sensitive std::string store = opts.certStore.empty() ? "my" : opts.certStore; std::transform(store.begin(), store.end(), store.begin(), ::tolower); // map confusing GUI name to actual registry store name if (store == "personal") store = "my"; certStore = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, NULL, CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, store.c_str()); if (!certStore) { HRESULT status = GetLastError(); clientCertError.set(Msg() << "Could not open system certificate store: " << store, status); return; } QPID_LOG(debug, "SslConnector using certifcates from system store: " << store); } else { // opening the store from file and populating it with a private key HANDLE certFileHandle = NULL; certFileHandle = CreateFile(opts.certFilename.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == certFileHandle) { HRESULT status = GetLastError(); clientCertError.set(Msg() << "Failed to open the file holding the private key: " << opts.certFilename, status); return; } std::vector certEncoded; DWORD certEncodedSize = 0L; const DWORD fileSize = GetFileSize(certFileHandle, NULL); if (INVALID_FILE_SIZE != fileSize) { certEncoded.resize(fileSize); bool result = false; result = ReadFile(certFileHandle, &certEncoded[0], fileSize, &certEncodedSize, NULL); if (!result) { // the read failed, return the error as an HRESULT HRESULT status = GetLastError(); CloseHandle(certFileHandle); clientCertError.set(Msg() << "Reading the private key from file failed " << opts.certFilename, status); return; } } else { HRESULT status = GetLastError(); clientCertError.set(Msg() << "Unable to read the certificate file " << opts.certFilename, status); return; } CloseHandle(certFileHandle); CRYPT_DATA_BLOB blobData; blobData.cbData = certEncodedSize; blobData.pbData = &certEncoded[0]; // get passwd from file and convert to null terminated wchar_t (Windows UCS2) std::string passwd = getPasswd(opts.certPasswordFile); if (clientCertError.pending()) return; int pwlen = passwd.length(); std::vector pwUCS2(pwlen + 1, L'\0'); int nwc = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, passwd.data(), pwlen, &pwUCS2[0], pwlen); if (!nwc) { HRESULT status = GetLastError(); clientCertError.set("Error converting password from UTF8", status); return; } certStore = PFXImportCertStore(&blobData, &pwUCS2[0], 0); if (certStore == NULL) { HRESULT status = GetLastError(); clientCertError.set("Failed to open the certificate store", status); return; } QPID_LOG(debug, "SslConnector using certificate from pkcs#12 file: " << opts.certFilename); } } PCCERT_CONTEXT SslConnector::findCertificate(const std::string& name) { loadPrivCertStore(); if (clientCertError.pending()) return NULL; // search for the certificate by Friendly Name PCCERT_CONTEXT tmpctx = NULL; while (tmpctx = CertEnumCertificatesInStore(certStore, tmpctx)) { DWORD len = CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0); if (len == 1) continue; std::vector ctxname(len); CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, &ctxname[0], len); bool found = !name.compare(&ctxname[0]); if (found) break; } // verify whether some certificate has been found if (tmpctx == NULL) { clientCertError.set(Msg() << "Client SSL/TLS certificate not found in the certificate store for name " << name, "client certificate not found"); } return tmpctx; } std::string SslConnector::getPasswd(const std::string& filename) { std::string passwd; if (filename == "") return passwd; HANDLE pwfHandle = CreateFile(filename.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == pwfHandle) { HRESULT status = GetLastError(); clientCertError.set(Msg() << "Failed to open the password file: " << filename, status); return passwd; } const DWORD fileSize = GetFileSize(pwfHandle, NULL); if (fileSize == INVALID_FILE_SIZE) { CloseHandle(pwfHandle); clientCertError.set("", "Cannot read password file"); return passwd; } std::vector pwbuf; pwbuf.resize(fileSize); DWORD nbytes = 0; if (!ReadFile(pwfHandle, &pwbuf[0], fileSize, &nbytes, NULL)) { HRESULT status = GetLastError(); CloseHandle(pwfHandle); clientCertError.set("Error reading password file", status); return passwd; } CloseHandle(pwfHandle); if (nbytes == 0) return passwd; while (nbytes) { if ((pwbuf[nbytes-1] == 012) || (pwbuf[nbytes-1] == 015)) nbytes--; else break; } if (nbytes) passwd.assign(&pwbuf[0], nbytes); return passwd; } void SslConnector::SavedError::set(const std::string &lm, const std::string es) { logMessage = lm; error = es; } void SslConnector::SavedError::set(const std::string &lm, int status) { logMessage = lm; error = qpid::sys::strError(status); } void SslConnector::SavedError::clear() { logMessage.clear(); error.clear(); } bool SslConnector::SavedError::pending() { return !logMessage.empty() || !error.empty(); } }}} // namespace qpid::client::windows