Verifying certificate using CertVerifyCertificateChainPolicy fails when the certificate chain is replaced by firewall, but .NET X509Chain verifies it
Asked Answered
Q

0

6

My C++ application (WinSCP) connects to my server to check for new versions. The HTTPS code uses neon and OpenSSL libraries. I'm verifying the TLS certificate using CertVerifyCertificateChainPolicy function.

A sample code that verifies a certificate against Windows system certificate store (for brevity, error checking was removed):

const CERT_CONTEXT * CertContext =
  CertCreateCertificateContext(
    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, Certificate, Len);

CERT_CHAIN_PARA ChainPara;
memset(&ChainPara, 0, sizeof(ChainPara));
ChainPara.cbSize = sizeof(ChainPara);

CERT_CHAIN_ENGINE_CONFIG ChainConfig;
memset(&ChainConfig, 0, sizeof(ChainConfig));
ChainConfig.cbSize = sizeof(ChainConfig);
ChainConfig.dwFlags = CERT_CHAIN_CACHE_END_CERT;

HCERTCHAINENGINE ChainEngine;
CertCreateCertificateChainEngine(&ChainConfig, &ChainEngine);
const CERT_CHAIN_CONTEXT * ChainContext = NULL;
CertGetCertificateChain(
  ChainEngine, CertContext, NULL, NULL, &ChainPara,
  CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
  NULL, &ChainContext);

CERT_CHAIN_POLICY_PARA PolicyPara;
PolicyPara.cbSize = sizeof(PolicyPara);

CERT_CHAIN_POLICY_STATUS PolicyStatus;
PolicyStatus.cbSize = sizeof(PolicyStatus);

CertVerifyCertificateChainPolicy(
  CERT_CHAIN_POLICY_SSL, ChainContext, &PolicyPara, &PolicyStatus);
int PolicyError = PolicyStatus.dwError;

This works. But I've got few reports that the verification fails in corporate environments. The problem seems to happens, when the certificate chain is replaced by the corporate firewall. The PolicyStatus.dwError is set to 800B0109 = CERT_E_UNTRUSTEDROOT.

As suggested by @bartonjs, I've also tried using HCCE_CURRENT_USER (=NULL) instead of using own engine created with CertCreateCertificateChainEngine. It made no difference.


But other apps can connect to my website:

  • Users can connect to my website using their webbrowsers (but I'm aware that they have their own trust store, so that is not so surprising to me).
  • But users can also connect using Invoke-WebRequest in PowerShell.
  • And my site's certificate is also successfully verified using .NET's X509Chain.Build (what I assume is what happens inside Invoke-WebRequest):
X509Chain chain = new();
chain.Build(sslStream.RemoteCertificate);
// chain.ChainStatus is now empty => certificate is valid

When I've checked the .NET code for X509Chain.Build, it seems to be doing something very similar to my C++ code. But there must be some subtle difference I cannot identify.

Does anyone know what .NET does differently, that it can successfully verify the replaced certificate chain?

Quandary answered 28/5 at 13:42 Comment(13)
It makes most sense that the user is using a browser for which the trust store doesn't get updated. Both FF and Chrome use their own trust store afaik. So the system's trust store may get updated, but not the trust store of the specific browser. Edge will probably always work as it uses the system's trust store. Except that you're then stuck with Edge of course.Stalag
@MaartenBodewes I'm aware that browsers have their own trust store. So that wasn't surprising to me. I'm rather curious about .NET/PowerShell.Quandary
Hmm, yeah, that should connect to the trust store. I can only imagine that there are differences with the local trust store not being updated due to differences in how the group policies are executed.Stalag
@bartonjs How am I using a custom chain engine?Quandary
You're calling CertCreateCertificateChainEngine, rather than using one of the predefined ones.Trifling
@Trifling Can you please be more specific? Which "predefined one" should I be using?Quandary
HCCE_CURRENT_USERTrifling
@Trifling Can you please be more specific? Google search fo "HCCE_CURRENT_USER" does not yield any result at all. Let only showing how to use it instead of "custom chain engine" (whatever you actually mean by that).Quandary
wincrypt.h: #define HCCE_CURRENT_USER ((HCERTCHAINENGINE)NULL)Trifling
@Trifling Unfortunately, using HCCE_CURRENT_USER made no difference.Quandary
Have you verified the corporate firewall is actually using a valid certificate? Many corporate firewalls don't, they just whitelist their self-signed certificate via Active Directory.Socialize
Ahhh. I suppose they're probably NOT using a valid certificate, and it's possible they're added it to their browser's truststore, but not the system trust store. (An easy way to check would be to open up a browser, and check the Issued by section, and verify that it's the same Issued By you see from within your C++ code, and it's also the same Issued by that you see when connecting from outside the corporate network. globalsign.com/en/blog/how-to-view-ssl-certificate-details )Socialize
@EliezerBerlin Do you think that .NET uses the Active directory?Quandary

© 2022 - 2024 — McMap. All rights reserved.