為了註冊 CA 憑證動作更簡化,於是花了三四天研究怎麼用 C# 控制註冊流程,過程真的挺累,結果挺爽的...
本文連結
上一篇講到使用 MMC 申請 CA 憑證
要能使用 MMC 申請 CA 憑證,要先加入網域,但我們透過 C# 就能打破這個限制
開發環境
- VS 2019
- CertEroll 1.0 Type Library
- CertCli1.0 Type Library
請求憑證步驟
建立憑證請求
IX509PrivateKey:定義私鑰
CX509CertificateRequestPkcs10:憑證類型
CX509ExtensionAlternativeNames:SAN,能被 Chrome 信任的關鍵欄位,它在 GetSAN 方法,根據 CN 欄位自動幫你建立
CX500DistinguishedName:定義 Subject
public string CreateRequest(string cn, string ou, string o, string l, string s, string c, string oid, int keyLength) { var csp = new CCspInformations(); csp.AddAvailableCsps(); var privateKey = (IX509PrivateKey) Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey")); privateKey.Length = keyLength; privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES; privateKey.MachineContext = false; privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG; privateKey.CspInformations = csp; privateKey.Create(); var pkcs10 = (CX509CertificateRequestPkcs10) Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestPkcs10")); pkcs10.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, string.Empty); var extensionKeyUsage = (CX509ExtensionKeyUsage) Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage")); extensionKeyUsage.InitializeEncode(X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE | X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE); pkcs10.X509Extensions.Add((CX509Extension) extensionKeyUsage); var objectId = new CObjectId(); var objectIds = new CObjectIds(); var extensionEnhancedKeyUsage = (CX509ExtensionEnhancedKeyUsage) Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage")); objectId.InitializeFromValue(oid); objectIds.Add(objectId); extensionEnhancedKeyUsage.InitializeEncode(objectIds); pkcs10.X509Extensions.Add((CX509Extension) extensionEnhancedKeyUsage); var san = GetSAN(cn); pkcs10.X509Extensions.Add((CX509Extension) san); var distinguishedName = (CX500DistinguishedName) Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName")); cn = ConvertCn(cn); var subjectName = $"{cn},OU = {ou},O = {o} ,L = {l},S = {s},C = {c}"; distinguishedName.Encode(subjectName); pkcs10.Subject = distinguishedName; var enroll = (CX509Enrollment) Activator.CreateInstance(Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment")); enroll.InitializeFromRequest(pkcs10); var request = enroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64HEADER); return request; }
這個步驟所得到的內容就能貼到 CA Server 的 Web 網頁手動要求憑證,如下圖
https://dotblogs.com.tw/yc421206/2013/07/26/112543#s2
下面的步驟則是透過 C# 完成憑證要求
送出請求
選擇向 CA Server 要求 WebServer 範本憑證
CA Server 的 URL 格式為 主機位置\CA名稱,比如 ad.lab.local\lab-ca
public string SendRequest(string createRequest, string caServer, string templateName, string additionalAttributes = "") { var attributes = string.Format("CertificateTemplate: {0}", templateName); if (!string.IsNullOrEmpty(additionalAttributes)) { attributes += "\n" + additionalAttributes; } var certRequest = new CCertRequest(); var requestResult = (RequestDisposition) certRequest.Submit((int) EncodingType.XCN_CRYPT_STRING_BASE64HEADER, createRequest, attributes, caServer); string cert = null; if (requestResult == RequestDisposition.CR_DISP_ISSUED) { cert = certRequest.GetCertificate((int) EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER); } return cert; }
註冊憑證
調用 enroll.InstallResponse 後,就會把憑證安裝在「目前使用者\個人」,這個位置不會被 IIS 認可,所以我要把它改安裝在「本機電腦\個人」
enroll.CreatePFX 產生 Base64 的憑證內容
public void InstallAndDownload(string certText, string password, string friendlyName) { var enroll = new CX509Enrollment(); enroll.Initialize(X509CertificateEnrollmentContext.ContextUser); enroll.CertificateFriendlyName = friendlyName; enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedRoot, certText, EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER, password ); var dir = Directory.GetParent(Assembly.GetExecutingAssembly().Location).ToString(); var pfx = enroll.CreatePFX(password, PFXExportOptions.PFXExportChainWithRoot); var fileName = "cert.pfx"; var filePath = $@"{dir}\{fileName}"; Download(filePath, pfx); Install(filePath, password); }
我要用 X509Certificate2 把 Base64 憑證安裝到「本機電腦\個人」,我在實作的過程當中一直出現 Encode 的例外訊息;
後來改成,將 Base64 轉成陣列後再存檔,才解決這個問題
private static void Download(string filePath, string content) { using (var fs = new FileStream(filePath, FileMode.Create)) { var base64String = Convert.FromBase64String(content); fs.Write(base64String, 0, base64String.Length); } }
private static void Install(string filePath, string password) { var cert = new X509Certificate2(filePath, password, X509KeyStorageFlags.PersistKeySet); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); }
專案位置
https://github.com/yaochangyu/sample.dotblog/tree/master/Cert/Lab.CertFromCA
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET