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

Rod Widdowson rdw at steadingsoftware.com
Mon May 19 16:16:12 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=555b6513bbdc4d4ceaf468f3851b9eb90af4b81f

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

commit 555b6513bbdc4d4ceaf468f3851b9eb90af4b81f
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Mon May 19 17:14:17 2025 +0100

    CPPSP-8 WinHTTPRemotingService Development
    
    https://shibboleth.atlassian.net/browse/CPPSP-8
    
    Commit work to date with CA trust support
     * Partial chains allowed
     * Revocation checking off right now
     * CN checking is enforced
     * Currently undetermined whether this works with a self signed cert.
---
 shibsp/remoting/impl/WinHTTPRemotingService.cpp | 187 +++++++++++++++---------
 1 file changed, 114 insertions(+), 73 deletions(-)

diff --git a/shibsp/remoting/impl/WinHTTPRemotingService.cpp b/shibsp/remoting/impl/WinHTTPRemotingService.cpp
index 527b0820..d6d08635 100644
--- a/shibsp/remoting/impl/WinHTTPRemotingService.cpp
+++ b/shibsp/remoting/impl/WinHTTPRemotingService.cpp
@@ -135,6 +135,17 @@ void WinHTTPRemotingService::setupCaChecking() {
 
     if (!getCAFile())
         return;
+
+    //
+    // In order to do CA checking we need to read the pem file into a volatile (in memory) certificate
+    // store and then set up a cert engine.
+    //
+    // When we set up the request (in WinHTTPRemotingService::send) we tell winHttp not to check any root certificate validity
+    // but to check the CN.  We get a callback (to be finalized but before BASICAUTH) and getgiven the 
+    // certificate presented.  We pass this to WinHTTPRemotingService::handleCert which calls into the crypto
+    // library to check the chain.
+    //
+    //This method sets up the CA store and the cert engine (as part of object construction)
     
     wstring caFile(utf8ToUtf16(getCAFile()));
     DWORD msgAndCertEncodingType, contentType, formatType;
@@ -182,6 +193,8 @@ void WinHTTPRemotingService::setupCaChecking() {
     CERT_CHAIN_ENGINE_CONFIG cfg = { 0 };
     cfg.cbSize = sizeof(cfg);
     cfg.hExclusiveRoot = m_caStore;
+    // This is the flag which allows partial chains
+    cfg.dwExclusiveFlags = CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG;
 
     if (!CertCreateCertificateChainEngine(&cfg, &m_caChainEngine)) {
         m_log.crit("Could not create chain engine: 0x%x", GetLastError());
@@ -190,78 +203,6 @@ void WinHTTPRemotingService::setupCaChecking() {
 
 }
 
-void WinHTTPRemotingService::logSecureFailure(DWORD Status) const
-{
-    string details("");
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE ";
-    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)
-        details += "WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR ";
-    m_log.crit("WinHttp Security Error 0x%x (%s)", Status, details.c_str());
-}
-
-void WinHTTPRemotingService::handleCert(HINTERNET Handle) const
-{
-    PCCERT_CONTEXT certCtx = NULL;
-    DWORD size = sizeof(certCtx);
-
-    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 {
-        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");
-        }
-        //
-        // Why do we only look at chain zero?
-        //
-        CERT_TRUST_STATUS status = chainContext->rgpChain[0]->TrustStatus;
-        CertFreeCertificateChain(chainContext);
-
-        m_log.debug("Connection Error %x Info %x", status.dwErrorStatus, status.dwInfoStatus);
-        //
-        // per CURL strip out (undocumented) CERT_TRUST_IS_NOT_TIME_NESTED  
-        //
-        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");
-        }
-    }
-}
-
 static
 void 
 _stdcall
@@ -424,8 +365,26 @@ void WinHTTPRemotingService::send(const char* path, istream& input, ostream& out
     HINTERNETJanitor req(request);
 
     //
-    // TODO: somethimg magical with ciphers
+    //  The flags we set https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-httpspolicycallbackdata
+    //      SECURITY_FLAG_IGNORE_UNKNOWN_CA   Ignore errors associated with an unknown certification authority.
+    //                                        We check this in WinHTTPRemotingService::handleCert
+    //      SECURITY_FLAG_IGNORE_WRONG_USAGE  Ignore errors associated with the use of a certificate.
+    //      SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
+    //                                        Ignore errors associated with an expired certificate.
+    //
+    // Critically we do NOT set
+    //      SECURITY_FLAG_IGNORE_CERT_CN_INVALID Ignore errors associated with a certificate that contains a common name that is not valid.
+    //      SECURITY_FLAG_IGNORE_REVOCATION      Ignore errors associated with a revoked certificate.
+    //                                           We need to add this (and in WinHTTPRemotingService::handleCert)
     //
+    DWORD securityFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
+                          SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
+                          SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
+    if (!WinHttpSetOption(request, WINHTTP_OPTION_SECURITY_FLAGS, &securityFlags, sizeof(securityFlags))) {
+        m_log.crit("Send.  Failed to set security flags : %d", GetLastError());
+        throw RemotingException("send failed");
+    }
+
     if (m_authScheme) {
         bool sendPass = (m_authScheme == WINHTTP_AUTH_SCHEME_BASIC || m_authScheme == WINHTTP_AUTH_SCHEME_DIGEST);
         if (!WinHttpSetCredentials(request,
@@ -570,3 +529,85 @@ void WinHTTPRemotingService::send(const char* path, istream& input, ostream& out
         delete[] buffer;
     }
 }
+
+//
+// Called if the TLS handshake (called when we call WinHttpSendRequest as part of ::send) failed
+//
+void WinHTTPRemotingService::logSecureFailure(DWORD Status) const
+{
+    string details("");
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE ";
+    if (Status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)
+        details += "WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR ";
+    m_log.crit("WinHttp Security Error 0x%x (%s)", Status, details.c_str());
+}
+
+//
+// Called during WinHttpSendRequest to allow us to police the certificate.
+//
+void WinHTTPRemotingService::handleCert(HINTERNET Handle) const
+{
+    PCCERT_CONTEXT certCtx = NULL;
+    DWORD size = sizeof(certCtx);
+
+    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 {
+        CERT_CHAIN_PARA chainPara = { 0 };
+        PCCERT_CHAIN_CONTEXT chainContext;
+
+        chainPara.cbSize = sizeof(chainPara);
+
+        DWORD flags = CERT_CHAIN_CACHE_END_CERT;
+        // REVOCATION!
+        //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 connect");
+            throw RemotingException("Could not get certificate chain");
+        }
+        //
+        // Why do we only look at chain zero?
+        //
+        CERT_TRUST_STATUS status = chainContext->rgpChain[0]->TrustStatus;
+        CertFreeCertificateChain(chainContext);
+
+        m_log.debug("Connection Error %x Info %x", status.dwErrorStatus, status.dwInfoStatus);
+        //
+        // per CURL strip out (undocumented) CERT_TRUST_IS_NOT_TIME_NESTED
+        //
+        DWORD error = status.dwErrorStatus & (~CERT_TRUST_IS_NOT_TIME_NESTED);
+        if (error != CERT_TRUST_NO_ERROR) {
+            // We might want to expand the errors, wbut which ones?
+            // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_trust_status
+            m_log.error("Certificate presented is not trusted.  Error : 0x%x", status.dwErrorStatus);
+            throw RemotingException("Could not get certificate chain");
+        }
+    }
+}

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


More information about the commits mailing list