使用mbedtls生成的ECDSA签名在JOSE中不可验证(当代码使用RSA密钥时)



我有一个小应用程序在ESP32开发板上运行(我将Arduino IDE与附带的mbedtls一起使用),它可以发布和验证JWT令牌。起初我成功地使用了RSA签名,但现在我想使用更短的签名,因此尝试使用ECDSA。应用程序本身也可以发布令牌并对其进行验证,但如果我试图在应用程序之外验证令牌,例如使用JOSE或调试器,我会遇到验证失败,我无法完全理解为什么会发生这种情况。

这是一个示例令牌(该令牌实际上不包含信息):

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzI0MTMxNjgsImV4cCI6MTY3MjQxNjc2OH0.MEUCIAjwEDXI424qjrAkSzZ_ydcVLOSAvfQ8YVddYvzDzMvQAiEAkVy4d-hZ01KpcMNKhPHk8E_SDYiB4JKwhm-Kc-Z81rI

这是相应的公钥(除了在此处显示问题的目的之外,该密钥不会在任何地方使用):

-----开始公钥-----MFkwEwyHKoZIzj0CAQYIKoZIzj 0DAQcDQgAEUGnNIOH PhZZSOg4A4BqAFtGO13W4BGDQpQ0ieTvLU9/CXrY7W77o7pNx7tvugeIoYJxS0NjmxvT4TMpo4Z8P7A==-----结束公钥-----

据我所知,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): %sn", 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): %sn", 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): %sn", 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);

我的期望是,我可以简单地用ECDSA(prime256v1)密钥替换RSA密钥,并保持其他一切不变,但生成的令牌在我的应用程序之外是不可验证的。我想再次强调,在我的应用程序内部,我可以肯定地验证令牌,并且代码与RSA密钥配合得非常好,即使在应用程序之外也是如此。这里一定有我遗漏的东西,我确信。我们非常感谢对研究的任何帮助或指导。

编辑:这是一个最小的可编译示例(Arduino草图)

您的ECDSA签名是一个DER编码的ASN.1结构,而不是IEEE-P1363提出的简单的r||s级联,这是JOSE规范所要求的。

尝试使用mbedtls_ecdsa_sign_det()方法以P1363格式登录

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("]");

最新更新