[cpp-sp] branch main updated: CPPSP-8 WinHTTPRemotingService Development

Rod Widdowson rdw at steadingsoftware.com
Thu May 8 15:21:17 UTC 2025


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch main
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=d573658911dcd1273967374483b4e5fb9ddce4d7

The following commit(s) were added to refs/heads/main by this push:
     new d5736589 CPPSP-8 WinHTTPRemotingService Development
d5736589 is described below

commit d573658911dcd1273967374483b4e5fb9ddce4d7
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Thu May 8 16:19:25 2025 +0100

    CPPSP-8 WinHTTPRemotingService Development
    
    https://shibboleth.atlassian.net/browse/CPPSP-8
    
    First pass at adding CaTrust support.  Entirely untested.
---
 shibsp/remoting/impl/WinHTTPRemotingService.cpp | 180 +++++++++++++++++++-----
 1 file changed, 144 insertions(+), 36 deletions(-)

diff --git a/shibsp/remoting/impl/WinHTTPRemotingService.cpp b/shibsp/remoting/impl/WinHTTPRemotingService.cpp
index a85db303..0d04b69d 100644
--- a/shibsp/remoting/impl/WinHTTPRemotingService.cpp
+++ b/shibsp/remoting/impl/WinHTTPRemotingService.cpp
@@ -85,6 +85,9 @@ namespace {
         wstring m_baseURLPath;
         wstring m_username;
         DWORD m_authScheme;
+        HCERTCHAINENGINE m_caChainEngine;
+        HCERTSTORE m_caStore;
+        void setupCaChecking();
     };
 
     class HINTERNETJanitor
@@ -126,30 +129,112 @@ wstring WinHTTPRemotingService::utf8ToUtf16(const char* input) const {
     return result;
 }
 
