summaryrefslogtreecommitdiff
path: root/qpid/cpp
diff options
context:
space:
mode:
authorClifford Allan Jansen <cliffjansen@apache.org>2013-10-22 18:10:49 +0000
committerClifford Allan Jansen <cliffjansen@apache.org>2013-10-22 18:10:49 +0000
commit2b5fdb50027767595612accd878e0712af954413 (patch)
treeae9bde3516bc75a352383c499b0bf1b7b43b8d9b /qpid/cpp
parent36a729cdaece12d37506772a1a9196719ab0d076 (diff)
downloadqpid-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/SSL34
-rw-r--r--qpid/cpp/src/CMakeLists.txt4
-rw-r--r--qpid/cpp/src/qpid/client/windows/SaslFactory.cpp22
-rw-r--r--qpid/cpp/src/qpid/client/windows/SslConnector.cpp192
-rw-r--r--qpid/cpp/src/qpid/sys/windows/util.cpp70
-rw-r--r--qpid/cpp/src/qpid/sys/windows/util.h50
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*/