Токены PKCS#11: сертификаты и закрытые ключи
Встает дилемма, как определить какой закрытый ключ (да и открытый тоже) соответствует тому или иному сертификату.
Такое соответствие, как правило, устанавливается путем задание идентичных параметров CKA_ID и/или CKA_LABEL для тройки объектов: сертификата (CKO_CERTIFICATE), публичного ключа (CKO_PUBLIC_KEY) и приватного ключа (CKO_PRIVATE_KEY).
Возникает вопрос — как задавать эти значения, чтобы, по крайней мере, не возникла коллизия, и насколько это безопасно с точки зрения получения корректного результата.
Наиболее распространенный способ задания CKA_ID — это использование значения хэш-функции от значения открытого ключа. Именно такой способ для связывания тройки объектов используется в проекте NSS (Network Security Services) и браузере Redfox. При этом в качестве хэш-функции используется алгоритм SHA1. С учетом того, что на токене реально будет храниться едва ли больше десятка личных сертификатов, то с точки зрения появления коллизии этот способ является хорошим. Вместе с тем CKA_ID для этой тройки могут устанавливаться в любой момент и любое значение. Именно в этом и состоит вся проблема. Если бы RFC или Рекомендации ТК-26 требовали установки параметра CKA_ID в момент появления объекта на токене (например, при генерации ключевой пары CKM_GOSTR3410_KEY_GEN_PAIR) и его нельзя было бы изменить, то на этом данное повествование можно было бы завершить. К сожалению, это не так. Как уже было сказано, CKA_ID можно установить в любой момент с любым значением. Таким образом, всегда существует вероятность, что сертификат окажется связанным с чужим приватным ключом. Не нужно объяснять, к каким это приведет последствиям.
А вообще, существует ли строгий математический алгоритм, который позволяет связать тройку CKO_CERTIFICATE x CKO_PRIVATE_KEY x CKO_PUBLIC_KEY в единое целое?
Да, такой алгоритм на базе криптографических механизмов (CKM_) токена существует. Связка сертификата и публичного ключа проверяется легко и просто. Берутся значение открытого ключа и его параметров из сертификата и сравниваются и аналогичными значениями публичного ключа.
Что касается сертификата и приватного ключа, то до недавнего времени этот алгоритм выглядел следующим образом. С помощью приватного ключа формируется подпись под некоторым текстом (например, «поиск закрытого ключа»), а затем с помощью открытого ключа, полученного из сертификата, проверяется корректность полученной подписи. Если подпись корректна, значит, мы получили закрытый ключ для выбранного сертификата. Если нет, то выбирается следующий закрытый ключ на токене.
Все, мы теперь не зависим ни от CKA_ID, ни от CKA_LABEL.
Но вот появляется документ «МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ. Расширение PKCS#11 для использования российских стандартов ГОСТ Р 34.10–2012, ГОСТ Р 34.11–2012, ГОСТ Р 34.12–2015 и ГОСТ Р 34.13–2015», в котором появляется новый механизм CKM_GOSTR3410_PUBLIC_KEY_DERIVE — механизм создания открытого ключа из закрытого. Данный механизм используется в C_DeriveKey. Теперь поиск закрытого ключа для сертификата значительно упрощается. Достаточно получить список закрытых ключей на токене, затем для каждого закрытого ключа получить открытый ключ:
…
CK_OBJECT_HANDLE priv_key = CK_INVALID_HANDLE;
CK_OBJECT_HANDLE pub_key = CK_INVALID_HANDLE;
CK_MECHANISM mechanism_der_desc =
{ CKM_GOSTR3410_PUBLIC_KEY_DERIVE, NULL, 0 };
CK_MECHANISM_PTR mechanism_der = &mechanism_der_desc;
…
// Получаем открытый ключ по закрытому
rc = funcs->C_DeriveKey(sess, mechanism_der, priv_key,
NULL, 0, &pub_key);
…
А далее сравниваем значения полученного публичного ключа, со значениями публичного ключа в сертификате.
Применение любого из этих алгоритмов избавляет от необходимости следить за значениями CKA_ID/CKA_LABEL и делает использованием сертификатов и приватных ключей, хранящихся на токенах PKCS#11, и надежным и безопасным.
Использование механизма CKM_GOSTR3410_PUBLIC_KEY_DERIVE предполагает его реализацию на том или другом токене. Посмотреть список реализованных механизмов удобно с помощью доступной для свободного скачивания утилиты p11conf:
$ /usr/local/bin64/p11conf -h
usage: /usr/local/bin64/p11conf [-hitsmIupPred] -A APIpath [-c slotID -U userPin -S SOPin -n newPin -L label]
-h display usage
-i display PKCS#11 library info
-s display slot(s) info (-c slotID is optional)
-t display token(s) info (-c slotID is optional)
Others must use -c slotID
-m display mechanism list
-I initialize token
-u initialize user PIN
-p set the user PIN
-P set the SO PIN
-r remove all objects
-e enumerate objects
-d dump all object attributes
Copyright(C) LISSI-Soft Ltd (http://soft.lissi.ru) 2011-2016
$
Список доступных механизмов можно посмотреть следующим образом:
$ /usr/local/bin64/p11conf -A /usr/local/lib64/libls11usb2016.so -m -c 0|grep GOSTR3410
Mechanism: CKM_GOSTR3410_KEY_PAIR_GEN (0x1200)
Mechanism: CKM_GOSTR3410_512_KEY_PAIR_GEN (0xD4321005)
Mechanism: CKM_GOSTR3410 (0x1201)
Mechanism: CKM_GOSTR3410_512 (0xD4321006)
Mechanism: CKM_GOSTR3410_WITH_GOSTR3411 (0x1202)
Mechanism: CKM_GOSTR3410_WITH_GOSTR3411_12_256 (0xD4321008)
Mechanism: CKM_GOSTR3410_WITH_GOSTR3411_12_512 (0xD4321009)
Mechanism: CKM_GOSTR3410_DERIVE (0x1204)
Mechanism: CKM_GOSTR3410_12_DERIVE (0xD4321007)
Mechanism: CKM_GOSTR3410_KEY_WRAP (0x1203)
Mechanism: CKM_GOSTR3410_PUBLIC_KEY_DERIVE (0xD432100A)
Mechanism: CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE (0xD4321037)
$
И последнее, есть ли сегодня токены, разработанные в соответствии с документом «МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ. Расширение PKCS#11 для использования российских стандартов ГОСТ Р 34.10–2012, ГОСТ Р 34.11–2012, ГОСТ Р 34.12–2015 и ГОСТ Р 34.13–2015»?
Да, есть, это семейство токенов LS11.