quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit 9e241f5866aa5e493af8641ade54df66eba4bfcd
parent c57beff4c4df73311fb9be9790097685af84d2c3
Author: Florian Dold <florian@dold.me>
Date:   Wed, 14 Dec 2022 00:29:45 +0100

blind signatures

Diffstat:
MMakefile | 2+-
Mquickjs-libc.c | 644+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 613 insertions(+), 33 deletions(-)

diff --git a/Makefile b/Makefile @@ -87,7 +87,7 @@ ifdef CONFIG_CLANG else HOST_CC=gcc CC=$(CROSS_PREFIX)gcc - CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d + CFLAGS=-ggdb -fno-omit-frame-pointer -Wall -MMD -MF $(OBJDIR)/$(@F).d CFLAGS += -Wno-array-bounds -Wno-format-truncation ifdef CONFIG_LTO AR=$(CROSS_PREFIX)gcc-ar diff --git a/quickjs-libc.c b/quickjs-libc.c @@ -82,8 +82,11 @@ typedef sig_t sighandler_t; #include <curl/curl.h> #include <sodium.h> #include <mbedtls/hkdf.h> +#include <mbedtls/bignum.h> #include <mbedtls/error.h> +#include <arpa/inet.h> + typedef struct { struct list_head link; int fd; @@ -4250,18 +4253,106 @@ uint8_t *expect_fixed_buffer(JSContext *ctx, JSValue val, size_t len, const char *msg) { - uint8_t *buf; - size_t sz; + uint8_t *buf; + size_t sz; - buf = JS_GetArrayBuffer(ctx, &sz, val); - if (!buf) { - return NULL; - } - if (sz != len) { - JS_ThrowTypeError(ctx, "invalid length for %s", msg); - return NULL; - } - return buf; + buf = JS_GetArrayBuffer(ctx, &sz, val); + if (!buf) { + return NULL; + } + if (sz != len) { + JS_ThrowTypeError(ctx, "invalid length for %s", msg); + return NULL; + } + return buf; +} + +int +expect_mpi(JSContext *ctx, + JSValue val, + const char *msg, + mbedtls_mpi *ret_mpi) +{ + uint8_t *buf; + size_t sz; + + buf = JS_GetArrayBuffer(ctx, &sz, val); + if (!buf) { + return -1; + } + if (0 != mbedtls_mpi_read_binary(ret_mpi, buf, sz)) { + return -1; + } + return 0; +} + +#define CHECK(x) do { if (!(x)) { abort(); } } while (0) + +typedef struct { + mbedtls_mpi N; + mbedtls_mpi e; +} RsaPub; + +typedef uint8_t BlindingKeySecret[32]; +typedef uint8_t HashCode[64]; + +int +rsa_public_key_decode(RsaPub *pkey, uint8_t *inbuf, size_t inbuf_len) +{ + size_t sz; + uint8_t *p; /* read pointer */ + size_t mod_len; + size_t exp_len; + int ret; + + CHECK(NULL != pkey); + if (inbuf_len < 4) { + ret = -1; + goto cleanup; + } + p = inbuf; + mod_len = ntohs(*((uint16_t *) p)); + p += sizeof(uint16_t); + exp_len = ntohs(*((uint16_t *) p)); + sz = 4 + mod_len + exp_len; + if (sz != inbuf_len) { + ret = -1; + goto cleanup; + } + p += sizeof(uint16_t); + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&pkey->N, p, mod_len)); + p += mod_len; + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&pkey->e, p, exp_len)); + +cleanup: + if (ret != 0) { + mbedtls_mpi_free(&pkey->N); + mbedtls_mpi_free(&pkey->e); + } + return ret; +} + +int +expect_rsa_pub(JSContext *ctx, + JSValue val, + const char *msg, + RsaPub *ret_rsa_pub) +{ + uint8_t *rsa_enc; + size_t rsa_enc_len; + int ret = -1; + + rsa_enc = JS_GetArrayBuffer(ctx, &rsa_enc_len, val); + if (!rsa_enc) { + goto cleanup; + } + if (0 != rsa_public_key_decode(ret_rsa_pub, rsa_enc, rsa_enc_len)) { + JS_ThrowTypeError(ctx, "rsa pubkey"); + goto cleanup; + } + ret = 0; +cleanup: + return ret; } #define REQUIRE(cond, _label) do { if (!(cond)) { goto _label; } } while (0) @@ -4278,6 +4369,42 @@ static JSValue make_js_ta_copy(JSContext *ctx, uint8_t *data, size_t size) return JS_NewTypedArray(ctx, array_buf, 1); } +/** + * Make a JS typed array from an mbedtls MPI. +*/ +static JSValue make_js_ta_mpi(JSContext *ctx, const mbedtls_mpi *v) +{ + JSValue array_buf; + size_t sz; + uint8_t *buf = NULL; + JSValue ret_val; + + sz = mbedtls_mpi_size(v); + buf = malloc(sz); + if (!buf) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + + if (0 != mbedtls_mpi_write_binary(v, buf, sz)) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + + // FIXME(#perf): Don't copy + array_buf = JS_NewArrayBufferCopy(ctx, buf, sz); + if (JS_IsException(array_buf)) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + ret_val = JS_NewTypedArray(ctx, array_buf, 1); +cleanup: + if (buf) { + free(buf); + } + return ret_val; +} + static JSValue js_talercrypto_hash(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { @@ -4414,6 +4541,40 @@ static JSValue js_talercrypto_eddsa_verify(JSContext *ctx, JSValue this_val, } /** + * Returns 0 on success. + */ +static int +kdf(void *okm, size_t okm_len, + const void *ikm, size_t ikm_len, + const void *salt, size_t salt_len, + const void *info, size_t info_len) +{ + const mbedtls_md_info_t *md_extract; + const mbedtls_md_info_t *md_expand; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + unsigned char prk[MBEDTLS_MD_MAX_SIZE]; + + md_extract = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + md_expand = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (NULL == md_extract) { + return -1; + } + if (NULL == md_expand) { + return -1; + } + + ret = mbedtls_hkdf_extract(md_extract, salt, salt_len, ikm, ikm_len, prk); + + if (ret != 0) { + return -1; + } + + ret = mbedtls_hkdf_expand(md_expand, prk, mbedtls_md_get_size(md_extract), + info, info_len, okm, okm_len); + return ret; +} + +/** * (outLen, ikm, salt?, info?) -> output */ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, @@ -4427,21 +4588,9 @@ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, uint8_t *ikm; uint8_t *info; uint8_t *okm = NULL; - const mbedtls_md_info_t *md_extract; - const mbedtls_md_info_t *md_expand; - int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - unsigned char prk[MBEDTLS_MD_MAX_SIZE]; uint32_t out_bytes; JSValue ret_val; - - md_extract = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - md_expand = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (NULL == md_extract) { - goto exception; - } - if (NULL == md_expand) { - goto exception; - } + int ret; if (0 != JS_ToUint32(ctx, &out_bytes, argv[0])) { goto exception; @@ -4473,16 +4622,10 @@ static JSValue js_talercrypto_kdf(JSContext *ctx, JSValue this_val, } } - ret = mbedtls_hkdf_extract(md_extract, salt, salt_len, ikm, ikm_len, prk); - - if (ret != 0) { - return JS_EXCEPTION; - } - okm = malloc(okm_len); - ret = mbedtls_hkdf_expand(md_expand, prk, mbedtls_md_get_size(md_extract), - info, info_len, okm, okm_len); + ret = kdf(okm, okm_len, ikm, ikm_len, salt, salt_len, info, info_len); + if (ret != 0) { return JS_EXCEPTION; } @@ -4567,6 +4710,437 @@ exception: goto done; } +/** FIXME: Should return int */ +void +kdf_mod_mpi(mbedtls_mpi *r, + const mbedtls_mpi *n, + const void *xts, size_t xts_len, + const void *skm, size_t skm_len, + const char *ctx) +{ + int rc; + unsigned int nbits; + uint16_t ctr; + size_t ctxlen = strlen(ctx); + size_t my_ctx_len = ctxlen + 2; + unsigned char *my_ctx = malloc(my_ctx_len); + uint16_t *ctr_nbo_p = (uint16_t *) (my_ctx + ctxlen); + + memcpy(my_ctx, ctx, ctxlen); + + nbits = mbedtls_mpi_bitlen(n); + ctr = 0; + while (1) { + /* Not clear if n is always divisible by 8 */ + size_t bsize = (nbits - 1) / 8 + 1; + uint8_t buf[bsize]; + + *ctr_nbo_p = htons (ctr); + + rc = kdf (buf, bsize, + skm, skm_len, + xts, xts_len, + my_ctx, my_ctx_len); + CHECK(0 == rc); + rc = mbedtls_mpi_read_binary(r, buf, bsize); + CHECK(0 == rc); + while (1) { + size_t rlen = mbedtls_mpi_bitlen(r); + if (rlen <= nbits) { + break; + } + mbedtls_mpi_set_bit(r, rlen - 1, 0); + } + ++ctr; + /* We reject this FDH if either r > n and retry with another ctr */ + if (0 > mbedtls_mpi_cmp_mpi (r, n)) { + break; + } + mbedtls_mpi_free (r); + } +} + + +/** + * Test for malicious RSA key. + * + * Assuming n is an RSA modulous and r is generated using a call to + * GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a + * malicious RSA key designed to deanomize the user. + * + * @param r KDF result + * @param n RSA modulus + * @return 0 if gcd(r,n) = 1, 1 means RSA key is malicious, negative is syserror + */ +static int +rsa_gcd_validate (mbedtls_mpi *r, + const mbedtls_mpi *n) +{ + mbedtls_mpi g; + int ret; + + mbedtls_mpi_init(&g); + MBEDTLS_MPI_CHK(mbedtls_mpi_gcd(&g, r, n)); + + if (mbedtls_mpi_cmp_int(&g, 1) == 0) { + ret = 0; + } else { + goto cleanup; + } + +cleanup: + mbedtls_mpi_free(&g); + return ret; +} + + +int +rsa_blinding_key_derive(mbedtls_mpi *r, + const RsaPub *pkey, + const BlindingKeySecret *bks) +{ + /* Trusts bks' randomness more */ + const char *xts = "Blinding KDF extractor HMAC key"; + + kdf_mod_mpi(r, + &pkey->N, + xts, strlen(xts), + bks, sizeof(*bks), + "Blinding KDF"); + + if (0 != rsa_gcd_validate (r, &pkey->N)) { + return -1; + } + + return 0; +} + +int +rsa_public_key_encode(const RsaPub *pkey, uint8_t **outbuf, size_t *outbuf_len) +{ + size_t sz; + uint8_t *buf; + uint8_t *p; /* write pointer */ + size_t mod_len; + size_t exp_len; + int ret; + + *outbuf = NULL; + *outbuf_len = 0; + + mod_len = mbedtls_mpi_size(&pkey->N); + exp_len = mbedtls_mpi_size(&pkey->e); + sz = 2 + 2 + exp_len + mod_len; + buf = malloc(sz); + if (!buf) { + return -1; + } + + p = buf; + *((uint16_t *) p) = htons(mod_len); + p += sizeof (uint16_t); + *((uint16_t *) p) = htons(exp_len); + p += sizeof (uint16_t); + MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&pkey->N, p, mod_len)); + p += mod_len; + MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&pkey->e, p, exp_len)); + + *outbuf = buf; + *outbuf_len = sz; + +cleanup: + if (0 != ret) { + free(buf); + } + return ret; +} + + +void +rsa_public_key_init(RsaPub *pkey) +{ + CHECK(NULL != pkey); + mbedtls_mpi_init(&pkey->e); + mbedtls_mpi_init(&pkey->N); +} + +void +rsa_public_key_free(RsaPub *pkey) +{ + if (!pkey) { + return; + } + mbedtls_mpi_free(&pkey->e); + mbedtls_mpi_free(&pkey->N); +} + + +int +rsa_full_domain_hash (mbedtls_mpi *r, const RsaPub *pkey, + const HashCode *hash) +{ + uint8_t *xts; + size_t xts_len; + + /* We key with the public denomination key as a homage to RSA-PSS by + Mihir Bellare and Phillip Rogaway. Doing this lowers the degree + of the hypothetical polyomial-time attack on RSA-KTI created by a + polynomial-time one-more forgary attack. Yey seeding! */ + rsa_public_key_encode(pkey, &xts, &xts_len); + + kdf_mod_mpi(r, + &pkey->N, + xts, xts_len, + hash, sizeof(*hash), + "RSA-FDA FTpsW!"); + free(xts); + if (0 == rsa_gcd_validate (r, &pkey->N)) { + return 0; + } + return 1; +} + +int +rsa_blind(const HashCode *hash, + const BlindingKeySecret *bks, + const RsaPub *pkey, + uint8_t **buf, + size_t *buf_size) +{ + mbedtls_mpi bkey, data, r_e, data_r_e; + size_t outsize; + uint8_t *outbuf; + int ret; + + CHECK(buf != NULL); + CHECK(buf_size != NULL); + + *buf = NULL; + *buf_size = 0; + + mbedtls_mpi_init(&bkey); + mbedtls_mpi_init(&data); + mbedtls_mpi_init(&r_e); + mbedtls_mpi_init(&data_r_e); + + MBEDTLS_MPI_CHK(rsa_full_domain_hash (&data, pkey, hash)); + MBEDTLS_MPI_CHK(rsa_blinding_key_derive (&bkey, pkey, bks)); + MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&r_e, &bkey, &pkey->e, &pkey->N, NULL)); + MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&data_r_e, &data, &r_e)); + MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&data_r_e, &data_r_e, &pkey->N)); + + outsize = (mbedtls_mpi_bitlen(&data_r_e) + 7) / 8; + outbuf = malloc(outsize); + + MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&data_r_e, outbuf, outsize)); + + *buf = outbuf; + *buf_size = outsize; + ret = 0; + +cleanup: + mbedtls_mpi_free(&data); + mbedtls_mpi_free(&bkey); + mbedtls_mpi_free(&r_e); + mbedtls_mpi_free(&data_r_e); + return ret; +} + +int +rsa_unblind (const mbedtls_mpi *sig_blinded, + const BlindingKeySecret *bks, + const RsaPub *pkey, + mbedtls_mpi *sig_ret) +{ + mbedtls_mpi bkey, r_inv, ubsig; + int ret; + + mbedtls_mpi_init(&bkey); + mbedtls_mpi_init(&r_inv); + mbedtls_mpi_init(&ubsig); + + MBEDTLS_MPI_CHK(rsa_blinding_key_derive (&bkey, pkey, bks)); + MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(&r_inv, &bkey, &pkey->N)); + MBEDTLS_MPI_CHK(mbedtls_mpi_mul_mpi(&ubsig, sig_blinded, &r_inv)); + MBEDTLS_MPI_CHK(mbedtls_mpi_copy(sig_ret, &ubsig)); + +cleanup: + mbedtls_mpi_free(&bkey); + mbedtls_mpi_free(&r_inv); + mbedtls_mpi_free(&ubsig); + return ret; +} + + +int +rsa_verify(const HashCode *hash, + const mbedtls_mpi *sig, + const RsaPub *pkey) +{ + mbedtls_mpi r; + mbedtls_mpi sig_2; + int ret; + + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&sig_2); + + /* Can fail if RSA key is malicious since rsa_gcd_validate failed here. + * It should have failed during GNUNET_CRYPTO_rsa_blind too though, + * so the exchange is being malicious in an unfamilair way, maybe + * just trying to crash us. Arguably, we've only an internal error + * though because we should've detected this in our previous call + * to GNUNET_CRYPTO_rsa_unblind. */// + MBEDTLS_MPI_CHK(rsa_full_domain_hash(&r, pkey, hash)); + + MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&sig_2, sig, &pkey->e, &pkey->N, NULL)); + + if (0 != mbedtls_mpi_cmp_mpi(sig, &sig_2)) { + ret = -1; + } else { + ret = 0; + } + +cleanup: + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&sig_2); + return ret; +} + + +/** + * (hmsg, bks, rsaPub) -> blinded + */ +static JSValue js_talercrypto_rsa_blind(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + HashCode *hmsg; + BlindingKeySecret *bks; + uint8_t *rsa_enc; + size_t rsa_enc_len; + RsaPub rsa_pub; + JSValue ret_val = JS_UNDEFINED; + uint8_t *out_buf; + size_t out_len; + + rsa_public_key_init(&rsa_pub); + + hmsg = (HashCode *) expect_fixed_buffer(ctx, argv[0], 64, "hmsg"); + if (!hmsg) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + bks = (BlindingKeySecret *) expect_fixed_buffer(ctx, argv[1], 32, "bks"); + if (!bks) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + rsa_enc = JS_GetArrayBuffer(ctx, &rsa_enc_len, argv[2]); + if (!rsa_enc) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + if (0 != rsa_public_key_decode(&rsa_pub, rsa_enc, rsa_enc_len)) { + ret_val = JS_ThrowTypeError(ctx, "rsa pubkey"); + goto cleanup; + } + if (0 != rsa_blind(hmsg, bks, &rsa_pub, &out_buf, &out_len)) { + ret_val = JS_ThrowInternalError(ctx, "blinding failed"); + goto cleanup; + } + + ret_val = make_js_ta_copy(ctx, out_buf, out_len); +cleanup: + rsa_public_key_free(&rsa_pub); + return ret_val; +} + +/** + * (blindSig, rsaPub, bks) -> ubsig + */ +static JSValue js_talercrypto_rsa_unblind(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret_val = JS_UNDEFINED; + mbedtls_mpi bsig; + mbedtls_mpi sig_ret; + RsaPub rsa_pub; + BlindingKeySecret *bks; + + mbedtls_mpi_init(&bsig); + mbedtls_mpi_init(&sig_ret); + rsa_public_key_init(&rsa_pub); + + if (0 != expect_mpi(ctx, argv[0], "blindSig", &bsig)) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + if (0 != expect_rsa_pub(ctx, argv[1], "rsaPub", &rsa_pub)) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + bks = (BlindingKeySecret *) expect_fixed_buffer(ctx, argv[2], 32, "bks"); + if (!bks) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + + if (0 != rsa_unblind(&bsig, bks, &rsa_pub, &sig_ret)) { + ret_val = JS_ThrowInternalError(ctx, "unblinding failed"); + goto cleanup; + } + + ret_val = make_js_ta_mpi(ctx, &sig_ret); + +cleanup: + mbedtls_mpi_free(&bsig); + mbedtls_mpi_free(&sig_ret); + rsa_public_key_free(&rsa_pub); + return ret_val; +} + +/** + * (hm, rsaSig, rsaPub) -> ubsig + */ +static JSValue js_talercrypto_rsa_verify(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret_val = JS_UNDEFINED; + HashCode *hmsg; + mbedtls_mpi sig; + RsaPub rsa_pub; + + mbedtls_mpi_init(&sig); + rsa_public_key_init(&rsa_pub); + + hmsg = (HashCode *) expect_fixed_buffer(ctx, argv[0], 64, "hmsg"); + if (!hmsg) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + + if (0 != expect_mpi(ctx, argv[1], "sig", &sig)) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + + if (0 != expect_rsa_pub(ctx, argv[2], "rsaPub", &rsa_pub)) { + ret_val = JS_EXCEPTION; + goto cleanup; + } + + if (0 != rsa_verify(hmsg, &sig, &rsa_pub)) { + ret_val = JS_FALSE; + goto cleanup; + } + + ret_val = JS_TRUE; + +cleanup: + mbedtls_mpi_free(&sig); + rsa_public_key_free(&rsa_pub); + return ret_val; + +} static JSValue js_decode_utf8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -4621,6 +5195,12 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) JS_NewCFunction(ctx, js_talercrypto_kx_eddsa_ecdh, "_keyExchangeEddsaEcdh", 2)); JS_SetPropertyStr(ctx, global_obj, "_keyExchangeEcdhEddsa", JS_NewCFunction(ctx, js_talercrypto_kx_ecdh_eddsa, "_keyExchangeEcdhEddsa", 2)); + JS_SetPropertyStr(ctx, global_obj, "_rsaBlind", + JS_NewCFunction(ctx, js_talercrypto_rsa_blind, "_rsaBlind", 3)); + JS_SetPropertyStr(ctx, global_obj, "_rsaUnblind", + JS_NewCFunction(ctx, js_talercrypto_rsa_unblind, "_rsaUnblind", 3)); + JS_SetPropertyStr(ctx, global_obj, "_rsaVerify", + JS_NewCFunction(ctx, js_talercrypto_rsa_verify, "_rsaVerify", 3)); /* same methods as the mozilla JS shell */ if (argc >= 0) {