[njs] WebCrypto: fixed sign and verify methods for ECDSA.
Dmitry Volyntsev
xeioex at nginx.com
Mon Aug 29 23:42:53 UTC 2022
details: https://hg.nginx.org/njs/rev/f70929728e86
branches:
changeset: 1936:f70929728e86
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Fri Aug 26 21:49:14 2022 -0700
description:
WebCrypto: fixed sign and verify methods for ECDSA.
diffstat:
external/njs_openssl.h | 2 +
external/njs_webcrypto_module.c | 277 ++++++++++++++++++++++++++++
test/webcrypto/README.rst | 12 +-
test/webcrypto/asn12ieeep1336.c | 49 ++++
test/webcrypto/text.base64.sha1.ecdsa.sig | 4 +-
test/webcrypto/text.base64.sha256.ecdsa.sig | 4 +-
6 files changed, 342 insertions(+), 6 deletions(-)
diffs (403 lines):
diff -r 43b31a943c08 -r f70929728e86 external/njs_openssl.h
--- a/external/njs_openssl.h Thu Aug 25 16:57:28 2022 -0700
+++ b/external/njs_openssl.h Fri Aug 26 21:49:14 2022 -0700
@@ -43,6 +43,8 @@
#else
#define njs_evp_md_ctx_new() EVP_MD_CTX_create()
#define njs_evp_md_ctx_free(_ctx) EVP_MD_CTX_destroy(_ctx)
+#define ECDSA_SIG_get0_s(sig) (sig)->s
+#define ECDSA_SIG_get0_r(sig) (sig)->r
#endif
diff -r 43b31a943c08 -r f70929728e86 external/njs_webcrypto_module.c
--- a/external/njs_webcrypto_module.c Thu Aug 25 16:57:28 2022 -0700
+++ b/external/njs_webcrypto_module.c Fri Aug 26 21:49:14 2022 -0700
@@ -1935,6 +1935,266 @@ njs_set_rsa_padding(njs_vm_t *vm, njs_va
}
+static const EC_KEY *
+njs_pkey_get_ec_key(EVP_PKEY *pkey)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ return EVP_PKEY_get0_EC_KEY(pkey);
+#else
+ if (pkey->type != EVP_PKEY_EC) {
+ return NULL;
+ }
+
+ return pkey->pkey.ec;
+#endif
+}
+
+
+static int
+njs_ec_group_order_bits(const EC_GROUP *group)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ return EC_GROUP_order_bits(group);
+#else
+ int bits;
+ BIGNUM *order;
+
+ order = BN_new();
+ if (order == NULL) {
+ return 0;
+ }
+
+ if (EC_GROUP_get_order(group, order, NULL) == 0) {
+ return 0;
+ }
+
+ bits = BN_num_bits(order);
+
+ BN_free(order);
+
+ return bits;
+#endif
+}
+
+
+static unsigned int
+njs_ec_rs_size(EVP_PKEY *pkey)
+{
+ int bits;
+ const EC_KEY *ec_key;
+ const EC_GROUP *ec_group;
+
+ ec_key = njs_pkey_get_ec_key(pkey);
+ if (ec_key == NULL) {
+ return 0;
+ }
+
+ ec_group = EC_KEY_get0_group(ec_key);
+ if (ec_group == NULL) {
+ return 0;
+ }
+
+ bits = njs_ec_group_order_bits(ec_group);
+ if (bits == 0) {
+ return 0;
+ }
+
+ return (bits + 7) / 8;
+}
+
+
+static int
+njs_bn_bn2binpad(const BIGNUM *bn, unsigned char *to, int tolen)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ return BN_bn2binpad(bn, to, tolen);
+#else
+ return BN_bn2bin(bn, &to[tolen - BN_num_bytes(bn)]);
+#endif
+}
+
+
+static njs_int_t
+njs_convert_der_to_p1363(njs_vm_t *vm, EVP_PKEY *pkey, const u_char *der,
+ size_t der_len, u_char **pout, size_t *out_len)
+{
+ u_char *data;
+ unsigned n;
+ njs_int_t ret;
+ ECDSA_SIG *ec_sig;
+
+ ret = NJS_OK;
+ ec_sig = NULL;
+
+ n = njs_ec_rs_size(pkey);
+ if (n == 0) {
+ goto fail;
+ }
+
+ data = njs_mp_alloc(njs_vm_memory_pool(vm), 2 * n);
+ if (njs_slow_path(data == NULL)) {
+ goto memory_error;
+ }
+
+ ec_sig = d2i_ECDSA_SIG(NULL, &der, der_len);
+ if (ec_sig == NULL) {
+ goto fail;
+ }
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+ memset(data, 0, 2 * n);
+#endif
+
+ if (njs_bn_bn2binpad(ECDSA_SIG_get0_r(ec_sig), data, n) <= 0) {
+ goto fail;
+ }
+
+ if (njs_bn_bn2binpad(ECDSA_SIG_get0_s(ec_sig), &data[n], n) <= 0) {
+ goto fail;
+ }
+
+ *pout = data;
+ *out_len = 2 * n;
+
+ goto done;
+
+fail:
+
+ *out_len = 0;
+
+done:
+
+ if (ec_sig != NULL) {
+ ECDSA_SIG_free(ec_sig);
+ }
+
+ return ret;
+
+memory_error:
+
+ njs_vm_memory_pool(vm);
+
+ return NJS_ERROR;
+}
+
+
+static int
+njs_ecdsa_sig_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ return ECDSA_SIG_set0(sig, r, s);
+#else
+ if (r == NULL || s == NULL) {
+ return 0;
+ }
+
+ BN_clear_free(sig->r);
+ BN_clear_free(sig->s);
+
+ sig->r = r;
+ sig->s = s;
+
+ return 1;
+#endif
+}
+
+
+static njs_int_t
+njs_convert_p1363_to_der(njs_vm_t *vm, EVP_PKEY *pkey, u_char *p1363,
+ size_t p1363_len, u_char **pout, size_t *out_len)
+{
+ int len;
+ BIGNUM *r, *s;
+ u_char *data;
+ unsigned n;
+ njs_int_t ret;
+ ECDSA_SIG *ec_sig;
+
+ ret = NJS_OK;
+ ec_sig = NULL;
+ r = NULL;
+ s = NULL;
+
+ n = njs_ec_rs_size(pkey);
+
+ if (njs_slow_path(n == 0 || p1363_len != 2 * n)) {
+ goto fail;
+ }
+
+ ec_sig = ECDSA_SIG_new();
+ if (njs_slow_path(ec_sig == NULL)) {
+ goto memory_error;
+ }
+
+ r = BN_new();
+ if (njs_slow_path(r == NULL)) {
+ goto memory_error;
+ }
+
+ s = BN_new();
+ if (njs_slow_path(s == NULL)) {
+ goto memory_error;
+ }
+
+ if (r != BN_bin2bn(p1363, n, r)) {
+ goto fail;
+ }
+
+ if (s != BN_bin2bn(&p1363[n], n, s)) {
+ goto fail;
+ }
+
+ if (njs_ecdsa_sig_set0(ec_sig, r, s) != 1) {
+ njs_webcrypto_error(vm, "njs_ecdsa_sig_set0() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ data = njs_mp_alloc(njs_vm_memory_pool(vm), 2 * n + 16);
+ if (njs_slow_path(data == NULL)) {
+ goto memory_error;
+ }
+
+ *pout = data;
+ len = i2d_ECDSA_SIG(ec_sig, &data);
+
+ if (len < 0) {
+ goto fail;
+ }
+
+ *out_len = len;
+
+ goto done;
+
+fail:
+
+ *out_len = 0;
+
+done:
+
+ if (ec_sig != NULL) {
+ ECDSA_SIG_free(ec_sig);
+
+ } else {
+ if (s != NULL) {
+ BN_free(s);
+ }
+
+ if (r != NULL) {
+ BN_free(r);
+ }
+ }
+
+ return ret;
+
+memory_error:
+
+ njs_vm_memory_pool(vm);
+
+ return NJS_ERROR;
+}
+
+
static njs_int_t
njs_ext_sign(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t verify)
@@ -2121,7 +2381,24 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t *
goto fail;
}
+ if (alg->type == NJS_ALGORITHM_ECDSA) {
+ ret = njs_convert_der_to_p1363(vm, key->pkey, dst, outlen,
+ &dst, &outlen);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+ }
+
} else {
+ if (alg->type == NJS_ALGORITHM_ECDSA) {
+ ret = njs_convert_p1363_to_der(vm, key->pkey, sig.start,
+ sig.length, &sig.start,
+ &sig.length);
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+ }
+
ret = EVP_PKEY_verify(pctx, sig.start, sig.length, m, m_len);
if (njs_slow_path(ret < 0)) {
njs_webcrypto_error(vm, "EVP_PKEY_verify() failed");
diff -r 43b31a943c08 -r f70929728e86 test/webcrypto/README.rst
--- a/test/webcrypto/README.rst Thu Aug 25 16:57:28 2022 -0700
+++ b/test/webcrypto/README.rst Fri Aug 26 21:49:14 2022 -0700
@@ -124,13 +124,21 @@ Signing data using RSA-PSS
Signing data using ECDSA
------------------------
+Note: there are two types of ECDSA signatures: ASN.1 and IEEE P1363
+Webcrypto requires IEEE P1363, but OpenSSL outputs only ASN.1 variety.
+To create P1363, we build an auxilary program asn12IEEEP1336
+
.. code-block:: shell
echo -n "SigneD-TExt" > text.txt
openssl dgst -sha256 -binary text.txt > text.sha256
openssl pkeyutl -sign -in text.sha256 -inkey test/webcrypto/ec.pkcs8 | \
- base64 > test/webcrypto/text.base64.sha256.ecdsa.sig
- base64 -d test/webcrypto/text.base64.sha256.ecdsa.sig > text.sha256.ecdsa.sig
+ base64 > test/webcrypto/text.base64.sha256.ecdsa.asn1.sig
+ base64 -d test/webcrypto/text.base64.sha256.ecdsa.asn1.sig > text.sha256.ecdsa.sig
openssl pkeyutl -verify -in text.sha256 -pubin -inkey test/webcrypto/ec.spki -sigfile text.sha256.ecdsa.sig
Signature Verified Successfully
+ # convert to IEEE P1363
+ gcc test/webcrypto/asn12ieeep1336.c -lcrypto -o test/webcrypto/asn12ieeep1336
+ base64 -d test/webcrypto/text.base64.sha256.ecdsa.asn1.sig | ./test/webcrypto/asn12IEEEP1336 | \
+ base64 > test/webcrypto/text.base64.sha256.ecdsa.sig
diff -r 43b31a943c08 -r f70929728e86 test/webcrypto/asn12ieeep1336.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/webcrypto/asn12ieeep1336.c Fri Aug 26 21:49:14 2022 -0700
@@ -0,0 +1,49 @@
+#include <openssl/ecdsa.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+int main(int argc, char * argv[]) {
+ int rbytes, sbytes, len, n;
+ ECDSA_SIG *ecSig;
+ unsigned char *p, *end;
+ const unsigned char *start;
+ unsigned char der[512];
+ unsigned char out[64];
+
+ p = der;
+ end = &der[sizeof(der)];
+
+ for ( ;; ) {
+ n = read(STDIN_FILENO, der, end - p);
+
+ if (n == 0) {
+ break;
+ }
+
+ if ((end - p) == 0) {
+ printf("too large (> 512) der length in stdin");
+ return EXIT_FAILURE;
+ }
+
+ p += n;
+ }
+
+ start = der;
+ ecSig = d2i_ECDSA_SIG(NULL, &start, p - der);
+ if (ecSig == NULL) {
+ printf("d2i_ECDSA_SIG() failed");
+ return EXIT_FAILURE;
+ }
+
+ rbytes = BN_num_bytes(ECDSA_SIG_get0_r(ecSig));
+ sbytes = BN_num_bytes(ECDSA_SIG_get0_s(ecSig));
+
+ BN_bn2binpad(ECDSA_SIG_get0_r(ecSig), out, rbytes);
+ BN_bn2binpad(ECDSA_SIG_get0_s(ecSig), &out[32], sbytes);
+
+ write(STDOUT_FILENO, out, sizeof(out));
+
+ return EXIT_SUCCESS;
+}
diff -r 43b31a943c08 -r f70929728e86 test/webcrypto/text.base64.sha1.ecdsa.sig
--- a/test/webcrypto/text.base64.sha1.ecdsa.sig Thu Aug 25 16:57:28 2022 -0700
+++ b/test/webcrypto/text.base64.sha1.ecdsa.sig Fri Aug 26 21:49:14 2022 -0700
@@ -1,2 +1,2 @@
-MEQCIAZ/sGPfuYivvm5UsqZgiR2jtT88d2moIgnAh6h1jKdVAiALKiu3myhI046rhEThSLyReuTu
-eIEgeCPBa2xGZnFXEg==
+Bn+wY9+5iK++blSypmCJHaO1Pzx3aagiCcCHqHWMp1ULKiu3myhI046rhEThSLyReuTueIEgeCPB
+a2xGZnFXEg==
diff -r 43b31a943c08 -r f70929728e86 test/webcrypto/text.base64.sha256.ecdsa.sig
--- a/test/webcrypto/text.base64.sha256.ecdsa.sig Thu Aug 25 16:57:28 2022 -0700
+++ b/test/webcrypto/text.base64.sha256.ecdsa.sig Fri Aug 26 21:49:14 2022 -0700
@@ -1,2 +1,2 @@
-MEUCIFEw11evEWohKswRe3Za0P0u7mvGj4kSnHix/EOKhxApAiEAq2QtwNvFg8RdY6t01ff8mUTP
-nT1lEfMSRZmtuVxQuQA=
+UTDXV68RaiEqzBF7dlrQ/S7ua8aPiRKceLH8Q4qHECmrZC3A28WDxF1jq3TV9/yZRM+dPWUR8xJF
+ma25XFC5AA==
More information about the nginx-devel
mailing list