29 июня 2021

Список персональных сертификатов

    Список персональных сертификатов пользователя в хранилище сертификатов Windows можно посмотреть с помощью менеджера сертификатов (certmgr.msc).
Менеджер сертификатов - Personal certificates
А как получить этот список в своей программе?
    Столкнувшись с этим вопросом, я сначала хотел взять какое-нибудь готовое решение из сторонних библиотек. Но потом среди исходных кодов Delphi нашел модуль System.Net.HttpClient.Win, который содержит импортированные из Crypt32.dll функции для работы с хранилищем сертификатов. Кроме того, в нем реализована обертка функции CryptUIDlgSelectCertificateFromStore из Cryptui.dll, которая отображает стандартное диалоговое окно для выбора персонального сертификата из их списка.
unit System.Net.HttpClient.Win;

interface

{$HPPEMIT NOUSINGNAMESPACE}

{$HPPEMIT '#pragma comment(lib, "winhttp")'}
{$HPPEMIT '#pragma comment(lib, "crypt32")'}

function ShowSelectCertificateDialog(AParentWnd: UIntPtr;
  const ATitle, ADisplayString: string; var ACertificate): Boolean;

implementation
где ACertificate – это параметр типа TCertificate для получения информации о выбранном сертификате.
// Record to store the information of a certificate
TCertificate = record
  // Name of the certificate
  CertName: string;
  // Serial number of the certificate
  SerialNum: string;
  // Expiry date of the certificate
  Expiry: TDateTime;
  // Start date of the certificate
  Start: TDateTime;
  // Subject of the certificate
  Subject: string;
  // Issuer of the certificate
  Issuer: string;
  // ProtocolName of the certificate
  ProtocolName: string;
  // Algorithm Signature of the certificate
  AlgSignature: string;
  // Algorithm Encryption of the certificate
  AlgEncryption: string;
  // Encryption's KeySize of the certificate
  KeySize: Integer;
  // Returns True when all key fields are empty
  function IsEmpty: Boolean;
end;
    Создадим небольшой тестовый проект. На его главную форму добавим TButton для вызова функции ShowSelectCertificateDialog и TMemo для вывода информации из ACertificate.
unit Unit1;

interface

uses
  Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, System.Classes, System.SysUtils,
  System.DateUtils, System.Net.HttpClient.Win, System.Net.URLClient;

type
  TForm1 = class(TForm)
    btnSelectCert: TButton;
    mCert: TMemo;
    procedure btnSelectCertClick(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnSelectCertClick(Sender: TObject);
var
  cert: TCertificate;
begin
  mCert.Clear;
  if ShowSelectCertificateDialog(Handle, 'Это ATitle', 'Это ADisplayString', cert) then
    begin
      mCert.Lines.Add('Name: ' + cert.CertName);
      mCert.Lines.Add('SerialNum: ' + cert.SerialNum);
      mCert.Lines.Add('Start: ' + DateTimeToStr(TTimeZone.Local.ToLocalTime(cert.Start)));
      mCert.Lines.Add('Expiry: ' + DateTimeToStr(TTimeZone.Local.ToLocalTime(cert.Expiry)));
      mCert.Lines.Add('Subject: ' + cert.Subject);
      mCert.Lines.Add('Issuer: ' + cert.Issuer);
    end;
end;

end.
Запускаем полученную программу и нажимаем кнопку "Сертификат".
Стандартное диалоговое окно для выбора персонального сертификата - один сертификат
В открывшемся диалоге мы видим первый сертификат из списка персональных сертификатов. Чтобы посмотреть перечень всех персональных сертификатов необходимо кликнуть на "More choices".
Стандартное диалоговое окно для выбора персонального сертификата - список сертификатов
Теперь выбираем нужный сертификат и нажимаем кнопку "OK".
Информация о сертификате
Как видите, все легко и просто. Диалоговое окно для выбора персонального сертификата можно получить вызовом всего лишь одной функции.
    Но, что делать, если стандартный диалог для выбора сертификата не нравится, или нужен только список сертификатов без диалога? Авторы System.Net.HttpClient.Win почему-то скрыли все типы данных, константы и функции Crypt32.dll в секции implementation. Удивительный приступ жадности.
    Копируем System.Net.HttpClient.Win.pas в каталог своей программы и делаем в нем небольшие исправления: переносим декларацию функции ShowSelectCertificateDialog (ее, как и другой неиспользуемый код, можно за ненадобностью удалить) и ключевое слово implementation в конец объявления типов (вставляем перед комментарием "Crypt API helper functions"), в конце модуля убираем секции initialization и finalization. Итак, модуль для работы с функциями Crypt32.dll готов, и мы можем писать свою функцию для получения списка персональных сертификатов:
program CertList;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.DateUtils,
  System.Net.URLClient,
  System.Net.HttpClient.Win;

procedure GetCertificates;
var
  hStore: HCERTSTORE;
  pCert : PCCERT_CONTEXT;
  CertInfo: TCertificate;
begin
  // открываем хранилище сертификатов пользователя
  hStore := CertOpenSystemStore(0, 'MY');
  if hStore = nil
    then WriteLn('Can''t open the store')
    else try
      // находим в хранилище первый сертификат
      pCert := CertEnumCertificatesInStore(hStore, nil);
      while pCert <> nil do
        begin
          // копируем информацию из структуры Win32 API в запись типа TCertificate
          CryptCertToTCertificate(pCert, CertInfo);
          if not CertInfo.IsEmpty then
            begin
              WriteLn('***');
              WriteLn('Name: ' + CertInfo.CertName);
              WriteLn('SerialNum: ' + CertInfo.SerialNum);
              WriteLn('Start: ' + DateTimeToStr(TTimeZone.Local.ToLocalTime(CertInfo.Start)));
              WriteLn('Expiry: ' + DateTimeToStr(TTimeZone.Local.ToLocalTime(CertInfo.Expiry)));
              WriteLn('Subject: ' + CertInfo.Subject);
              WriteLn('Issuer: ' + CertInfo.Issuer);
            end;
          // находим в хранилище следующий сертификат
          pCert := CertEnumCertificatesInStore(hStore, pCert);
        end;
    finally
      // закрываем хранилище сертификатов
      CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
    end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  try
    GetCertificates;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Запускаем программу и наслаждаемся списком сертификатов и базовой информацией о них:
Список сертификатов

Комментариев нет:

Отправить комментарий