/*
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;
}