/* This file is part of GNU Taler Copyright (C) 2022 Taler Systems SA GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with GNU Taler; see the file COPYING. If not, see */ #include "quickjs/cutils.h" #include "quickjs/list.h" #include "quickjs/quickjs-libc.h" #include #include #include #include #include #include #include #include static JSValue js_encode_utf8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *str; size_t len; JSValue buf; str = JS_ToCStringLen2(ctx, &len, argv[0], FALSE); // FIXME: Don't copy buffer but pass destructor function buf = JS_NewArrayBufferCopy(ctx, (const uint8_t*) str, len); JS_FreeCString(ctx, str); return buf; } static JSValue js_random_bytes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint32_t nbytes; JSValue buf; if (0 != JS_ToUint32(ctx, &nbytes, argv[0])) { return JS_EXCEPTION; } { uint8_t randbuf[nbytes]; randombytes_buf (randbuf, nbytes); buf = JS_NewArrayBufferCopy(ctx, randbuf, nbytes); } return buf; } /** * Get the decoded value corresponding to a character according to Crockford * Base32 encoding. * * @param a a character * @return corresponding numeric value */ static unsigned int getValue__ (unsigned char a) { unsigned int dec; switch (a) { case 'O': case 'o': a = '0'; break; case 'i': case 'I': case 'l': case 'L': a = '1'; break; /* also consider U to be V */ case 'u': case 'U': a = 'V'; break; default: break; } if ((a >= '0') && (a <= '9')) return a - '0'; if ((a >= 'a') && (a <= 'z')) a = toupper (a); /* return (a - 'a' + 10); */ dec = 0; if ((a >= 'A') && (a <= 'Z')) { if ('I' < a) dec++; if ('L' < a) dec++; if ('O' < a) dec++; if ('U' < a) dec++; return(a - 'A' + 10 - dec); } return -1; } static JSValue js_talercrypto_encode_crock(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { size_t size; uint8_t *buf; uint8_t *out = NULL; size_t out_size; // 32 characters for encoding static char *encTable__ = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; unsigned int wpos; unsigned int rpos; unsigned int bits; unsigned int vbit; const unsigned char *udata; JSValue ret_val; ret_val = JS_UNDEFINED; buf = JS_GetArrayBuffer(ctx, &size, argv[0]); if (!buf) { goto exception; } assert (size < SIZE_MAX / 8 - 4); out_size = size * 8; if (out_size % 5 > 0) out_size += 5 - out_size % 5; out_size /= 5; out = malloc (out_size + 1); memset (out, 0, out_size + 1); if (!out) { goto exception; } udata = buf; if (out_size < (size * 8 + 4) / 5) { goto exception; } vbit = 0; wpos = 0; rpos = 0; bits = 0; while ((rpos < size) || (vbit > 0)) { if ((rpos < size) && (vbit < 5)) { bits = (bits << 8) | udata[rpos++]; /* eat 8 more bits */ vbit += 8; } if (vbit < 5) { bits <<= (5 - vbit); /* zero-padding */ assert (vbit == ((size * 8) % 5)); vbit = 5; } if (wpos >= out_size) { goto exception; } out[wpos++] = encTable__[(bits >> (vbit - 5)) & 31]; vbit -= 5; } assert (0 == vbit); if (wpos < out_size) out[wpos] = '\0'; ret_val = JS_NewString(ctx, (char *) out); done: if (NULL != out) { free(out); } return ret_val; exception: ret_val = JS_EXCEPTION; goto done; } static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { size_t rpos; size_t wpos; unsigned int bits; unsigned int vbit; int ret; int shift; size_t enclen; size_t encoded_len; const char *enc; JSValue ret_val = JS_UNDEFINED; unsigned char *uout = NULL; size_t out_size; JSValue abuf; enc = JS_ToCStringLen2(ctx, &enclen, argv[0], FALSE); if (!enc) { goto exception; } out_size = (enclen * 5) / 8; encoded_len = out_size * 8; uout = malloc(out_size); assert (out_size < SIZE_MAX / 8); wpos = out_size; rpos = enclen; if ((encoded_len % 5) > 0) { vbit = encoded_len % 5; /* padding! */ shift = 5 - vbit; bits = (ret = getValue__ (enc[--rpos])) >> shift; } else { vbit = 5; shift = 0; bits = (ret = getValue__ (enc[--rpos])); } if ((encoded_len + shift) / 5 != enclen) { JS_ThrowTypeError(ctx, "wrong encoded length"); goto exception; } if (-1 == ret) { JS_ThrowTypeError(ctx, "invalid character in encoding"); goto exception; } while (wpos > 0) { if (0 == rpos) { goto exception; } bits = ((ret = getValue__ (enc[--rpos])) << vbit) | bits; if (-1 == ret) { goto exception; } vbit += 5; if (vbit >= 8) { uout[--wpos] = (unsigned char) bits; bits >>= 8; vbit -= 8; } } if ((0 != rpos) || (0 != vbit)) { JS_ThrowTypeError(ctx, "rpos or vbit not zero"); goto exception; } abuf = JS_NewArrayBufferCopy(ctx, uout, out_size); if (JS_IsException(abuf)) { goto exception; } ret_val = JS_NewTypedArray(ctx, abuf, 1); done: JS_FreeCString(ctx, enc); if (uout) { free(uout); } return ret_val; exception: ret_val = JS_EXCEPTION; goto done; } uint8_t *expect_fixed_buffer(JSContext *ctx, JSValue val, size_t len, const char *msg) { 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; } 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) static JSValue make_js_ta_copy(JSContext *ctx, uint8_t *data, size_t size) { JSValue array_buf; array_buf = JS_NewArrayBufferCopy(ctx, data, size); if (JS_IsException(array_buf)) { return JS_EXCEPTION; } 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) { size_t size; uint8_t *buf; unsigned char h[crypto_hash_BYTES]; buf = JS_GetArrayBuffer(ctx, &size, argv[0]); if (!buf) { return JS_EXCEPTION; } crypto_hash_sha512(h, buf, size); return make_js_ta_copy(ctx, h, crypto_hash_BYTES); } static JSValue js_talercrypto_eddsa_key_get_public(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { uint8_t *buf; unsigned char pk[crypto_sign_PUBLICKEYBYTES]; unsigned char sk[crypto_sign_SECRETKEYBYTES]; buf = expect_fixed_buffer(ctx, argv[0], 32, "eddsa private key"); if (!buf) { return JS_EXCEPTION; } crypto_sign_seed_keypair(pk, sk, buf); // FIXME: clean up stack! return make_js_ta_copy(ctx, pk, crypto_sign_PUBLICKEYBYTES); } static JSValue js_talercrypto_ecdhe_key_get_public(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { uint8_t *buf; unsigned char pk[crypto_scalarmult_BYTES]; buf = expect_fixed_buffer(ctx, argv[0], 32, "ecdh private key"); if (!buf) { return JS_EXCEPTION; } if (0 != crypto_scalarmult_base(pk, buf)) { return JS_EXCEPTION; } // FIXME: clean up stack! return make_js_ta_copy(ctx, pk, crypto_sign_PUBLICKEYBYTES); } /** * (msg, priv) => sig */ static JSValue js_talercrypto_eddsa_sign(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { unsigned char *seed; size_t seed_size; unsigned char *data; size_t data_size; unsigned char sk[crypto_sign_SECRETKEYBYTES]; unsigned char pk[crypto_sign_PUBLICKEYBYTES]; unsigned char sig[64]; int res; data = JS_GetArrayBuffer(ctx, &data_size, argv[0]); if (!data) { return JS_EXCEPTION; } seed = JS_GetArrayBuffer(ctx, &seed_size, argv[1]); if (!seed) { return JS_EXCEPTION; } if (seed_size != 32) { return JS_ThrowTypeError(ctx, "invalid private key size"); } if (0 != crypto_sign_seed_keypair(pk, sk, seed)) { return JS_EXCEPTION; } res = crypto_sign_detached((uint8_t *)sig, NULL, (uint8_t *)data, data_size, sk); if (res != 0) { return JS_EXCEPTION; } return make_js_ta_copy(ctx, sig, 64); } /** * (msg, sig, pub) -> bool */ static JSValue js_talercrypto_eddsa_verify(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { unsigned char *msg; size_t msg_size; unsigned char *sig; size_t sig_size; unsigned char *pub; size_t pub_size; int res; msg = JS_GetArrayBuffer(ctx, &msg_size, argv[0]); if (!msg) { return JS_EXCEPTION; } sig = JS_GetArrayBuffer(ctx, &sig_size, argv[1]); if (!sig) { return JS_EXCEPTION; } if (sig_size != 64) { return JS_ThrowTypeError(ctx, "invalid signature size"); } pub = JS_GetArrayBuffer(ctx, &pub_size, argv[2]); if (!pub) { return JS_EXCEPTION; } if (pub_size != 32) { return JS_ThrowTypeError(ctx, "invalid public key size"); } res = crypto_sign_verify_detached (sig, msg, msg_size, pub); return (res == 0) ? JS_TRUE : JS_FALSE; } /** * 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, int argc, JSValueConst *argv) { size_t salt_len; size_t ikm_len; size_t info_len; size_t okm_len; uint8_t *salt; uint8_t *ikm; uint8_t *info; uint8_t *okm = NULL; uint32_t out_bytes; JSValue ret_val; int ret; if (0 != JS_ToUint32(ctx, &out_bytes, argv[0])) { goto exception; } okm_len = out_bytes; ikm = JS_GetArrayBuffer(ctx, &ikm_len, argv[1]); if (!ikm) { goto exception; } if (JS_IsUndefined(argv[2])) { salt = NULL; salt_len = 0; } else { salt = JS_GetArrayBuffer(ctx, &salt_len, argv[2]); if (!salt) { goto exception; } } if (JS_IsUndefined(argv[3])) { info = NULL; info_len = 0; } else { info = JS_GetArrayBuffer(ctx, &info_len, argv[3]); if (!info) { goto exception; } } okm = malloc(okm_len); ret = kdf(okm, okm_len, ikm, ikm_len, salt, salt_len, info, info_len); if (ret != 0) { return JS_EXCEPTION; } ret_val = make_js_ta_copy(ctx, okm, okm_len); done: if (NULL != okm) { free(okm); } return ret_val; exception: ret_val = JS_EXCEPTION; goto done; } /** * (ecdhePriv, eddsaPub) -> keyMaterial */ static JSValue js_talercrypto_kx_ecdh_eddsa(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { JSValue ret_val; uint8_t p[crypto_scalarmult_BYTES]; uint8_t *ecdh_priv; uint8_t *eddsa_pub; uint8_t curve25510_pk[crypto_scalarmult_BYTES]; uint8_t key_material[crypto_hash_BYTES]; ecdh_priv = expect_fixed_buffer(ctx, argv[0], 32, "ecdhe priv"); REQUIRE(ecdh_priv, exception); eddsa_pub = expect_fixed_buffer(ctx, argv[1], 32, "eddsa pub"); REQUIRE(eddsa_pub, exception); if (0 != crypto_sign_ed25519_pk_to_curve25519(curve25510_pk, eddsa_pub)) { goto exception; } if (0 != crypto_scalarmult(p, ecdh_priv, curve25510_pk)) { goto exception; } if (0 != crypto_hash(key_material, p, 32)) { JS_ThrowTypeError(ctx, "hashing failed"); goto exception; } ret_val = make_js_ta_copy(ctx, key_material, crypto_hash_BYTES); done: return ret_val; exception: ret_val = JS_EXCEPTION; goto done; } /** * (eddsaPriv, ecdhePub) -> keyMaterial */ static JSValue js_talercrypto_kx_eddsa_ecdh(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { JSValue ret_val; uint8_t *priv; uint8_t *pub; uint8_t hc[crypto_hash_BYTES]; uint8_t a[crypto_scalarmult_SCALARBYTES]; uint8_t p[crypto_scalarmult_BYTES]; uint8_t key_material[crypto_hash_BYTES]; priv = expect_fixed_buffer(ctx, argv[0], 32, "eddsa priv"); REQUIRE(priv, exception); pub = expect_fixed_buffer(ctx, argv[1], 32, "ecdh pub"); REQUIRE(pub, exception); crypto_hash(hc, priv, 32); memcpy (a, &hc, 32); if (0 != crypto_scalarmult(p, a, pub)) { goto exception; } crypto_hash(key_material, p, crypto_scalarmult_BYTES); ret_val = make_js_ta_copy(ctx, key_material, crypto_hash_BYTES); done: return ret_val; exception: ret_val = JS_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) -> bool */ 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) { size_t psize; uint8_t *utf8_buf; utf8_buf = JS_GetArrayBuffer(ctx, &psize, argv[0]); if (NULL == utf8_buf) { return JS_EXCEPTION; } return JS_NewStringLen(ctx, (char *) utf8_buf, psize); } static JSValue js_structured_clone(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *buf; size_t len; JSValue obj; buf = JS_WriteObject(ctx, &len, argv[0], JS_WRITE_OBJ_REFERENCE); if (NULL == buf) { return JS_EXCEPTION; } obj = JS_ReadObject(ctx, buf, len, JS_WRITE_OBJ_REFERENCE); return obj; } static JSClassID js_hash_state_class_id; typedef struct { crypto_hash_sha512_state h; int finalized; } TART_HashState; static void js_hash_state_finalizer(JSRuntime *rt, JSValue val) { TART_HashState *s = JS_GetOpaque(val, js_hash_state_class_id); /* Note: 's' can be NULL in case JS_SetOpaque() was not called */ js_free_rt(rt, s); } static JSClassDef js_hash_state_class = { "HashState", .finalizer = js_hash_state_finalizer, }; static JSValue js_talercrypto_hash_state_init(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { TART_HashState *hstate; JSValue obj; hstate = js_malloc_rt(JS_GetRuntime(ctx), sizeof (TART_HashState)); if (!hstate) { obj = JS_EXCEPTION; goto done; } hstate->finalized = FALSE; obj = JS_NewObjectClass(ctx, js_hash_state_class_id); crypto_hash_sha512_init(&hstate->h); JS_SetOpaque(obj, hstate); hstate = NULL; return obj; done: if (NULL != hstate) { js_free_rt(JS_GetRuntime(ctx), hstate); } return obj; } static JSValue js_talercrypto_hash_state_update(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { JSValue state = argv[0]; JSValue data_val = argv[1]; TART_HashState *hstate; uint8_t *data; size_t data_len; hstate = JS_GetOpaque(state, js_hash_state_class_id); if (!hstate) { return JS_ThrowTypeError(ctx, "expected HashState"); } if (hstate->finalized) { return JS_ThrowTypeError(ctx, "already finalized"); } data = JS_GetArrayBuffer(ctx, &data_len, data_val); if (0 != crypto_hash_sha512_update(&hstate->h, data, data_len)) { return JS_ThrowInternalError(ctx, "hashing failed"); } return JS_UNDEFINED; } static JSValue js_talercrypto_hash_state_finish(JSContext *ctx, JSValue this_val, int argc, JSValueConst *argv) { JSValue state = argv[0]; TART_HashState *hstate; uint8_t hashval[crypto_hash_sha512_BYTES]; hstate = JS_GetOpaque(state, js_hash_state_class_id); if (!hstate) { return JS_ThrowTypeError(ctx, "expected HashState"); } if (hstate->finalized) { return JS_ThrowTypeError(ctx, "already finalized"); } if (0 != crypto_hash_sha512_final(&hstate->h, hashval)) { return JS_ThrowInternalError(ctx, "hashing failed"); } hstate->finalized = TRUE; return make_js_ta_copy(ctx, hashval, crypto_hash_sha512_BYTES); } static const JSCFunctionListEntry tart_talercrypto_funcs[] = { JS_CFUNC_DEF("structuredClone", 1, js_structured_clone), JS_CFUNC_DEF("encodeUtf8", 1, js_encode_utf8), JS_CFUNC_DEF("decodeUtf8", 1, js_decode_utf8), JS_CFUNC_DEF("randomBytes", 1, js_random_bytes), JS_CFUNC_DEF("encodeCrock", 1, js_talercrypto_encode_crock), JS_CFUNC_DEF("decodeCrock", 1, js_talercrypto_decode_crock), JS_CFUNC_DEF("hash", 1, js_talercrypto_hash), JS_CFUNC_DEF("hashStateInit", 0, js_talercrypto_hash_state_init), JS_CFUNC_DEF("hashStateUpdate", 2, js_talercrypto_hash_state_update), JS_CFUNC_DEF("hashStateFinish", 1, js_talercrypto_hash_state_finish), JS_CFUNC_DEF("eddsaGetPublic", 1, js_talercrypto_eddsa_key_get_public), JS_CFUNC_DEF("ecdheGetPublic", 1, js_talercrypto_ecdhe_key_get_public), JS_CFUNC_DEF("eddsaSign", 2, js_talercrypto_eddsa_sign), JS_CFUNC_DEF("eddsaVerify", 3, js_talercrypto_eddsa_verify), JS_CFUNC_DEF("kdf", 3, js_talercrypto_kdf), JS_CFUNC_DEF("keyExchangeEcdhEddsa", 2, js_talercrypto_kx_ecdh_eddsa), JS_CFUNC_DEF("keyExchangeEddsaEcdh", 2, js_talercrypto_kx_eddsa_ecdh), JS_CFUNC_DEF("rsaBlind", 3, js_talercrypto_rsa_blind), JS_CFUNC_DEF("rsaUnblind", 3, js_talercrypto_rsa_unblind), JS_CFUNC_DEF("rsaVerify", 3, js_talercrypto_rsa_verify), }; static int tart_talercrypto_init(JSContext *ctx, JSModuleDef *m) { /* create the HashState class */ JS_NewClassID(&js_hash_state_class_id); JS_NewClass(JS_GetRuntime(ctx), js_hash_state_class_id, &js_hash_state_class); return JS_SetModuleExportList(ctx, m, tart_talercrypto_funcs, countof(tart_talercrypto_funcs)); } JSModuleDef *tart_init_module_talercrypto(JSContext *ctx, const char *module_name) { JSModuleDef *m; m = JS_NewCModule(ctx, module_name, tart_talercrypto_init); if (!m) return NULL; JS_AddModuleExportList(ctx, m, tart_talercrypto_funcs, countof(tart_talercrypto_funcs)); return m; }