PKCS#11 и OpenSSL имеют разный формат представления объектов. Так, они по-разному представляют ECDSA ключи и подписи на них. Поэтому, когда мы хотим проверить в OpenSSL сырую ECDSA подпись полученную через PKCS#11, ее необходимо сконвертировать в OpenSSL формат. Для проверки подписи нужно также сконвертировать и открытый ключ.
Ручной конвертации можно избежать воспользовавшись OpenSSL движком rtengine. Он предназначен для работы с объектами на токене средствами OpenSSL. Но не во всех задачах использование OpenSSL приемлемо, поэтому мы написали статью, как конвертировать объекты вручную.
Конвертация открытого ключа
Описание формата открытого ключа в PKCS#11
Открытый ключ
Библиотека PKCS#11 возвращает открытый ключ ECDSA в через поле атрибут CKA_EC_POINT в формате OCTET_STRING. Значение атрибута – это число представленное в виде TLV структуры – OCTET_STRING:
- T – тег типа размером один байт. В нашем случае это 04 – OCTET_STRING.
- L – длина значения V. Она может быть представлена в виде нескольких байт. Если длина не превышает 7F байт, то L состоит из одного байта. Если больше 7F байт, то старший бит устанавливается в 0, а младшие 7 бит определяют длину L. Например, L=7A – длина 0x7A (122). L=81 80 – длина 0x80(128). L=82 7F FF – длина 0x7FFF(32767).
- V – значение размером L байт.
Пример CKA_EC_POINT
Снизу представлен пример TLV структуры открытого ключа. Первый байт 04 – это тег OCTET_STRING. Второй – длина открытого ключа (64 байта). Последующие 64 байта – значение ключа.
04 41 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67
...
Описание формата открытого ключа в OpenSSL
OpenSSL позволяет представлять открытый ECDSA ключ в виде ASN.1 структуры. Эта структура хранит не только значение открытого ключа, но и его параметры.
Пример открытого ключа в формате OpenSSL:
Code Block | ||||
---|---|---|---|---|
| ||||
30 56 -- SEQUENCE. первый байт -- тег типа (30), второй -- длина значения (56).
30 10 -- SEQUENCE
06 07 2A 86 48 CE 3D 02 01 -- OBJECT IDENTIFIER. Идентифицирует типа ключа. В нашем случае это ecPublicKey (открытый ключ эл. кривой).
06 05 2B 81 04 00 0A -- OBJECT IDENTIFIER. Идентифицирует параметры ключа. В нашем случае это secp256k1 (параметр эл. кривой)
03 42 00 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67 -- BIT STRING. Значение открытого ключа |
Описание процесса конвертации
Для того, чтобы сконцентрировать ECDSA ключ из PKCS#11 формата, нужно:
- Создать SEQUENCE со значениями типа OBJECT IDENTIFIER. Первый элемент – OID типа открытого ключа на эллиптической кривой. Второй – OID параметра эл. кривой.
- Сконвертировать открытый ключ из структуры OCTET_STRING в структуру BIT_STRING.
- Положить результаты в SEQUENCE. Нужно проставить правильную длину выходной последовательности.
Конвертация из OCTET_STRING происходит следующим образом
- Тег 04 заменяется на 03 (тег BIT STRING).
- После байтов длины (L) ставится байт 00.
- L увеличивается на 1.
Пример конвертации
Для открытого ключа сверху
разбор по частям
04 41 04 (открытый ключ - 64 байта)
04 - тег
41 - размер
04 - тег
далее - открытый ключ
Что нужно сделать
- добавить параметры эллиптической кривой
- добавить OID открытого ключа
- изменить первый тег 04 на 03
- добавить 00 байт перед вторым тегом 04
- увеличить размер ключа на единицу (до 42)
Что должно получиться
...
- На первом этапе сформируем последовательность из идентификаторов типа открытого ключа и параметра эл. кривой:
- 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A
...
- 30 - тег
56 - размер
30 - тег
- 10 - размер
- 06 - тег
- 07 - размер
- 2A 86 48 CE 3D 02 01 - id-ecPublicKey
- 06 - тег
- 05 - размер
- 2B 81 04 00 0A - secp256k1
03 - тег
42 - размер
00 04 - магия
Пример правильной структуры
30 56 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A 03 42 00 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67
Формат файла
Для утилиты openssl нужен открытый ключ в формате PEM
Чтобы перевести в PEM формат нужно выполнить команду
openssl ec -pubin -inform DER -in pubkey.der -outform PEM -out pubkey.pem
Проверка открытого ключа
openssl ec -check -pubin -in pubkey.pem
Команда напишет EC Key valid. если открытый ключ корректный
Подпись
Библиотека PKCS#11 возвращает подпись через буфер в функции С_Sign в виде R|S
Пример R|S подписи
EC 8D 6D 05 96 B9 8A AE 04 F6 AE 83 D8 04 99 FA D4 A3 EA 37 86 95 A9 DC 0D 3F 09 7F 76 5A F8 40 AA 09 E6 8D D6 56 82 02 4B CF 15 78 21 A6 23 75 81 6C 93 51 90 ED 58 91 33 6E E2 91 4D 57 B5 50
Первые 32 байта - R, остальные 32 байта - S
Что нужно сделать
- добавить тег 30
- добавить размер 44, 45 или 46
- к каждой из половинок добавить тег 02
- к каждой из половинок добавить длину (размер) 20 или 21
- в зависимости от первого байта R и S, если первый байт больше 7F то нужно добавить лишний 00 байт в начало строки и увеличить длину на 1
Что должно получиться
30 46 02 21 00 (R - 32 байта) 02 21 00 (S - 32 байта)
30 - тег
46 - размер
02 - тег
21 - размер
00 - нулевой байт (добавлен так как первый байт R - ЕС, а ЕС > 7F)
02 - тег
21 - размер
00 - нулевой байт (добавлен так как первый байт S - AA, а AA > 7F)
Пример правильной структуры
30 46 02 21 00 EC 8D 6D 05 96 B9 8A AE 04 F6 AE 83 D8 04 99 FA D4 A3 EA 37 86 95 A9 DC 0D 3F 09 7F 76 5A F8 40 02 21 00 AA 09 E6 8D D6 56 82 02 4B CF 15 78 21 A6 23 75 81 6C 93 51 90 ED 58 91 33 6E E2 91 4D 57 B5 50
Формат файла
Никак преобразовывать подпись далее не нужно, можно оставить в этом же виде
Проверка подписи
Проверка подписи утилитой openssl
openssl dgst -sha256 -verify pubkey.pem -signature sign.der data.bin
- Сконвертируем ключ из OCTET_STRING в BIT_STRING :
03 42 00 04 8B 92 09 ...- 03 – тег
- 42 – размер
- 00 – заполнитель
- 04 8B 92 09... – 0x41 байт открытого ключа
- Сложим результаты в SEQUENCE:
30 56 30 10 06 07 2A 86 ...- 30 – тег
- 56 – размер (0x2+0x2+0x42+0x10)
Результат:
30 56 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 0A 03 42 00 04 8B 92 09 CC C0 A1 B4 19 BA 80 2E 44 5D A2 16 E6 92 AA 9C BB B9 CC B0 CE 5C 76 71 C6 DF E4 CA 83 46 BB 92 2B C8 6F FC 15 20 D8 11 5F 32 4F 7F CA BB DE 9F E3 62 5E 8E 2C D2 2D C2 51 EC 3B 8C 67
Info | |||||||
---|---|---|---|---|---|---|---|
| |||||||
Если записать в файл байты в том виде, в котором мы их получили, мы получим открытый ключ в DER формате. Если мы хотим представить его в виде печатных символов, его нужно перевести в PEM формат. Это можно сделать с помощью команды:
|
Проверка открытого ключа
Чтобы проверить, что ключ сконвертирован верно, выполните команду:
Code Block | ||||
---|---|---|---|---|
| ||||
openssl ec -check -pubin -in pubkey.pem |
Конвертация подписи
Представление подписи в PKCS#11
Для подписи данных библиотека PKCS#11 использует функцию С_Sign. Результат возвращается в виде R|S структуры. Формат у этой структуры простой:
- 32 байта числа R
- 32 байта числа S
Пример R|S подписи
Code Block | ||||
---|---|---|---|---|
| ||||
43 1C 62 38 11 04 91 43 76 28 A5 D8 9E 5F EE 90 B0 DC 68 39 D2 B8 11 F3 22 2C D4 DB E2 05 49 BF -- R
8A EF 68 77 8B 0D B8 E6 11 FD 55 56 37 71 62 7E B7 31 22 67 8D 61 7F B6 41 EA 7E 1F 84 C7 36 41 -- S |
Описание формата подписи в OpenSSL
OpenSSL представляет подпись в виде ASN.1 структуры.
Code Block | ||||
---|---|---|---|---|
| ||||
30 45 -- SEQUENCE
02 20 43 1C 62 38 11 04 91 43 76 28 A5 D8 9E 5F EE 90 B0 DC 68 39 D2 B8 11 F3 22 2C D4 DB E2 05 49 BF -- R
02 21 00 8A EF 68 77 8B 0D B8 E6 11 FD 55 56 37 71 62 7E B7 31 22 67 8D 61 7F B6 41 EA 7E 1F 84 C7 36 41 -- S |
Описание процесса конвертации
- Перевести R и S в TLV тип INTEGER
- Результаты положить в SEQUENCE. Нужно проставить правильную длину выходной последовательности.
Числа R и S приводятся к TLV. типу INTEGER с помощью такого алгоритма:
- T = 02.
- L = кол-во байт для хранения числа. Если первый байт числа превышает 7F, то длина увеличивается на 1 и после L записывается байт 00.
- V = значение числа.
Пример конвертации
- Число R преобразуется к типу INTEGER:
02 20 43 1C 62 38 11 04 ...- 02 - тег
- 20 - длина
- 43 1C 62 38 11 04 ... - само число
- Число S преобразуется к типу INTEGER:
02 21 00 8A EF 68 77 8B- 02 - тег
- 21 - длина. На единицу больше т.к. старший байт числа 0x8A больше 0x7F
- 00 - заполнитель
- 8A EF 68 77 8B ... - само число
- Запишем результат в SEQUENCE:
30 45 02 20 43 1C- 30 - тег
- 45 - длина последоватльности (0x02 + 0x02 + 0x20 +0x21)
- 02 20 43 1C ... - значение последовательности (R|S)
Проверка подписи
Проверка подписи можно с помощью команды openssl
Code Block | ||||
---|---|---|---|---|
| ||||
openssl dgst -sha256 -verify pubkey.pem -signature sign.der data.bin |
...