diff options
| author | Clifford Jansen <cliffjansen@apache.org> | 2013-10-22 18:10:49 +0000 |
|---|---|---|
| committer | Clifford Jansen <cliffjansen@apache.org> | 2013-10-22 18:10:49 +0000 |
| commit | ced1f45f021aad78f7874e78f049a134b7e1e995 (patch) | |
| tree | a2905b78b93f5ad6cdad923341d2da1cb3a94c5a /cpp/src/qpid/client | |
| parent | 52c551660915f723a442745c1f7c7a8a5dceadbe (diff) | |
| download | qpid-python-ced1f45f021aad78f7874e78f049a134b7e1e995.tar.gz | |
QPID-3914: Windows C++ SSL client certificate authentication support
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1534714 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/src/qpid/client')
| -rw-r--r-- | cpp/src/qpid/client/windows/SaslFactory.cpp | 22 | ||||
| -rw-r--r-- | cpp/src/qpid/client/windows/SslConnector.cpp | 192 |
2 files changed, 203 insertions, 11 deletions
diff --git a/cpp/src/qpid/client/windows/SaslFactory.cpp b/cpp/src/qpid/client/windows/SaslFactory.cpp index 5ffd14596f..25cae05543 100644 --- a/cpp/src/qpid/client/windows/SaslFactory.cpp +++ b/cpp/src/qpid/client/windows/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 @@ -117,14 +117,15 @@ std::auto_ptr<SaslServer> SaslFactory::createServer( const std::string& realm, b namespace { const std::string ANONYMOUS = "ANONYMOUS"; const std::string PLAIN = "PLAIN"; + const std::string EXTERNAL = "EXTERNAL"; } WindowsSasl::WindowsSasl( const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf ) - : settings(username, password, serviceName, hostName, minSsf, maxSsf) + : settings(username, password, serviceName, hostName, minSsf, maxSsf) { } -WindowsSasl::~WindowsSasl() +WindowsSasl::~WindowsSasl() { } @@ -135,21 +136,28 @@ bool WindowsSasl::start(const std::string& mechanisms, std::string& response, typedef boost::tokenizer<boost::char_separator<char> > tokenizer; boost::char_separator<char> sep(" "); + bool haveExt = false; bool havePlain = false; bool haveAnon = false; tokenizer mechs(mechanisms, sep); for (tokenizer::iterator mech = mechs.begin(); mech != mechs.end(); ++mech) { - if (*mech == ANONYMOUS) + if (*mech == EXTERNAL) + haveExt = true; + else if (*mech == ANONYMOUS) haveAnon = true; else if (*mech == PLAIN) havePlain = true; } - if (!haveAnon && !havePlain) + if (!haveAnon && !havePlain && !haveExt) throw InternalErrorException(QPID_MSG("Sasl error: no common mechanism")); - if (havePlain) { + if (haveExt) { + mechanism = EXTERNAL; + response = ((char)0) + settings.username.c_str(); + } + else if (havePlain) { mechanism = PLAIN; response = ((char)0) + settings.username + ((char)0) + settings.password; } diff --git a/cpp/src/qpid/client/windows/SslConnector.cpp b/cpp/src/qpid/client/windows/SslConnector.cpp index 61ccd2751d..1951401a89 100644 --- a/cpp/src/qpid/client/windows/SslConnector.cpp +++ b/cpp/src/qpid/client/windows/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 @@ -30,6 +30,7 @@ #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 <boost/bind.hpp> @@ -51,8 +52,10 @@ using qpid::sys::Socket; class SslConnector : public qpid::client::TCPConnector { qpid::sys::windows::ClientSslAsynchIO *shim; - boost::shared_ptr<qpid::sys::Poller> poller; + boost::shared_ptr<qpid::sys::Poller> poller; std::string brokerHost; + HCERTSTORE certStore; + PCCERT_CONTEXT cert; SCHANNEL_CRED cred; CredHandle credHandle; TimeStamp credExpiry; @@ -62,11 +65,13 @@ class SslConnector : public qpid::client::TCPConnector void connect(const std::string& host, const std::string& port); void connected(const Socket&); + void loadPrivCertStore(); + void importHostCert(const ConnectionSettings&); public: SslConnector(boost::shared_ptr<qpid::sys::Poller>, framing::ProtocolVersion pVersion, - const ConnectionSettings&, + const ConnectionSettings&, ConnectionImpl*); }; @@ -82,13 +87,21 @@ namespace { 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; + + std::string getPasswd(const std::string& filename); + PCCERT_CONTEXT findCertificate(const std::string& name, HCERTSTORE& store); } void SslConnector::negotiationDone(SECURITY_STATUS status) @@ -107,6 +120,19 @@ SslConnector::SslConnector(boost::shared_ptr<qpid::sys::Poller> p, { memset(&cred, 0, sizeof(cred)); cred.dwVersion = SCHANNEL_CRED_VERSION; + + // In case EXTERNAL SASL mechanism has been selected, we need to find + // the client certificate with the private key which should be used + if (settings.mechanism == std::string("EXTERNAL")) { + const std::string& name = (settings.sslCertName != "") ? + settings.sslCertName : qpid::sys::ssl::SslOptions::global.certName; + loadPrivCertStore(); + cert = findCertificate(name, certStore); + // assign the certificate into the credentials + cred.paCred = &cert; + cred.cCreds = 1; + } + SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, @@ -123,6 +149,9 @@ SslConnector::SslConnector(boost::shared_ptr<qpid::sys::Poller> p, SslConnector::~SslConnector() { + if (cert) + ::CertFreeCertificateContext(cert); + ::CertCloseStore(certStore, CERT_CLOSE_STORE_FORCE_FLAG); ::FreeCredentialsHandle(&credHandle); } @@ -146,4 +175,159 @@ void SslConnector::connected(const Socket& s) { 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 the system store + const char *store = opts.certStore.empty() ? "MY" : opts.certStore.c_str(); + certStore = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, NULL, + CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | + CERT_SYSTEM_STORE_CURRENT_USER, store); + if (!certStore) { + HRESULT status = GetLastError(); + QPID_LOG(warning, "Could not open system certificate store: " << store); + throw QPID_WINDOWS_ERROR(status); + } + 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(); + QPID_LOG(warning, "Failed to open the file holding the private key: " << opts.certFilename); + throw QPID_WINDOWS_ERROR(status); + } + std::vector<BYTE> 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(); + QPID_LOG(warning, "Reading the private key from file failed " << opts.certFilename); + CloseHandle(certFileHandle); + throw QPID_WINDOWS_ERROR(status); + } + } + else { + HRESULT status = GetLastError(); + QPID_LOG(warning, "Unable to read the certificate file " << opts.certFilename); + throw QPID_WINDOWS_ERROR(status); + } + 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); + int pwlen = passwd.length(); + std::vector<wchar_t> 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(); + QPID_LOG(warning, "Error converting password from UTF8"); + throw QPID_WINDOWS_ERROR(status); + } + + certStore = PFXImportCertStore(&blobData, &pwUCS2[0], 0); + if (certStore == NULL) { + HRESULT status = GetLastError(); + QPID_LOG(warning, "Failed to open the certificate store"); + throw QPID_WINDOWS_ERROR(status); + } + QPID_LOG(debug, "SslConnector using certificate from pkcs#12 file: " << opts.certFilename); + } +} + + +namespace { + +PCCERT_CONTEXT findCertificate(const std::string& name, HCERTSTORE& store) +{ + // search for the certificate by Friendly Name + PCCERT_CONTEXT tmpctx = NULL; + while (tmpctx = CertEnumCertificatesInStore(store, tmpctx)) { + DWORD len = CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE, + 0, NULL, NULL, 0); + if (len == 1) + continue; + std::vector<char> 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) { + QPID_LOG(warning, "Certificate not found in the certificate store for name " << name); + throw qpid::Exception(QPID_MSG("client certificate not found")); + } + return tmpctx; +} + + +std::string 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(); + QPID_LOG(warning, "Failed to open the password file: " << filename); + throw QPID_WINDOWS_ERROR(status); + } + + const DWORD fileSize = GetFileSize(pwfHandle, NULL); + if (fileSize == INVALID_FILE_SIZE) { + CloseHandle(pwfHandle); + throw qpid::Exception(QPID_MSG("Cannot read password file")); + } + + std::vector<char> pwbuf; + pwbuf.resize(fileSize); + DWORD nbytes = 0; + if (!ReadFile(pwfHandle, &pwbuf[0], fileSize, &nbytes, NULL)) { + HRESULT status = GetLastError(); + CloseHandle(pwfHandle); + QPID_LOG(warning, "Error reading password file"); + throw QPID_WINDOWS_ERROR(status); + } + 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; +} +} // namespace + }}} // namespace qpid::client::windows |