+void WinHTTPRemotingService::setupCaChecking() {
+
+    if (!getCAFile())
+        return;
+    
+    wstring caFile(utf8ToUtf16(getCAFile()));
+    DWORD msgAndCertEncodingType, contentType, formatType;
+    PCERT_CONTEXT certContext = NULL;
+
+    m_caStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL);
+
+    if (m_caStore == NULL) {
+
+        m_log.crit("Could not create caStore : 0x%x", GetLastError());
+        throw runtime_error("WinHHHTP failed to initialize tlsCa store");
+    }
+
+    if (!CryptQueryObject(CERT_QUERY_OBJECT_FILE,
+                          caFile.c_str(),
+                          CERT_QUERY_CONTENT_FLAG_CERT,
+                          CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED,
+                          0,
+                          &msgAndCertEncodingType,
+                          &contentType,
+                          &formatType,
+                          NULL,
+                          NULL,
+                          (void const**)&certContext)) {
+        m_log.crit("CryptQueryObject failure: %d", GetLastError());
+        throw runtime_error("WinHHHTP failed to initialize: failed to open tlsCAFile");
+    }
+
+    if ((msgAndCertEncodingType != X509_ASN_ENCODING) ||
+        (contentType != CERT_QUERY_CONTENT_CERT) ||
+        (formatType != CERT_QUERY_FORMAT_BASE64_ENCODED)) {
+
+        m_log.crit("Unexpected tlsCaFile format: Encoding 0x%x type 0x%x format 0x%x", msgAndCertEncodingType, contentType, formatType);
+        CertFreeCertificateContext(certContext);
+        throw runtime_error("WinHHHTP failed to initialize: failed bad TtsCaFile format");
+    }
+
+    if (!CertAddCertificateContextToStore(m_caStore, certContext, CERT_STORE_ADD_ALWAYS, NULL)) {
+        m_log.crit("Could not add cert to store: 0x%x", GetLastError());
+        CertFreeCertificateContext(certContext);
+        throw runtime_error("WinHHHTP failed to initialize: could not add cert");
+    }
+    CertFreeCertificateContext(certContext);
+
+    CERT_CHAIN_ENGINE_CONFIG cfg = { 0 };
+    cfg.cbSize = sizeof(cfg);
+    cfg.hExclusiveRoot = m_caStore;
+
+    if (!CertCreateCertificateChainEngine(&cfg, &m_caChainEngine)) {
+        m_log.crit("Could not create chain engine: 0x%x", GetLastError());
+        throw runtime_error("WinHHHTP failed to initialize: could not create chain engine");
+    }
+
+}
+
 void WinHTTPRemotingService::handleCert(HINTERNET Handle) const
 {
-    PCCERT_CONTEXT pCert = NULL;
-    DWORD dwSize = sizeof(pCert);
+    PCCERT_CONTEXT certCtx = NULL;
+    DWORD size = sizeof(certCtx);
 
-    if (!m_secure) {
-        m_log.debug("Skipping certificate check on insecure attach");
-    }
-    else if (!WinHttpQueryOption(Handle, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &pCert, &dwSize)) {
+    if (!WinHttpQueryOption(Handle, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &certCtx, &size)) {
         m_log.crit("Could not get the certificate on conect");
         throw RemotingException("Could not get certificate");
-    }
-    else {
+
+    } else {
+        CERT_CHAIN_PARA chainPara = { 0 };
+        PCCERT_CHAIN_CONTEXT chainContext;
+
+        chainPara.cbSize = sizeof(chainPara);
+
+        DWORD flags = CERT_CHAIN_CACHE_END_CERT;
+        //flags |= isCheckRecovation() ? CERT_CHAIN_REVOCATION_CHECK_CHAIN : 0;
+        // flags != getAdditionalGlags();
+
+        BOOL gotCertChain = CertGetCertificateChain(m_caChainEngine,
+                                                    certCtx,
+                                                    NULL,
+                                                    certCtx->hCertStore,
+                                                    &chainPara,
+                                                    flags,
+                                                    NULL,
+                                                    &chainContext);
+        CertFreeCertificateContext(certCtx);
+        if (!gotCertChain) {
+            m_log.error("Could not get the certificate chain on conect");
+            throw RemotingException("Could not get certificate chain");
+        }
         //
-        // We probably want to put some caching in here - this is called twice for each WinHttpSend...
+        // Why do we only look at chain zero?
         //
-        CHAR buffer[1024] = {0};
-        CertNameToStrA(X509_ASN_ENCODING, &pCert->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, buffer, sizeof(buffer));
-        m_log.debug("Checking certificate with subject %s", buffer);
+        CERT_TRUST_STATUS status = chainContext->rgpChain[0]->TrustStatus;
+        CertFreeCertificateChain(chainContext);
+
+        m_log.debug("Connection Error %x Info %x", status.dwErrorStatus, status.dwInfoStatus);
         //
-        // TODO check the certificate
+        // per CURL strip out (undocumented) CERT_TRUST_IS_NOT_TIME_NESTED  
         //
-        cout << "Subject name " << buffer << endl;
-        CertFreeCertificateContext(pCert);
+        DWORD error = status.dwErrorStatus & (~CERT_TRUST_IS_NOT_TIME_NESTED);
+        if (error != CERT_TRUST_NO_ERROR) {
+            m_log.error("Certificate presented is not trusted.  Error : 0x%x", status.dwErrorStatus);
+            throw RemotingException("Could not get certificate chain");
+        }
     }
 }
 
@@ -174,6 +259,7 @@ WinHTTPRemotingService::WinHTTPRemotingService(ptree& pt)
     : AbstractHTTPRemotingService(pt), AbstractRemotingService(pt),
     m_log(Category::getInstance(SHIBSP_LOGCAT ".RemotingService.WinHTTP")),
     m_winHTTPlog(Category::getInstance(SHIBSP_LOGCAT ".winHTTP")),
+    m_secure(false), m_caChainEngine(nullptr), m_caStore(nullptr),
     m_init(false), m_chunked(defaultChunking)
 {
     if (getUserAgent() == nullptr) {
@@ -206,22 +292,12 @@ WinHTTPRemotingService::WinHTTPRemotingService(ptree& pt)
         m_log.crit("WinHttpOpen failure: %d", GetLastError());
         throw runtime_error("WinHHHTP failed to initialize service");
     }
+
     //
-    // Arrange to be called back so we can check certificates
+    // Set up our CaPath environment
     //
-    DWORD_PTR context = reinterpret_cast<DWORD_PTR>(this);
+    setupCaChecking();
 
-    // Set up this as the context
-    if (!WinHttpSetOption(m_session, WINHTTP_OPTION_CONTEXT_VALUE, &context, sizeof(context))) {
-        m_log.crit("WinHttpSetOption failure: %d", GetLastError());
-        throw runtime_error("WinHHHTP failed to initialize: Could not set callback option");
-    }
-
-    // And register the callback
-    if (WinHttpSetStatusCallback(m_session, StatusCallback, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL) == WINHTTP_INVALID_STATUS_CALLBACK) {
-        m_log.crit("WinHttpSetStatusCallback failure: %d", GetLastError());
-        throw runtime_error("WinHHHTP failed to initialize: Could not register callback");
-    }
 
     //
     // Pop the URL into a wstring which we can parse and then point
@@ -231,10 +307,11 @@ WinHTTPRemotingService::WinHTTPRemotingService(ptree& pt)
     //
     // Split it into little bits
     //
-    URL_COMPONENTS components = {0};
+    URL_COMPONENTS components = { 0 };
     components.dwStructSize = sizeof(components);
     components.dwHostNameLength = -1;
     components.dwUrlPathLength = -1;
+
     if (!WinHttpCrackUrl(wURL.c_str(), 0, 0, &components)) {
         m_log.crit("WinHttpCrackUrl failure: %d", GetLastError());
         throw runtime_error("WinHHHTP failed to initialize: failed to parse baseURL");
@@ -242,14 +319,40 @@ WinHTTPRemotingService::WinHTTPRemotingService(ptree& pt)
 
     if (components.dwHostNameLength == 0 || components.dwUrlPathLength == 0) {
         m_log.crit("Invalid baseUrl '%s' HostNameLength: %d, pathLength: %d",
-                   getBaseURL(), components.dwHostNameLength == 0, components.dwUrlPathLength);
+            getBaseURL(), components.dwHostNameLength == 0, components.dwUrlPathLength);
         throw runtime_error("WinHHHTP failed to initialize: Invalid baseUrl");
     }
+    m_baseURLPath = wstring(components.lpszUrlPath, components.dwUrlPathLength);
 
+    if ((components.nScheme != INTERNET_SCHEME_HTTP) && (components.nScheme != INTERNET_SCHEME_HTTPS)) {
+        //
+        // FTP/ Socks?  Just say no
+        //
+        m_log.crit("Protocol Scheme not supported : %d", GetLastError());
+        throw runtime_error("WinHHHTP failed to initialize: Could not register callback");
+    }
     m_secure = (components.nScheme == INTERNET_SCHEME_HTTPS);
-    m_baseURLPath = wstring(components.lpszUrlPath, components.dwUrlPathLength);
-    wstring host(components.lpszHostName, components.dwHostNameLength);
 
+    if (m_secure && (m_caChainEngine != NULL)) {
+        //
+        // Arrange to be called back so we can check certificates
+        //
+        DWORD_PTR context = reinterpret_cast<DWORD_PTR>(this);
+
+        // Set up this as the context
+        if (!WinHttpSetOption(m_session, WINHTTP_OPTION_CONTEXT_VALUE, &context, sizeof(context))) {
+            m_log.crit("WinHttpSetOption failure: %d", GetLastError());
+            throw runtime_error("WinHHHTP failed to initialize: Could not set callback option");
+        }
+
+        // And register the callback
+        if (WinHttpSetStatusCallback(m_session, StatusCallback, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL) == WINHTTP_INVALID_STATUS_CALLBACK) {
+            m_log.crit("WinHttpSetStatusCallback failure: %d", GetLastError());
+            throw runtime_error("WinHHHTP failed to initialize: Could not register callback");
+        }
+    }
+
+    wstring host(components.lpszHostName, components.dwHostNameLength);
     m_connection = WinHttpConnect(m_session, host.c_str(), components.nPort, 0);
     if (m_connection == nullptr) {
         // Note use of WINDOWS formatting
@@ -262,12 +365,17 @@ WinHTTPRemotingService::WinHTTPRemotingService(ptree& pt)
 
 WinHTTPRemotingService::~WinHTTPRemotingService()
 {
-    if (m_session) {
+    if (m_session)
         WinHttpCloseHandle(m_session);
-    }
-    if (m_connection) {
+
+    if (m_connection)
         WinHttpCloseHandle(m_connection);
-    }
+
+    if (m_caChainEngine)
+        CertFreeCertificateChainEngine(m_caChainEngine);
+
+    if (m_caStore)
+        CertCloseStore(m_caStore, 0);
 }
 
 void WinHTTPRemotingService::send(const char* path, istream& input, ostream& output) const

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list