Привет, habr!
Хочу поделиться опытом работы с API системой маркировки товаров «Честный Знак» (ЧЗ) / МДЛП (маркировка лекарственных препаратов), в части аутентификация и авторизация пользователей.
Не раз сталкивался с интеграцией различных источников данных в аналитические системы, но конкретно эта задача показалась достойной публикации по той причине, что в процессе работы особо не нашлось готовых гайдов в интернете, хотя такой инструмент в бизнесе может быть довольно широко востребован – и при этом его реализация может наткнуться на некоторые подводные камни.
Задача: авторизоваться и забрать в ЧЗ статистические данные (информация о движении единиц продукции между юридическими лицами, статистика конечных продаж с адресами, и тп.)
В рамках данной статьи опускается вопрос о том, что такое система «Честный знак», и каковы основы ее работы – если вы начали читать эту статью, наверняка основы вам уже известны.
Итак, по порядку…
Для работы нам понадобится:
КРИПТОГРАФИЧЕСКОЕ ПО - в нашем случае - КриптоПро. Включает в себя следующие элементы:
КриптоПро CSP 5.0 ссылка https://cryptopro.ru/products/csp
плагин КриптоПро ЭЦП Browser plug-in ссылка https://www.cryptopro.ru/products/cades/plugin
расширение для браузера CryptoPro Extension for CAdES Browser Plug-in
Чтобы проверить, все ли стоит на нашем компе ЧЗ предоставляет небольшую страничку для проверки https://markirovka.crpt.ru/plugins/cryptopro
![](https://habrastorage.org/getpro/habr/upload_files/17d/459/389/17d459389b6f477d9e026a9fd0f18f99.jpg)
Сам сертификат КЭП. И не просто сертификат, а тот который зарегистрирован в ЧЗ.
Ну и Python. Хотя в большинстве своем мы обращаемся к com-Объектам и х можно дергать из любого языка который вам по душе.
Собственно, решение вопросов с КЭП – это основное, чем хочу поделиться в рамках данной статьи.
В нашем примере КЭП уже есть (она не простая, а открепляемая, что позволило установить триальную версию КриптоПро на разработческий контур). При импорте сертификата рекомендую устанавливать его в личное хранилище.
![](https://habrastorage.org/getpro/habr/upload_files/105/8f0/f3e/1058f0f3e16005e37d131ac1eb871cb0.jpg)
После того, как вы установили Сертификат, КриптоПро и все его плагины, было бы не лишним перейти в личный кабинет (ЛК) на сайте, поскольку оттуда нам необходимо взять креды для авторизации. На текущий момент в документации (https://mdlp.crpt.ru/static/document/api_mdlp_ru.pdf) к API сказано, что для авторизации вам необходимо client_id, client_secret, user_id, первые два мы как раз таки смотрим в ЛК в разделе Администрирование – Учетные Системы:
![](https://habrastorage.org/getpro/habr/upload_files/9d3/3ff/b8d/9d33ffb8deb7b2342a387b828f78a35b.jpg)
Рекомендую также скопировать серийный номер, он нам тоже вскоре понадобиться.
Теперь переходим к самому интересному - аутентификация и авторизация. Происходит это чудо в 3 этапа. Сначала мы получаем «код аутентификации» потом подписываем его сертификатом и отправляем обратно в ЧЗ и потом можем получить авторизационный токен.
Отдельно отметим, что в документации к API ЧЗ нет ни слова о том, как подписывать этот «код аутентификации».
Тут нам пришлось идти в документацию от КриптоПро https://docs.cryptopro.ru/cades/usage
И так если ваш проект на Ubuntu вам повезло для вас сделали «Сборку расширения для языка Python», что там они насобирали и работает ли оно я не проверял. Ну а если вы на Windows далее актуально для вас.
В документации от КриптоПро есть раздел «Интерфейс COM» он то нам и нужен там даже есть примеры на VBScript, которые помогли нам отчасти. Для авторизации на нужны следующие объекты:
"CAdESCOM.Store" – поможет в поиске нужного сертификата в хранилище сертификатов
"CAdESCOM.CPSigner" – объект для подписания
"CAdESCOM.CPAttribute" – объект который добавляет атрибут к подписи
"CAdESCOM.CadesSignedData" – объект усовершенствованной подписи
Для работы нам нужно установить библиотеку win32com, установка pywin32, которую написал Mark Hammond храни его Господь. Еще одна рекомендация: держите под рукой ссылку на страницу Tim Golden http://timgolden.me.uk/python/ на этом ресурсе есть еще много подробностей о том, как использовать Python в Windows для автоматизации и других административных задач.
И так по порядку что нужно сделать чтобы получить токен или, как он называется в документации ЧЗ, «Ключ сессии»
Импортируем нужные библиотеки
import win32com.client
import os, sys
import json
import datetime
import base64
Объявляем константы для методов КриптоПро
CADES_BES = 1
CADES_DEFAULT = 0
CAPICOM_ENCODE_BASE64 = 0
CAPICOM_CURRENT_USER_STORE = 2
CAPICOM_MY_STORE = 'My'
CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED = 2
Ищем нужный сертификат в хранилище. Тут то нам и нужно передать, сохранённый ранее, серийный номер
oStore = win32com.client.Dispatch("CAdESCOM.Store")
oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED)
for val in oStore.Certificates:
if val.SerialNumber == sSerialNumber:
oCert = val
oStore.Close
Вызываем объект для подписания и объект передачи атрибутов
oSigner = win32com.client.Dispatch(CAdESCOM.CPSigner")
oSigner.Certificate = oCert
oSigningTimeAttr = win32com.client.Dispatch("CAdESCOM.CPAttribute")
oSigningTimeAttr.Name = 0
oSigningTimeAttr.Value = datetime.datetime.now()
oSigner.AuthenticatedAttributes2.Add(oSigningTimeAttr)
Получаем код аутентификации, а вот тут важно заменить особенность что нам не удалось достучаться до ЧЗ через всем известную библиотеку requests, на этом этапе мы потеряли немало времени, пока не догадались, что нужно использовать COM объекты Windows "WinHTTP.WinHTTPRequest.5.1"
url = "https://api.mdlp.crpt.ru/api/v1/auth"
params = {
'client_id': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'client_secret':'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'user_id':'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'auth_type':'SIGNED_CODE'
}
win_http = win32com.client.Dispatch('WinHTTP.WinHTTPRequest.5.1')
win_http.Open("POST", url, False)
win_http.SetRequestHeader("Content-Type","application/json;charset=UTF-8")
win_http.SetRequestHeader("Accept","application/json;charset=UTF-8")
win_http.Send(json.dumps(params))
win_http.WaitForResponse()
print(win_http.ResponseText)
items = json.loads(win_http.ResponseText)
CodeAuth = items['code']
Далее вызываем объект усовершенствованной подписи и её создание. Данный пункт у нас тоже отнял не мало сил и поэтому я перечислю наши ошибки, чтобы вы их не повторили:
Обязательно убедитесь, что вы передали в переменную из полученного ответа сам код, а не весь json, потому что вы с легкостью подпишете его, а потом в ответ раз за разом вам будет прилетать ошибка авторизации.
Обязательно закодируйте в base64 код
И проверьте настройки времени, которые атрибутом добавляются к подписи на предыдущем шаге
oSignedData = win32com.client.Dispatch("CAdESCOM.CadesSignedData")
oSignedData.ContentEncoding = 1
message = CodeAuth
message_bytes = message.encode('ascii')
base64_bytes = base64.b64encode(message_bytes)
base64_message = base64_bytes.decode('ascii')
oSignedData.Content = base64_message
sSignedData = oSignedData.SignCades(oSigner, CADES_BES, False, CAPICOM_ENCODE_BASE64)
Ну и заключительный шаг - получение ключа сессии
url = "https://api.mdlp.crpt.ru/api/v1/token"
paramskey ={
'code': CodeAuth,
'signature': sSignedData
}
print(json.dumps(paramskey))
win_http.Open("POST", url, False)
win_http.SetRequestHeader("Content-Type","application/json;charset=UTF-8")
win_http.SetRequestHeader("Accept","application/json;charset=UTF-8")
win_http.Send(json.dumps(paramskey))
win_http.WaitForResponse()
print(win_http.ResponseText)
Если в результате вы получили:
{"token":"0d90d966-5027-416f-a0cd-0697db8c79f3","life_time":600}
- поздравляю, вы сделали все верно!
В данной статье не стоит цель переписать всю документацию КриптоПро и ЧЗ, а только рассказать о решении тех проблем, которые заняли у нас больше всего времени. Если у кого-то остались вопросы – буду рад ответить в комментариях.