Подпись ECDSA, сгенерированная с помощью mbedtls, не поддающаяся проверке в JOSE (пока код работал с ключом RSA)
У меня есть небольшое приложение, работающее на плате разработки ESP32 (я использую Arduino IDE вместе с поставляемым mbedtls), которое выдает и проверяет токены JWT. Сначала я успешно использовал подписи RSA, но теперь хотел использовать более короткие подписи и поэтому попытался использовать ECDSA. Само приложение может выдавать токены и проверять их, но если я попытаюсь проверить токены вне моего приложения, например, с помощью JOSE или отладчика , я получу ошибку проверки и не могу понять, почему это происходит.
Это пример токена (данный токен практически не содержит информации):
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzI0MTMxNjgsImV4cCI6MTY3MjQxNjc2OH0.MEUCIAjwEDXI424qjrAkSzZ_ydcVLOSAvfQ8YVddYvzDzMvQAiEAkVy4d-hZ01KpcMNKhPHk8E_SDYiB4JKwhm-Kc-Z81rI
Это соответствующий публичный ключ (этот ключ нигде не используется, кроме цели изложения проблемы здесь):
-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUGnNIOhPhZZSOg4A4BqAFtGO13W4BGDQpQ0ieTvLU9/CXrY7W77o7pNx7tvugeIoYJxS0NjmxvT4TMpo4Z8P7A==-----END PUBLICA== -----END PUBLICA==
Насколько я понимаю, токены JWT можно выпускать и верифицировать с помощью ECDSA. Предполагается , что так называемый метод «ES256» использует Prime256v1 в сочетании с SHA256, поэтому я сгенерировал свой ключевой материал с помощью следующих команд:
openssl ecparam -name prime256v1 -genkey -noout -out ecc-private.pem
openssl ec -in ecc-private.pem -pubout -out ecc-public.pem
Для подписывающей части закрытый ключ загружается следующим образом, где ecc_priv — это строка, содержащая PEM-представление ключа:
//get the key
byte *keybuffer = (byte*)malloc((ecc_priv.length()+1)*sizeof(byte));
ecc_priv.getBytes(keybuffer, ecc_priv.length() + 1);
mbedtls_pk_context pk_context;
mbedtls_pk_init(&pk_context);
int rc = mbedtls_pk_parse_key(&pk_context, keybuffer, ecc_priv.length() + 1, NULL, 0);
if (rc != 0){
printf("Failed to mbedtls_pk_parse_key: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
return -1;
}
free(keybuffer);
Поскольку это сработало для меня с ключами RSA, я просто заменил ключи и сохранил весь остальной код для подписи фактического сообщения. Насколько я понимаю, это должно быть возможно с помощью методов mbedtls_pk:
//mbedtls context
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
const char* pers="some entropy";
mbedtls_ctr_drbg_seed(
&ctr_drbg,
mbedtls_entropy_func,
&entropy,
(const unsigned char*)pers,
strlen(pers));
//get the header and payload bytes
byte *headerAndPayloadbytes = (byte*)malloc((headerAndPayload.length()+1)*sizeof(byte));
headerAndPayload.getBytes(headerAndPayloadbytes, headerAndPayload.length() + 1);
//prepare digest
uint8_t digest[32];
rc = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), headerAndPayloadbytes, headerAndPayload.length(), digest);
if (rc != 0) {
printf("Failed to mbedtls_md: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
return -1;
}
free(headerAndPayloadbytes);
//prepare output
byte *oBuf = (byte*)malloc(5000*sizeof(byte));
size_t retSize;
//sign digest
rc = mbedtls_pk_sign(&pk_context, MBEDTLS_MD_SHA256, digest, sizeof(digest), oBuf, &retSize, mbedtls_ctr_drbg_random, &ctr_drbg);
if (rc != 0) {
printf("Failed to mbedtls_pk_sign: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc));
return -1;
}
//encode signature to base64
unsigned int osize = encode_base64_length(retSize);
byte *output = (byte*)malloc((osize+1)*sizeof(byte));
encode_base64(oBuf, retSize, output);
String sig = String((char*)output);
free(output);
//base64 URL specific
sig.replace('+','-');
sig.replace('/','_');
sig.replace("=","");
String completejwt = headerAndPayload + "." + sig;
//free resources
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
mbedtls_pk_free(&pk_context);
free(oBuf);
Я ожидал, что смогу просто заменить ключи RSA на ключи ECDSA (prime256v1) и оставить все остальное как есть, но полученный токен нельзя проверить вне моего приложения. Еще раз хочу подчеркнуть, что внутри моего приложения я точно могу проверить токен и что код прекрасно работает с ключами RSA даже вне моего приложения. Здесь должно быть что-то, что я упускаю, я в этом уверен. Любая помощь или направления исследований высоко ценятся.
РЕДАКТИРОВАТЬ: Вот минимальный компилируемый пример (эскиз Arduino)
1 ответ
Попробуйте войти в формате P1363, используя метод mbedtls_ecdsa_sign_det().
auto ecdsa = mbedtls_pk_ec(pkContext);
if (ecdsa == NULL)
{
return String("INVALID-ECDSA-CONTEXT");
}
unsigned char signature[64];
size_t signatureLength;
mbedtls_mpi r, s;
mbedtls_mpi_init(&r);
mbedtls_mpi_init(&s);
int ret = mbedtls_ecdsa_sign_det(&ecdsa->grp, &r, &s, &ecdsa->d, sha256_b64h_b64p, 32, MBEDTLS_MD_SHA256);
if (ret != 0)
{
return String("CAN-NOT-SIGN");
}
// Write the signature in P1363 format
mbedtls_mpi_write_binary(&r, signature, mbedtls_mpi_size(&r));
mbedtls_mpi_write_binary(&s, signature + mbedtls_mpi_size(&r), mbedtls_mpi_size(&s));
signatureLength = mbedtls_mpi_size(&r) + mbedtls_mpi_size(&s);
/* mbedtls_ecdsa_write_signature writes signature out as DER format */
// int success = mbedtls_ecdsa_write_signature(ecdsa, MBEDTLS_MD_SHA256, sha256_b64h_b64p, 32, buf, &signatureLength, NULL, NULL);
/* Print signature as hex */
Serial.print("signature (hex) (" + String(signatureLength) + ") : [");
for (size_t i = 0; i < signatureLength; i++)
{
Serial.printf("%02x", signature[i]);
}
Serial.println("]");