Skip to content

Instantly share code, notes, and snippets.

@fperana
Last active April 24, 2019 12:03
Show Gist options
  • Save fperana/826fd2f749b129b5fcfc7ded6405b47b to your computer and use it in GitHub Desktop.
Save fperana/826fd2f749b129b5fcfc7ded6405b47b to your computer and use it in GitHub Desktop.
Get Subject's Common Name from Certificates stored on Smart Card [Delphi + WinCryptographyAPIs]
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
Winapi.NCrypt
, Winapi.WinCrypt
;
procedure TForm1.Button1Click(Sender: TObject);
var
hProvider: NCRYPT_PROV_HANDLE;
ppKeyName: PNCryptKeyName;
ppEnumState: PVOID;
hKey: NCRYPT_KEY_HANDLE;
cbResult: DWORD;
aOutput: TBytes;
ppCertContext: PCertContext;
aOutput2: TBytes;
hCS: HCERTSTORE;
pCNV: PCertNameValue;
begin
Memo1.Clear;
if NCryptOpenStorageProvider(hProvider,
MS_SMART_CARD_KEY_STORAGE_PROVIDER,
0) = ERROR_SUCCESS then
try
// {Attempt to use NCryptGetProperty to get a CertStore, but why should I get one?!}
// if NCryptGetProperty(hProvider,
// NCRYPT_USER_CERTSTORE_PROPERTY,
// nil,
// 0,
// cbResult,
// NCRYPT_SILENT_FLAG) = ERROR_SUCCESS then
// begin
// if NCryptGetProperty(hProvider,
// NCRYPT_USER_CERTSTORE_PROPERTY,
// @hCS,
// cbResult,
// cbResult,
// NCRYPT_SILENT_FLAG) = ERROR_SUCCESS then
// begin
// CertGetStoreProperty(hCS,
// CERT_STORE_LOCALIZED_NAME_PROP_ID,
// nil,
// cbResult);
// cbResult := GetLastError; //// CRYPT_E_NOT_FOUND
// end;
// end;
{Enumerate keys in storage provider}
ppKeyName := nil;
ppEnumState := nil;
while NCryptEnumKeys(hProvider,
nil,
ppKeyName,
ppEnumState,
NCRYPT_SILENT_FLAG) = ERROR_SUCCESS do
begin
Memo1.Lines.Add('szName = ' + ppKeyName.pszName + sLineBreak +
'szAlgid = ' + ppKeyName.pszAlgid + sLineBreak +
'dwLegacyKeySpec = ' + ppKeyName.dwLegacyKeySpec.ToString + sLineBreak +
'dwFlags = ' + ppKeyName.dwFlags.ToString);
if NCryptOpenKey(hProvider,
hKey,
ppKeyName.pszName,
ppKeyName.dwLegacyKeySpec,
0) = ERROR_SUCCESS then
try
if NCryptGetProperty(hKey,
NCRYPT_CERTIFICATE_PROPERTY,
nil,
0,
cbResult,
NCRYPT_SILENT_FLAG) = ERROR_SUCCESS then
begin
SetLength(aOutput, cbResult);
if NCryptGetProperty(hKey,
NCRYPT_CERTIFICATE_PROPERTY,
PByte(aOutput),
cbResult,
cbResult,
NCRYPT_SILENT_FLAG) = ERROR_SUCCESS then
begin
ppCertContext := CertCreateCertificateContext(X509_ASN_ENCODING,
PByte(aOutput),
cbResult);
if ppCertContext <> nil then
try
{1st attempt: Decode whole Subject into CERT_NAME_VALUE structure}
if CryptDecodeObjectEx(X509_ASN_ENCODING,
X509_NAME_VALUE,
ppCertContext.pCertInfo.Subject.pbData,
ppCertContext.pCertInfo.Subject.cbData,
0,
nil,
nil,
cbResult) then
begin
SetLength(aOutput2, cbResult);
if CryptDecodeObjectEx(X509_ASN_ENCODING,
X509_NAME_VALUE,
ppCertContext.pCertInfo.Subject.pbData,
ppCertContext.pCertInfo.Subject.cbData,
0,
nil,
Pointer(aOutput2),
cbResult) then
begin
pCNV := PCertNameValue(aOutput2);
Memo1.Lines.Add('X509_NAME_VALUE = ' + PAnsiChar(pCNV.Value.pbData));
end;
end;
{2nd attempt: Get whole Subject in string format}
cbResult := CertNameToStr(X509_ASN_ENCODING,
@ppCertContext.pCertInfo.Subject,
CERT_OID_NAME_STR,
nil,
0);
SetLength(aOutput2, cbResult * 2);
CertNameToStr(X509_ASN_ENCODING,
@ppCertContext.pCertInfo.Subject,
CERT_OID_NAME_STR,
PChar(aOutput2),
cbResult);
Memo1.Lines.Add('CERT_OID_NAME_STR = ' + WideStringOf(aOutput2));
{3rd attempt: Get Subject Common Name (my goal!)}
cbResult := CertGetNameString(ppCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
nil,
nil,
0);
SetLength(aOutput2, cbResult * 2);
CertGetNameString(ppCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
nil,
Pointer(aOutput2),
cbResult);
Memo1.Lines.Add('CERT_NAME_SIMPLE_DISPLAY_TYPE = ' + WideStringOf(aOutput2));
finally
CertFreeCertificateContext(ppCertContext);
end;
end;
end;
finally
NCryptFreeObject(hKey);
end;
end;
finally
NCryptFreeBuffer(ppKeyName);
NCryptFreeBuffer(ppEnumState);
NCryptFreeObject(hProvider);
end;
end;
end.
@fperana
Copy link
Author

fperana commented Apr 24, 2019

My goal was to get the CN (common name) from the Subject field of the certificates stored on a Smart Card.
I've used CNG to:

  • open the Storage Provider associated with the Smart Card
  • enumerate the Keys
  • open each key
  • get the Certificate property

From here on I've used the Crypto API (not CNG) - this part seems to be still current, no deprecation remarks... and seemingly no equivalent CNG APIs to achieve the same results:

  • create a Certificate Context
  • 3rd attempt: get the Subject's Common Name from the certificate context
    (I've kept the other two attempts just for future reference)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment