Retired Microsoft Blog disclaimer

This directory is a mirror of retired "Decrypt My World" MSDN blog and is provided as is. All posting authorship and copyrights belong to respective authors.

Posts on this page:

Original URL: https://blogs.msdn.microsoft.com/alejacma/2012/01/18/how-to-use-internet_option_server_cert_chain_context-with-internetqueryoption-in-c/
Post name: How to use INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT with InternetQueryOption in C#
Original author: Alejandro Campos Magencio
Posting date: 2012-01-18T01:50:00+00:00


Hi all,

The other day I needed to use the certificate chain context (CERT_CHAIN_CONTEXT structure) returned by a call to InternetQueryOption, in C#.

The call in C++looks like this:

PCCERT_CHAIN_CONTEXT CertCtx=NULL; 

if (InternetQueryOption(hReq, INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT, (LPVOID)&CertCtx, &cbCertSize))
{
PCCERT_CHAIN_CONTEXT pChainContext=CertCtx;

}

Some additional info about this call can be found here:

Option Flags

"INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT
105
Retrieves the server’s certificate-chain context as a duplicated PCCERT_CHAIN_CONTEXT. You may pass this duplicated context to any Crypto API function
which takes a PCCERT_CHAIN_CONTEXT. You must call CertFreeCertificateChain on the returned PCCERT_CHAIN_CONTEXT when you are done with the certificate-chain context.
Version: Requires Internet Explorer 8.0."

I got a good C++ sample from here: “Understanding the new WinInet option INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT”. So I took that sample as a base. I also used some of the p/invoke code I had already created for this C# sample: How to call InternetErrorDlg to deal with certificate issues on SSL connections (C#).

This is theC# sample I created:

<SAMPLE file="form1.cs">

using System; 
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
// Consts
const int SIZE = 255;

// Variables
Win32.CERT_CHAIN_CONTEXT certChainContext = new Win32.CERT_CHAIN_CONTEXT();
Win32.CERT_SIMPLE_CHAIN certSimpleChain = new Win32.CERT_SIMPLE_CHAIN();
//Win32.CERT_CONTEXT certContext = new Win32.CERT_CONTEXT();
Win32.CERT_CHAIN_ELEMENT certChainElement = new Win32.CERT_CHAIN_ELEMENT();
StringBuilder pszSubject = new StringBuilder(SIZE);
StringBuilder pszIssuer = new StringBuilder(SIZE);
IntPtr hInternet = IntPtr.Zero;
IntPtr hConnect = IntPtr.Zero;
IntPtr hRequest = IntPtr.Zero;
IntPtr lpBufferData = IntPtr.Zero;
IntPtr lpBufferChain = IntPtr.Zero;
ulong ulOptionMask = 0;
int dwContext = 0;
int lpdwBufferLength = 0;
int dwError = 0;
int iResult = 0;
int dwNumberOfBytesAvailable = 0;
int dwNumberOfBytesRead = 0;
int cchNameString = 0;
bool bResult = false;

