Я работаю над проектом, в котором мне нужно работать с 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
Спасибо за любой совет!
Это плохо документировано, но 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
.
Извините, мне кажется, я неправильно прочитал ваш пост. Итак, вы предлагаете мне буквально использовать значение байта «4», объединенное необработанными данными координаты «x», объединенными необработанными данными координаты «y»? Если да, то как OpenSSL узнает, где заканчивается одно значение и начинается следующее? Есть ли где-нибудь какая-либо спецификация входного формата, ожидаемая o2i_ECPublicKey()
???
@user24805910 user24805910 Я добавил подробную информацию о том, как именно загрузить ключ. Что касается длины, то это значение фиксировано для данной кривой ЕС. Итак, как только вы установите тип кривой, длина станет известна.
Я понимаю. Обязательно попробую. Еще раз спасибо ;-)
@user24805910 И обязательно прочитайте предупреждения на странице руководства i2d_* OpenSSL. В частности, не удаляйте const unsigned char *p = buf;
и пытайтесь использовать buffer
напрямую.
А как насчет EC_KEY_set_public_key_affine_coordinates()
? Разве это не был бы более простой способ заполнить EC_KEY координатами «x» и «y»???
@user24805910 user24805910 Нет, сначала вам нужно преобразовать координаты X и Y в пару объектов BIGNUM
. Учитывая вышеизложенное, вам не понадобится этот дополнительный шаг.
Функции EC_KEY_new_by_curve_name()
и т. д. устарели в OpenSSL 3.0+, и теперь действительно следует использовать общие API EVP_PKEY!
В 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.
Спасибо за подсказку! Но эта функция принимает на вход только один массив байтов и говорит «буфер памяти с закодированным открытым ключом». Как именно закодировано? Я полагаю, что просто объединить массивы «x» и «y» из TPM не получится. Верно? Кроме того, вы говорите о значениях «4»? Какие четыре ценности? У меня есть только две: координаты «x» и «y» общественной точки...