Как создать открытый ключ ECC в формате OpenSSL «EVP_PKEY» из структуры данных «TPMT_PUBLIC», специфичной для TPM?

Я работаю над проектом, в котором мне нужно работать с TPM (доверенным платформенным модулем), и мне нужно проверить подпись ECC, созданную TPM. Проверка должна выполняться программно, то есть вне TPM, и для этой цели мы хотим использовать OpenSSL (libcrypto). По этой причине открытый ключ ECC доверенного платформенного модуля необходимо преобразовать из структуры данных TPMT_PUBLIC, специфичной для доверенного платформенного модуля, в которой он предоставляется доверенным платформенным модулем (это невозможно изменить!), в формат EVP_PKEY, который можно использовать с OpenSSL ( libcrypto) API для проверки подписи.


TPM предоставляет открытый ключ ECC в следующей структуре, которая не определена мной, но определена программным стеком TPM (TSS), и с которой мне приходится работать:

Общая структура открытого ключа, которую я получаю от TPM (показывает только соответствующее поле):

/* Definition of TPMT_PUBLIC Structure */
typedef struct TPMT_PUBLIC TPMT_PUBLIC;
struct TPMT_PUBLIC {
    /* ... */
    TPMU_PUBLIC_ID unique; /* For an asymmetric key this would be the public key. */
};

Структура, специфичная для типа ключа, которая находится в «уникальном» поле сверху:

/* Definition of TPMU_PUBLIC_ID Union <INOUT S> */
typedef union TPMU_PUBLIC_ID TPMU_PUBLIC_ID;
union TPMU_PUBLIC_ID {
    TPM2B_DIGEST keyedHash;
    TPM2B_DIGEST sym;
    TPM2B_PUBLIC_KEY_RSA rsa;
    TPMS_ECC_POINT ecc; /* <-- This is what is used for ECC key !!! */
    TPMS_DERIVE derive;
};

Фактический открытый ключ ECC, предоставленный TPM, как в поле «ecc» выше:

/* Definition of ECC TPMS_ECC_POINT Structure */
typedef struct TPMS_ECC_POINT TPMS_ECC_POINT;
struct TPMS_ECC_POINT {
    TPM2B_ECC_PARAMETER x; /* X coordinate */
    TPM2B_ECC_PARAMETER y; /* Y coordinate */
};

Координаты x и y сохраняются как:

/* Definition of ECC TPM2B_ECC_PARAMETER Structure */
typedef struct TPM2B_ECC_PARAMETER TPM2B_ECC_PARAMETER;
struct TPM2B_ECC_PARAMETER {
    UINT16 size;
    BYTE buffer[128];
};

Более подробную информацию (не мой код) можно найти здесь:
https://github.com/tpm2-software/tpm2-tss/blob/master/include/tss2/tss2_tpm2_types.h


Таким образом, по сути, мы получаем координаты «x» и «y» открытого ключа ECC из TPM, каждая из которых предоставляется в виде «необработанного» массива байтов. Это не какой-то формат, подобный открытому ключу, закодированному в PEM, который OpenSSL может использовать напрямую! Итак, как мне преобразовать это в экземпляр EVP_PKEY, который OpenSSL сможет обработать, например. по функции EVP_PKEY_verify()? К сожалению, мне не удалось найти пример того, как ключ ECC в формате EVP_PKEY можно создать из «необработанных» значений (координат x и y). Ни в документации OpenSSL, ни где-либо еще...

Я уже успешно создал ключ RSA как EVP_PKEY из «необработанных» значений модуля и показателя степени RSA, поэтому я думаю, что можно сделать очень похожую вещь, но с ключом ECC:
https://pastebin.com/VBdVBt2C

Спасибо за любой совет!

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
97
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это плохо документировано, но o2i_ECPublicKey можно использовать для преобразования потока октетов (т. е. последовательности байтов) в открытый ключ EC типа EC_KEY.

Заголовочный файл ec.h содержит следующее:

/** Decodes a ec public key from a octet string.
 *  \param  key  a pointer to a EC_KEY object which should be used
 *  \param  in   memory buffer with the encoded public key
 *  \param  len  length of the encoded public key
 *  \return EC_KEY object with decoded public key or NULL if an error
 *          occurred.
 */
EC_KEY *o2i_ECPublicKey(EC_KEY **key, const unsigned char **in, long len);

Первый параметр необходимо инициализировать, например, с помощью EC_KEY_new_by_curve_name. Ожидается, что входной буфер будет одним байтом со значением 4, за которым следует координата X точки, а затем координата Y точки.

Например:

TPMS_ECC_POINT *tpms_point = /* load as appropriate */
// assumes curve secp521r1, set as appropriate
EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp521r1);  

