summaryrefslogtreecommitdiff
path: root/cpp/src/qpid/client
diff options
context:
space:
mode:
authorClifford Jansen <cliffjansen@apache.org>2013-10-22 18:10:49 +0000
committerClifford Jansen <cliffjansen@apache.org>2013-10-22 18:10:49 +0000
commitced1f45f021aad78f7874e78f049a134b7e1e995 (patch)
treea2905b78b93f5ad6cdad923341d2da1cb3a94c5a /cpp/src/qpid/client
parent52c551660915f723a442745c1f7c7a8a5dceadbe (diff)
downloadqpid-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.cpp22
-rw-r--r--cpp/src/qpid/client/windows/SslConnector.cpp192
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