Подпись 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("]");
Другие вопросы по тегам