diff options
| author | Clifford Allan Jansen <cliffjansen@apache.org> | 2013-10-22 18:10:49 +0000 |
|---|---|---|
| committer | Clifford Allan Jansen <cliffjansen@apache.org> | 2013-10-22 18:10:49 +0000 |
| commit | 2b5fdb50027767595612accd878e0712af954413 (patch) | |
| tree | ae9bde3516bc75a352383c499b0bf1b7b43b8d9b /qpid/cpp | |
| parent | 36a729cdaece12d37506772a1a9196719ab0d076 (diff) | |
| download | qpid-python-2b5fdb50027767595612accd878e0712af954413.tar.gz | |
QPID-3914: Windows C++ SSL client certificate authentication support
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1534714 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/cpp')
| -rw-r--r-- | qpid/cpp/SSL | 34 | ||||
| -rw-r--r-- | qpid/cpp/src/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/client/windows/SaslFactory.cpp | 22 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/client/windows/SslConnector.cpp | 192 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/sys/windows/util.cpp | 70 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/sys/windows/util.h | 50 |
6 files changed, 355 insertions, 17 deletions
diff --git a/qpid/cpp/SSL b/qpid/cpp/SSL index 06aa0db267..5added62d9 100644 --- a/qpid/cpp/SSL +++ b/qpid/cpp/SSL @@ -83,10 +83,9 @@ Windows SSL support for Qpid-C++ on Windows is implemented using the Microsoft Secure Channel (Schannel) package. Currently, only registry based -certificates scoped to the local machine are supported, however -Schannel also supports file based and user scoped certificates, so -additional support could be added as required. Client certificate -authentication is not supported at this time. +certificates scoped to the local machine are supported on the broker. +The client may specify client certificates in a user scoped store or in +a pkcs#12 file. For testing purposes, a self signed certificate can be created as follows (requiring Administrator privilege on more recent versions of @@ -109,7 +108,7 @@ that will be using qpid, you must import the self signed certificate as a trusted root. This can be done from the MMC certificate snapin or directly using certmgr.exe. From the main window: - select "Third-Party Root Certification Authorities" + select "Trusted Root Certification Authorities" select "Action" -> "Import..." then direct the Certificate Import Wizard to the "myhost.cer" file @@ -124,3 +123,28 @@ clients if they support the DER format. Otherwise the certificate can be converted to PEM format using OpenSSL openssl x509 -in myhost.cer -inform DER -out myhost.pem -outform PEM + +Client certificates operate much the same as for Linux, except for +identifying the certificate storage. Process environment variables +are used but the certificate name may be set or overridden by its Qpid +Messaging connection option. For Windows registry stores, you specify +the store: + + QPID_SSL_CERT_STORE=teststore + +If you omit the certificate store name, it defaults to the "Personal" or +"MY" store. For a certificate stored in a pkcs#12 format file, you must +supply the filename and a file containing the password for the +certificate's private key: + + QPID_SSL_CERT_FILENAME=wg444.pfx + QPID_SSL_CERT_PASSWORD_FILE=pw_wg444.txt + +The certificate is specified by its "friendly name", i.e. + + QPID_SSL_CERT_NAME=guest123 + +as an environment variable, or in the case of a Qpid Messaging +connection option: + + {transport:ssl,sasl-mechanism:EXTERNAL,ssl-cert-name:guest789} diff --git a/qpid/cpp/src/CMakeLists.txt b/qpid/cpp/src/CMakeLists.txt index f5d9a80eb7..eafa45ea7c 100644 --- a/qpid/cpp/src/CMakeLists.txt +++ b/qpid/cpp/src/CMakeLists.txt @@ -636,6 +636,8 @@ if (BUILD_SSL) if (CMAKE_SYSTEM_NAME STREQUAL Windows) set (sslcommon_SOURCES qpid/sys/windows/SslAsynchIO.cpp + qpid/sys/windows/util.cpp + qpid/sys/windows/util.h ) set (ssl_SOURCES @@ -647,7 +649,7 @@ if (BUILD_SSL) ) set (ssl_INCLUDES "") set (ssl_LIBDIRS "") - set (ssl_LIBS Secur32.lib) + set (ssl_LIBS Crypt32.lib Secur32.lib) set (ssl_server_LIBS Crypt32.lib Secur32.lib) else (CMAKE_SYSTEM_NAME STREQUAL Windows) if (NOT NSS_FOUND) diff --git a/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp index 5ffd14596f..25cae05543 100644 --- a/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp +++ b/qpid/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/qpid/cpp/src/qpid/client/windows/SslConnector.cpp b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp index 61ccd2751d..1951401a89 100644 --- a/qpid/cpp/src/qpid/client/windows/SslConnector.cpp +++ b/qpid/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 diff --git a/qpid/cpp/src/qpid/sys/windows/util.cpp b/qpid/cpp/src/qpid/sys/windows/util.cpp new file mode 100644 index 0000000000..75aef26c35 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/util.cpp @@ -0,0 +1,70 @@ +/* + * + * 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/windows/util.h" +#include "qpid/Exception.h" +#include "qpid/sys/SystemInfo.h" + +#include <iostream> +#include <fstream> + +namespace qpid { +namespace sys { +namespace ssl { + +static const std::string LOCALHOST("127.0.0.1"); + +std::string defaultCertName() +{ + Address address; + if (SystemInfo::getLocalHostname(address)) { + return address.host; + } else { + return LOCALHOST; + } +} + +SslOptions::SslOptions() : qpid::Options("SSL Settings"), + certName(defaultCertName()) +{ + addOptions() + ("ssl-cert-password-file", optValue(certPasswordFile, "PATH"), "File containing password to use for accessing certificates") + ("ssl-cert-store", optValue(certStore, "NAME"), "Windows certificate store containing the certificate") + ("ssl-cert-Filename", optValue(certFilename, "PATH"), "Path to PKCS#12 file containing the certificate") + ("ssl-cert-name", optValue(certName, "NAME"), "Friendly Name of the certificate to use"); +} + +SslOptions& SslOptions::operator=(const SslOptions& o) +{ + certStore = o.certStore; + certName = o.certName; + certPasswordFile = o.certPasswordFile; + certFilename = o.certFilename; + + return *this; +} + +SslOptions SslOptions::global; + +void initWinSsl(const SslOptions& options, bool) +{ + SslOptions::global = options; +} +}}} // namespace qpid::sys::ssl diff --git a/qpid/cpp/src/qpid/sys/windows/util.h b/qpid/cpp/src/qpid/sys/windows/util.h new file mode 100644 index 0000000000..2855a90955 --- /dev/null +++ b/qpid/cpp/src/qpid/sys/windows/util.h @@ -0,0 +1,50 @@ +#ifndef QPID_SYS_SSL_UTIL_H +#define QPID_SYS_SSL_UTIL_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/CommonImportExport.h" +#include "qpid/Options.h" +#include <string> + +namespace qpid { +namespace sys { +namespace ssl { + +struct SslOptions : qpid::Options +{ + QPID_COMMON_EXTERN static SslOptions global; + + std::string certStore; + std::string certName; + std::string certPasswordFile; + std::string certFilename; + + QPID_COMMON_EXTERN SslOptions(); + QPID_COMMON_EXTERN SslOptions& operator=(const SslOptions&); +}; + +QPID_COMMON_EXTERN void initWinSsl(const SslOptions& options, bool server = false); + +}}} // namespace qpid::sys::ssl + +#endif /*!QPID_SYS_SSL_UTIL_H*/ |