try
{
// Initialize WinINet
hInternet = Win32.InternetOpen("alejacma", Win32.INTERNET_OPEN_TYPE_PRECONFIG, null, null, 0);
if (hInternet == IntPtr.Zero)
{
throw new Exception("InternetOpen error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Open HTTP session
hConnect = Win32.InternetConnect(hInternet, textBox1.Text, Win32.INTERNET_DEFAULT_HTTPS_PORT, null, null, Win32.INTERNET_SERVICE_HTTP, 0, ref dwContext);
if (hConnect == IntPtr.Zero)
{
throw new Exception("InternetConnect error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Create HTTP request
hRequest = Win32.HttpOpenRequest(hConnect, "GET", textBox2.Text, null, null, IntPtr.Zero, Win32.INTERNET_FLAG_SECURE, ref dwContext);
if (hRequest == IntPtr.Zero)
{
throw new Exception("HttpOpenRequest error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Configure request to get combined cert errors
ulOptionMask = Win32.INTERNET_ERROR_MASK_COMBINED_SEC_CERT;
bResult = Win32.InternetSetOption(hRequest, Win32.INTERNET_OPTION_ERROR_MASK, ref ulOptionMask, Marshal.SizeOf(typeof(IntPtr)));
if (!bResult)
{
throw new Exception("InternetSetOption Error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

do
{
// Send request to the server
bResult = Win32.HttpSendRequest(hRequest, null, 0, IntPtr.Zero, 0);
if (bResult == false)
{
// Deal with possible errors
dwError = Marshal.GetLastWin32Error();
switch (dwError)
{
case Win32.ERROR_INTERNET_SEC_CERT_ERRORS:
case Win32.ERROR_INTERNET_INVALID_CA:
case Win32.ERROR_INTERNET_SEC_CERT_CN_INVALID:
case Win32.ERROR_INTERNET_SEC_CERT_DATE_INVALID:
case Win32.ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED:
break;
default:
// Unknown error
throw new Exception("HttpSendRequest error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Display cert error dialog box
iResult = Win32.InternetErrorDlg(this.Handle, hRequest, dwError, Win32.FLAGS_ERROR_UI_FLAGS_GENERATE_DATA + Win32.FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS, IntPtr.Zero);
switch (iResult)
{
case Win32.ERROR_SUCCESS:
break;
case Win32.ERROR_CANCELLED:
throw new Exception("InternetErrorDlg error\nThe function was canceled by the user");
case Win32.ERROR_INTERNET_FORCE_RETRY:
throw new Exception("InternetErrorDlg error\nFunction needs to redo its request. In the case of authentication this indicates that the user clicked the OK button.");
case Win32.ERROR_INVALID_HANDLE:
throw new Exception("InternetErrorDlg error\nThe handle to the parent window is invalid");
default:
throw new Exception("InternetErrorDlg error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}
}

} while (!bResult);

// Retrieve the server’s certificate-chain context
lpdwBufferLength = Marshal.SizeOf(typeof(IntPtr));
bResult = Win32.InternetQueryOption(hRequest, Win32.INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT, ref lpBufferChain, ref lpdwBufferLength);
if (bResult == false)
{
throw new Exception("InternetQueryOption error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Display some of the contents of the chain
certChainContext = (Win32.CERT_CHAIN_CONTEXT)Marshal.PtrToStructure(lpBufferChain, typeof(Win32.CERT_CHAIN_CONTEXT));
switch(certChainContext.TrustStatus.dwErrorStatus)
{
case Win32.CERT_TRUST_NO_ERROR:
MessageBox.Show("No error found for this certificate or chain");
break;
case Win32.CERT_TRUST_IS_NOT_TIME_VALID:
MessageBox.Show("This certificate or one of the certificates in the certificate chain is not time-valid");
break;
case Win32.CERT_TRUST_IS_REVOKED:
MessageBox.Show("Trust for this certificate or one of the certificates in the certificate chain has been revoked");
break;
case Win32.CERT_TRUST_IS_NOT_SIGNATURE_VALID:
MessageBox.Show("The certificate or one of the certificates in the certificate chain does not have a valid signature");
break;
case Win32.CERT_TRUST_IS_NOT_VALID_FOR_USAGE:
MessageBox.Show("The certificate or certificate chain is not valid in its proposed usage");
break;
case Win32.CERT_TRUST_IS_UNTRUSTED_ROOT:
MessageBox.Show("The certificate or certificate chain is based on an untrusted root");
break;
case Win32.CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
MessageBox.Show("The revocation status of the certificate or one of the certificates in the certificate chain is unknown");
break;
case Win32.CERT_TRUST_IS_CYCLIC:
MessageBox.Show("One of the certificates in the chain was issued by a certification authority that the original certificate had certified");
break;
case Win32.CERT_TRUST_IS_PARTIAL_CHAIN:
MessageBox.Show("The certificate chain is not complete");
break;
case Win32.CERT_TRUST_CTL_IS_NOT_TIME_VALID:
MessageBox.Show("A CTL used to create this chain was not time-valid");
break;
case Win32.CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID:
MessageBox.Show("A CTL used to create this chain did not have a valid signature");
break;
case Win32.CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE:
MessageBox.Show("A CTL used to create this chain is not valid for this usage");
break;
default:
MessageBox.Show("TrustStatus.dwErrorStatus = " + certChainContext.TrustStatus.dwErrorStatus.ToString());
break;
}

switch (certChainContext.TrustStatus.dwInfoStatus)
{
case 0:
MessageBox.Show("No information status reported");
break;
case Win32.CERT_TRUST_HAS_EXACT_MATCH_ISSUER:
MessageBox.Show("An exact match issuer certificate has been found for this certificate");
break;
case Win32.CERT_TRUST_HAS_KEY_MATCH_ISSUER:
MessageBox.Show("A key match issuer certificate has been found for this certificate");
break;
case Win32.CERT_TRUST_HAS_NAME_MATCH_ISSUER:
MessageBox.Show("A name match issuer certificate has been found for this certificate");
break;
case Win32.CERT_TRUST_IS_SELF_SIGNED:
MessageBox.Show("This certificate is self-signed");
break;
case Win32.CERT_TRUST_IS_COMPLEX_CHAIN:
MessageBox.Show("The certificate chain created is a complex chain");
break;
case Win32.CERT_TRUST_HAS_PREFERRED_ISSUER:
MessageBox.Show("The certificate chain has a preferred issuer");
break;
case Win32.CERT_TRUST_HAS_ISSUANCE_CHAIN_POLICY:
MessageBox.Show("The certificate chain has issuance chain policy");
break;
case Win32.CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS:
MessageBox.Show("The certificate chain valid name contraints");
break;
case Win32.CERT_TRUST_IS_PEER_TRUSTED:
MessageBox.Show("The certificate chain is peer trusted");
break;
case Win32.CERT_TRUST_HAS_CRL_VALIDITY_EXTENDED:
MessageBox.Show("The certificate chain has CRL validity extended");
break;
case Win32.CERT_TRUST_IS_FROM_EXCLUSIVE_TRUST_STORE:
MessageBox.Show("The certificate chain was found in a store specified by hExclusiveRoot or hExclusiveTrustedPeople");
break;
}

for (int i = 0; i < certChainContext.cChain; i++)
{

certSimpleChain = (Win32.CERT_SIMPLE_CHAIN)Marshal.PtrToStructure(Marshal.ReadIntPtr(certChainContext.rgpChain + i * Marshal.SizeOf(typeof(IntPtr))), typeof(Win32.CERT_SIMPLE_CHAIN));

// For each certificate chain in this context...
for (int simpleCertChainIndex = 0; simpleCertChainIndex < certSimpleChain.cElement; simpleCertChainIndex++)
{
// get the certificates in it
certChainElement = (Win32.CERT_CHAIN_ELEMENT)Marshal.PtrToStructure(Marshal.ReadIntPtr(certSimpleChain.rgpElement + simpleCertChainIndex * Marshal.SizeOf(typeof(IntPtr))), typeof(Win32.CERT_CHAIN_ELEMENT));
//certContext = (Win32.CERT_CONTEXT)Marshal.PtrToStructure(certChainElement.pCertContext, typeof(Win32.CERT_CONTEXT));

// Find and print the name of the subject of the certificate
cchNameString = Win32.CertGetNameString(certChainElement.pCertContext, Win32.CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, IntPtr.Zero, pszSubject, SIZE);
if (cchNameString == 0)
{
throw new Exception("CertGetNameString error");
}

// Get the issuer
cchNameString = Win32.CertGetNameString(certChainElement.pCertContext, Win32.CERT_NAME_SIMPLE_DISPLAY_TYPE, Win32.CERT_NAME_ISSUER_FLAG, IntPtr.Zero, pszIssuer, SIZE);
if (cchNameString == 0)
{
throw new Exception("CertGetNameString error");
}

MessageBox.Show("Chain " + i.ToString() + "\nCert " + simpleCertChainIndex.ToString() + "\nSubject: " + pszSubject.ToString() + "\nIssuer: " + pszIssuer.ToString());
}
}

// Determine the amount of data available
dwNumberOfBytesAvailable = 0;
bResult = Win32.InternetQueryDataAvailable(hRequest, ref dwNumberOfBytesAvailable, 0, IntPtr.Zero);
if (!bResult)
{
throw new Exception("InternetQueryDataAvailable error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Read data
lpBufferData = Marshal.AllocHGlobal(dwNumberOfBytesAvailable);
dwNumberOfBytesRead = 0;
bResult = Win32.InternetReadFile(hRequest, lpBufferData, dwNumberOfBytesAvailable, ref dwNumberOfBytesRead);
if (bResult == false)
{
throw new Exception("InternetReadFile error #" + Marshal.GetLastWin32Error().ToString(), new Win32Exception(Marshal.GetLastWin32Error()));
}

// Everything went well. Show data
MessageBox.Show(Marshal.PtrToStringAnsi(lpBufferData, dwNumberOfBytesRead));
}
catch (Exception ex)
{
// Show exception
if (ex.InnerException != null)
{
MessageBox.Show(ex.Message + "\n" + ex.InnerException.Message);
}
else
{
MessageBox.Show(ex.Message);
}
}
finally
{
// Clean up stuff
if (lpBufferData != IntPtr.Zero)
{
Marshal.FreeHGlobal(lpBufferData);
}
if (lpBufferChain != IntPtr.Zero)
{
Win32.CertFreeCertificateChain(lpBufferChain);
}
if (hRequest != IntPtr.Zero)
{
Win32.InternetCloseHandle(hRequest);
}
if (hConnect != IntPtr.Zero)
{
Win32.InternetCloseHandle(hConnect);
}
if (hInternet != IntPtr.Zero)
{
Win32.InternetCloseHandle(hInternet);
}
}
}
}
}

</SAMPLE>

<SAMPLE file="win32.cs">

using System; 
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
class Win32
{
#region "CONSTS"

public const int INTERNET_OPEN_TYPE_PRECONFIG = 0;

public const int INTERNET_DEFAULT_HTTP_PORT = 80;
public const int INTERNET_DEFAULT_HTTPS_PORT = 443;

public const int INTERNET_SERVICE_HTTP = 3;

public const int INTERNET_FLAG_SECURE = 0x00800000;

public const int INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT = 105;

public const int INTERNET_OPTION_ERROR_MASK = 62;

public const ulong INTERNET_ERROR_MASK_COMBINED_SEC_CERT = 0x2;

public const int INTERNET_ERROR_BASE = 12000;

public const int ERROR_INTERNET_FORCE_RETRY = INTERNET_ERROR_BASE + 32;

public const int ERROR_INTERNET_SEC_CERT_DATE_INVALID = INTERNET_ERROR_BASE + 37;

public const int ERROR_INTERNET_SEC_CERT_CN_INVALID = INTERNET_ERROR_BASE + 38;

public const int ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED = INTERNET_ERROR_BASE + 44;

public const int ERROR_INTERNET_SEC_CERT_ERRORS = INTERNET_ERROR_BASE + 55;

public const int ERROR_INTERNET_INVALID_CA = INTERNET_ERROR_BASE + 45;

public const int FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS = 0x02;

public const int FLAGS_ERROR_UI_FLAGS_GENERATE_DATA = 0x04;

public const int ERROR_SUCCESS = 0;

public const int ERROR_CANCELLED = 1223;

public const int ERROR_INVALID_HANDLE = 6;

// http://msdn.microsoft.com/en-us/library/windows/desktop/aa377590(v=vs.85).aspx
public const int CERT_TRUST_NO_ERROR = 0x00000000;
public const int CERT_TRUST_IS_NOT_TIME_VALID = 0x00000001;
public const int CERT_TRUST_IS_REVOKED = 0x00000004;
public const int CERT_TRUST_IS_NOT_SIGNATURE_VALID = 0x00000008;
public const int CERT_TRUST_IS_NOT_VALID_FOR_USAGE = 0x00000010;
public const int CERT_TRUST_IS_UNTRUSTED_ROOT = 0x00000020;
public const int CERT_TRUST_REVOCATION_STATUS_UNKNOWN = 0x00000040;
public const int CERT_TRUST_IS_CYCLIC = 0x00000080;
public const int CERT_TRUST_INVALID_EXTENSION = 0x00000100;
public const int CERT_TRUST_INVALID_POLICY_CONSTRAINTS = 0x00000200;
public const int CERT_TRUST_INVALID_BASIC_CONSTRAINTS = 0x00000400;
public const int CERT_TRUST_INVALID_NAME_CONSTRAINTS = 0x00000800;
public const int CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT = 0x00001000;
public const int CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT = 0x00002000;
public const int CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT = 0x00004000;
public const int CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT = 0x00008000;
public const int CERT_TRUST_IS_OFFLINE_REVOCATION = 0x01000000;
public const int CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY = 0x02000000;
public const int CERT_TRUST_IS_EXPLICIT_DISTRUST = 0x04000000;
public const int CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT = 0x08000000;
public const int CERT_TRUST_IS_PARTIAL_CHAIN = 0x00010000;
public const int CERT_TRUST_CTL_IS_NOT_TIME_VALID = 0x00020000;
public const int CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID = 0x00040000;
public const int CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE = 0x00080000;

public const int CERT_TRUST_HAS_EXACT_MATCH_ISSUER = 0x00000001;
public const int CERT_TRUST_HAS_KEY_MATCH_ISSUER = 0x00000002;
public const int CERT_TRUST_HAS_NAME_MATCH_ISSUER = 0x00000004;
public const int CERT_TRUST_IS_SELF_SIGNED = 0x00000008;
public const int CERT_TRUST_HAS_PREFERRED_ISSUER = 0x00000100;
public const int CERT_TRUST_HAS_ISSUANCE_CHAIN_POLICY = 0x00000200;
public const int CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS = 0x00000400;
public const int CERT_TRUST_IS_PEER_TRUSTED = 0x00000800;
public const int CERT_TRUST_HAS_CRL_VALIDITY_EXTENDED = 0x00001000;
public const int CERT_TRUST_IS_FROM_EXCLUSIVE_TRUST_STORE = 0x00002000;
public const int CERT_TRUST_IS_COMPLEX_CHAIN = 0x00010000;

public const int CERT_NAME_SIMPLE_DISPLAY_TYPE = 4;

public const int CERT_NAME_ISSUER_FLAG = 0x1;

#endregion

#region "STRUCTS"

[StructLayout(LayoutKind.Sequential)]
public struct CERT_TRUST_STATUS
{
public int dwErrorStatus;
public int dwInfoStatus;
}

[StructLayout(LayoutKind.Sequential)]
public struct BLOB
{
public int cbData;
public IntPtr pbData;
}

[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_ALGORITHM_IDENTIFIER
{
public string pszObjId;
public BLOB Parameters;
}

[StructLayout(LayoutKind.Sequential)]
public struct FILETIME
{
public int dwLowDateTime;
public int dwHighDateTime;
}

[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_BIT_BLOB
{
public int cbData;
public IntPtr pbData;
public int cUnusedBits;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_PUBLIC_KEY_INFO
{
public CRYPT_ALGORITHM_IDENTIFIER Algorithm;
public CRYPT_BIT_BLOB PublicKey;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_EXTENSION
{
public string pszObjId;
public bool fCritical;
public BLOB Value;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_INFO
{
public int dwVersion;
public BLOB SerialNumber;
public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
public BLOB Issuer;
public FILETIME NotBefore;
public FILETIME NotAfter;
public BLOB Subject;
public CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
public CRYPT_BIT_BLOB IssuerUniqueId;
public CRYPT_BIT_BLOB SubjectUniqueId;
public int cExtension;
public IntPtr rgExtension;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_CONTEXT
{
public int dwCertEncodingType;
public IntPtr pbCertEncoded;
public int cbCertEncoded;
public IntPtr pCertInfo;
public IntPtr hCertStore;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_REVOCATION_INFO
{
public int cbSize;
public int dwRevocationResult;
public string pszRevocationOid;
public IntPtr pvOidSpecificInfo;
public bool fHasFreshnessTime;
public int dwFreshnessTime;
}

[StructLayout(LayoutKind.Sequential)]
public struct CTL_USAGE
{
public int cUsageIdentifier;
public IntPtr rgpszUsageIdentifier;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_CHAIN_ELEMENT
{
public int cbSize;
public IntPtr pCertContext;
public CERT_TRUST_STATUS TrustStatus;
public IntPtr pRevocationInfo;
public IntPtr pIssuanceUsage;
public IntPtr pApplicationUsage;
public string pwszExtendedErrorInfo;
}

[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_ATTRIBUTE
{
public string pszObjId;
public int cValue;
public IntPtr rgValue;
}

[StructLayout(LayoutKind.Sequential)]
public struct CTL_ENTRY
{
public BLOB SubjectIdentifier;
public int cAttribute;
public IntPtr rgAttribute;
}

[StructLayout(LayoutKind.Sequential)]
public struct CTL_INFO
{
public int dwVersion;
public CTL_USAGE SubjectUsage;
public BLOB ListIdentifier;
public BLOB SequenceNumber;
public FILETIME ThisUpdate;
public FILETIME NextUpdate;
public CRYPT_ALGORITHM_IDENTIFIER SubjectAlgorithm;
public int cCTLEntry;
public IntPtr rgCTLEntry;
public int cExtension;
public IntPtr rgExtension;
}

[StructLayout(LayoutKind.Sequential)]
public struct CTL_CONTEXT
{
public int dwMsgAndCertEncodingType;
public IntPtr pbCtlEncoded;
public int cbCtlEncoded;
public IntPtr pCtlInfo;
public IntPtr hCertStore;
public IntPtr hCryptMsg;
public IntPtr pbCtlContent;
public int cbCtlContent;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_TRUST_LIST_INFO
{
public int cbSize;
public IntPtr pCtlEntry;
public IntPtr pCtlContext;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_SIMPLE_CHAIN
{
public int cbSize;
public CERT_TRUST_STATUS TrustStatus;
public int cElement;
public IntPtr rgpElement;
public IntPtr pTrustListInfo;
public bool fHasRevocationFreshnessTime;
public int dwRevocationFreshnessTime;
}

[StructLayout(LayoutKind.Sequential)]
public struct CERT_CHAIN_CONTEXT
{
public int cbSize;
public CERT_TRUST_STATUS TrustStatus;
public int cChain;
public IntPtr rgpChain;
public int cLowerQualityChainContext;
public IntPtr rgpLowerQualityChainContext;
public bool fHasRevocationFreshnessTime;
public int dwRevocationFreshnessTime;
}

#endregion

#region "FUNCTIONS"

[DllImport("Wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr InternetOpen(
string lpszAgent,
int dwAccessType,
string lpszProxyName,
string lpszProxyBypass,
int dwFlags
);

[DllImport("Wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr InternetCloseHandle(
IntPtr hInternet
);

[DllImport("Wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr InternetConnect(
IntPtr hInternet,
string lpszServerName,
short nServerPort,
string lpszUsername,
string lpszPassword,
int dwService,
int dwFlags,
ref int dwContext
);

[DllImport("Wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr HttpOpenRequest(
IntPtr hConnect,
string lpszVerb,
string lpszObjectName,
string lpszVersion,
string lpszReferer,
IntPtr lplpszAcceptTypes,
int dwFlags,
ref int dwContext
);

[DllImport("Wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool HttpSendRequest(
IntPtr hRequest,
string lpszHeaders,
int dwHeadersLength,
IntPtr lpOptional,
int dwOptionalLength
);

[DllImport("wininet.dll", SetLastError = true)]
public static extern bool InternetSetOption
(
IntPtr hInternet,
int dwOption,
ref ulong lpBuffer,
int dwBufferLength
);

[DllImport("Wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool InternetQueryOption(
IntPtr hInternet,
int dwOption,
ref IntPtr lpBuffer,
ref int lpdwBufferLength
);

[DllImport("wininet.dll", SetLastError = true)]
public static extern int InternetErrorDlg
(
IntPtr hWnd,
IntPtr hRequest,
int dwError,
int dwFlags,
IntPtr lppvData
);

[DllImport("wininet.dll", SetLastError = true)]
public static extern bool InternetQueryDataAvailable
(
IntPtr hFile,
ref int dwNumberOfBytesAvailable,
int dwFlags,
IntPtr dwContext
);

[DllImport("wininet.dll", SetLastError = true)]
public static extern bool InternetReadFile
(
IntPtr hFile,
IntPtr lpBuffer,
int dwNumberOfBytesToRead,
ref int dwNumberOfBytesRead
);

[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void CertFreeCertificateChain(
IntPtr pChainContext
);

[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int CertGetNameString(
IntPtr pCertContext,
int dwType,
int dwFlags,
IntPtr pvTypePara,
StringBuilder pszNameString,
int cchNameString
);

#endregion

}
}

</SAMPLE>

I hope this helps.

Regards,

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/11/09/we-cant-manage-printers-with-wmi-in-a-clustered-environment/
Post name: We can’t manage printers with WMI in a clustered environment
Original author: Alejandro Campos Magencio
Posting date: 2011-11-09T06:20:00+00:00


Hi all,

 

Some time ago a customer of mine wanted to develop PowerShell scripts to manage printers in their clustered environment. They wanted to use WMI for that. The problem they found is that while they could list the printers on a stand-alone server by executing "get-wmi-object -class Win32_printer", they didn't get any result on a clustered print server.

After some research I found out that what they wanted to do on the cluster cannot be done with WMI. WMI is not cluster aware, so WMI classes like Win32_Printer won’t return anything specific for clusters.

Note there is nothing to fix on WMI. The cluster needs to expose itself via management layer which is missing at this point of time on Windows 7 / Server 2008 R2 and older OS.

 

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/11/09/unexpected-black-screen-when-using-a-custom-credential-provider/
Post name: Unexpected black screen when using a custom Credential Provider
Original author: Alejandro Campos Magencio
Posting date: 2011-11-09T03:55:08+00:00


Hi all,

Some time ago I had a customer how had developed a custom credential provider to use with a third-party smartcard provider. This credential provider was a wrapper of MS Smartcard Credential Provider. They also put a credential provider filter in place to hide MS Smartcard Credential Provider.

Now, from time to time, user tried to do logon, selected her smartcard, and suddently the screen asking for credentials disappeared and they could only see a black screen. User had to press Ctrl+Alt+Sup to recover and get back to the initial logon screen, and try to enter credentials once more. It was very easy to reproduce the issue for her if she removed the smartcard and re-inserted it when she was being asked for her PIN, for example.

First of all, the black screen has an explanation. During logon, logonui.exe shows a maximized window to ask the user for credentials. If logonui.exe crashes for some reason, you will see a black screen which is actually the background of the winlogon desktop where logonui shows that maximized window.

So why did logonui.exe crashed in this case?

Logonui loads all credential providers at the beginning of its execution, and it doesn't unload them until it ends. When we filter the providers we are not unloading them. We are just removing them from a list of references to all those loaded providers,leaving the list with just the providers we want to use. But I repeat, the providers won't get unloaded.

When MS Smartcard Credential Provider gets loaded, it creates a thread to monitor card readers. This monitor thread will be running until we unload the provider. So taking into account how filters work, in customer's scenario we end up with 2 identical threads monitoring card readers at the same time, one created when logonui loads the MS Smartcard Credential Provider by itself, and one created when customer's provider loads its own instance of the MS Smartcard Credential Provider.

In this particular scenario, when the issue happened, logonui was crashing with an Access Violation exception caused by a memory corruption on the third-party smartcard provider, which was not thread-safe and was being used in both card reader monitor threads at the same time. The action of removing and inserting the card back made both threads to resume their work and try to get the credentials from the card, thus using the third-party smartcard provider at the same time.

So there could be two ways to solve this:

1) Use a thread-safe smartcard provider.

2) Get rid of one of those card reader monitor threads. The only way to achieve this would be to tell logonui not to load the MS Smartcard Credential Provider at all, as we don't need it and our custom provider is already loading it when needed.

Because a credential provider filter won't help here, we have to disable the MS Smartcard Credential Provider by adding REG_DWORD "Disabled"=1 under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{8bf9a910-a8ff-457f-999f-a5ca10b4a885}.

Note: Customer also realized that user got a blank screen when she entered Ctrl+Alt+Sup, saw the credential providers, waited for a while without inserting the smartcard until Ctrl+Alt+Sup appeared again (because of winlogon timer), then she entered Ctrl+Alt+Sup, saw the providers once more, selected theirs, entered the card and Bang! Black screen once more!

In this scenario where winlogon timer causes the Ctrl+Alt+Sup window to appear again, the existing instance of logonui gets reused when trying to enter credentials once more. Under these circumstances, every time the user entered Ctrl+Alt+Sup, customer's credential provider ended up loading a newinstance of MS Smartcard Credential Provider, thus creating a new reader monitoring thread every time. So when this issue happened, we had at least 2 reader monitor threads accessing their card at the same time. So same crash as before for the same reasons.

Customer had to modify their credential provider to avoid loading MS Smartcard Credential Provider more than once.

I hope this helps.

Regards,

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/11/07/problems-when-compiling-and-running-code-that-uses-certenroll-with-net-4-0-and-x64/
Post name: Problems when compiling and running code that uses CertEnroll with .NET 4.0 and x64
Original author: Alejandro Campos Magencio
Posting date: 2011-11-07T09:14:04+00:00


Hi all,

 

The other day a customer of mine was trying to compile and run this CertEnrollsample of mine: How to create a certificate request with CertEnroll and .NET (C#)

I had developed this sample against .NET 2.0 some time ago, and it compiled and run just fine.

But my customer created a .NET 4.0 project with Visual Studio 2010, he added the required references to CERTCLIENTLib (CertCli 1.0 Type Library in certcli.dll) and CERTENROLLLib (CertEnroll 1.0 Type Library in certenroll.dll), and when he tried to copile in Debug mode with Any CPU platform, he got many errors like this:

Error3The type 'CERTENROLLLib.CX509PrivateKeyClass' has no constructors definedC:\CATest\CATest\Form1.cs3745CATest
Error17The type 'CERTENROLLLib.CX509ExtensionKeyUsageClass' has no constructors definedC:\CATest\CATest\Form1.cs4459CATest
Error19The type 'CERTENROLLLib.CX509ExtensionEnhancedKeyUsageClass' has no constructors definedC:\CATest\CATest\Form1.cs4579CATest
Error11The type 'CERTENROLLLib.CX509EnrollmentClass' has no constructors definedC:\CATest\CATest\Form1.cs4141CATest
Error25The type 'CERTENROLLLib.CX509EnrollmentClass' has no constructors definedC:\CATest\CATest\Form1.cs18641CATest
Error1The type 'CERTENROLLLib.CX509CertificateRequestPkcs10Class' has no constructors definedC:\CATest\CATest\Form1.cs3655CATest
Error9The type 'CERTENROLLLib.CX500DistinguishedNameClass' has no constructors definedC:\CATest\CATest\Form1.cs4044CATest
Error13The type 'CERTENROLLLib.CObjectIdsClass' has no constructors definedC:\CATest\CATest\Form1.cs4239CATest
Error15The type 'CERTENROLLLib.CObjectIdClass' has no constructors definedC:\CATest\CATest\Form1.cs4337CATest
Error7The type 'CERTENROLLLib.CCspInformationsClass' has no constructors definedC:\CATest\CATest\Form1.cs3940CATest
Error5The type 'CERTENROLLLib.CCspInformationClass' has no constructors definedC:\CATest\CATest\Form1.cs3838CATest
Error23The type 'CERTCLIENTLib.CCertRequestClass' has no constructors definedC:\CATest\CATest\Form1.cs12843CATest
Error21The type 'CERTCLIENTLib.CCertConfigClass' has no constructors definedC:\CATest\CATest\Form1.cs12741CATest
Error4Interop type 'CERTENROLLLib.CX509PrivateKeyClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs3749CATest
Error18Interop type 'CERTENROLLLib.CX509ExtensionKeyUsageClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs4463CATest
Error20Interop type 'CERTENROLLLib.CX509ExtensionEnhancedKeyUsageClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs4583CATest
Error12Interop type 'CERTENROLLLib.CX509EnrollmentClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs4145CATest
Error26Interop type 'CERTENROLLLib.CX509EnrollmentClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs18645CATest
Error2Interop type 'CERTENROLLLib.CX509CertificateRequestPkcs10Class' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs3659CATest
Error10Interop type 'CERTENROLLLib.CX500DistinguishedNameClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs4048CATest
Error14Interop type 'CERTENROLLLib.CObjectIdsClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs4243CATest
Error16Interop type 'CERTENROLLLib.CObjectIdClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs4341CATest
Error8Interop type 'CERTENROLLLib.CCspInformationsClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs3944CATest
Error6Interop type 'CERTENROLLLib.CCspInformationClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs3842CATest
Error24Interop type 'CERTCLIENTLib.CCertRequestClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs12847CATest
Error22Interop type 'CERTCLIENTLib.CCertConfigClass' cannot be embedded. Use the applicable interface instead.C:\CATest\CATest\Form1.cs12745CATest

To get rid of these compilation errors we changed "Embed Interop Types" to "False" in the Properties of the CERTCLIENTLib and CERTENROLLLib references.

Still, when we tried to run the code, we got this exception:

System.BadImageFormatException was unhandled
Message=Could not load file or assembly 'Interop.CERTENROLLLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format.
Source=CATest
FileName=Interop.CERTENROLLLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
StackTrace:
at CATest.Form1.createRequestButton_Click(Object sender, EventArgs e)
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at CATest.Program.Main() in C:\_PUBLIC\CATest\CATest\Program.cs:line 17
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

I still didn't have time to figure out why this happens, but if you compile against x86 platform instead of Any CPU platform (which makes the code to run against x64 platform on x64 systems by default), it will work fine.

I hope this helps. Please, if someone knows why this happens, please share it with the community. And if someone needs to run this code in x64 with .NET 4.0 and we still don't know the answer, open a Technical Support case with us so we can spend the time required to investigate this. Thank you.

Regards,

 

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2011/11/07/how-to-add-issuer-alternative-name-to-your-certificate-request-c/
Post name: How to add Issuer Alternative Name to your certificate request (C#)
Original author: Alejandro Campos Magencio
Posting date: 2011-11-07T08:40:08+00:00


Hi all,

 

A customer of mine wanted to set Issuer Alternative Name (XCN_OID_ISSUER_ALT_NAME2 - "2.5.29.18") extension to his certificate requests in C# in the same way we did this, and he didn't know how:

How to add Subject Alternative Name to your certificate requests (C#)

How to add Alternative Directory Name to your certificate request (C#)

 

All I knew was that we should use this generic interface as we don't have a custom object in CertEnrolllike we do for Subject Alternative Name or Alternative Directory Name:

IX509Extension interface
"
To create the version 3 extensions for which Microsoft does not provide a custom object, you can use the IX509Extension interface.
...
you can use the IX509Extension interface to define private extensions that contain information that is unique to a specific community.

Extensions are added to the Attributes structure of a PKCS #10 request and to the TaggedAttributes structure of a CMC request. To add extensions to either request format, you must first add them to an IX509Extensions collection and use the collection to initialize an IX509AttributeExtensions object. For more information, see the PKCS #10 Extensions and the CMC Extensions topics.
"

This sounded a bit complicated, specially as I couldn't find any samples on how to use this the way my customer needed.

Fortunatelly I was ableto set Issuer Alternative Name in an easy way. Basically the idea is based on the fact that Issuer Alternative Name is encoded the same way than Subject Alternative Name, so we used the same classes and methods we used for the Subject Alternative Name (see the post above), but then changed the OID that identifies the extension to convert it to an Issuer Alternative Name extension. This is the code that worked for us:

 CObjectId objOID = new CObjectIdClass(); 
 CAlternativeName objAlternativeName = new CAlternativeName(); 
 CAlternativeNames objAlternativeNames = new CAlternativeNames(); 
 CX509ExtensionAlternativeNames objExtensionAlternativeNames = new CX509ExtensionAlternativeNames(); 
 CX509Extension objExtension = new CX509Extension(); 
 string rawData = null; 
 
 // Create the Issuer Alternative Name as if it were a Subject Alternative Name 
 objAlternativeName.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, "My DNS Name"); 
 objAlternativeNames.Add(objAlternativeName); 
 objExtensionAlternativeNames.InitializeEncode(objAlternativeNames); 
 
 // Change the OID of the Subject Alternative Name extension to convert it to an Issuer Alternative Name extension 
 rawData = objExtensionAlternativeNames.get_RawData(EncodingType.XCN_CRYPT_STRING_BINARY); 
 objOID.InitializeFromValue("2.5.29.18"); // XCN_OID_ISSUER_ALT_NAME2 
 objExtension.Initialize(objOID, EncodingType.XCN_CRYPT_STRING_BINARY, rawData); 
 
 // Add the extension to the request 
 objPkcs10.X509Extensions.Add(objExtension);

 

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)