ECDSA签名编码r值和s值的方式没有很好的定义:例如,OpenSSL专用于使用DER编码的ASN.1序列,而Windows则使用IEEE P1363编码(详见this excellent SO answer )。
为了使用OpenSSL启用ECDSA签名验证,我修补了ossl_ecdsa_verify(..)在ec/ecdsa_assl.c中(在OpenSSL 3中,即;在1.0.2中是ECDSA_verify(.)在ecdsa/ecs_vrf.c中)。(有关调试修补程序的相关问题,请参见this question of mine。)
股票代码是这样做的:
/*-
* returns
* 1: correct signature
* 0: incorrect signature
* -1: error
*/
int ossl_ecdsa_verify(int type, const unsigned char *dgst, int dgst_len,
const unsigned char *sigbuf, int sig_len, EC_KEY *eckey)
{
ECDSA_SIG *s;
const unsigned char *p = sigbuf;
unsigned char *der = NULL;
int derlen = -1;
int ret = -1;
s = ECDSA_SIG_new();
if (s == NULL)
return ret;
if (d2i_ECDSA_SIG(&s, &p, sig_len) == NULL)
goto err;
/* Ensure signature uses DER and doesn't have trailing garbage */
derlen = i2d_ECDSA_SIG(s, &der);
if (derlen != sig_len || memcmp(sigbuf, der, derlen) != 0)
goto err;
ret = ECDSA_do_verify(dgst, dgst_len, s, eckey);
err:
OPENSSL_free(der);
ECDSA_SIG_free(s);
return ret;
}
我的补丁是这样的:
int ossl_ecdsa_verify(int type, const unsigned char *dgst, int dgst_len,
const unsigned char *sigbuf, int sig_len, EC_KEY *eckey)
{
ECDSA_SIG *s;
const unsigned char *p = sigbuf;
unsigned char *der = NULL;
int derlen = -1;
int ret = -1;
s = ECDSA_SIG_new();
if (s == NULL)
return ret;
#ifdef P1363_PATCH
if (d2i_ECDSA_SIG(&s, &p, sig_len) == NULL) {
/*
* ASN.1 decoding failed, see crypto/asn1/tasn_dec.c line 515ff.
* Assume s is encoded as IEEE P1363. for a comprehensive description see
* ttps://stackoverflow.com/questions/36542645/does-openssl-sign-for-ecdsa-apply-asn1-encoding-to-the-hash-before-signing
* Fill the ECDSA_SIG from the P1363.
*/
if ((sig_len % 2) != 0)
return (ret);
if (s == NULL)
s = ECDSA_SIG_new();
if (s == NULL)
return (ret);
/*
* Prepare a buffer large enough to hold either r or s part of the P1363.
* Add 1 to the size to allow for padding if needed.
* Define some variables for pointer arithmetic.
*/
int buf_size = sig_len / 2 + 1;
void *buf = malloc(buf_size);
const unsigned char *sigbuf_half = sigbuf + sig_len / 2;
const unsigned char *sigbuf_full = sigbuf + sig_len;
/*
* Skip possible padding of the r part of the P1363.
* I /think/ only the s part may be padded, but it does no harm to skip them
* for the r part, too.
*/
const unsigned char *q = sigbuf;
while (*q == '\0' && q < sigbuf_half)
q++;
int buf_len = sigbuf_half - q;
/*
* Prepare buf for BIGNUM creation.
*/
memcpy(buf, q, buf_len);
if (*(char*)buf & 0x80) {
/* Add padding if needed to assert positive integer. */
memmove((char*)buf + 1, buf, buf_len);
memset(buf, '\0', 1);
buf_len++;
}
/*
* Finally create the BIGNUM and put it in the r part of the ECDSA_SIG.
*/
s->r = BN_bin2bn((const unsigned char *)buf, buf_len, NULL);
/*
* Now do the same for the s part...
*/
q = sigbuf_half;
while (*q == '\0' && q < sigbuf_full)
q++;
buf_len = sigbuf_full - q;
memcpy(buf, q, buf_len);
if (*(char*)buf & 0x80) {
/*Add padding if needed to assert positive integer. */
memmove((char*)buf + 1, buf, buf_len);
memset(buf, '\0', 1);
buf_len++;
}
s->s = BN_bin2bn((const unsigned char *)buf, buf_len, NULL);
free(buf);
}
else {
/* Ensure signature uses DER and doesn't have trailing garbage */
derlen = i2d_ECDSA_SIG(s, &der);
if (derlen != sig_len || memcmp(sigbuf, der, derlen))
goto err;
}
ret = ECDSA_do_verify(dgst, dgst_len, s, eckey);
err:
if (derlen > 0) {
OPENSSL_cleanse(der, derlen);
OPENSSL_free(der);
}
#else
if (d2i_ECDSA_SIG(&s, &p, sig_len) == NULL)
goto err;
/* Ensure signature uses DER and doesn't have trailing garbage */
derlen = i2d_ECDSA_SIG(s, &der);
if (derlen != sig_len || memcmp(sigbuf, der, derlen) != 0)
goto err;
ret = ECDSA_do_verify(dgst, dgst_len, s, eckey);
err:
OPENSSL_free(der);
#endif /* P1363_PATCH */
ECDSA_SIG_free(s);
return ret;
}
这允许我验证P1363编码的ECDSA签名。sigbuf中的垃圾仍然被ECDSA_do_verify(..)捕获。
但是,OpenSSL提供的ecdsa测试在修补程序中失败:
15-test_ecdsa.t ....................
# INFO:
# testing ECDSA for curve secp112r1 as EC key type
# ERROR: (int) 'EVP_DigestVerify(mctx, sig, sig_len - 1, tbs, sizeof(tbs)) == -1' failed @ ..\..\..\3rdparty\openssl-3.0.5-RIB\test\ecdsatest.c:262
# [0] compared to [-1]
# 442C0000:error:0800009C:elliptic curve routines:ossl_ecdsa_simple_verify_sig:bad signature:..\..\..\3rdparty\openssl-3.0.5-RIB\crypto\ec\ecdsa_ossl.c:482:
# OPENSSL_TEST_RAND_ORDER=1666859286
not ok 1 - iteration 1
对每一条曲线都是如此。我还不能完全分析,但我认为这是由于阴性测试失败造成的。这可能是因为测试提供的垃圾现在在我的代码中运行,并且不会绕过对ECDSA_do_verify(..)的调用。对吗?
为了使测试成功,并且作为对我的代码的一般改进,库存OpenSSL只区分2种情况(ASN.1或垃圾),现在我需要区分3种情况(ASN.1、P1363或垃圾)。,一旦ASN.1解码失败,有没有办法区分P1363和垃圾?
发布于 2022-11-03 20:35:09
显而易见的事情要做(我想!)尝试按OpenSSL的方式解码,检查是否消耗了整个八进制字符串,并确保使用最小字节数正确编码了两个INTEGER
值(这意味着它们不能让前9位全部为零或全部为零)。如果是,那么假设ASN.1/DER,否则尝试MSFT方式。您可以做的另一件事是检查八进制字符串的长度是否为64字节:如果不是64字节,则不能用MSFT方式对其进行编码,但是,如果是64个字节,那么r
的前导字节可能看起来就像SEQUENCE
的标记长度,然后是r
INTEGER
的标记长度(它必须足够小到需要在MSFT编码中填充零),然后s
也需要它的前两个字节看起来像INTEGER
的标签长度,而这个值也需要30个字节+两个零填充的零填充。
https://stackoverflow.com/questions/74220741
复制相似问题