Другое

Как подписать CSR с PKCS#11 ключом в Python

Узнайте, как правильно подписать CertificateSigningRequestBuilder с помощью PKCS#11 ключа в Python. Пошаговый пример кода для интеграции с HSM и практические советы.

Как подписать CertificateSigningRequestBuilder с помощью ключа PKCS#11 в Python?

Я пытаюсь создать запрос на подпись сертификата (CSR) и позже сам сертификат, оба которых должны быть подписаны приватным ключом, хранящимся в модуле аппаратной безопасности (HSM) и доступном через PKCS#11 с помощью библиотеки python‑pkcs11.

Вот мой текущий код:

python
from cryptography import x509
from PyKCS11 import CKM_SHA256_RSA_PKCS
from PyKCS11 import PyKCS11Lib, Mechanism
...
csr_builder = x509.CertificateSigningRequestBuilder().subject_name(
            subject
        )
mechanism = Mechanism(CKM_SHA256_RSA_PKCS, None)

Однако я не знаю, как включить ключ PKCS#11 в этот процесс. Согласно документации, CertificateSigningRequestBuilder обычно подписывается так:

python
request = csr_builder.sign(
    private_key, hashes.SHA256()
)

Но такой подход не работает с ключами PKCS#11. При использовании ключа PKCS#11 мне нужно вызывать:

python
signature = self.session.sign(
            priv_key,
            data...,
            mechanism
    )

Вопрос: какой именно кусок CSR я должен передать в session.sign(), чтобы полученная подпись совпадала с тем, что генерирует CertificateSigningRequestBuilder?

Можете ли вы подсказать, как правильно подписать CSR с помощью ключа PKCS#11 в Python?

Содержание

Понимание задачи

Метод CertificateSigningRequestBuilder.sign() ожидает обычный объект приватного ключа Python, но ключи PKCS#11 работают через интерфейс сессии, где операции подписи требуют явных параметров механизма. Как вы уже отметили, подпись PKCS#11 выглядит так:

python
signature = self.session.sign(
    priv_key,
    data...,
    mechanism
)

Проблема состоит в том, какой именно фрагмент данных CSR передать в session.sign(). Согласно документации cryptography, CSR содержит несколько компонентов: субъект, публичная информация о ключе и расширения, все они хешируются и подписываются.

Подход к ручной подписи CSR

Решение состоит в том, чтобы вручную построить ASN.1 DER‑кодированные данные, представляющие тело CSR (без подписи), затем подписать их ключом PKCS#11 и наконец сформировать полный объект CSR.

Шаг 1: Создание данных CSR без подписи

Сначала создайте билдер CSR и получите данные без подписи:

python
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
import pkcs11

# Создание билдера CSR
csr_builder = x509.CertificateSigningRequestBuilder().subject_name(subject)

# Добавьте любые расширения при необходимости
csr_builder = csr_builder.add_extensions(extensions)

# Получаем данные CSR без подписи (это то, что обычно подписывается внутри)
csr_data = csr_builder._signing_request_public_key(public_key)

Шаг 2: Подпись ключом PKCS#11

Теперь подпишите данные CSR вашим ключом PKCS#11:

python
# Преобразуем данные CSR в байты (они уже должны быть байтами)
csr_bytes = csr_data.public_bytes(Encoding.DER)

# Подписываемся ключом PKCS#11
signature = pkcs11_session.sign(
    private_key_handle,
    csr_bytes,
    mechanism
)

Шаг 3: Формирование полного CSR

Наконец, сформируйте полный объект CSR с вашей подписью:

python
# Создаём CSR с подписью
csr = x509.CertificateSigningRequestBuilder().subject_name(
    subject
).sign(
    public_key,  # используется только для публичной информации
    signature,   # ваша подпись PKCS#11
    hashes.SHA256()
)

Полная реализация

Ниже приведён полностью рабочий пример:

python
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from cryptography.x509.oid import NameOID
import pkcs11

