mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 07:17:28 +00:00
[idd] network: provision persistent server identity
This commit is contained in:
516
idd/LGIddInstall/NetworkIdentity.c
Normal file
516
idd/LGIddInstall/NetworkIdentity.c
Normal file
@@ -0,0 +1,516 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include "NetworkIdentity.h"
|
||||
#include "../LGCommon/NetworkIdentity.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <ncrypt.h>
|
||||
#include <sddl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
#define LGIDD_REGKEY L"Software\\LookingGlass\\IDD"
|
||||
#define LG_NETWORK_CERTIFICATE_LIFETIME_YEARS 30
|
||||
|
||||
static void networkIdentityError(const wchar_t *operation, DWORD status)
|
||||
{
|
||||
fwprintf(stderr, L"%s failed with code 0x%08lx\n", operation, status);
|
||||
}
|
||||
|
||||
static bool networkIdentitySetKeySecurity(NCRYPT_KEY_HANDLE key)
|
||||
{
|
||||
static const wchar_t securityDescriptor[] =
|
||||
L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;LS)"
|
||||
L"(A;;GA;;;S-1-5-84-0-0-0-0-0)";
|
||||
|
||||
PSECURITY_DESCRIPTOR descriptor = NULL;
|
||||
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
||||
securityDescriptor, SDDL_REVISION_1, &descriptor, NULL))
|
||||
{
|
||||
networkIdentityError(
|
||||
L"ConvertStringSecurityDescriptorToSecurityDescriptorW",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
const SECURITY_STATUS status = NCryptSetProperty(
|
||||
key,
|
||||
NCRYPT_SECURITY_DESCR_PROPERTY,
|
||||
(PBYTE)descriptor,
|
||||
GetSecurityDescriptorLength(descriptor),
|
||||
DACL_SECURITY_INFORMATION | NCRYPT_PERSIST_FLAG);
|
||||
LocalFree(descriptor);
|
||||
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
networkIdentityError(
|
||||
L"NCryptSetProperty(key security)", (DWORD)status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool networkIdentityOpenKey(
|
||||
NCRYPT_PROV_HANDLE *provider,
|
||||
NCRYPT_KEY_HANDLE *key)
|
||||
{
|
||||
bool retry = false;
|
||||
SECURITY_STATUS status = NCryptOpenStorageProvider(
|
||||
provider, MS_KEY_STORAGE_PROVIDER, 0);
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
networkIdentityError(L"NCryptOpenStorageProvider", (DWORD)status);
|
||||
return false;
|
||||
}
|
||||
|
||||
openKey:
|
||||
status = NCryptOpenKey(*provider, key,
|
||||
LG_NETWORK_CERTIFICATE_KEY_NAME, 0, NCRYPT_MACHINE_KEY_FLAG);
|
||||
if (status == NTE_BAD_KEYSET && !retry)
|
||||
{
|
||||
status = NCryptCreatePersistedKey(
|
||||
*provider,
|
||||
key,
|
||||
NCRYPT_ECDSA_P256_ALGORITHM,
|
||||
LG_NETWORK_CERTIFICATE_KEY_NAME,
|
||||
0,
|
||||
NCRYPT_MACHINE_KEY_FLAG);
|
||||
if (status == ERROR_SUCCESS)
|
||||
{
|
||||
const DWORD usage = NCRYPT_ALLOW_SIGNING_FLAG;
|
||||
status = NCryptSetProperty(
|
||||
*key,
|
||||
NCRYPT_KEY_USAGE_PROPERTY,
|
||||
(PBYTE)&usage,
|
||||
sizeof usage,
|
||||
0);
|
||||
if (status == ERROR_SUCCESS)
|
||||
status = NCryptFinalizeKey(*key, 0);
|
||||
}
|
||||
else if (status == NTE_EXISTS)
|
||||
{
|
||||
retry = true;
|
||||
goto openKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
networkIdentityError(
|
||||
L"NCryptOpenKey/NCryptCreatePersistedKey", (DWORD)status);
|
||||
NCryptFreeObject(*provider);
|
||||
*provider = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!networkIdentitySetKeySecurity(*key))
|
||||
{
|
||||
NCryptFreeObject(*key);
|
||||
NCryptFreeObject(*provider);
|
||||
*key = 0;
|
||||
*provider = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool networkIdentityMatchesKey(PCCERT_CONTEXT certificate)
|
||||
{
|
||||
DWORD size = 0;
|
||||
if (!CertGetCertificateContextProperty(
|
||||
certificate, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size))
|
||||
return false;
|
||||
|
||||
CRYPT_KEY_PROV_INFO *info = malloc(size);
|
||||
if (!info)
|
||||
return false;
|
||||
|
||||
const bool result = CertGetCertificateContextProperty(
|
||||
certificate, CERT_KEY_PROV_INFO_PROP_ID, info, &size) &&
|
||||
info->pwszContainerName &&
|
||||
wcscmp(info->pwszContainerName,
|
||||
LG_NETWORK_CERTIFICATE_KEY_NAME) == 0 &&
|
||||
info->pwszProvName &&
|
||||
wcscmp(info->pwszProvName, MS_KEY_STORAGE_PROVIDER) == 0 &&
|
||||
CertVerifyTimeValidity(NULL, certificate->pCertInfo) == 0;
|
||||
|
||||
free(info);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PCCERT_CONTEXT networkIdentityFindCertificate(HCERTSTORE store)
|
||||
{
|
||||
PCCERT_CONTEXT certificate = NULL;
|
||||
while ((certificate = CertEnumCertificatesInStore(store, certificate)))
|
||||
if (networkIdentityMatchesKey(certificate))
|
||||
return certificate;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool networkIdentityEncodeExtension(
|
||||
LPCSTR objectId,
|
||||
LPCSTR structureType,
|
||||
const void *value,
|
||||
bool critical,
|
||||
CERT_EXTENSION *extension)
|
||||
{
|
||||
extension->pszObjId = (LPSTR)objectId;
|
||||
extension->fCritical = critical;
|
||||
|
||||
if (!CryptEncodeObjectEx(
|
||||
X509_ASN_ENCODING,
|
||||
structureType,
|
||||
value,
|
||||
CRYPT_ENCODE_ALLOC_FLAG,
|
||||
NULL,
|
||||
&extension->Value.pbData,
|
||||
&extension->Value.cbData))
|
||||
{
|
||||
networkIdentityError(L"CryptEncodeObjectEx", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void networkIdentityFreeExtensions(
|
||||
CERT_EXTENSION *extensions,
|
||||
size_t count)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (extensions[i].Value.pbData)
|
||||
LocalFree(extensions[i].Value.pbData);
|
||||
}
|
||||
|
||||
static PCCERT_CONTEXT networkIdentityCreateCertificate(
|
||||
HCERTSTORE store,
|
||||
NCRYPT_KEY_HANDLE key)
|
||||
{
|
||||
BYTE *encodedName = NULL;
|
||||
DWORD encodedNameSize = 0;
|
||||
PCCERT_CONTEXT result = NULL;
|
||||
|
||||
if (!CertStrToNameW(
|
||||
X509_ASN_ENCODING,
|
||||
LG_NETWORK_CERTIFICATE_SUBJECT,
|
||||
CERT_X500_NAME_STR,
|
||||
NULL,
|
||||
NULL,
|
||||
&encodedNameSize,
|
||||
NULL))
|
||||
{
|
||||
networkIdentityError(L"CertStrToNameW(size)", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encodedName = malloc(encodedNameSize);
|
||||
if (!encodedName)
|
||||
{
|
||||
networkIdentityError(L"malloc(certificate name)", ERROR_OUTOFMEMORY);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!CertStrToNameW(
|
||||
X509_ASN_ENCODING,
|
||||
LG_NETWORK_CERTIFICATE_SUBJECT,
|
||||
CERT_X500_NAME_STR,
|
||||
NULL,
|
||||
encodedName,
|
||||
&encodedNameSize,
|
||||
NULL))
|
||||
{
|
||||
networkIdentityError(L"CertStrToNameW", GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
CERT_BASIC_CONSTRAINTS2_INFO constraints =
|
||||
{
|
||||
.fCA = FALSE,
|
||||
.fPathLenConstraint = FALSE,
|
||||
.dwPathLenConstraint = 0,
|
||||
};
|
||||
LPSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
|
||||
CERT_ENHKEY_USAGE enhancedKeyUsage =
|
||||
{
|
||||
.cUsageIdentifier = ARRAYSIZE(usages),
|
||||
.rgpszUsageIdentifier = usages,
|
||||
};
|
||||
BYTE keyUsageByte = CERT_DIGITAL_SIGNATURE_KEY_USAGE;
|
||||
CRYPT_BIT_BLOB keyUsage =
|
||||
{
|
||||
.cbData = sizeof keyUsageByte,
|
||||
.pbData = &keyUsageByte,
|
||||
.cUnusedBits = 0,
|
||||
};
|
||||
CERT_EXTENSION extensions[3] = { 0 };
|
||||
if (!networkIdentityEncodeExtension(
|
||||
szOID_BASIC_CONSTRAINTS2,
|
||||
X509_BASIC_CONSTRAINTS2,
|
||||
&constraints,
|
||||
true,
|
||||
&extensions[0]) ||
|
||||
!networkIdentityEncodeExtension(
|
||||
szOID_ENHANCED_KEY_USAGE,
|
||||
X509_ENHANCED_KEY_USAGE,
|
||||
&enhancedKeyUsage,
|
||||
false,
|
||||
&extensions[1]) ||
|
||||
!networkIdentityEncodeExtension(
|
||||
szOID_KEY_USAGE,
|
||||
X509_KEY_USAGE,
|
||||
&keyUsage,
|
||||
true,
|
||||
&extensions[2]))
|
||||
{
|
||||
networkIdentityFreeExtensions(extensions, ARRAYSIZE(extensions));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
CERT_EXTENSIONS certificateExtensions =
|
||||
{
|
||||
.cExtension = (DWORD)ARRAYSIZE(extensions),
|
||||
.rgExtension = extensions,
|
||||
};
|
||||
CRYPT_KEY_PROV_INFO keyProvider =
|
||||
{
|
||||
.pwszContainerName = LG_NETWORK_CERTIFICATE_KEY_NAME,
|
||||
.pwszProvName = MS_KEY_STORAGE_PROVIDER,
|
||||
.dwProvType = 0,
|
||||
.dwFlags = CRYPT_MACHINE_KEYSET,
|
||||
.cProvParam = 0,
|
||||
.rgProvParam = NULL,
|
||||
.dwKeySpec = CERT_NCRYPT_KEY_SPEC,
|
||||
};
|
||||
CRYPT_ALGORITHM_IDENTIFIER signature =
|
||||
{
|
||||
.pszObjId = szOID_ECDSA_SHA256,
|
||||
};
|
||||
CERT_NAME_BLOB subject =
|
||||
{
|
||||
.cbData = encodedNameSize,
|
||||
.pbData = encodedName,
|
||||
};
|
||||
SYSTEMTIME start;
|
||||
SYSTEMTIME end;
|
||||
FILETIME expiration;
|
||||
GetSystemTime(&start);
|
||||
if (!SystemTimeToFileTime(&start, &expiration))
|
||||
{
|
||||
networkIdentityError(L"SystemTimeToFileTime", GetLastError());
|
||||
networkIdentityFreeExtensions(extensions, ARRAYSIZE(extensions));
|
||||
goto cleanup;
|
||||
}
|
||||
ULARGE_INTEGER expirationValue;
|
||||
expirationValue.LowPart = expiration.dwLowDateTime;
|
||||
expirationValue.HighPart = expiration.dwHighDateTime;
|
||||
expirationValue.QuadPart +=
|
||||
(ULONGLONG)LG_NETWORK_CERTIFICATE_LIFETIME_YEARS * 365U * 24U *
|
||||
60U * 60U * 10000000U;
|
||||
expiration.dwLowDateTime = expirationValue.LowPart;
|
||||
expiration.dwHighDateTime = expirationValue.HighPart;
|
||||
if (!FileTimeToSystemTime(&expiration, &end))
|
||||
{
|
||||
networkIdentityError(L"FileTimeToSystemTime", GetLastError());
|
||||
networkIdentityFreeExtensions(extensions, ARRAYSIZE(extensions));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT certificate = CertCreateSelfSignCertificate(
|
||||
(HCRYPTPROV_OR_NCRYPT_KEY_HANDLE)key,
|
||||
&subject,
|
||||
0,
|
||||
&keyProvider,
|
||||
&signature,
|
||||
&start,
|
||||
&end,
|
||||
&certificateExtensions);
|
||||
networkIdentityFreeExtensions(extensions, ARRAYSIZE(extensions));
|
||||
if (!certificate)
|
||||
{
|
||||
networkIdentityError(L"CertCreateSelfSignCertificate", GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
CRYPT_DATA_BLOB friendlyName =
|
||||
{
|
||||
.cbData = (DWORD)sizeof LG_NETWORK_CERTIFICATE_FRIENDLY_NAME,
|
||||
.pbData = (BYTE *)LG_NETWORK_CERTIFICATE_FRIENDLY_NAME,
|
||||
};
|
||||
if (!CertSetCertificateContextProperty(
|
||||
certificate,
|
||||
CERT_FRIENDLY_NAME_PROP_ID,
|
||||
0,
|
||||
&friendlyName))
|
||||
{
|
||||
networkIdentityError(
|
||||
L"CertSetCertificateContextProperty(friendly name)",
|
||||
GetLastError());
|
||||
CertFreeCertificateContext(certificate);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!CertAddCertificateContextToStore(
|
||||
store,
|
||||
certificate,
|
||||
CERT_STORE_ADD_REPLACE_EXISTING,
|
||||
&result))
|
||||
{
|
||||
networkIdentityError(L"CertAddCertificateContextToStore", GetLastError());
|
||||
}
|
||||
CertFreeCertificateContext(certificate);
|
||||
|
||||
cleanup:
|
||||
free(encodedName);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool networkIdentityStoreHashes(PCCERT_CONTEXT certificate)
|
||||
{
|
||||
BYTE certificateHash[20];
|
||||
DWORD certificateHashSize = sizeof certificateHash;
|
||||
if (!CertGetCertificateContextProperty(
|
||||
certificate,
|
||||
CERT_HASH_PROP_ID,
|
||||
certificateHash,
|
||||
&certificateHashSize))
|
||||
{
|
||||
networkIdentityError(
|
||||
L"CertGetCertificateContextProperty(CERT_HASH_PROP_ID)",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
BYTE fingerprint[32];
|
||||
DWORD fingerprintSize = sizeof fingerprint;
|
||||
if (!CryptHashCertificate2(
|
||||
L"SHA256",
|
||||
0,
|
||||
NULL,
|
||||
certificate->pbCertEncoded,
|
||||
certificate->cbCertEncoded,
|
||||
fingerprint,
|
||||
&fingerprintSize))
|
||||
{
|
||||
networkIdentityError(L"CryptHashCertificate2(SHA256)", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY key = NULL;
|
||||
const LSTATUS openStatus = RegOpenKeyExW(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
LGIDD_REGKEY,
|
||||
0,
|
||||
KEY_SET_VALUE | KEY_WOW64_64KEY,
|
||||
&key);
|
||||
if (openStatus != ERROR_SUCCESS)
|
||||
{
|
||||
networkIdentityError(
|
||||
L"RegOpenKeyExW(network identity)", (DWORD)openStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
LSTATUS status = RegSetValueExW(
|
||||
key,
|
||||
LG_NETWORK_CERTIFICATE_HASH_VALUE,
|
||||
0,
|
||||
REG_BINARY,
|
||||
certificateHash,
|
||||
certificateHashSize);
|
||||
if (status == ERROR_SUCCESS)
|
||||
{
|
||||
status = RegSetValueExW(
|
||||
key,
|
||||
LG_NETWORK_CERTIFICATE_FINGERPRINT_VALUE,
|
||||
0,
|
||||
REG_BINARY,
|
||||
fingerprint,
|
||||
fingerprintSize);
|
||||
}
|
||||
RegCloseKey(key);
|
||||
|
||||
if (status != ERROR_SUCCESS)
|
||||
{
|
||||
networkIdentityError(
|
||||
L"RegSetValueExW(network identity)", (DWORD)status);
|
||||
return false;
|
||||
}
|
||||
|
||||
wprintf(L"Network identity SHA256 fingerprint: ");
|
||||
for (DWORD i = 0; i < fingerprintSize; ++i)
|
||||
{
|
||||
if (i)
|
||||
wprintf(L":");
|
||||
wprintf(L"%02X", (unsigned)fingerprint[i]);
|
||||
}
|
||||
wprintf(L"\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool networkIdentityProvision(void)
|
||||
{
|
||||
bool result = false;
|
||||
NCRYPT_PROV_HANDLE provider = 0;
|
||||
NCRYPT_KEY_HANDLE key = 0;
|
||||
HCERTSTORE store = NULL;
|
||||
PCCERT_CONTEXT certificate = NULL;
|
||||
|
||||
if (!networkIdentityOpenKey(&provider, &key))
|
||||
goto cleanup;
|
||||
|
||||
store = CertOpenStore(
|
||||
CERT_STORE_PROV_SYSTEM_W,
|
||||
0,
|
||||
0,
|
||||
CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_OPEN_EXISTING_FLAG,
|
||||
L"MY");
|
||||
if (!store)
|
||||
{
|
||||
networkIdentityError(L"CertOpenStore(LocalMachine\\MY)", GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
certificate = networkIdentityFindCertificate(store);
|
||||
if (!certificate)
|
||||
certificate = networkIdentityCreateCertificate(store, key);
|
||||
if (!certificate)
|
||||
goto cleanup;
|
||||
|
||||
result = networkIdentityStoreHashes(certificate);
|
||||
|
||||
cleanup:
|
||||
if (certificate)
|
||||
CertFreeCertificateContext(certificate);
|
||||
if (store)
|
||||
CertCloseStore(store, 0);
|
||||
if (key)
|
||||
NCryptFreeObject(key);
|
||||
if (provider)
|
||||
NCryptFreeObject(provider);
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user