Автор: Андрей *
CryptGetProvParam (..PP_ENUMCONTAINERS...)
потом по контейнерам (или в цикле выше):
CryptAcquireContext(или использовать ранее полученный указатель)
CryptGetUserKey ( AT_KEYEXCHANGE + проверить, если будет ошибка на тип AT_SIGNATURE)
CryptGetKeyParam(phUserKey, KP_CERTIFICATE...
выделить память под сертификат
CryptGetKeyParam(phUserKey, KP_CERTIFICATE, указатель на память = pbCertificate)
pCertContext := CertCreateCertificateContext(X509_ASN_ENCODING, pbCertificate, ..)
из pCertContext получаем информацию по субъекту и нужные значения по OID-ам (ИНН\СНИЛС\ФИО\...)
Добрый день. Ой-ей. На мой взгляд это не совсем соответствует поставленной задаче.
Суть в том, что есть ведь 2 подхода к сопоставлению контейнеров и сертификатов, отличающиеся тем что происходит вперед - перечисление контейнеров или перечисление сертификатов в хранилище. У обоих подходов свои плюсы и минусы. Описанный выше вариант оптимален только если перечислять контейнеры и вложенным циклом перечислять сертификаты. Поясню минусы такого подхода:
1) сертификат должен быть установлен в контейнер, если сертификата в контейнере нет, то ой (смотрите работу плагина госуслуг);
2) если пользователь вставил контейнер, то все перечисляем сначала, а ведь обращение к токенам занимает намного больше времени и иногда приводит к запросу паролей контейнера;
3) нет никакого встроенного кэширования в разрезе "этому контейнеру соответствует такой-то сертификат", только вычитка по одному;
4) а в контейнере чисто теоретически может быть даже 2 ключа те самые AT_KEYEXCHANGE и AT_SIGNATURE.
Плюсы:
1) сертификат не нужно устанавливать в "Личные";
2) сразу знаем что токен точно вставлен, но не знаем что на нем.
В то же время при перечислении сертификатов в хранилище минусы:
1) неизвестно заранее ли вставлен токен (но можно выясниить чуть позже);
2) сертификат надо установить в хранилище (обычно в Личные) иначе не увидится, желательно с привязкой к контейнеру, чтобы использовать все возможности. Не установлен, с неправильной привязкой или без привязки, то ой (смотрите работу плагина криптопро). Впрочем, служба смарт-карт каждый раз перепривязывает сертификаты с токена (в отличие от флешек, реестра) когда токен вставляется, так что достаточно включить службу и своевременно чистить старые контейнеры с токена.
Плюсы:
1) можем получить всю информацию по сертификату (ИНН и т.д.) сразу, не обращаясь к контейнеру и не запрашивая никаких пин-кодов;
2) из хранилища сертификатов помимо самого сертификата можно прочитать ту самую привязку на контейнер и вообще исключить шаг перечисления контейнеров и чтения данных сертификата из контейнера. Привязка содержит имя контейнера и те самые константы AT_KEYEXCHANGE или AT_SIGNATURE смотря уже какая нужна - их перебирать не требуется. Другими словами, при установке в хранилище эти данные запоминаются и доступны в любой момент до удаления сертификата из хранилища. Кроме того, со времен 3.6 R3 сертифицировано использование функции автоматического получения HPROV по привязке из хранилища сертификатов (но правда она выдаст запрос пользователю если токен не вставлен). Если же хотите запрос обойти, то придется получить и запомнить имена "нужных" контейнеров, а после перечисления всех сертификатов разок перечислить контейнеры (это быстрее чем их перечислять во вложенном цикле и быстрее чем вычитывать сертификат из каждого контейнера на токене);
3) в функцию перечисления сертификатов в хранилище встроен утилизатор ненужных контекстов сертификатов. передаете прошлый перечисленный, получаете следующий. Уменьшается вероятность, что забудете закрыть какой-то контекст сертификата и вызвать утечку памяти.
Особенно актуально для сервисов с подписью тысяч документов за короткий срок, так как они при малейшей утечке резво поглощают память сервера и когда память кончается возникает ошибка подписания, приходится перезагружать сервис или весь сервер (смотря где возникла утечка - да, Майкрософт не даст закрыть хранилище пока открыты сертификаты, а некоторые функции подписания еще и дублируют контекст сертификата, так что неправильной работой можно раздуть размер памяти криптопровайдера до огромного размера).
Когда я начинал знакомство с темой криптографии, меня тоже направили через контейнер узнавать сертификат, но немного поварившись в теме я понял, что перечисление сертификатов в хранилище намного более быстрый подход. Пользователь, честно говоря, переживет напоминание о том, что надо вставить токен, а вот медленное формирование списка доступных сертификатов очень раздражает. Уже при 15 сертификатах и контейнерах плагин Госуслуг может висеть полторы-две-три минуты. В то время как плагин Криптопро резво выдает перечень сертификатов на страничке тестирования.
По поводу поиска по ИНН, ну тоже есть нюансы:
1) вспомните, что ИНН организации немного раньше писали в тоже поле что ИНН физлица, где с нулями впереди, где без нулей;
2) соответственно новое поле ИНН организации есть не во всех сертификатах;
3) даже если есть, структурированный разбор субъекта на поля и сравнение данных занимает время. Да, небольшое. Однако помножить на число сертификатов и на число подписываемых документов - выйдет немало времени.
Как по мне быстрее получить бинарный вид сертификата из контекста и напрямую искать в нем ИНН. Нашелся - берем в работу (на подписание или на детальный разбор), не нашелся - детальный разбор тоже не найдет, "давай до свидания". Минус разве что в том, что бинарное значение может содержать нулевые байты и строковые функции поиска не подойдут, надо двоичные.
Ну и собственно для полноты - в хранилище есть встроенный индекс по идентификатору ключа и по отпечатку сертификата, так что искать по ним будет гораздо быстрее чем по ИНН. Как вариант, добавить регистрацию сертификата в Вашей программе и сопоставить ИНН с отпечатком, искать по отпечатку.