Обновлено 01.02.2020
Добрый день уважаемые читатели и гости блога pyatilistnik.org, в прошлый раз я вам подробно рассказал про ошибку с отсутствием библиотеки VCRUNTIME140.dll, сегодня хочу поговорить , о том, как узнать хэш сертификата безопасности, иногда бывают ситуации, что требуется предоставить данную информацию. Думаю, что начинающим специалистам, это будет интересно.
Что такое отпечаток сертификата (Certificate thumbprint)
Отпечаток сертификата (Certificate thumbprint) — это хэш сертификата, вычисляемый по всем данным сертификата и его подпись. Отпечатки используются в качестве уникальных идентификаторов для сертификатов, в приложениях при принятии решений о доверии, в файлах конфигурации и отображаются в интерфейсах.
И так про определение хэша и его виды, я вам подробно уже рассказывал, кто не видел эту статью, советую ее посмотреть, будет очень познавательно. Там алгоритмов очень много, нас это сегодня не интересует, нам нужно его значение. Я покажу вам два метода, но уверен, что их гораздо больше.
- Посмотреть через браузер или оснастку mmc
- Посмотреть через командную строку
- Посмотреть через PowerShell
Узнаем кэш сертификата в браузере
Найдите любой интересующий вас сайт, я выберу свой проект https://basis.myseldon.com/ru. Как видите у него есть сертификат, об этом говорит замочек перед адресом сайта.
Чтобы посмотреть сертификат используемый в нем, вам нужно просто на него щелкнуть, в Internet Explore этого достаточно, но в Google Chrome придется сделать вот таким методом. Далее вы заходите во вкладку «Состав» и находите поле «Отпечаток», именно это значение и будет вам показывать хэш сертификата.
Если вы используете сертификат для подписи и он установлен у вас локально, то откройте оснастку mmc сертификаты и в ветке личное найди нужный, далее все как описано выше.
Через командную строку
Откройте командную строку cmd и введите команду:
Как видите, данный метод еще быстрее, для примера я вам показал вывод командной строки и сертификат открытый в Internet Explore. Надеюсь вам помогла данная информация в поиске значения хэш у сертификата.
Получить отпечаток сертификата с помощью PowerShell
Если вам необходимо получить отпечаток сертификата через PowerShell, то запустите оболочку и введите команду:
Get-ChildItem -path cert:LocalMachineMy
У вас будет столбец Thumbprint, это и есть отпечаток сертификата, в моем примере, это сертификат для Windows Admin Center.
Та же можно вывести более детальную информацию по сертификатам и сделать небольшое форматирование выходных данных:
Get-ChildItem -Path cert:LocalMachineMy | Format-Table Subject, FriendlyName, Thumbprint, NotAfter -AutoSize
В результате полезное еще видеть столбец NotAfter для понимания срока действия сертификата.
На этом у меня все. Мы с вами разобрали методы получения информации, о отпечатке сертификатов (Certificate thumbprint), с вами был Иван Семин, автор и создатель IT портала Pyatilistnik.org.
Где взять отпечаток сертификата?
отпечаток сертификата (последняя строка);
…
Просмотреть информацию о сертификате ключа подписи можно:
- В «Личном кабинете» в нижней части вкладки «Договор» — «Список сертификатов ».
- В Internet Explorer в меню «Сервис» — «Свойства обозревателя» — закладка «Содержание» — «Сертификаты».
Как скопировать отпечаток сертификата?
Дважды щелкните сертификат . В диалоговом окне Сертификат перейдите на вкладку Состав . Найдите в списке поле Отпечатоки щелкните его. Скопируйте шестнадцатеричные значения из текстового поля.
Как посмотреть хэш сертификата?
Чтобы посмотреть сертификат используемый в нем, вам нужно просто на него щелкнуть, в Internet Explore этого достаточно, но в Google Chrome придется сделать вот таким методом. Далее вы заходите во вкладку «Состав» и находите поле «Отпечаток», именно это значение и будет вам показывать хэш сертификата .
Как найти хранилище сертификатов?
Откройте «Пуск/Панель управления/Свойства браузера» или «Пуск/Панель управления/Сеть и интернет/Свойства браузера». В открывшемся окне перейдите на вкладку «Содержание». Нажмите кнопку « Сертификаты ». Перейдите на вкладку «Доверенные корневые центры сертификации».
Как посмотреть данные сертификата?
Как посмотреть сертификат в Chrome?
- Шаг 1. В активном окне браузера нажать на кнопку F12.
- Шаг 2. В появившемся окне «Инструменты разработчика» перейти на вкладку «Security».
- Шаг 3. Нажать на кнопку « Просмотреть сертификат ».
- Шаг 3. Информация о сертификате будет открыта в новом окне браузера.
Как узнать хэш сертификата?
Чтобы посмотреть сертификат используемый в нем, вам нужно просто на него щелкнуть, в Internet Explore этого достаточно, но в Google Chrome придется сделать вот таким методом. Далее вы заходите во вкладку «Состав» и находите поле «Отпечаток», именно это значение и будет вам показывать хэш сертификата
Как скопировать отпечаток сертификата?
Дважды щелкните сертификат . В диалоговом окне Сертификат перейдите на вкладку Состав . Найдите в списке поле Отпечатоки щелкните его. Скопируйте шестнадцатеричные значения из текстового пол
Что содержит в себе SSL сертификат?
В SSL -сертификате содержится пара криптографических ключей: открытый и секретный, которые собственно и позволяют организовать зашифрованное соединение браузера пользователя с веб-сервером. Значение открытого ключа содержится в этом пол
Где находятся сертификаты на компьютере?
В среде Windows (начиная с Vista и в более поздних версиях операционной системы) абсолютно все пользовательские сертификаты хранятся по адресу C:UsersПОЛЬЗОВАТЕЛЬAppDataRoamingMicrosoftSystemCertificates, где вместо ПОЛЬЗОВАТЕЛЬ – имя учетной записи, по которой сейчас и работает операционная система.
Как зайти в хранилище сертификатов?
Просмотр сертификатов с помощью средства диспетчера сертификатов
- Выберите параметр Выполнить в меню Пуск, а затем введите certmgr.
- Чтобы просмотреть сертификаты , в разделе Сертификаты — текущий пользователь в левой области разверните каталог для типа сертификата , который нужно просмотреть.
Где хранятся сертификаты Крипто Про?
Где на жестком диске находится сертификат
В реестре OS Windows (для версии 32 бита это папка HKEY_LOCАL_MАCHINE\SOFTWARE\ CryptoPrо\Setting\Users\(имя пользователя ПК)\Keys\, а для версии 64 бита это директория HKEY_LOCAL_MАCHINE\SOFTWARE\Wow6432Node\CryptoPrо\Setting\Users\(имя пользователя ПК)\Keys.
Как загрузить новый сертификат ЭЦП?
Как установить ЭЦП на компьютер с Windows?
- Установите на устройство специально программное обеспечение для взаимодействия с электронно-цифровыми подписями. …
- Подключите носитель ключа к устройству. …
- Следуя инструкциям на экране, выполните установку сертификата ЭЦП в операционную систему.
Как посмотреть информацию о сертификате?
Информацию о сертификате вы также можете просмотреть через стандартное окно просмотра свойств сертификата Windows. Для этого в окне просмотра информации о сертификате в закладке Сертификат нажмите на кнопку Сертификат , либо в закладке Статус сертификата — Просмотреть .
Где в Google Chrome хранятся сертификаты?
Откройте страницу chrome ://settings.
- В левой части страницы нажмите Конфиденциальность и безопасность.
- Нажмите Безопасность.
- Прокрутите страницу вниз до раздела Дополнительные.
- Нажмите Настроить сертификаты .
- В списке найдите недавно добавленные центры сертификации.
Как посмотреть сертификаты?
Чтобы узнать какие сертификаты установлены на ПК:
- Откройте «Пуск/Панель управления/Свойства браузера» или «Пуск/Панель управления/Сеть и интернет/Свойства браузера».
- В открывшемся окне перейдите на вкладку «Содержание».
- Нажмите кнопку « Сертификаты ».
- Перейдите на вкладку «Доверенные корневые центры сертификации ».
Article Number
000037679
Applies To
RSA Product Set: SecurID Access
RSA Product/Service Type: RSA Cloud Authentication Service
Issue
When configuring SAML SSO, some service providers require the fingerprint of the SSL certificate used to sign the SAML Assertion.
Requirements of different service providersvary. Some need a SHA-1 fingerprint, some need an MD5 fingerprint, etc. Depending on the server platform, only the SHA-1 or MD5 fingerprint/thumbprint may be displayed.
Task
OpenSSL can be used to generate the certificate fingerprint with any of the algorithms you might need.
Resolution
This solution assumes the use of Windows.
- Install the latest version of OpenSSL for Windows.
- Open the Windows Command Line.
- Navigate to the OpenSSL installation directory (the default directory is C:OpenSSL-Win32bin).
- Run one of the following commands to view the certificate fingerprint/thumbprint:
-
SHA-256
openssl x509 -noout -fingerprint -sha256 -inform pem -in [certificate-file.crt]
-
SHA-1
openssl x509 -noout -fingerprint -sha1 -inform pem -in [certificate-file.crt]
-
MD5
openssl x509 -noout -fingerprint -md5 -inform pem -in [certificate-file.crt]
The example below displays the value of the same certificate using each algorithm:
C:OpenSSL-Win32bin>openssl x509 -noout -fingerprint -sha256 -inform pem -in c:testcert.cer
SHA256 Fingerprint=E6:5A:5D:37:22:FC:EF:EA:4B:22:92:45:BC:49:D2:29:3D:84:19:BC:C3:45:23:A1:22:A4:99:20:9D:03:E6:47
C:OpenSSL-Win32bin>openssl x509 -noout -fingerprint -sha1 -inform pem -in c:testcert.cer
SHA1 Fingerprint=1E:DD:AD:32:C3:54:3F:C3:6F:7F:94:51:8D:5E:F7:ED:7C:DB:5D:A5
C:OpenSSL-Win32bin>openssl x509 -noout -fingerprint -md5 -inform pem -in c:testcert.cer
MD5 Fingerprint=AA:6F:C8:3F:37:78:7A;BE:A6:BE:2C:51:26:16:3F:D3
C:OpenSSL-Win32bin>
Notes
The algorithm of the fingerprint/thumbprint is unrelated to the encryption algorithm of the certificate. The fingerprint/thumbprint is a identifier used by some server platforms to locate the certificate in a certificate store. You can generate a MD5 fingerprint for a SHA2 certificate.
Одним из вариантов сертификата при программном добавлении цифровой подписи в PDF документ может быть идентифицированный по SHA1-хешу сертификат из хранилища сертификатов Windows. Получение без использования сторонних библиотек списка персональных сертификатов из хранилища я рассмотрел в статье Список персональных сертификатов. Но структура CERT_CONTEXT (record TCertificate в Delphi) для хранения информации о сертификате не содержит нужный нам SHA1-хеш. Давайте посмотрим, как достать его из сертификата.
Для этого нам понадобится функция CertGetCertificateContextProperty, которая позволяет по указателю на структуру CERT_CONTEXT получить SHA1-хеш сертификата. Дополним программу получения списка персональных сертификатов импортом из Crypt32.dll функции CertGetCertificateContextProperty (в System.Net.HttpClient.Win.pas она не импортирована) и небольшой функцией для ее вызова:
program CertList; {$APPTYPE CONSOLE} uses System.Classes, System.SysUtils, System.DateUtils, Winapi.Windows, System.Net.URLClient, System.Net.HttpClient.Win; function CertGetCertificateContextProperty(pCertContext: PCCERT_CONTEXT; dwPropId: DWORD; pvData : PVOID; pcbData : PDWORD):BOOL; stdcall; external Crypt32 name 'CertGetCertificateContextProperty' delayed; function GetCertSHA1Hash(const pCert: PCCERT_CONTEXT): String; const CERT_SHA1_HASH_PROP_ID = 3; var dwSize: DWORD; pbData: PByte; begin Result := ''; dwSize := 0; // получаем размер буфера для хеша if CertGetCertificateContextProperty(pCert, CERT_SHA1_HASH_PROP_ID, nil, @dwSize) and (dwSize > 0) then begin GetMem(pbData, dwSize); try // получаем хеш и преобразуем его в строку if CertGetCertificateContextProperty(pCert, CERT_SHA1_HASH_PROP_ID, pbData, @dwSize) then for var i := 0 to dwSize - 1 do Result := Result + IntToHex(pbData[i], 2); finally FreeMem(pbData); end; end; end; 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('SHA1 digest: ' + GetCertSHA1Hash(pCert)); 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.
Теперь давайте разберемся с программой, которая использует для выбора персонального сертификата стандартное диалоговое окно Windows. Вызывающая его функция ShowSelectCertificateDialog возвращает информацию о сертификате в параметре типа TCertificate, а нам нужен указатель на структуру CERT_CONTEXT. Поэтому сначала найдем выбранный сертификат по его серийному номеру (в System.Net.HttpClient.Win.pas уже есть готовая для этого функция – FindCertWithSerialNumber), а потом вызовем CertGetCertificateContextProperty:
unit Unit1; interface uses Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, System.Classes, System.SysUtils, System.DateUtils, Winapi.Windows, System.Net.HttpClient.Win, System.Net.URLClient; type TForm1 = class(TForm) btnSelectCert: TButton; mCert: TMemo; procedure btnSelectCertClick(Sender: TObject); private function GetCertSHA1Hash(const sSerialNumber: String): String; end; var Form1: TForm1; implementation {$R *.dfm} function CertGetCertificateContextProperty(pCertContext: PCCERT_CONTEXT; dwPropId: DWORD; pvData : PVOID; pcbData : PDWORD):BOOL; stdcall; external Crypt32 name 'CertGetCertificateContextProperty' delayed; function TForm1.GetCertSHA1Hash(const sSerialNumber: String): String; const CERT_SHA1_HASH_PROP_ID = 3; var hStore: HCERTSTORE; pCert : PCCERT_CONTEXT; begin Result := ''; // открываем хранилище сертификатов пользователя hStore := CertOpenSystemStore(0, 'MY'); if hStore = nil then Result := 'Ошибка открытия хранилища сертификатов ' + IntToStr(GetLastError) else try // ищем сертификат по его серийному номеру pCert := FindCertWithSerialNumber(hStore, sSerialNumber); if pCert = nil then Result := 'Сертификат ' + sSerialNumber + ' не найден' else try // получаем хеш var dwSize: DWORD := 0; // получаем размер буфера для хеша if CertGetCertificateContextProperty(pCert, CERT_SHA1_HASH_PROP_ID, nil, @dwSize) and (dwSize > 0) then begin var pbData: PByte; GetMem(pbData, dwSize); try // получаем хеш и преобразуем его в строку if CertGetCertificateContextProperty(pCert, CERT_SHA1_HASH_PROP_ID, pbData, @dwSize) then for var i := 0 to dwSize - 1 do Result := Result + IntToHex(pbData[i], 2); finally FreeMem(pbData); end; end; finally // освобождаем память CertFreeCertificateContext(pCert); end; finally // закрываем хранилище сертификатов CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG); end; end; 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('SHA1 digest: ' + GetCertSHA1Hash(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.
P.S. Обе программы и модуль System.Net.HttpClient.Win.pas более подробно рассмотрены в статье Список персональных сертификатов.
Время на прочтение
15 мин
Количество просмотров 8.1K
Предисловие
Передо мной стояла задача по интеграции нашего сервиса с госуслугами. Казалось ничего сложного не предстоит, но учитывая что наш сервис базируется на технологии ASP.NET всё было не так оптимистично. В начале были поиски.. много поисков, которые привели к множеству разрозненной и чаще всего неактуальной информации. Так же были найдены уже готовые решения, но как заявляли некоторые товарищи на форумах за такое могут и по головке погладить. Поэтому было решено писать самому.
Эта статья скорее больше актуализация и дополнение информации из этой статьи.
Введение
На сайте Минцифр есть методичка максимально раздутая и очень запутанная, но пользоваться ею нам всё равно придётся. Мы будем работать с ЕСИА версии 3.11 (актуальная на момент написания статьи). Кратко наши действия заключаются вот в чем:
-
Регистрация ИС в регистре информационных систем ЕСИА
-
Регистрация ИС в тестовой среде
-
Выполнение доработки системы для взаимодействия с ЕСИА
Звучит довольно просто, но каждый шаг целая отдельная история приключений. Регистрация ИС в ЕСИА приключение для бюрократа. Поэтому в этой статье мы немного посмотрим на второй шаг, и детально распишем реализацию.
Содержание
-
Настройка ИС
-
Сертификаты ИС
-
Получаем client_certificate_hash
-
Формирование client_secret
-
Собираем ссылку для авторизации в Госуслугах
-
Получение токена доступа
-
Проверка токена
-
Получение данных пользователя из ЕСИА
Всё необходимое
Минцифры требуют использование сертифицированного ПО для криптографии. Поэтому мы будем использовать КриптоПРО CSP + КриптоПРО .Net + КриптоПРО .NetSDK. Всё это можно скачать с офф. сайта КриптоПРО. На время разработки лучше использовать триал версию.
Наш инвентарь для путешествия:
-
КриптоПРО CSP
-
КриптоПРО .Net
-
КриптоПРО .NetSDK
-
Контейнер закрытого ключа с сертификатом нашей организации
-
Много терпения
Немного о КриптоПРО CSP + .Net Core 5+
Вот тут и начинаются первые проблемы. На момент написания статьи у КриптоПРО .Net нет поддержки .Net Core 5 и выше. Есть сборка под .Net Core 3.1 но и она выглядит сомнительно. Поэтому было решено поднять сервис для .Net Framework 4.8 который будет использовать средства КриптоПРО CSP для подписания с использованием ЭЦП, а так же проверки ответов от ЕСИА.
Немного о контейнере закрытого ключа и сертификата
Когда мы начинали делать эту задачу у нас была КЭП на токене, но как оказалось на нём был неэкспортируемый контейнер. Скажу сразу, что экспортировать контейнер с такого токена запрещено ФНС. Поэтому необходимо заранее получить токен на имя сотрудника с экспортируемым контейнером. Так как его необходимо будет скопировать на сервер.
Приступаем
Начнём с того, что вы уже отправили заявку регистрации ИС в ЕСИА и её приняли. А так же отправили заявка на тестовую среду. Приступим к этапу настройки ИС в тестовом кабинете электронного правительства. Вот ссылка на тестовую страницу. Логинимся под тестовой учетной записью тестового пользователя 006(все данные лежат в приложении к работе с тестовой средой), так как он имеет доступ к управлением ИС.
Здесь ищем нашу систему по Мнемонике или полному названию, если таковой нет то создаём. Напротив нашей системы есть две кнопки:
Первая кнопка — изменить нашу ИС (информация о ИС, редиректы и тд)
Вторая кнопка — наши сертификаты с помощью которых мы подписываем сообщения в ЕСИА
Настройка ИС
Есть важный момент в настройки ИС. Это URL системы. Тут мы указываем ссылки куда ЕСИА может делать переадресацию при запросе от нашей ИС. На эти точки будет приходить авторизационный код (Если он указан в запросе).
Сертификаты ИС
Здесь мы можем загрузить наш сертификаты или же удалить их. Есть один важный момент, каждая ИС может иметь только один уникальный сертификат. А связи с тем, что на тестовой среде все системы регистрируются под одним пользователем и сертификаты тестовые одни на всех часта такая ситуация, что кто-то удаляет у вас сертификат и загружает к себе. А ваши запросы теперь падают с ошибкой) Но если у вас уже готов ЭЦП на сотудника, то лучше используйте её.
Реализуем
Мы закончили с настройки нашей ИС и можем приступить к реализации. Надеюсь вы уже установили КриптоПРО и всё необходимое для него. Если нет, я подожду…
Устанавливаем сертификаты
Такс~ Всё готово. Качаем сертификаты по ссылке из методички. Специально не буду вставлять, так как может измениться.
Здесь нам интересен сертификат ТЕСИА ГОСТ 2012.cer — это сертификат с помощью которого ЕСИА подписывает сообщения отправляя в нашу ИС. (Соответственно для продуктовой среды свой сертификат). Устанавливаем сертификат как доверенный. Здесь ничего сложного думаю разберётесь.
Теперь устанавливаем тестовый контейнер и сертификат. Для примера будем использовать предоставленные ЕСИА контейнеры, но вы можете использовать свои. Всё это лежит внутри архива.
В архиве лежит папка d1f73ca5.000 — это контейнер нам необходимо его переместить по пути C:UsersUserAppDataLocalCrypto Pro
Теперь открываем КриптоПРО CSP. Выбираем установить личный сертификат и указываем Тестовое ведомство Фамилия006 ИО.cer и нажимаем найти автоматически. Выполняем оставшиеся шаги сами.
Механизм подписания
Пожалуй начинается самая важная и самая запутанная часть всего пути. Здесь мы реализуем сервис для работы с подписью. И так делаю выжимку из методических материалов, чтобы Вам не пришлось читать много текста.
Для получения авторизационный ссылки — ссылка на которую мы будем переадресовывать пользователя для авторизации в ЕСИА. Нам необходимо собрать ссылку из параметров.
-
client_id
— наша Мнемоника -
client_secret
— Отсоединённая подпись от параметров запроса в кодировке UTF-8 -
redirect_uri
— ссылка на которую ЕСИА будет переадресовывать пользователя вместе с авторизационным кодом -
scope
— перечень запрашиваемой информации. Напримерfullname birthdate gender
-
response_type
— тип ответа от ЕСИА, в нашем случае это просто строчкаcode
-
state
— Идентификатор текущего запроса. Генерируется таким образомGuid.NewGuid().ToString("D");
-
timestamp
— время запроса авторизационного кода в формате yyyy.MM.dd HH:mm:ss Z. Генерируется таким образомDateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");
-
client_certificate_hash
— это fingerprint сертификата в HEX-формате.
Обозначили наш зоопарк. Самый важный зверь здесь client_secret
Получаем client_certificate_hash
В методическом указании от Минцифр есть ссылка на специальную утилиту с помощью которой мы можем получить этот хэш. Разархивировали архив и видим перед нами sh. Windows пользователи не пугаемся, на самом деле тут же лежит .exe файл. Чтобы вычислить хэш нашего сертификат просто необходимо из cmd запустить вот такой скрипт:
cpverify.exe test.cer -mk -alg GR3411_2012_256 -inverted_halfbytes 0
Формирование client_secret
Такс перед тем как просто получит client_secret
нам необходимо сделать:
-
ASP.Net Framework 4.8 WebAPI — тот самый сервис который будет работать с КриптоПРО CSP
Пропустим множество шагов создания этого сервиса и перейдём сразу к его настройки для работы с КриптоПРО CSP.
Настройка сервиса для работы с КриптоПРО CSP
Добавляем ссылки на DLL КриптоПРО.
Переходим по пути C:Program Files (x86)Crypto Pro.NET SDKAssemblies4.0
Выбираем всё что нам нужно. (подробная информация)
Теперь мы имеем доступ к API КриптоПРО CSP из кода .Net Framework
Теперь создаём контроллер:
Код контроллера
Итак нам необходимо получать строку для подписания. Создадим метод
const string CertSerialNumber = "01f290e7008caed0904b967783fd0e4ad6";
const string EsiaCertSerialNumber = "0125657e00a1ae59804d92116214e53466";
[HttpGet]
public string Get(string msg)
{
msg = Base64UrlEncoder.Decode(msg);
var data = Encoding.UTF8.GetBytes(msg);
var client_secret = Sign(data);
return client_secret;
}
Мы заранее укажем константами серийные номера сертификатов.
В методе Get получаем строку в Base64Url формате, чтобы спокойно передавать наши длинные сообщения.
Декодируем строку из Base64Url в текст. После чего переводим текст в байты используя UTF-8. А теперь подписываем.
string Sign(byte[] data)
{
var gost3411 = new Gost3411_2012_256CryptoServiceProvider();
var hashValue = gost3411.ComputeHash(data);
gost3411.Clear();
var signerCert = GetSignerCert();
var SignedHashValue = GostSignHash(hashValue,
signerCert.PrivateKey as Gost3410_2012_256CryptoServiceProvider, "Gost3411_2012_256");
var client_secret = Base64UrlEncoder.Encode(SignedHashValue);
return client_secret;
}
И так что мы тут делаем. С помощью ГОСТ 34.11-2012 мы вычисляем хэш нашего сообщения. И используя полученный сертификат подписываем сообщение.
X509Certificate2 GetSignerCert()
{
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySerialNumber, CertSerialNumber, false);
if (certificates.Count != 1)
{
return null;
}
var certificate = certificates[0];
if (certificate.PrivateKey == null)
{
return null;
}
return certificate;
}
Здесь мы открываем наш склад с контейнерами и ищем именно тот где лежит наш сертификат. После чего извлекаем из него сертификат.
byte[] GostSignHash(byte[] HashToSign, Gost3410_2012_256CryptoServiceProvider key, string HashAlg)
{
try
{
//Создаем форматтер подписи с закрытым ключом из переданного
//функции криптопровайдера.
var Formatter = new Gost2012_256SignatureFormatter(
(Gost3410_2012_256CryptoServiceProvider) key);
//Устанавливаем хэш-алгоритм.
Formatter.SetHashAlgorithm(HashAlg);
//Создаем подпись для HashValue и возвращаем ее.
return Formatter.CreateSignature(HashToSign);
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
С помощью этого кода как раз и создаётся наша подпись на хэш строки. Здесь используется ГОСТ 34.10-2012.
Итак контроллер готов. Теперь переходим в наш основной проект на .Net Core
Создаём строку подписания. Просто выполняем конкатенацию параметры без разделителей. Здесь я использую IOptions чтобы брать параметры из appsettings.json.
var msg = $"{esiaSettings.Value.ClientId}{esiaSettings.Value.Scope}{timestamp}{state}{redirectUri}";
Мы получил строку для подписания. Теперь нам необходимо эту строку закодировать в Base64Url и отправляем её на подписание в написанный нами заранее сервис
private string GetClientSecret(string msg){
var client = new HttpClient();
var msgBase64 = Base64UrlEncoder.Encode(msg);
var response = await client.GetAsync($"{cryptoProSettings.Value.BaseUrl}/Get?msg={msgBase64}");
var clientSecret = await response.Content.ReadAsStringAsync();
clientSecret = JsonConvert.DeserializeObject<string>(clientSecret);
return clientSecret;
}
Собираем ссылку для авторизации в Госуслугах
Наконец-то мы получили этот долгожданный секрет. Но вы могли бы подумать это всё, дальше всё просто и ясно. Не тут то было! Дело в том, что ЕСИА требует Base64 Url Safe кодироку. И она немного отличается от Base64Url кодировки доступной из коробки .Net
Итак дело за малым, собираем нашего гомункула из секрета и параметров.
Класс помощник для сборки ссылки
Возможно излишне, но мне понравился метод сбора вот таким способом.
public class RequestBuilder
{
List<RequesItemClass> items = new List<RequesItemClass>();
public void AddParam(string name, string value)
{
items.Add(new RequesItemClass { name = name, value = value });
}
public override string ToString()
{
return string.Join("&", items.Select(a => a.name + "=" + a.value));
}
}
public class RequesItemClass
{
public string name;
public string value;
}
Код сборки ссылки
async Task<string> UrlBuild(string redirectUri)
{
using var client = new HttpClient();
var timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");
var state = Guid.NewGuid().ToString("D");
var msg = $"{esiaSettings.Value.ClientId}{esiaSettings.Value.Scope}{timestamp}{state}{redirectUri}";
var clientSecret = await GetClientSecret(msg);
var builder = new RequestBuilder();
builder.AddParam("client_secret", clientSecret);
builder.AddParam("client_id", esiaSettings.Value.ClientId);
builder.AddParam("scope", esiaSettings.Value.Scope);
builder.AddParam("timestamp", timestamp);
builder.AddParam("state", state);
builder.AddParam("redirect_uri", redirectUri);
builder.AddParam("client_certificate_hash", esiaSettings.Value.ClientCertificateHash);
builder.AddParam("response_type", "code");
builder.AddParam("access_type", "online");
//Вот тут самый важный момент на который было потрачено множество времени. Просто заменяем символы на безопасные
var url = esiaSettings.Value.EsiaAuthUrl + "?" + builder.ToString().Replace("+", "%2B")
.Replace(":", "%3A")
.Replace(" ", "+");
return url;
}
Получаем ссылку на подобии вот такой:
Здесь https://esia-portal1.test.gosuslugi.ru/aas/oauth2/v2/ac
ссылка на конечную точку получения авторизационно кода, указана в методическом материале.
https://esia-portal1.test.gosuslugi.ru/aas/oauth2/v2/ac?client_secret=v_c33_-LpkyKJbopTEYqBMbGZrBy9r9u1pzbRmMLNlJPcBnPTJj6Xx5DuxXba3EZZoXdMsb0YIwPDCoF0dfYjQ&client_id=MEMONIKA&scope=fullname+birthdate+gender×tamp=2022.12.23+16%3A37%3A45+%2B0000&state=3a19c4d7-594b-496f-aa6e-970c75a925a4&redirect_uri=https%3A//api.site/users/esia&client_certificate_hash=EED1079A4FF154E117EAA196DCB551930807825DE1DE15EAF7607F354BA47423&response_type=code&access_type=online
Теперь перенаправляем пользователя по этой ссылке и ожидаем пока он авторизуется. После авторизации ЕСИА переадресует его на нашу ссылку и отправит туда в виде аргументов авторизационный код и state.
Получение токена доступа
Теперь время получить токен взамен на авторизационный код.
Метод для получение токена
public async Task<EsiaAuthToken> GetToken(string authorizationCode, string redirectUrl)
{
var timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000");
var state = Guid.NewGuid().ToString("D");
var msg =
$"{esiaSettings.Value.ClientId}{esiaSettings.Value.Scope}{timestamp}{state}{redirectUrl}{authorizationCode}";
var clientSecret = await GetClientSecret(msg);
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", esiaSettings.Value.ClientId),
new KeyValuePair<string, string>("code", authorizationCode), //Здесь мы передаём полученный код
new KeyValuePair<string, string>("grant_type", "authorization_code"), //Просто указываем тип
new KeyValuePair<string, string>("state", state),
new KeyValuePair<string, string>("scope", esiaSettings.Value.Scope),
new KeyValuePair<string, string>("timestamp", timestamp),
new KeyValuePair<string, string>("token_type", "Bearer"), //Какой токен мы хотим получить
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("redirect_uri", redirectUrl),
new KeyValuePair<string, string>("client_certificate_hash", esiaSettings.Value.ClientCertificateHash)
};
using var client = new HttpClient();
using var response = await client.PostAsync(esiaSettings.Value.EsiaTokenUrl,
new FormUrlEncodedContent(requestParams));
response.EnsureSuccessStatusCode();
var tokenResponse = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<EsiaAuthToken>(tokenResponse);
if (!await ValidatingAccessToken(token))
{
throw new Exception("Ошибка проверки маркера индентификации");
}
return token;
}
Тут всё простенько, снова генерируем client_secret
указываем остальные параметры и отправляем запрос в ЕСИА на получение токена. Тестовый Uri https://esia-portal1.test.gosuslugi.ru/aas/oauth2/v3/te
Класс токена
public class EsiaAuthToken
{
/// <summary>
/// Токен доступа
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Идентификатор запроса
/// </summary>
public string State { get; set; }
string[] parts => AccessToken.Split('.');
/// <summary>
/// Хранилище данных в токене
/// </summary>
public EsiaAuthTokenPayload Payload
{
get
{
if (string.IsNullOrEmpty(AccessToken))
{
return null;
}
if (parts.Length < 2)
{
throw new Exception($"При расшифровке токена доступа произошла ошибка. Токен: {AccessToken}");
}
var payload = Encoding.UTF8.GetString(Base64UrlEncoder.DecodeBytes(parts[1]));
return JsonConvert.DeserializeObject<EsiaAuthTokenPayload>(payload);
}
}
/// <summary>
/// Сообщение для проверки подписи
/// </summary>
[Newtonsoft.Json.JsonIgnore]
public string Message
{
get
{
if (string.IsNullOrEmpty(AccessToken))
{
return null;
}
if (parts.Length < 2)
{
throw new Exception($"При расшифровке токена доступа произошла ошибка. Токен: {AccessToken}");
}
return parts[0] + "." + parts[1];
}
}
/// <summary>
/// Сигнатура подписи
/// </summary>
[Newtonsoft.Json.JsonIgnore]
public string Signature
{
get
{
if (string.IsNullOrEmpty(AccessToken))
{
return null;
}
if (parts.Length < 2)
{
throw new Exception($"При расшифровке токена доступа произошла ошибка. Токен: {AccessToken}");
}
return parts[2];
}
}
public class EsiaAuthTokenPayload
{
[JsonConstructor]
public EsiaAuthTokenPayload(string tokenId, string userId, string nbf, string exp, string iat, string iss,
string client_id)
{
TokenId = tokenId;
UserId = userId;
BeginDate = EsiaHelper.DateFromUnixSeconds(double.Parse(nbf));
ExpireDate = EsiaHelper.DateFromUnixSeconds(double.Parse(exp));
CreateDate = EsiaHelper.DateFromUnixSeconds(double.Parse(iat));
Iss = iss;
ClientId = client_id;
}
/// <summary>
/// Идентификатор токена
/// </summary>
[JsonProperty("urn:esia:sid")]
public string TokenId { get; private set; }
/// <summary>
/// Идентификатор пользователя
/// </summary>
[JsonProperty("urn:esia:sbj_id")]
public string UserId { get; private set; }
/// <summary>
/// Время начала действия токена
/// </summary>
[JsonPropertyName("nbf")]
public DateTime BeginDate { get; private set; }
/// <summary>
/// Время окончания действия токена
/// </summary>
[JsonPropertyName("exp")]
public DateTime ExpireDate { get; private set; }
/// <summary>
/// Время выпуска токена
/// </summary>
[JsonPropertyName("iat")]
public DateTime CreateDate { get; private set; }
/// <summary>
/// Организация, выпустившая маркер
/// </summary>
[JsonPropertyName("iss")]
public string Iss { get; private set; }
/// <summary>
/// Адресат маркера
/// </summary>
[JsonPropertyName("client_id")]
public string ClientId { get; private set; }
}
}
public static class EsiaHelper
{
public static DateTime DateFromUnixSeconds(double seconds)
{
var date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
return date.AddSeconds(seconds).ToLocalTime();
}
}
Проверка токена
Итак помимо того, что нам нужно получить токен, нам так же необходимо проверить его.
Сам токен состоит из 3 частей.
1 часть — заголовок JWT токена
2 часть — payload токена, там вся основная информация о токене
3 часть — RAW подпись в формате UTF-8
Код конечной точки для проверки подписи
[HttpPost]
public bool Verify(VerifyMessage message)
{
try
{
return VerifyRawSignString(message.Message, message.Signature);
}
catch (Exception ex)
{
return false;
}
}
public class VerifyMessage
{
public string Signature { get; set; }
public string Message { get; set; }
}
Код проверки подписи на нашем сервисе
/// <summary>
/// Проверка подписи JWT в формате HEADER.PAYLOAD.SIGNATURE.
/// </summary>
/// <param name="message">HEADER.PAYLOAD в формате Base64url</param>
/// <param name="signature">SIGNATURE в формате Base64url</param>
bool VerifyRawSignString(string message, string signature)
{
var signerCert = GetEsiaSignerCert();
var messageBytes = Encoding.UTF8.GetBytes(message);
var signatureBytes = Base64UrlEncoder.DecodeBytes(signature);
//Переварачиваем байты, так как используется RAW подпись
Array.Reverse(signatureBytes, 0, signatureBytes.Length);
using (var GostHash = new Gost3411_2012_256CryptoServiceProvider())
{
var csp = (Gost3410_2012_256CryptoServiceProvider) signerCert.PublicKey.Key;
//Используем публичный ключ сертификата для проверки
return csp.VerifyData(messageBytes, GostHash, signatureBytes);
}
}
Код получения сертификата ЕСИА
X509Certificate2 GetEsiaSignerCert()
{
var store = new X509Store(StoreName.AddressBook, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySerialNumber, EsiaCertSerialNumber, false);
var certificate = certificates[0];
return certificate;
}
Здесь используем введённые ранее константы. И Получаем сертификат из доверенных сертификатов.
Отправка токена на проверку
public async Task<bool> ValidatingAccessToken(EsiaAuthToken token)
{
if (token.Payload.ExpireDate <= DateTime.Now ||
token.Payload.BeginDate >= DateTime.Now ||
token.Payload.CreateDate >= DateTime.Now ||
token.Payload.ExpireDate <= token.Payload.BeginDate ||
token.Payload.CreateDate > token.Payload.BeginDate ||
token.Payload.CreateDate > token.Payload.ExpireDate ||
token.Payload.Iss != esiaSettings.Value.ISS ||
token.Payload.ClientId != esiaSettings.Value.ClientId)
{
return false;
}
var client = new HttpClient();
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("signature", token.Signature),
new KeyValuePair<string, string>("message", token.Message)
};
var response = await client.PostAsync($"{cryptoProSettings.Value.BaseUrl}/Verify",
new FormUrlEncodedContent(requestParams));
response.EnsureSuccessStatusCode();
var resultResponse = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<bool>(resultResponse);
return result;
}
Этот код используем в нашем основном сервисе.
Проверяем поля токена на актуальность, чтобы его не могли подделать. А потом уже проверяем подпись токена, как указано в методических указаниях.
Получение данных пользователя из ЕСИА
Имея токен мы может отправить запрос на получение данных о пользователе указанных в scope токена. Пример кода, где мы получаем данные пользователя. Здесь esiaUserId содержится в самом токене, это уникальный идентификатор пользователя ЕСИА. Наш токен указываем в заголовке авторизации.
public async Task<EsiaUser> ExecuteAsync(string esiaUserId, string accessToken)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetStringAsync($"{esiaSettings.Value.EsiaRestUrl}/prns/{esiaUserId}");
var user = JsonConvert.DeserializeObject<EsiaUser>(response);
user.Id = user.Id ?? esiaUserId;
return user;
}
}
Код класса EsiaUser
public class EsiaUser
{
/// <summary>
/// Идентификатор
/// </summary>
[JsonProperty("oid")]
public string Id { get; set; }
/// <summary>
/// Фамилия
/// </summary>
[JsonProperty("firstName")]
public string FirstName { get; set; }
/// <summary>
/// Имя
/// </summary>
[JsonProperty("lastName")]
public string LastName { get; set; }
/// <summary>
/// Отчество
/// </summary>
[JsonProperty("middleName")]
public string MiddleName { get; set; }
/// <summary>
/// Дата рождения
/// </summary>
[JsonProperty("birthdate")]
public string Birthdate { get; set; }
/// <summary>
/// Пол
/// </summary>
[JsonProperty("gender")]
public string Gender { get; set; }
/// <summary>
/// Подтвержден ли пользователь
/// </summary>
[JsonProperty("trusted")]
public bool Trusted { get; set; }
}
Заключение
Наконец мы закончили интеграцию с ЕСИА. Это был длинный путь полный странных вещей. Неясных решений и множество потраченного времени. Надеюсь этой статьёй я помог Вам реализовать задачу интеграции гораздо быстрее и легче. Спасибо за потраченное время.