我有一个在 ESP32 开发板上运行的小应用程序(我将 Arduino IDE 与附带的 mbedtls 一起使用),用于发布和验证 JWT token 。我最初成功地使用了 RSA 签名,但现在想要更短的签名,因此尝试使用 ECDSA。 应用程序本身可以颁发 token 并验证它们,但是如果我尝试在应用程序之外验证 token - 例如使用 JOSE或Debugger - 我收到验证失败的消息,但我无法完全理解为什么会发生这种情况。
这是一个示例 token (该 token 实际上不包含信息):
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzI0MTMxNjgsImV4cCI6MTY3MjQxNjc2OH0.MEUCIAjwEDXI424qjrAkSzZ_ydcVLOSAvfQ8YVddYvzDzMvQAiEAkVy4d-hZ01KpcMNKhPHk8E_SDYiB4JKwhm-Kc-Z81rI
这是相应的公钥(除了在此提出问题的目的之外,此 key 不会在任何地方使用):
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUGnNIOhPhZZSOg4A4BqAFtGO13W4BGDQpQ0ieTvLU9/CXrY7W77o7pNx7tvugeIoYJxS0NjmxvT4TMpo4Z8P7A== -----END PUBLIC KEY-----
据我了解,JWT token 可以使用 ECDSA 发行和验证。所谓的“ES256”方法is supposed to use prime256v1结合 SHA256,因此我使用以下命令生成了 key Material :
openssl ecparam -name prime256v1 -genkey -noout -out ecc-private.pem
openssl ec -in ecc-private.pem -pubout -out ecc-public.pem
对于签名部分,私钥加载如下,其中 ecc_priv 是包含 key 的 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 key ,因此我只是替换了 key 并保留了所有其他代码来签署实际消息。据我了解,这应该可以通过 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 key 替换为 ECDSA (prime256v1) key ,并保持其他所有内容不变,但生成的 token 在我的应用程序之外无法验证。我想再次强调,在我的应用程序内部,我绝对可以验证 token ,并且即使在我的应用程序外部,代码也可以与 RSA key 完美配合。我确信这里一定有什么东西不见了。 非常感谢任何帮助或研究指导。
编辑:这是一个 minimal compilable example (Arduino草图)
最佳答案
您的 ECDSA 签名是 DER 编码的 ASN.1 结构,而不是简单的 r || IEEE-P1363 提议的串联,这是 JOSE 规范的要求。
关于jwt - 使用 mbedtls 生成的 ECDSA 签名在 JOSE 中无法验证(而代码使用 RSA key ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74963265/