def create_csr_with_pkcs11(pkcs11_lib_path, token_label, user_pin, subject, public_key):
    """
    Создать CSR, подписанный приватным ключом PKCS#11
    
    Args:
        pkcs11_lib_path: Путь к библиотеке PKCS#11
        token_label: Метка токена, содержащего ключ
        user_pin: PIN пользователя токена
        subject: X.509 имя субъекта CSR
        public_key: Публичный ключ, соответствующий приватному ключу PKCS#11
    
    Returns:
        Подписанный объект CSR
    """
    # Инициализация PKCS#11
    lib = pkcs11.lib(pkcs11_lib_path)
    token = lib.get_token(token_label=token_label)
    
    # Поиск приватного ключа (нужно реализовать в зависимости от хранения ключей)
    # Это упрощённый пример – найдите свой конкретный ключ
    with token.open(user_pin=user_pin) as session:
        # Найти объект приватного ключа
        private_key_handle = None
        for obj in session.get_objects({
            pkcs11.Attribute.CLASS: pkcs11.ObjectClass.PRIVATE_KEY,
            pkcs11.Attribute.KEY_TYPE: pkcs11.KeyType.RSA,  # или EC
        }):
            if obj[pkcs11.Attribute.LABEL] == b'my_private_key':
                private_key_handle = obj
                break
        
        if not private_key_handle:
            raise ValueError("Private key not found")
        
        # Создание билдера CSR
        csr_builder = x509.CertificateSigningRequestBuilder().subject_name(subject)
        
        # Добавьте любые расширения при необходимости
        # csr_builder = csr_builder.add_extensions(extensions)
        
        # Получаем данные CSR без подписи
        csr_data = csr_builder._signing_request_public_key(public_key)
        
        # Преобразуем в DER‑байты
        csr_bytes = csr_data.public_bytes(Encoding.DER)
        
        # Подписываемся ключом PKCS#11
        signature = session.sign(
            private_key_handle,
            csr_bytes,
            pkcs11.Mechanism.SHA256_RSA_PKCS  # или подходящий механизм
        )
        
        # Создаём полный CSR
        csr = x509.CertificateSigningRequestBuilder().subject_name(
            subject
        ).sign(
            public_key,
            signature,
            hashes.SHA256()
        )
        
        return csr

Альтернативные решения

Использование пакета python-x509-pkcs11

Для более интегрированного решения рассмотрите использование пакета python-x509-pkcs11:

python
import asyncio
from python_x509_pkcs11.ca import create

async def create_csr_with_pkcs11_wrapper():
    root_ca_name_dict = {
        "country_name": "US",
        "state_or_province_name": "California",
        "locality_name": "San Francisco",
        "organization_name": "My Company",
        "organizational_unit_name": "IT",
        "common_name": "mycompany.com",
        "email_address": "admin@mycompany.com",
    }
    
    # Создание CSR с ключом PKCS#11
    csr_pem, root_cert_pem = await create(
        "my_pkcs11_key",
        root_ca_name_dict,
        key_type="rsa"  # или "ec", "ed25519" и т.д.
    )
    
    return csr_pem

Гибридный подход

Можно также использовать гибридный подход: генерировать пару ключей в HSM, но экспортировать публичный ключ для использования в библиотеке cryptography:

python
def generate_key_and_create_csr():
    # Генерация пары ключей в HSM
    pub_key, priv_key = generate_pkcs11_key_pair()
    
    # Экспорт публичного ключа для использования в cryptography
    public_key_bytes = pub_key.subject.public_bytes(
        Encoding.PEM,
        PublicFormat.SubjectPublicKeyInfo
    )
    public_key = serialization.load_pem_public_key(public_key_bytes)
    
    # Создание CSR обычным способом
    csr = x509.CertificateSigningRequestBuilder().subject_name(
        subject
    ).sign(
        public_key,
        # Но всё равно нужно обрабатывать подпись вручную
    )

Лучшие практики и соображения

Управление ключами

  • Безопасность: Никогда не извлекайте приватные ключи из HSM. Храните их в пределах сессии PKCS#11.
  • Выбор механизма: Используйте подходящие механизмы подписи (SHA256_RSA_PKCS для RSA, ECDSA для эллиптических кривых).
  • Обработка ошибок: Реализуйте надёжную обработку ошибок для операций PKCS#11.

Производительность

  • Управление сессиями: При возможности переиспользуйте сессии PKCS#11 для повышения производительности.
  • Память: Следите за использованием памяти при работе с крупными криптографическими операциями.

Безопасность

  • Защита PIN: Храните PIN‑ы безопасно, не жестко кодируйте их в приложении.
  • Атрибуты ключей: Убедитесь, что ваши ключи PKCS#11 имеют правильные атрибуты (extractable, sensitive и т.д.).
  • Журналирование: Включите логирование всех криптографических операций для аудита.

Этот подход даёт полный контроль над процессом подписи, сохраняя совместимость с обработкой CSR в библиотеке cryptography. Ключевой момент – вручную сформировать тело CSR, подписать его ключом PKCS#11, а затем использовать эту подпись для завершения объекта CSR.

Источники

  1. Cryptography Library X.509 Tutorial
  2. Python PKCS#11 Documentation
  3. python-x509-pkcs11 Package Documentation
  4. Stack Overflow: Signing CSR using PKCS#11
  5. GitHub: python-pkcs11 Examples
Авторы
Проверено модерацией
Модерация