How to get info from client certificates issued by a CA (C#)
Hi all,
The following C# sample shows how to use Certadm.dll and CryptoAPI to get the name of the template and the enhanced usages of client certificates in a CA:
<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.Collections;
using System.Runtime.InteropServices;
using System.DirectoryServices;
using CERTADMINLib;
namespace CertAdminTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// Variables
string strServerName = "MyServer";
DirectoryEntry rootEntry = null;
DirectoryEntry templatesEntry = null;
try
{
// Get AD entry that we will use to translate a certificate template OID to its correspondent name
rootEntry = new DirectoryEntry("LDAP://" + strServerName + "/rootDSE");
templatesEntry = new DirectoryEntry("LDAP://" + strServerName + "/cn=certificate templates,cn=public key services,cn=services,cn=configuration," + (string)rootEntry.Properties["defaultNamingContext"][0]);
// Get Certificate Services Database info
ViewCertificateServicesDatabase(strServerName, strServerName, templatesEntry);
}
catch (Exception ex)
{
// Errors?
MessageBox.Show(ex.Message);
}
finally
{
// Clean up
if (rootEntry != null)
{
rootEntry.Dispose();
}
if (templatesEntry != null)
{
templatesEntry.Dispose();
}
}
}
private void ViewCertificateServicesDatabase(string strServer, string strCAName, DirectoryEntry templatesEntry)
{
// Variables
CERTADMINLib.CCertView certView = null;
CERTADMINLib.IEnumCERTVIEWROW certViewRow = null;
CERTADMINLib.IEnumCERTVIEWCOLUMN certViewColumn = null;
CERTADMINLib.IEnumCERTVIEWEXTENSION certViewExt = null;
int iColumnCount = 0;
string strBase64Value = "";
string strValue = "";
string strOID = "";
int iStartIndex = 0;
string strDisplayName = "";
object objValue = null;
string strOutput = "";
// Connecting to the Certificate Authority
certView = new CERTADMINLib.CCertViewClass();
certView.OpenConnection(strServer + "\\" + strCAName);
// Get a column count and place columns into the view
iColumnCount = certView.GetColumnCount(0);
certView.SetResultColumnCount(iColumnCount);
// Place each column in the view.
for (int x = 0; x < iColumnCount; x++)
{
certView.SetResultColumn(x);
}
// Open the View and reset the row position
certViewRow = certView.OpenView();
certViewRow.Reset();
// Enumerate Row and Column Information
// Rows
for (int x = 0; certViewRow.Next() != -1; x++)
{
// Extensions
strOutput = "ROW #" + x.ToString() + " EXTENSIONS\n\n";
certViewExt = certViewRow.EnumCertViewExtension(0);
certViewExt.Reset();
while (certViewExt.Next() != -1)
{
switch (certViewExt.GetName())
{
// Certificate Template
case "1.3.6.1.4.1.311.21.7":
// Certificate Template OID, Mayor Version Number and Minor Version Number
strBase64Value = (string)certViewExt.GetValue(Win32.PROPTYPE_BINARY, Win32.CV_OUT_BASE64);
strValue = FormatObject("1.3.6.1.4.1.311.21.7", Convert.FromBase64String(strBase64Value));
strOutput += "Certificate Template OID = \"" + strValue + "\"\n\n";
strDisplayName = "";
if (strValue.StartsWith("Template="))
{
// Certificate Template OID
iStartIndex = strValue.IndexOf("=") + 1;
strOID = strValue.Substring(iStartIndex, strValue.IndexOf(",") - iStartIndex);
// Certificate Template Display Name
strDisplayName = TranslateTemplateOID(strOID, templatesEntry);
}
strOutput += "Certificate Template Display Name = \"" + strDisplayName + "\"\n\n";
break;
// Enhanced Key Usage
case "2.5.29.37":
strBase64Value = (string)certViewExt.GetValue(Win32.PROPTYPE_BINARY, Win32.CV_OUT_BASE64);
strValue = FormatObject("2.5.29.37", Convert.FromBase64String(strBase64Value));
strOutput += "Enhanced Key Usage = \"" + strValue + "\"\n\n";
break;
default:
break;
}
}
// Columns
strOutput += "ROW #" + x.ToString() + " COLUMNS\n\n";
certViewColumn = certViewRow.EnumCertViewColumn();
while (certViewColumn.Next() != -1)
{
switch (certViewColumn.GetDisplayName())
{
// Certificate Template
case "Certificate Template":
objValue = certViewColumn.GetValue(Win32.PROPTYPE_STRING);
if (objValue != null)
{
strOutput += "Certificate Template Name = \"" + objValue.ToString() + "\"\n\n";
}
else
{
strOutput += "Certificate Template Name = \"\"\n\n";
}
break;
// "Certificate Expiration Date"
case "Certificate Expiration Date":
objValue = certViewColumn.GetValue(Win32.PROPTYPE_DATE);
if (objValue != null)
{
strOutput += "Certificate Expiration Date = \"" + objValue.ToString() + "\"\n\n";
}
else
{
strOutput += "Certificate Expiration Date = \"\"\n\n";
}
break;
default:
break;
}
}
// Show row info
MessageBox.Show(strOutput);
}
}
string FormatObject(string strOID, byte[] pbEncoded)
{
// Variables
IntPtr pbFormat = IntPtr.Zero;
int cbFormat = 0;
string strValue = "";
bool bWorked = false;
try
{
// Get size for decoded data
bWorked = Win32.CryptFormatObject(
Win32.X509_ASN_ENCODING,
0,
Win32.CRYPT_FORMAT_STR_NO_HEX,
IntPtr.Zero,
strOID,
pbEncoded,
pbEncoded.Length,
IntPtr.Zero,
ref cbFormat
);
if (!bWorked) { throw new Win32Exception(Marshal.GetLastWin32Error()); }
// Create buffer for decoded data
pbFormat = Marshal.AllocHGlobal(cbFormat);
// Get decoded data
bWorked = Win32.CryptFormatObject(
Win32.X509_ASN_ENCODING,
0,
Win32.CRYPT_FORMAT_STR_NO_HEX,
IntPtr.Zero,
strOID,
pbEncoded,
pbEncoded.Length,
pbFormat,
ref cbFormat
);
if (!bWorked) { throw new Win32Exception(Marshal.GetLastWin32Error()); }
strValue = Marshal.PtrToStringUni(pbFormat);
}
catch (Exception ex)
{
// Error?
strValue = "FormatObject error: " + ex.Message;
}
finally
{
// Clean up
if (!pbFormat.Equals(IntPtr.Zero))
{
Marshal.FreeHGlobal(pbFormat);
}
}
return strValue;
}
string TranslateTemplateOID(string strOID, DirectoryEntry templatesEntry)
{
// Variables
DirectorySearcher searcher = null;
SearchResult result = null;
string strDisplayName = "";
try
{
// Look for the Display Name of a template OID in AD
searcher = new DirectorySearcher(templatesEntry);
searcher.Filter = "(&(msPKI-Cert-Template-OID=" + strOID + ")) ";
result = searcher.FindOne();
strDisplayName = (string)result.Properties["displayName"][0];
}
catch (Exception ex)
{
// Error?
strDisplayName = "TranslateTemplateOID error: " + ex.Message;
}
finally
{
// Clean up
if (searcher != null)
{
searcher.Dispose();
}
}
return strDisplayName;
}
}
}
</SAMPLE>
<SAMPLE file="Win32.cs">
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace CertAdminTest
{
class Win32
{
public const int X509_ASN_ENCODING = 0x00000001;
public const int CRYPT_FORMAT_STR_NO_HEX = 0x0010;
public const int CV_OUT_BASE64 = 0x1;
public const int PROPTYPE_DATE = 0x2;
public const int PROPTYPE_BINARY = 0x3;
public const int PROPTYPE_STRING = 0x4;
[DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern Boolean CryptFormatObject(
int dwCertEncodingType,
int dwFormatType,
int dwFormatStrType,
IntPtr pFormatStruct,
String lpszStructType,
Byte[] pbEncoded,
int cbEncoded,
IntPtr pbFormat,
ref int pcbFormat
);
}
}
</SAMPLE>
I hope it helps.
Regards,
Alex (Alejandro Campos Magencio)
Comments
- Anonymous
April 18, 2012
The ViewCertificateServicesDatabase function does not properly cleanup after itself.It should release certView and certViewRow and it should release and certViewExt, certViewColumn need to be released each time they are assigned.