int keylen = tpms_point->x.size + tpms_point->y.size + 1;
unsigned char buffer[keylen];
const unsigned char *p = buf;

buffer[0] = 4;
memcpy(buffer + 1, tpms_point->x.buffer, tpms_point->x.size);
memcpy(buffer + 1 + tpms_point->x.size, tpms_point->y.buffer, tpms_point->y.size);

if (!o2i_ECPublicKey(&key, &p, keylen)) {
    printf("key load failed\n");
}

После загрузки EC_KEY вы можете использовать EVP_PKEY_new, чтобы создать новый EVP_PKEY, а затем использовать EVP_PKEY_set1_EC_KEY, чтобы загрузить EC_KEY в EC_KEY.

Спасибо за подсказку! Но эта функция принимает на вход только один массив байтов и говорит «буфер памяти с закодированным открытым ключом». Как именно закодировано? Я полагаю, что просто объединить массивы «x» и «y» из TPM не получится. Верно? Кроме того, вы говорите о значениях «4»? Какие четыре ценности? У меня есть только две: координаты «x» и «y» общественной точки...

user24805910 30.04.2024 17:57

Извините, мне кажется, я неправильно прочитал ваш пост. Итак, вы предлагаете мне буквально использовать значение байта «4», объединенное необработанными данными координаты «x», объединенными необработанными данными координаты «y»? Если да, то как OpenSSL узнает, где заканчивается одно значение и начинается следующее? Есть ли где-нибудь какая-либо спецификация входного формата, ожидаемая o2i_ECPublicKey() ???

user24805910 30.04.2024 18:07

@user24805910 user24805910 Я добавил подробную информацию о том, как именно загрузить ключ. Что касается длины, то это значение фиксировано для данной кривой ЕС. Итак, как только вы установите тип кривой, длина станет известна.

dbush 30.04.2024 18:09

Я понимаю. Обязательно попробую. Еще раз спасибо ;-)

user24805910 30.04.2024 18:13

@user24805910 И обязательно прочитайте предупреждения на странице руководства i2d_* OpenSSL. В частности, не удаляйте const unsigned char *p = buf; и пытайтесь использовать buffer напрямую.

Andrew Henle 30.04.2024 18:16

А как насчет EC_KEY_set_public_key_affine_coordinates()? Разве это не был бы более простой способ заполнить EC_KEY координатами «x» и «y»???

user24805910 30.04.2024 19:03

@user24805910 user24805910 Нет, сначала вам нужно преобразовать координаты X и Y в пару объектов BIGNUM. Учитывая вышеизложенное, вам не понадобится этот дополнительный шаг.

dbush 30.04.2024 19:15

Функции EC_KEY_new_by_curve_name() и т. д. устарели в OpenSSL 3.0+, и теперь действительно следует использовать общие API EVP_PKEY!

Schraubstock1990 02.05.2024 13:30

В OpenSSL 3.0 прямое использование EC_KEY устарело.

Вы можете создать EVP_PKEY с помощью «общего» API и таким образом избежать предупреждения об устаревании:

EVP_PKEY *evp_key = NULL;

OSSL_PARAM_BLD *param_builder = OSSL_PARAM_BLD_new();
OSSL_PARAM_BLD_push_utf8_string(param_builder, OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
OSSL_PARAM_BLD_push_octet_string(param_builder, OSSL_PKEY_PARAM_PUB_KEY, pubkey_data, pubkey_size);

OSSL_PARAM *key_params = OSSL_PARAM_BLD_to_param(param_builder);

EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
EVP_PKEY_fromdata_init(ctx);
EVP_PKEY_fromdata(ctx, &evp_key, EVP_PKEY_KEYPAIR, key_params);

Примечание. Ожидается, что данные открытого ключа будут иметь формат, определенный SECG SEC 1, раздел 2.3.4. Но на самом деле это просто означает, что в «несжатом» случае у вас есть массив байтов, который начинается с одного байта 0x04, за которым следуют байтовые строки координаты X и координаты Y.

Если P = (xP, yP) ≠ Ø и сжатие точек не используется, действуйте следующим образом:
...
Выход: M = 0416 ‖ X ‖ Y

Его можно создать следующим образом:

#define MAX_PARAMSIZE 66 /* sufficient for P-521 curve  */
uint8_t pubkey_data[1U + (2U * MAX_PARAMSIZE)];

param_size = ((curve_bits % CHAR_BIT) != 0U) ? 1U + (curve_bits / 8U) : curve_bits / 8U;

pubkey_data[0U] = 0x04;
memcpy(pubkey_data + 1U, x_buffer, param_size);
memcpy(pubkey_data + 1U + param_size, y_buffer, param_size);

...где будет curve_bits, например. 521 для кривой P-521 или 256 для кривой P-256.

Другие вопросы по теме