Skip to content

Instantly share code, notes, and snippets.

@sevaa
Created March 24, 2025 19:18
Show Gist options
  • Save sevaa/3bd98898c68c36111a1cbac443f52fbc to your computer and use it in GitHub Desktop.
Save sevaa/3bd98898c68c36111a1cbac443f52fbc to your computer and use it in GitHub Desktop.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <comdef.h>
#include <process.h>
#include <list>
#include <string>
#include <sstream>
#include <Wincrypt.h>
#include <certenroll.h>
#include "certenrollui.h"
using std::endl;
#pragma comment(lib, "certenrollui.lib")
#pragma comment (lib, "Crypt32.lib")
_COM_SMARTPTR_TYPEDEF(IX509Enrollment, __uuidof(IX509Enrollment));
_COM_SMARTPTR_TYPEDEF(IX509CertificateRequestPkcs10, __uuidof(IX509CertificateRequestPkcs10));
_COM_SMARTPTR_TYPEDEF(IX509CertificateRequest, __uuidof(IX509CertificateRequest));
_COM_SMARTPTR_TYPEDEF(IX509EnrollmentStatus, __uuidof(IX509EnrollmentStatus));
_COM_SMARTPTR_TYPEDEF(IX509CertificateRequestCertificate, __uuidof(IX509CertificateRequestCertificate));
_COM_SMARTPTR_TYPEDEF(IX509PrivateKey, __uuidof(IX509PrivateKey));
_COM_SMARTPTR_TYPEDEF(IX500DistinguishedName, __uuidof(IX500DistinguishedName));
_COM_SMARTPTR_TYPEDEF(IX509Extensions, __uuidof(IX509Extensions));
_COM_SMARTPTR_TYPEDEF(IX509Extension, __uuidof(IX509Extension));
_COM_SMARTPTR_TYPEDEF(IObjectId, __uuidof(IObjectId));
// The user inputs, collected by the worker thread
_bstr_t FName, Desc, Subj;
struct CExt
{
_bstr_t OID, Value;
VARIANT_BOOL Critical;
};
std::list<CExt> Extensions;
X509PrivateKeyExportFlags KeyExportPolicy;
X509PrivateKeyProtection KeyProt;
LONG KeyLength;
static void GetCertRequestData(const IX509EnrollmentPtr& pEnroll, const IX509CertificateRequestPkcs10Ptr& pReq)
{
BSTR bs;
//Name and friendly name - technically not a part of the certificate
pEnroll->get_CertificateFriendlyName(&bs);
FName.Attach(bs);
pEnroll->get_CertificateDescription(&bs);
Desc.Attach(bs);
//Subject
IX500DistinguishedNamePtr pSubj;
pReq->get_Subject(&pSubj);
if(pSubj)
{
pSubj->get_Name(&bs);
Subj.Attach(bs);
}
else
Subj = L"(no subject)";
//Extensions - do not parse, just dump
IX509ExtensionsPtr pExts;
pReq->get_X509Extensions(&pExts);
long i, n;
pExts->get_Count(&n);
for(i = 0; i < n; i++)
{
CExt Ext;
IX509ExtensionPtr pExt;
pExts->get_ItemByIndex(i, &pExt);
IObjectIdPtr pOID;
pExt->get_ObjectId(&pOID);
pOID->get_Value(&bs);
Ext.OID.Attach(bs);
pExt->get_Critical(&Ext.Critical);
pExt->get_RawData(EncodingType::XCN_CRYPT_STRING_BINARY, &bs);
Ext.Value.Attach(bs);
Extensions.push_back(Ext);
}
// Private key parameters
IX509PrivateKeyPtr pPK;
pReq->get_PrivateKey(&pPK);
pPK->get_ExportPolicy(&KeyExportPolicy);
pPK->get_KeyProtection(&KeyProt);
pPK->get_Length(&KeyLength);
}
class __declspec(uuid("13B79018-2181-11DA-B2A4-000E7BBB2B09"))
IX509EnrollmentInternal : public IUnknown
{
public:
STDMETHOD(Dummy0)() = 0; STDMETHOD(Dummy1)() = 0; STDMETHOD(Dummy2)() = 0;
STDMETHOD(Dummy3)() = 0; STDMETHOD(Dummy4)() = 0; STDMETHOD(Dummy5)() = 0;
STDMETHOD(Dummy6)() = 0; STDMETHOD(Dummy7)() = 0; STDMETHOD(Dummy8)() = 0;
STDMETHOD(Dummy9)() = 0; STDMETHOD(Dummya)() = 0; STDMETHOD(Dummyb)() = 0;
STDMETHOD(Dummyc)() = 0; STDMETHOD(Dummyd)() = 0; STDMETHOD(Dummye)() = 0;
STDMETHOD(PutQueryStatus)(EnrollmentQueryStatus) = 0;
};
_COM_SMARTPTR_TYPEDEF(IX509EnrollmentInternal, __uuidof(IX509EnrollmentInternal));
class CEnrollments : public IX509Enrollments
{
IX509EnrollmentPtr& m_pEnrollment;
public:
CEnrollments(IX509EnrollmentPtr& pe)
:m_pEnrollment(pe)
{
}
private:
STDMETHOD(get_ItemByIndex)(long i, IX509Enrollment** ppo)
{
if(i == 0)
{
m_pEnrollment.AddRef();
*ppo = m_pEnrollment;
return S_OK;
}
else
return E_INVALIDARG;
}
STDMETHOD(get_Count)(long* n)
{
*n = 1;
return S_OK;
}
STDMETHOD(get__NewEnum)(IUnknown**) { return E_NOTIMPL; }
STDMETHOD(Add)(IX509Enrollment*) { return E_NOTIMPL; }
STDMETHOD(Remove)(long) { return E_NOTIMPL; }
STDMETHOD(Clear)(void) { return E_NOTIMPL; }
STDMETHOD(get_ItemByTemplateName)(wchar_t*, IX509Enrollment**) { return E_NOTIMPL; }
STDMETHOD(get_Item)(long, tagVARIANT*) { return E_NOTIMPL; }
STDMETHOD(p_SetErrorInfo)(HRESULT, const wchar_t*, const wchar_t*) { return E_NOTIMPL; }
//IDispatch
STDMETHOD(GetTypeInfoCount)(UINT*) { return E_NOTIMPL; }
STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID, ITypeInfo**) { return E_NOTIMPL; }
STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*) { return E_NOTIMPL; }
STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*) { return E_NOTIMPL; }
//IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void** ppo)
{
if(InlineIsEqualGUID(riid, IID_IUnknown) || InlineIsEqualGUID(riid, IID_IDispatch))
{
*ppo = this;
return S_OK;
}
else
return E_NOINTERFACE;
}
STDMETHOD_(ULONG, AddRef)() { return 1; }
STDMETHOD_(ULONG, Release)() { return 1; }
};
static void CreateCertEnrollment(IX509EnrollmentPtr& pEnroll, IX509CertificateRequestPkcs10Ptr& pReq, HWND hWnd)
{
pReq.CreateInstance(__uuidof(CX509CertificateRequestPkcs10));
pReq->put_ParentWindow((LONG)hWnd);
pReq->Initialize(ContextUser);
IX509PrivateKeyPtr pPK;
pReq->get_PrivateKey(&pPK);
pPK->put_LegacyCsp(VARIANT_TRUE);
pEnroll.CreateInstance(__uuidof(CX509Enrollment));
pEnroll->put_ParentWindow((LONG)hWnd);
pEnroll->InitializeFromRequest(pReq);
IX509EnrollmentStatusPtr pSta;
pEnroll->get_Status(&pSta);
pSta->put_Selected(SelectedYes);
pSta->put_Display(DisplayYes);
pSta->put_Status(EnrollUnknown);
pSta->put_Error(S_OK);
IX509EnrollmentInternalPtr pEInt(pEnroll);
pEInt->PutQueryStatus(QueryEnrollRequired);
}
void ThreadProcWithCOM(ICertificateEnrollmentUI* pUI)
{
UserAction ua = UA_OK;
HWND hWnd = pUI->GetHWND();
//Start page
long l = pUI->ShowTextPage(TPI_Welcome, &ua);
if(ua == UserAction::UA_Cancel)
return;
// Policy selection page - but no policies are retrieved
CSimpleArray<POLICY_SERVER_GROUP> psga = {0, 0, 0};
l = pUI->ShowCEPSelectionPage(&psga, ContextUser, true, false, &ua);
if(ua == UserAction::UA_Cancel)
{
pUI->ShowErrorPage(L"The operation was canceled by the user.");
return;
}
// Template selection page
int SelTemplate = -1;
bool SuppressDefault = false, CNGRequest = false;
X509RequestType ReqType = TypePkcs10;
__int64 ll = pUI->SelectCustomRequestTemplate(0, 0, &SelTemplate, &ReqType, &SuppressDefault, &CNGRequest, &ua);
if(ua == UserAction::UA_Cancel)
{
pUI->ShowErrorPage(L"The operation was canceled by the user.");
return;
}
//Certificate details pags
IX509EnrollmentPtr pEnroll;
IX509CertificateRequestPkcs10Ptr pReq;
CreateCertEnrollment(pEnroll, pReq, hWnd);
CEnrollments Enrollments(pEnroll);
CSimpleArray<FAILED_POLICY_SERVER> fpsa = {0, 0, 0};
int r = pUI->ShowTemplateSelectionPage(&Enrollments, &fpsa, TSPI_OfflineRequest, &ua);
if(ua == UserAction::UA_Cancel)
{
pUI->ShowErrorPage(L"The operation was canceled by the user.");
return;
}
GetCertRequestData(pEnroll, pReq);
// Done page
pUI->ShowErrorPage(L"No error, sorry for the misleading title. Click Finish to proceed.");
}
static unsigned ThreadProc(void* a)
{
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
ThreadProcWithCOM((ICertificateEnrollmentUI*)a);
CoUninitialize();
return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int)
{
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
// Pop the enrollment wizard
ICertificateEnrollmentUI* pceui;
CreateUIObject(&pceui);
HANDLE hth = (HANDLE)_beginthreadex(0, 0, ThreadProc, pceui, 0, 0);
pceui->Run(0, UIF_CUSTOMREQUEST);
pceui->Delete();
CloseHandle(hth);
// Show your work
std::wostringstream oss;
oss << (LPCWSTR)Subj << endl << KeyLength << L" bit key" << endl;
if(KeyExportPolicy & XCN_NCRYPT_ALLOW_EXPORT_FLAG)
oss << L"The private key is exportable" << endl;
if(KeyExportPolicy & XCN_NCRYPT_ALLOW_ARCHIVING_FLAG)
oss << L"The private key allows archiving" << endl;
if(KeyProt & XCN_NCRYPT_UI_PROTECT_KEY_FLAG)
oss << L"Strong private key protection is enabled" << endl;
for(const CExt& Ext : Extensions)
{
const CRYPT_OID_INFO* pOID = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, (void*)(LPCSTR)Ext.OID, CRYPT_EXT_OR_ATTR_OID_GROUP_ID | CRYPT_OID_DISABLE_SEARCH_DS_FLAG);
oss << pOID->pwszName;
if(Ext.Critical)
oss << L" (critical)";
oss << L": " << Ext.Value.length() << L" bytes" << endl;
}
MessageBox(0, oss.str().c_str(), L"CertEnrollUIDemo", MB_OK | MB_ICONINFORMATION);
CoUninitialize();
return 0;
}
@sevaa
Copy link
Author

sevaa commented Mar 24, 2025

Companion gist for this blog post.

This gist depends on the CertEnrollUI API gist - before building this, you'd need to grab the .h file and the .def file, and build the import library from the .def. This was initially built with Visual Studio 2022.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment