Created
March 24, 2025 19:18
-
-
Save sevaa/3bd98898c68c36111a1cbac443f52fbc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.