Как правильно оформить данные с помощью открепленной подписи для заказа контрольного знака СУЗ?

Здравствуйте. Я создаю этот вопрос, чтобы в интернете появилась информация о том, как подписывать документы открепленной подписью для Честного Знака на Python. На Хабре уже имеется статья о подписании прикрепленной подписью, но я не смог найти информацию об открепленной. Буду рад, если это кому-то поможет, и было бы замечательно, если кто-то распространит эти сведения в нужных сообществах. 

Итак, я работаю с API Честного Знака СУЗ и мне нужно создать заказ на эмиссию кодов. В заголовок запроса необходимо добавить параметр X-Signature - открепленная подпись в кодировке Base64. 

Однако я столкнулся с проблемами: каждый раз приходит ответ с кодом 200, а статус заказа показывает "Подпись не прошла проверку". В итоге мне удалось решить проблему, и я собираюсь выложить код в его текущем виде. Вы можете критиковать его стиль, но главное — это работает! 

Вот код: 

```python
import win32com.client
import base64
import pythoncom
import json
import requests

def signed_data(_data):
    # Функция подписывает данные открепленной подписью
    # Возвращает подписанные данные и открепленную подпись
    CADES_BES = 1
    CAPICOM_ENCODE_BASE64 = 0
    CAPICOM_CURRENT_USER_STORE = 2
    CAPICOM_MY_STORE = "My"
    CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED = 2
    sSerialNumber = "SERIAL_NUMBER"  # ID ключа, берется из крипто-про
    # Инструменты КриптоПро -> контейнеры -> Найти нужный ключ (не просроченный, видно при тестировании контейнера) -> Протестировать контейнер -> серийный номер

    # Ищем сертификат в хранилище
    oCert = None
    oStore = win32com.client.Dispatch("CAdESCOM.Store", pythoncom.CoInitialize())
    oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STOR
```
  • 15 ноября 2024 г. 11:03
Ответы на вопрос 2
Для оформления открепленной подписи к заказу контрольного знака в системе "Честный Знак" (СУЗ) с использованием Python, необходимо правильно сформировать и подписать данные. Ваш код уже имеет хорошую основу, и я постараюсь дополнить его и предоставить окончательную версию.

Вот шаги, которые необходимо выполнить для корректного создания открепленной подписи:

1. Убедиться, что у вас установлены необходимые библиотеки (например, `pywin32` для работы с компонентами Windows и `requests` для отправки HTTP-запросов).

2. Сформировать JSON-данные для запроса.

3. Подписать данные с помощью открепленной подписи.

4. Отправить запрос с подписью на сервер.

Вот полный пример кода, который демонстрирует вышеуказанные шаги:

```python
import win32com.client
import base64
import pythoncom
import json
import requests

def signed_data(data):
    # Функция подписывает данные открепленной подписью
    # Возвращает подписанные данные и открепленную подпись
    CADES_BES = 1
    CAPICOM_ENCODE_BASE64 = 0
    CAPICOM_CURRENT_USER_STORE = 2
    CAPICOM_MY_STORE = "My"
    
    sSerialNumber = "YOUR_SERIAL_NUMBER"  # Замените на реальный серийный номер

    # Ищем сертификат в хранилище
    oStore = win32com.client.Dispatch("CAdESCOM.Store", pythoncom.CoInitialize())
    oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE)

    oCert = None
    for cert in oStore.Certificates:
        if cert.SerialNumber == sSerialNumber:
            oCert = cert
            break

    if oCert is None:
        raise ValueError("Certificate not found.")

    # Создаем объект для подписи
    oSigner = win32com.client.Dispatch("CAdESCOM.Signer", pythoncom.CoInitialize())
    oSigner.Certificate = oCert

    oCades = win32com.client.Dispatch("CAdESCOM.CadesSignature", pythoncom.CoInitialize())
    oCades.SetContent(data)
    oSignature = oCades.Sign(oSigner, CADES_BES)

    # Получаем открепленную подпись
    detached_signature = base64.b64encode(oSignature).decode('utf-8')
    return detached_signature


# Основная функция для заказа контрольного знака
def create_order(data):
    # JSON данные для запроса
    json_data = json.dumps(data)
    
    # Подписываем данные
    signature = signed_data(json_data)

    # URL для API Честного Знака
    url = "https://api.chestnyznak.ru/orders"
    
    # Заголовки запроса
    headers = {
        "Content-Type": "application/json",
        "X-Signature": signature,
    }

    # Отправка запроса
    response = requests.post(url, headers=headers, data=json_data)

    # Проверка ответа
    if response.status_code == 200:
        print("Заказ успешно создан:", response.json())
    else:
        print("Ошибка при создании заказа:", response.status_code, response.text)


# Пример использования
if __name__ == "__main__":
    order_data = {
        "order": {
            "items": [
                {
                    "gtin": "1234567890123",
                    "quantity": 1,
                    # Добавьте остальные параметры заказа
                }
            ]
        }
    }
    create_order(order_data)
```

