[英]Display X509Certificate with chain elements not in certificate store
I have an object of type X509Certificate2
and want to display it to the user. 我有一个X509Certificate2
类型的对象,并希望将其显示给用户。 I'm doing this with the X509Certificate2UI.DisplayCertificate
method. 我正在使用X509Certificate2UI.DisplayCertificate
方法执行此操作。
The problem I have is that this certificate I want to show is issued by an intermediate CA whichs certificate is not in the machines certificate store , but its root is. 我遇到的问题是我想要显示的这个证书是由中间CA颁发的,证书不在机器证书存储区中 ,但其根目录是。
Now if I display said certificate the dialog is not able to build the chain (opposite to me, as I am able to with the X509Chain
and the intermediate CA as an extra element) 现在,如果我显示所述证书,则对话框无法构建链 (与我相反,因为我能够将X509Chain
和中间CA作为额外元素)
How do I display the certificate with the whole chain? 如何使用整个链显示证书?
X509Certificate2 endCert = ...;
X509Certificate2 intermediateCA = ...;
X509Chain chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(intermediateCA);
chain.Build(endCert); // Whole chain!
X509Certificate2UI.DisplayCertificate(endCert); // Dialog shows: "The issuer of this certificate could not be found."
(I'm not able/allowed to add the intermediate CA to the user/machine store!) (我无法/允许将中间CA添加到用户/机器商店!)
(I'm not able/allowed to create my own dialog. It has to be the default Windows dialog!) (我无法/允许创建自己的对话框。它必须是默认的Windows对话框!)
(P/Invoke is allowed if required) (如果需要,可以使用P / Invoke)
Just as an example you can try these certificates to test above, but you have to reference System.Security.dll
for X509Certificate2UI.DisplayCertificate
: 作为示例,您可以尝试使用这些证书进行上述测试,但您必须为X509Certificate2UI.DisplayCertificate
引用System.Security.dll
:
X509Certificate2 endCert = new X509Certificate2(
Convert.FromBase64String(
"MIIE8zCCA9ugAwIBAgIQSBDq+mlsLsCZqWMIWj/YADANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRYwFAYDVQQDEw1UaGF3dGUgU1NMIENBMB4XDTExMTI" +
"yMDAwMDAwMFoXDTE0MDIxNzIzNTk1OVowgYsxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxQLRm9yZXN0IEhpbGwxIzAhBgNVBAoUGkFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW" +
"9uMRcwFQYDVQQLFA5JbmZyYXN0cnVjdHVyZTEVMBMGA1UEAxQMKi5hcGFjaGUub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApyhxElzdnWks7MMCEx24FMhHCbFcgKbO+fh/+JYrV91Cs" +
"xsdqsAsvAU37P/eLMQ3ZVm93c6uQbt6cq+0VXniviFjXS3qUUJVUC60Q/YDzaYrTFZdY8ccA5wWdFTiMlJgwIqdlvB7JLkOzotvawRfJxeH+aucY756TdYGapAyno+3pWNXnU5sr1oaJ4uGchaS7LUAqpfP" +
"fA3oTv63ZmIzHh2MTfDeUgdVSxeqEj3FCObLdps4Fs6c08Re2KAEZ+0UcMwNyJh0y6aP6PBgZAdt3qODONrI56TCDxjMC47lmIrm/U2Vy+v1LB90uU/1ESAiKvIKLjVZucO0U4Ol8VgiSDIH1FezXEhl+fP" +
"zY1N18u6kMx0AGDKDO0fBkUpkA6r6K4Kk/YvEJBLiIvLwLLnQhcwJjhRZItA52dNvKHMRYh5er1xVbLj7X+ujDfA6RpJYOmmPUxYzsZpZhTk0wybuGrkuvrm5t9ONP4p/2lan1G9aXqK6OLNh4W9IVUs1o1" +
"KvMP86ToBOsZY/g50cld0kh7AMR+W/Lg9WtPxs1nq98k2J7HZBmMnYTEqwzSFtsMzGlqcFXO170JnfgklUjzi12vwQYO0bf/q+3e7QQsYRXzSGUEdKJZvzs0P09jJ6W/mDdnMdaoh7eYP5eynleZtElUgcd" +
"NNgVAHn8NEUnJpwbGUCAwEAAaOBoDCBnTAMBgNVHRMBAf8EAjAAMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9zdnItb3YtY3JsLnRoYXd0ZS5jb20vVGhhd3RlT1YuY3JsMB0GA1UdJQQWMBQGCCsGAQUF" +
"BwMBBggrBgEFBQcDAjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAA6BnlWlsAXvTmDpqijPpBUkD9Xkbys7UC/FOuUVr3P" +
"K3d3GCQynwhooBe2CAshtxjb3Cc8zJfeqb5IQfjTcuEznIpONvqFvSmU4/INS+3/TPLoyQ81wpsIUbJzhhJY78CH8TZ5cn2BtWkI9fEydAXYe9a64GVdjPBJhneBon3J63s895GSSucQAIQZEiXBAqoklS5" +
"n0Ud2aSYrNZJUVN3o8Rh0tvd0W2l6KjBaIZLUTieDZb3eRrValvjYDcCp9uI3aTdhht6zxUuE+OZ7DPWIWz3EYTMVTTtQdojJK9mM++JC74Y4s+JSCgRzTn3CxDMWPG5FWxavENub0FfsXfnY="));
X509Certificate2 intermediateCA = new X509Certificate2(
Convert.FromBase64String(
"MIIEbDCCA1SgAwIBAgIQTV8sNAiyTCDNbVB+JE3J7DANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWN" +
"lcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjA4MDAwMD" +
"AwWhcNMjAwMjA3MjM1OTU5WjA8MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRYwFAYDVQQDEw1UaGF3dGUgU1NMIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmeSFW" +
"3ZJfS8F2MWsyMip09yY5tc0pi8M8iIm2KPJFEyPBaRF6BQMWJAFGrfFwQalgK+7HUlrUjSIw1nn72vEJ0GMK2Yd0OCjl5gZNEtB1ZjVxwWtouTX7QytT8G1sCH9PlBTssSQ0NQwZ2ya8Q50xMLciuiX/8mS" +
"rgGKVgqYMrAAI+yQGmDD7bs6yw9jnw1EyVLhJZa/7VCViX9WFLG3YR0cB4w6LPf/gN45RdWvGtF42MdxaqMZpzJQIenyDqHGEwNESNFmqFJX1xG0k4vlmZ9d53hR5U32t1m0drUJN00GOBN6HAiYXMRISst" +
"SoKn4sZ2Oe3mwIC88lqgRYke7EQIDAQABo4H7MIH4MDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29jc3AudGhhd3RlLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEAMDQGA1UdHwQtMCswKa" +
"AnoCWGI2h0dHA6Ly9jcmwudGhhd3RlLmNvbS9UaGF3dGVQQ0EuY3JsMA4GA1UdDwEB/wQEAwIBBjAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVmVyaVNpZ25NUEtJLTItOTAdBgNVHQ4EFgQUp6KDuzRFQ" +
"D381TBPErk+oQGf9tswHwYDVR0jBBgwFoAUe1tFz6/Oy3r9MZIaarbzRutXSFAwDQYJKoZIhvcNAQEFBQADggEBAIAigOBsyJUW11cmh/NyNNvGclYnPtOW9i4lkaU+M5enS+Uv+yV9Lwdh+m+DdExMU3Ig" +
"pHrPUVFWgYiwbR82LMgrsYiZwf5Eq0hRfNjyRGQq2HGn+xov+RmNNLIjv8RMVR2OROiqXZrdn/0Dx7okQ40tR0Tb9tiYyLL52u/tKVxpEvrRI5YPv5wN8nlFUzeaVi/oVxBw9u6JDEmJmsEj9cIqzEHPIqt" +
"lbreUgm0vQF9Y3uuVK6ZyaFIZkSqudZ1OkubK3lTqGKslPOZkpnkfJn1h7X3S5XFV2JMXfBQ4MDzfhuNMrUnjl1nOG5srztxl1Asoa06ERlFE9zMILViXIa4="));
I am confident that this should be somehow possible, as the Internet Explorer is doing the same. 我相信这应该是某种可能的,因为Internet Explorer也在做同样的事情。 You can try it with https://httpd.apache.org/ (The certificates above are from there) 您可以使用https://httpd.apache.org/进行尝试(以上证书来自那里)
In case anybody needs the code: 如果有人需要代码:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CRYPTUI_VIEWCERTIFICATE_STRUCT
{
public int dwSize;
public IntPtr hwndParent;
public int dwFlags;
[MarshalAs(UnmanagedType.LPWStr)]
public String szTitle;
public IntPtr pCertContext;
public IntPtr rgszPurposes;
public int cPurposes;
public IntPtr pCryptProviderData;
public Boolean fpCryptProviderDataTrustedUsage;
public int idxSigner;
public int idxCert;
public Boolean fCounterSigner;
public int idxCounterSigner;
public int cStores;
public IntPtr rghStores;
public int cPropSheetPages;
public IntPtr rgPropSheetPages;
public int nStartPage;
}
public static class CryptAPI
{
public static void ShowCertificateDialog(X509Chain chain, string title, IntPtr parent)
{
const int certStoreProvMemory = 2; // CERT_STORE_PROV_MEMORY
const int certCloseStoreCheckFlag = 2; // CERT_CLOSE_STORE_CHECK_FLAG
const uint certStoreAddAlways = 4; // CERT_STORE_ADD_ALWAYS
const uint x509AsnEncoding = 1; // X509_ASN_ENCODING
var storeHandle = CertOpenStore(certStoreProvMemory, 0, 0, 0, null);
if (storeHandle == IntPtr.Zero)
throw new Win32Exception();
try
{
foreach (var element in chain.ChainElements)
{
var certificate = element.Certificate;
var certificateBytes = certificate.Export(X509ContentType.Cert);
var certContextHandle = CertCreateCertificateContext(
x509AsnEncoding, certificateBytes, (uint)certificateBytes.Length);
if (certContextHandle == IntPtr.Zero)
throw new Win32Exception();
CertAddCertificateContextToStore(storeHandle, certContextHandle, certStoreAddAlways, IntPtr.Zero);
}
var extraStoreArray = new[] { storeHandle };
var extraStoreArrayHandle = GCHandle.Alloc(extraStoreArray, GCHandleType.Pinned);
try
{
var extraStorePointer = extraStoreArrayHandle.AddrOfPinnedObject();
var viewInfo = new CRYPTUI_VIEWCERTIFICATE_STRUCT();
viewInfo.hwndParent = parent;
viewInfo.dwSize = Marshal.SizeOf(viewInfo);
viewInfo.pCertContext = chain.ChainElements[0].Certificate.Handle;
viewInfo.szTitle = title;
viewInfo.nStartPage = 0;
viewInfo.cStores = 1;
viewInfo.rghStores = extraStorePointer;
var fPropertiesChanged = false;
CryptUIDlgViewCertificate(ref viewInfo, ref fPropertiesChanged);
}
finally
{
if (extraStoreArrayHandle.IsAllocated)
extraStoreArrayHandle.Free();
}
}
finally
{
CertCloseStore(storeHandle, certCloseStoreCheckFlag);
}
}
[DllImport("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertOpenStore(int storeProvider, int encodingType, int hcryptProv, int flags, string pvPara);
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CertCreateCertificateContext([In] uint dwCertEncodingType, [In] byte[] pbCertEncoded, [In] uint cbCertEncoded);
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CertAddCertificateContextToStore([In] IntPtr hCertStore, [In] IntPtr pCertContext, [In] uint dwAddDisposition, [In, Out] IntPtr ppStoreContext);
[DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CertFreeCertificateContext([In] IntPtr pCertContext);
[DllImport("CRYPT32", EntryPoint = "CertCloseStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CertCloseStore(IntPtr storeProvider, int flags);
[DllImport("CryptUI.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptUIDlgViewCertificate(ref CRYPTUI_VIEWCERTIFICATE_STRUCT pCertViewInfo, ref bool pfPropertiesChanged);
}
It takes a certificate chain, where each certificate may not be in user/machine store and shows a dialog with the first certificate in chain. 它需要一个证书链,其中每个证书可能不在用户/机器存储中,并显示一个带有链中第一个证书的对话框。 Other certificates are used to build a certificate path. 其他证书用于构建证书路径。
如果可能,您需要做的是使用CAPI 创建内存证书存储并将中间证书添加到该证书存储中,然后使用对CryptUIDlgViewCertificate的底层调用以使用临时存储的方式显示对话框。
Found in this MSDN forum thread , the example follows your original code: 在此MSDN论坛帖子中找到,该示例遵循您的原始代码:
X509Certificate2 endCert = new X509Certificate2(
Convert.FromBase64String(
"MIIE8zCCA9ugAwIBAgIQSBDq+mlsLsCZqWMIWj/YADANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRYwFAYDVQQDEw1UaGF3dGUgU1NMIENBMB4XDTExMTI" +
"yMDAwMDAwMFoXDTE0MDIxNzIzNTk1OVowgYsxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxQLRm9yZXN0IEhpbGwxIzAhBgNVBAoUGkFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW" +
"9uMRcwFQYDVQQLFA5JbmZyYXN0cnVjdHVyZTEVMBMGA1UEAxQMKi5hcGFjaGUub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApyhxElzdnWks7MMCEx24FMhHCbFcgKbO+fh/+JYrV91Cs" +
"xsdqsAsvAU37P/eLMQ3ZVm93c6uQbt6cq+0VXniviFjXS3qUUJVUC60Q/YDzaYrTFZdY8ccA5wWdFTiMlJgwIqdlvB7JLkOzotvawRfJxeH+aucY756TdYGapAyno+3pWNXnU5sr1oaJ4uGchaS7LUAqpfP" +
"fA3oTv63ZmIzHh2MTfDeUgdVSxeqEj3FCObLdps4Fs6c08Re2KAEZ+0UcMwNyJh0y6aP6PBgZAdt3qODONrI56TCDxjMC47lmIrm/U2Vy+v1LB90uU/1ESAiKvIKLjVZucO0U4Ol8VgiSDIH1FezXEhl+fP" +
"zY1N18u6kMx0AGDKDO0fBkUpkA6r6K4Kk/YvEJBLiIvLwLLnQhcwJjhRZItA52dNvKHMRYh5er1xVbLj7X+ujDfA6RpJYOmmPUxYzsZpZhTk0wybuGrkuvrm5t9ONP4p/2lan1G9aXqK6OLNh4W9IVUs1o1" +
"KvMP86ToBOsZY/g50cld0kh7AMR+W/Lg9WtPxs1nq98k2J7HZBmMnYTEqwzSFtsMzGlqcFXO170JnfgklUjzi12vwQYO0bf/q+3e7QQsYRXzSGUEdKJZvzs0P09jJ6W/mDdnMdaoh7eYP5eynleZtElUgcd" +
"NNgVAHn8NEUnJpwbGUCAwEAAaOBoDCBnTAMBgNVHRMBAf8EAjAAMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9zdnItb3YtY3JsLnRoYXd0ZS5jb20vVGhhd3RlT1YuY3JsMB0GA1UdJQQWMBQGCCsGAQUF" +
"BwMBBggrBgEFBQcDAjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAA6BnlWlsAXvTmDpqijPpBUkD9Xkbys7UC/FOuUVr3P" +
"K3d3GCQynwhooBe2CAshtxjb3Cc8zJfeqb5IQfjTcuEznIpONvqFvSmU4/INS+3/TPLoyQ81wpsIUbJzhhJY78CH8TZ5cn2BtWkI9fEydAXYe9a64GVdjPBJhneBon3J63s895GSSucQAIQZEiXBAqoklS5" +
"n0Ud2aSYrNZJUVN3o8Rh0tvd0W2l6KjBaIZLUTieDZb3eRrValvjYDcCp9uI3aTdhht6zxUuE+OZ7DPWIWz3EYTMVTTtQdojJK9mM++JC74Y4s+JSCgRzTn3CxDMWPG5FWxavENub0FfsXfnY="));
X509Certificate2 intermediateCA = new X509Certificate2(
Convert.FromBase64String(
"MIIEbDCCA1SgAwIBAgIQTV8sNAiyTCDNbVB+JE3J7DANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWN" +
"lcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjA4MDAwMD" +
"AwWhcNMjAwMjA3MjM1OTU5WjA8MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRYwFAYDVQQDEw1UaGF3dGUgU1NMIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmeSFW" +
"3ZJfS8F2MWsyMip09yY5tc0pi8M8iIm2KPJFEyPBaRF6BQMWJAFGrfFwQalgK+7HUlrUjSIw1nn72vEJ0GMK2Yd0OCjl5gZNEtB1ZjVxwWtouTX7QytT8G1sCH9PlBTssSQ0NQwZ2ya8Q50xMLciuiX/8mS" +
"rgGKVgqYMrAAI+yQGmDD7bs6yw9jnw1EyVLhJZa/7VCViX9WFLG3YR0cB4w6LPf/gN45RdWvGtF42MdxaqMZpzJQIenyDqHGEwNESNFmqFJX1xG0k4vlmZ9d53hR5U32t1m0drUJN00GOBN6HAiYXMRISst" +
"SoKn4sZ2Oe3mwIC88lqgRYke7EQIDAQABo4H7MIH4MDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29jc3AudGhhd3RlLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEAMDQGA1UdHwQtMCswKa" +
"AnoCWGI2h0dHA6Ly9jcmwudGhhd3RlLmNvbS9UaGF3dGVQQ0EuY3JsMA4GA1UdDwEB/wQEAwIBBjAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVmVyaVNpZ25NUEtJLTItOTAdBgNVHQ4EFgQUp6KDuzRFQ" +
"D381TBPErk+oQGf9tswHwYDVR0jBBgwFoAUe1tFz6/Oy3r9MZIaarbzRutXSFAwDQYJKoZIhvcNAQEFBQADggEBAIAigOBsyJUW11cmh/NyNNvGclYnPtOW9i4lkaU+M5enS+Uv+yV9Lwdh+m+DdExMU3Ig" +
"pHrPUVFWgYiwbR82LMgrsYiZwf5Eq0hRfNjyRGQq2HGn+xov+RmNNLIjv8RMVR2OROiqXZrdn/0Dx7okQ40tR0Tb9tiYyLL52u/tKVxpEvrRI5YPv5wN8nlFUzeaVi/oVxBw9u6JDEmJmsEj9cIqzEHPIqt" +
"lbreUgm0vQF9Y3uuVK6ZyaFIZkSqudZ1OkubK3lTqGKslPOZkpnkfJn1h7X3S5XFV2JMXfBQ4MDzfhuNMrUnjl1nOG5srztxl1Asoa06ERlFE9zMILViXIa4="));
X509Chain chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(intermediateCA);
chain.Build(endCert); // Whole chain!
X509Certificate2 fMainCertificate = null;
X509Certificate2Collection fExtraCertificates = new X509Certificate2Collection();
fMainCertificate = endCert;
fExtraCertificates.Add(intermediateCA);
X509Store lStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
// No need to write to the user's store, just read.
lStore.Open(OpenFlags.ReadOnly);
try
{
List<X509Certificate2> lAddedCertificates = new List<X509Certificate2>();
try
{
foreach (X509Certificate2 lCertificate in fExtraCertificates)
if (!lStore.Certificates.Contains(lCertificate))
{
lStore.Add(lCertificate);
lAddedCertificates.Add(lCertificate);
}
X509Certificate2UI.DisplayCertificate(fMainCertificate);
}
finally
{
foreach (X509Certificate2 lCertificate in lAddedCertificates)
lStore.Remove(lCertificate);
}
}
finally { lStore.Close(); }
When run, I get the default Windows dialog and the full chain appears to be present: 运行时,我得到默认的Windows对话框,似乎存在完整的链:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.