You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by cl...@apache.org on 2013/10/22 20:10:49 UTC

svn commit: r1534714 - in /qpid/trunk/qpid/cpp: SSL src/CMakeLists.txt src/qpid/client/windows/SaslFactory.cpp src/qpid/client/windows/SslConnector.cpp src/qpid/sys/windows/util.cpp src/qpid/sys/windows/util.h

Author: cliffjansen
Date: Tue Oct 22 18:10:49 2013
New Revision: 1534714

URL: http://svn.apache.org/r1534714
Log:
QPID-3914: Windows C++ SSL client certificate authentication support

Added:
    qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.cpp
    qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.h
Modified:
    qpid/trunk/qpid/cpp/SSL
    qpid/trunk/qpid/cpp/src/CMakeLists.txt
    qpid/trunk/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp
    qpid/trunk/qpid/cpp/src/qpid/client/windows/SslConnector.cpp

Modified: qpid/trunk/qpid/cpp/SSL
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/SSL?rev=1534714&r1=1534713&r2=1534714&view=diff
==============================================================================
--- qpid/trunk/qpid/cpp/SSL (original)
+++ qpid/trunk/qpid/cpp/SSL Tue Oct 22 18:10:49 2013
@@ -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
 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. 
 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}

Modified: qpid/trunk/qpid/cpp/src/CMakeLists.txt
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/src/CMakeLists.txt?rev=1534714&r1=1534713&r2=1534714&view=diff
==============================================================================
--- qpid/trunk/qpid/cpp/src/CMakeLists.txt (original)
+++ qpid/trunk/qpid/cpp/src/CMakeLists.txt Tue Oct 22 18:10:49 2013
@@ -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)

Modified: qpid/trunk/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp?rev=1534714&r1=1534713&r2=1534714&view=diff
==============================================================================
--- qpid/trunk/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp (original)
+++ qpid/trunk/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp Tue Oct 22 18:10:49 2013
@@ -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::c
 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::strin
 
     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;
     }

Modified: qpid/trunk/qpid/cpp/src/qpid/client/windows/SslConnector.cpp
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/src/qpid/client/windows/SslConnector.cpp?rev=1534714&r1=1534713&r2=1534714&view=diff
==============================================================================
--- qpid/trunk/qpid/cpp/src/qpid/client/windows/SslConnector.cpp (original)
+++ qpid/trunk/qpid/cpp/src/qpid/client/windows/SslConnector.cpp Tue Oct 22 18:10:49 2013
@@ -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
 
     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
 {
     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
 
 SslConnector::~SslConnector()
 {
+    if (cert)
+        ::CertFreeCertificateContext(cert);
+    ::CertCloseStore(certStore, CERT_CLOSE_STORE_FORCE_FLAG);
     ::FreeCredentialsHandle(&credHandle);
 }
 
@@ -146,4 +175,159 @@ void SslConnector::connected(const Socke
     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

Added: qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.cpp
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.cpp?rev=1534714&view=auto
==============================================================================
--- qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.cpp (added)
+++ qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.cpp Tue Oct 22 18:10:49 2013
@@ -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

Added: qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.h
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.h?rev=1534714&view=auto
==============================================================================
--- qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.h (added)
+++ qpid/trunk/qpid/cpp/src/qpid/sys/windows/util.h Tue Oct 22 18:10:49 2013
@@ -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*/



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org