### Объяснение работы кода:

1. **Подписывание данных**: Используется метод `sign_data`, чтобы создать открепленную подпись для переданных данных.
2. **Поиск сертификата**: Код ищет сертификат в хранилище Windows по серийному номеру. Убедитесь, что серийный номер правильный.
3. **Отправка запроса**: Данные и подпись отправляются на сервер API Честного Знака через HTTP POST запрос.

### Замечания:
- Убедитесь, что у вас есть доступ к Windows API и установлен криптографический компонент КриптоПро.
- Если у вас возникают ошибки, проверьте правильность серийного номера и настройки получаемых данных для подписи.
- Не забудьте заполнить `YOUR_SERIAL_NUMBER` правильным серийным номером ключа.
- Убедитесь, что ваш сертификат не просрочен. 

Надеюсь, это поможет вам и другим пользователям, которые сталкиваются с аналогичными задачами.
Код который у меня работает! Пользуйтесь на здоровье! 

import win32com.client
import base64
import pythoncom
import json
import requests


def signed_data(_data):
    # Функция подписывает данные открепленной подписью
    # Возращает обратно данные которые подписаны, и открепленную подпись
    CADES_BES = 1
    CAPICOM_ENCODE_BASE64 = 0
    CAPICOM_CURRENT_USER_STORE = 2
    CAPICOM_MY_STORE = "My"
    CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED = 2
    sSerialNumber = "SERIAL_NUMBER"  # ID ключа, берется из крипто-про
    # Инструменты криптопро -> контейнеры -> Найти нужный ключ (не просроченный, видно при тестировании контейнера) -> Протестировать контейнер -> серийный номер

    # Ищем сертификат в хранилище
    oCert = None
    oStore = win32com.client.Dispatch("CAdESCOM.Store", pythoncom.CoInitialize())
    oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED)
    for val in oStore.Certificates:
        if val.SerialNumber.upper() == sSerialNumber.upper():
            oCert = val
    oStore.Close
    if not oCert:
        print("Не найден сертификат в хранилище")
        return None

    oSigner = win32com.client.Dispatch("CAdESCOM.CPSigner", pythoncom.CoInitialize())
    oSigner.Certificate = oCert

    # Строка JSON БЕЗ ПРОБЕЛОВ
    message = _data
    message_bytes = message.encode()
    base64_bytes = base64.b64encode(message_bytes)
    base64_message = base64_bytes.decode()

    signedData = win32com.client.Dispatch("CAdESCOM.CadesSignedData", pythoncom.CoInitialize())
    signedData.ContentEncoding = 1
    signedData.Content = base64_message
    sSignedData = signedData.SignCades(oSigner, CADES_BES, True, CAPICOM_ENCODE_BASE64)

    # Удаляем из подписи символы переноса строки, иначе не вставить в заголовок запроса
    sSignedData = sSignedData.replace('\r', '')
    sSignedData = sSignedData.replace('\n', '')

    return _data, sSignedData


# token выдается при авторизации по URL <url стенда>/auth/simpleSignIn/{omsConnection}
# Статья есть на хабре.
headers = {"clientToken": "token", "Accept": "application/json", 'Content-type': 'application/json'}
# oms_id - Находим в личном кабинете - управление заказами - устройства - OMS ID:
query_params = {"omsId": "oms_id"}
# Заполнение поля products я пропущу, оно хорошо описано в документации к СУЗ
body = {
    "productGroup": "lp",
    "attributes": {
        "releaseMethodType": "PRODUCTION",
        "createMethodType": "SELF_MADE",
    },
    "products": [],
}

# Переводим словарь в JSON БЕЗ ПРОБЕЛОВ!
json_str = json.dumps(body, separators=(',', ':'))
sData, signed = signed_data(json_str)  # получаем подпись

headers["X-Signature"] = signed

response = requests.post(f"https://suz.sandbox.crptech.ru/api/v3/order", params=query_params, headers=headers, data=sData)
if response.status_code == 200:
    response.json()
Похожие вопросы