[idd] network: validate server certificate identity

This commit is contained in:
Geoffrey McRae
2026-10-01 07:29:27 +10:00
parent c497fe00c6
commit d1bbcbcb45
2 changed files with 138 additions and 20 deletions

View File

@@ -42,10 +42,12 @@
#include <limits.h> #include <limits.h>
#include <memory> #include <memory>
#include <new> #include <new>
#include <ncrypt.h>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <wincrypt.h>
namespace namespace
{ {
@@ -199,7 +201,7 @@ namespace
return QuicAddrFromString(text.c_str(), port, &address) != FALSE; return QuicAddrFromString(text.c_str(), port, &address) != FALSE;
} }
bool ReadCertificateHash(QUIC_CERTIFICATE_HASH_STORE& certificate) bool ReadCertificateHash(QUIC_CERTIFICATE_HASH& certificate)
{ {
HKEY key = nullptr; HKEY key = nullptr;
const LSTATUS openStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, const LSTATUS openStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
@@ -225,10 +227,83 @@ namespace
return false; return false;
} }
certificate.Flags = QUIC_CERTIFICATE_HASH_STORE_FLAG_MACHINE_STORE; return true;
if (strcpy_s(certificate.StoreName, }
sizeof(certificate.StoreName), "MY") != 0)
bool LoadCertificateContext(HCERTSTORE& store,
PCCERT_CONTEXT& certificate)
{
QUIC_CERTIFICATE_HASH certificateHash = {};
if (!ReadCertificateHash(certificateHash))
return false; return false;
store = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0,
0,
CERT_SYSTEM_STORE_LOCAL_MACHINE |
CERT_STORE_OPEN_EXISTING_FLAG |
CERT_STORE_READONLY_FLAG,
L"MY");
if (!store)
{
DEBUG_ERROR("Failed to open the network identity certificate store: "
"0x%lx", static_cast<unsigned long>(GetLastError()));
return false;
}
CRYPT_HASH_BLOB hash = {};
hash.cbData = static_cast<DWORD>(
sizeof(certificateHash.ShaHash));
hash.pbData = certificateHash.ShaHash;
certificate = CertFindCertificateInStore(store,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_HASH,
&hash, nullptr);
if (!certificate)
{
const DWORD status = GetLastError();
DEBUG_ERROR("Failed to find the network identity certificate: 0x%lx",
static_cast<unsigned long>(status));
CertCloseStore(store, 0);
store = nullptr;
return false;
}
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE privateKey = 0;
DWORD keySpec = 0;
BOOL callerFree = FALSE;
const DWORD flags =
CRYPT_ACQUIRE_CACHE_FLAG |
CRYPT_ACQUIRE_COMPARE_KEY_FLAG |
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG |
CRYPT_ACQUIRE_SILENT_FLAG;
if (!CryptAcquireCertificatePrivateKey(certificate,
flags,
nullptr, &privateKey, &keySpec, &callerFree))
{
const DWORD status = GetLastError();
DEBUG_ERROR("Failed to acquire the network identity private key: "
"0x%lx", static_cast<unsigned long>(status));
CertFreeCertificateContext(certificate);
CertCloseStore(store, 0);
certificate = nullptr;
store = nullptr;
return false;
}
if (!privateKey || keySpec != CERT_NCRYPT_KEY_SPEC || callerFree)
{
DEBUG_ERROR("The network identity did not provide a cached CNG "
"private key");
if (callerFree && privateKey)
NCryptFreeObject(static_cast<NCRYPT_HANDLE>(privateKey));
CertFreeCertificateContext(certificate);
CertCloseStore(store, 0);
certificate = nullptr;
store = nullptr;
return false;
}
return true; return true;
} }
} }
@@ -467,17 +542,19 @@ struct CNetworkServer::Impl
CNetworkClipboard& clipboard; CNetworkClipboard& clipboard;
CNetworkVideo& video; CNetworkVideo& video;
NetworkSettings settings; NetworkSettings settings;
const QUIC_API_TABLE * api = nullptr; const QUIC_API_TABLE * api = nullptr;
HQUIC registration = nullptr; HQUIC registration = nullptr;
HQUIC configuration = nullptr; HQUIC configuration = nullptr;
HQUIC listener = nullptr; HQUIC listener = nullptr;
HANDLE drainedEvent = nullptr; HCERTSTORE certificateStore = nullptr;
PCCERT_CONTEXT certificate = nullptr;
HANDLE drainedEvent = nullptr;
CSRWLock lock; CSRWLock lock;
std::vector<Connection *> connections; std::vector<Connection *> connections;
uint32_t nextClient = 1; uint32_t nextClient = 1;
unsigned activeCallbacks = 0; unsigned activeCallbacks = 0;
bool stopping = true; bool stopping = true;
bool running = false; bool running = false;
void EnterCallback() void EnterCallback()
{ {
@@ -4718,8 +4795,7 @@ retry:
return false; return false;
} }
QUIC_CERTIFICATE_HASH_STORE certificate = {}; if (!LoadCertificateContext(certificateStore, certificate))
if (!ReadCertificateHash(certificate))
{ {
DEBUG_ERROR("The persistent network identity is unavailable; run the " DEBUG_ERROR("The persistent network identity is unavailable; run the "
"Looking Glass IDD installer to provision it"); "Looking Glass IDD installer to provision it");
@@ -4728,10 +4804,10 @@ retry:
} }
QUIC_CREDENTIAL_CONFIG credential = {}; QUIC_CREDENTIAL_CONFIG credential = {};
credential.Type = credential.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_CONTEXT;
QUIC_CREDENTIAL_TYPE_CERTIFICATE_HASH_STORE; credential.Flags = QUIC_CREDENTIAL_FLAG_NONE;
credential.Flags = QUIC_CREDENTIAL_FLAG_NONE; credential.CertificateContext = reinterpret_cast<QUIC_CERTIFICATE *>(
credential.CertificateHashStore = &certificate; const_cast<CERT_CONTEXT *>(certificate));
status = api->ConfigurationLoadCredential(configuration, &credential); status = api->ConfigurationLoadCredential(configuration, &credential);
if (QUIC_FAILED(status)) if (QUIC_FAILED(status))
{ {
@@ -4806,6 +4882,16 @@ retry:
api->ConfigurationClose(configuration); api->ConfigurationClose(configuration);
configuration = nullptr; configuration = nullptr;
} }
if (certificate)
{
CertFreeCertificateContext(certificate);
certificate = nullptr;
}
if (certificateStore)
{
CertCloseStore(certificateStore, 0);
certificateStore = nullptr;
}
if (registration && api) if (registration && api)
{ {
api->RegistrationClose(registration); api->RegistrationClose(registration);

View File

@@ -140,6 +140,37 @@ openKey:
return true; return true;
} }
static bool networkIdentityCanUsePrivateKey(PCCERT_CONTEXT certificate)
{
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE key = 0;
DWORD keySpec = 0;
BOOL callerFree = FALSE;
const DWORD flags =
CRYPT_ACQUIRE_COMPARE_KEY_FLAG |
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG |
CRYPT_ACQUIRE_SILENT_FLAG;
const bool acquired = CryptAcquireCertificatePrivateKey(
certificate,
flags,
NULL,
&key,
&keySpec,
&callerFree);
const bool result = acquired &&
key &&
keySpec == CERT_NCRYPT_KEY_SPEC;
if (callerFree && key)
{
if (keySpec == CERT_NCRYPT_KEY_SPEC)
NCryptFreeObject((NCRYPT_HANDLE)key);
else
CryptReleaseContext((HCRYPTPROV)key, 0);
}
return result;
}
static bool networkIdentityMatchesKey(PCCERT_CONTEXT certificate) static bool networkIdentityMatchesKey(PCCERT_CONTEXT certificate)
{ {
DWORD size = 0; DWORD size = 0;
@@ -158,7 +189,8 @@ static bool networkIdentityMatchesKey(PCCERT_CONTEXT certificate)
LG_NETWORK_CERTIFICATE_KEY_NAME) == 0 && LG_NETWORK_CERTIFICATE_KEY_NAME) == 0 &&
info->pwszProvName && info->pwszProvName &&
wcscmp(info->pwszProvName, MS_KEY_STORAGE_PROVIDER) == 0 && wcscmp(info->pwszProvName, MS_KEY_STORAGE_PROVIDER) == 0 &&
CertVerifyTimeValidity(NULL, certificate->pCertInfo) == 0; CertVerifyTimeValidity(NULL, certificate->pCertInfo) == 0 &&
networkIdentityCanUsePrivateKey(certificate);
free(info); free(info);
return result; return result;