Как подписать CSR с PKCS#11 ключом в Python
Узнайте, как правильно подписать CertificateSigningRequestBuilder с помощью PKCS#11 ключа в Python. Пошаговый пример кода для интеграции с HSM и практические советы.
Как подписать CertificateSigningRequestBuilder с помощью ключа PKCS#11 в Python?
Я пытаюсь создать запрос на подпись сертификата (CSR) и позже сам сертификат, оба которых должны быть подписаны приватным ключом, хранящимся в модуле аппаратной безопасности (HSM) и доступном через PKCS#11 с помощью библиотеки python‑pkcs11.
Вот мой текущий код:
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 обычно подписывается так:
request = csr_builder.sign(
private_key, hashes.SHA256()
)
Но такой подход не работает с ключами PKCS#11. При использовании ключа PKCS#11 мне нужно вызывать:
signature = self.session.sign(
priv_key,
data...,
mechanism
)
Вопрос: какой именно кусок CSR я должен передать в session.sign(), чтобы полученная подпись совпадала с тем, что генерирует CertificateSigningRequestBuilder?
Можете ли вы подсказать, как правильно подписать CSR с помощью ключа PKCS#11 в Python?
Содержание
- Понимание задачи
- Подход к ручной подписи CSR
- Полная реализация
- Альтернативные решения
- Лучшие практики и соображения
Понимание задачи
Метод CertificateSigningRequestBuilder.sign() ожидает обычный объект приватного ключа Python, но ключи PKCS#11 работают через интерфейс сессии, где операции подписи требуют явных параметров механизма. Как вы уже отметили, подпись PKCS#11 выглядит так:
signature = self.session.sign(
priv_key,
data...,
mechanism
)
Проблема состоит в том, какой именно фрагмент данных CSR передать в session.sign(). Согласно документации cryptography, CSR содержит несколько компонентов: субъект, публичная информация о ключе и расширения, все они хешируются и подписываются.
Подход к ручной подписи CSR
Решение состоит в том, чтобы вручную построить ASN.1 DER‑кодированные данные, представляющие тело CSR (без подписи), затем подписать их ключом PKCS#11 и наконец сформировать полный объект CSR.
Шаг 1: Создание данных CSR без подписи
Сначала создайте билдер CSR и получите данные без подписи:
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:
# Преобразуем данные CSR в байты (они уже должны быть байтами)
csr_bytes = csr_data.public_bytes(Encoding.DER)
# Подписываемся ключом PKCS#11
signature = pkcs11_session.sign(
private_key_handle,
csr_bytes,
mechanism
)
Шаг 3: Формирование полного CSR
Наконец, сформируйте полный объект CSR с вашей подписью:
# Создаём CSR с подписью
csr = x509.CertificateSigningRequestBuilder().subject_name(
subject
).sign(
public_key, # используется только для публичной информации
signature, # ваша подпись PKCS#11
hashes.SHA256()
)
Полная реализация
Ниже приведён полностью рабочий пример:
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:
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:
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.