blob: fbef019ff595b110c65e645de1d23e3d47e43ca1 [file] [log] [blame]
/*
* Copyright (C) Dmitry Volyntsev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
typedef void (*njs_hash_init)(void *ctx);
typedef void (*njs_hash_update)(void *ctx, const void *data, size_t size);
typedef void (*njs_hash_final)(u_char *result, void *ctx);
typedef njs_int_t (*njs_digest_encode)(njs_vm_t *vm, njs_value_t *value,
const njs_str_t *src);
typedef struct {
njs_str_t name;
size_t size;
njs_hash_init init;
njs_hash_update update;
njs_hash_final final;
} njs_hash_alg_t;
typedef struct {
union {
njs_md5_t md5;
njs_sha1_t sha1;
njs_sha2_t sha2;
} u;
njs_hash_alg_t *alg;
} njs_digest_t;
typedef struct {
u_char opad[64];
union {
njs_md5_t md5;
njs_sha1_t sha1;
njs_sha2_t sha2;
} u;
njs_hash_alg_t *alg;
} njs_hmac_t;
typedef struct {
njs_str_t name;
njs_digest_encode encode;
} njs_crypto_enc_t;
static njs_hash_alg_t *njs_crypto_algorithm(njs_vm_t *vm,
const njs_value_t *value);
static njs_crypto_enc_t *njs_crypto_encoding(njs_vm_t *vm,
const njs_value_t *value);
static njs_int_t njs_buffer_digest(njs_vm_t *vm, njs_value_t *value,
const njs_str_t *src);
static njs_int_t njs_crypto_create_hash(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t njs_hash_prototype_update(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t hmac);
static njs_int_t njs_hash_prototype_digest(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t hmac);
static njs_int_t njs_crypto_create_hmac(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t njs_crypto_init(njs_vm_t *vm);
static njs_hash_alg_t njs_hash_algorithms[] = {
{
njs_str("md5"),
16,
(njs_hash_init) njs_md5_init,
(njs_hash_update) njs_md5_update,
(njs_hash_final) njs_md5_final
},
{
njs_str("sha1"),
20,
(njs_hash_init) njs_sha1_init,
(njs_hash_update) njs_sha1_update,
(njs_hash_final) njs_sha1_final
},
{
njs_str("sha256"),
32,
(njs_hash_init) njs_sha2_init,
(njs_hash_update) njs_sha2_update,
(njs_hash_final) njs_sha2_final
},
{
njs_null_str,
0,
NULL,
NULL,
NULL
}
};
static njs_crypto_enc_t njs_encodings[] = {
{
njs_str("buffer"),
njs_buffer_digest
},
{
njs_str("hex"),
njs_string_hex
},
{
njs_str("base64"),
njs_string_base64
},
{
njs_str("base64url"),
njs_string_base64url
},
{
njs_null_str,
NULL
}
};
static njs_external_t njs_ext_crypto_hash[] = {
{
.flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
.name.symbol = NJS_SYMBOL_TO_STRING_TAG,
.u.property = {
.value = "Hash",
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("update"),
.writable = 1,
.configurable = 1,
.u.method = {
.native = njs_hash_prototype_update,
.magic8 = 0,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("digest"),
.writable = 1,
.configurable = 1,
.u.method = {
.native = njs_hash_prototype_digest,
.magic8 = 0,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("constructor"),
.writable = 1,
.configurable = 1,
.u.method = {
.native = njs_crypto_create_hash,
}
},
};
static njs_external_t njs_ext_crypto_hmac[] = {
{
.flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
.name.symbol = NJS_SYMBOL_TO_STRING_TAG,
.u.property = {
.value = "Hmac",
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("update"),
.writable = 1,
.configurable = 1,
.u.method = {
.native = njs_hash_prototype_update,
.magic8 = 1,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("digest"),
.writable = 1,
.configurable = 1,
.u.method = {
.native = njs_hash_prototype_digest,
.magic8 = 1,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("constructor"),
.writable = 1,
.configurable = 1,
.u.method = {
.native = njs_crypto_create_hmac,
.magic8 = 0,
}
},
};
static njs_external_t njs_ext_crypto_crypto[] = {
{
.flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
.name.symbol = NJS_SYMBOL_TO_STRING_TAG,
.u.property = {
.value = "crypto",
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("createHash"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = njs_crypto_create_hash,
.magic8 = 0,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("createHmac"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = njs_crypto_create_hmac,
.magic8 = 0,
}
},
};
static njs_int_t njs_crypto_hash_proto_id;
static njs_int_t njs_crypto_hmac_proto_id;
njs_module_t njs_crypto_module = {
.name = njs_str("crypto"),
.init = njs_crypto_init,
};
static njs_int_t
njs_crypto_create_hash(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_digest_t *dgst;
njs_hash_alg_t *alg;
alg = njs_crypto_algorithm(vm, njs_arg(args, nargs, 1));
if (njs_slow_path(alg == NULL)) {
return NJS_ERROR;
}
dgst = njs_mp_alloc(vm->mem_pool, sizeof(njs_digest_t));
if (njs_slow_path(dgst == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
dgst->alg = alg;
alg->init(&dgst->u);
return njs_vm_external_create(vm, &vm->retval, njs_crypto_hash_proto_id,
dgst, 0);
}
static njs_int_t
njs_hash_prototype_update(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t hmac)
{
njs_str_t data;
njs_int_t ret;
njs_hmac_t *ctx;
njs_value_t *this, dst;
njs_digest_t *dgst;
njs_typed_array_t *array;
const njs_value_t *value;
njs_array_buffer_t *buffer;
const njs_buffer_encoding_t *encoding;
this = njs_argument(args, 0);
if (!hmac) {
dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, this);
if (njs_slow_path(dgst == NULL)) {
njs_type_error(vm, "\"this\" is not a hash object");
return NJS_ERROR;
}
if (njs_slow_path(dgst->alg == NULL)) {
njs_error(vm, "Digest already called");
return NJS_ERROR;
}
ctx = NULL;
} else {
ctx = njs_vm_external(vm, njs_crypto_hmac_proto_id, this);
if (njs_slow_path(ctx == NULL)) {
njs_type_error(vm, "\"this\" is not a hmac object");
return NJS_ERROR;
}
if (njs_slow_path(ctx->alg == NULL)) {
njs_error(vm, "Digest already called");
return NJS_ERROR;
}
dgst = NULL;
}
value = njs_arg(args, nargs, 1);
switch (value->type) {
case NJS_STRING:
encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2));
if (njs_slow_path(encoding == NULL)) {
return NJS_ERROR;
}
ret = njs_buffer_decode_string(vm, value, &dst, encoding);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
njs_string_get(&dst, &data);
break;
case NJS_TYPED_ARRAY:
case NJS_DATA_VIEW:
array = njs_typed_array(value);
buffer = array->buffer;
if (njs_slow_path(njs_is_detached_buffer(buffer))) {
njs_type_error(vm, "detached buffer");
return NJS_ERROR;
}
data.start = &buffer->u.u8[array->offset];
data.length = array->byte_length;
break;
default:
njs_type_error(vm, "data argument \"%s\" is not a string "
"or Buffer-like object", njs_type_string(value->type));
return NJS_ERROR;
}
if (!hmac) {
dgst->alg->update(&dgst->u, data.start, data.length);
} else {
ctx->alg->update(&ctx->u, data.start, data.length);
}
vm->retval = *this;
return NJS_OK;
}
static njs_int_t
njs_hash_prototype_digest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t hmac)
{
njs_str_t str;
njs_hmac_t *ctx;
njs_value_t *this;
njs_digest_t *dgst;
njs_hash_alg_t *alg;
njs_crypto_enc_t *enc;
u_char hash1[32], digest[32];
this = njs_argument(args, 0);
if (!hmac) {
dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, this);
if (njs_slow_path(dgst == NULL)) {
njs_type_error(vm, "\"this\" is not a hash object");
return NJS_ERROR;
}
if (njs_slow_path(dgst->alg == NULL)) {
goto exception;
}
ctx = NULL;
} else {
ctx = njs_vm_external(vm, njs_crypto_hmac_proto_id, this);
if (njs_slow_path(ctx == NULL)) {
njs_type_error(vm, "\"this\" is not a hmac object");
return NJS_ERROR;
}
if (njs_slow_path(ctx->alg == NULL)) {
goto exception;
}
dgst = NULL;
}
enc = njs_crypto_encoding(vm, njs_arg(args, nargs, 1));
if (njs_slow_path(enc == NULL)) {
return NJS_ERROR;
}
if (!hmac) {
alg = dgst->alg;
alg->final(digest, &dgst->u);
dgst->alg = NULL;
} else {
alg = ctx->alg;
alg->final(hash1, &ctx->u);
alg->init(&ctx->u);
alg->update(&ctx->u, ctx->opad, 64);
alg->update(&ctx->u, hash1, alg->size);
alg->final(digest, &ctx->u);
ctx->alg = NULL;
}
str.start = digest;
str.length = alg->size;
return enc->encode(vm, &vm->retval, &str);
exception:
njs_error(vm, "Digest already called");
return NJS_ERROR;
}
static njs_int_t
njs_crypto_create_hmac(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_str_t key;
njs_uint_t i;
njs_hmac_t *ctx;
njs_hash_alg_t *alg;
njs_typed_array_t *array;
const njs_value_t *value;
njs_array_buffer_t *buffer;
u_char digest[32], key_buf[64];
alg = njs_crypto_algorithm(vm, njs_arg(args, nargs, 1));
if (njs_slow_path(alg == NULL)) {
return NJS_ERROR;
}
value = njs_arg(args, nargs, 2);
switch (value->type) {
case NJS_STRING:
njs_string_get(value, &key);
break;
case NJS_TYPED_ARRAY:
case NJS_DATA_VIEW:
array = njs_typed_array(value);
buffer = array->buffer;
if (njs_slow_path(njs_is_detached_buffer(buffer))) {
njs_type_error(vm, "detached buffer");
return NJS_ERROR;
}
key.start = &buffer->u.u8[array->offset];
key.length = array->byte_length;
break;
default:
njs_type_error(vm, "key argument \"%s\" is not a string "
"or Buffer-like object", njs_type_string(value->type));
return NJS_ERROR;
}
ctx = njs_mp_alloc(vm->mem_pool, sizeof(njs_hmac_t));
if (njs_slow_path(ctx == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
ctx->alg = alg;
if (key.length > sizeof(key_buf)) {
alg->init(&ctx->u);
alg->update(&ctx->u, key.start, key.length);
alg->final(digest, &ctx->u);
memcpy(key_buf, digest, alg->size);
njs_explicit_memzero(key_buf + alg->size, sizeof(key_buf) - alg->size);
} else {
memcpy(key_buf, key.start, key.length);
njs_explicit_memzero(key_buf + key.length,
sizeof(key_buf) - key.length);
}
for (i = 0; i < 64; i++) {
ctx->opad[i] = key_buf[i] ^ 0x5c;
}
for (i = 0; i < 64; i++) {
key_buf[i] ^= 0x36;
}
alg->init(&ctx->u);
alg->update(&ctx->u, key_buf, 64);
return njs_vm_external_create(vm, &vm->retval, njs_crypto_hmac_proto_id,
ctx, 0);
}
static njs_hash_alg_t *
njs_crypto_algorithm(njs_vm_t *vm, const njs_value_t *value)
{
njs_str_t name;
njs_hash_alg_t *e;
if (njs_slow_path(!njs_is_string(value))) {
njs_type_error(vm, "algorithm must be a string");
return NULL;
}
njs_string_get(value, &name);
for (e = &njs_hash_algorithms[0]; e->name.length != 0; e++) {
if (njs_strstr_eq(&name, &e->name)) {
return e;
}
}
njs_type_error(vm, "not supported algorithm: \"%V\"", &name);
return NULL;
}
static njs_crypto_enc_t *
njs_crypto_encoding(njs_vm_t *vm, const njs_value_t *value)
{
njs_str_t name;
njs_crypto_enc_t *e;
if (njs_slow_path(!njs_is_string(value))) {
if (njs_is_defined(value)) {
njs_type_error(vm, "encoding must be a string");
return NULL;
}
return &njs_encodings[0];
}
njs_string_get(value, &name);
for (e = &njs_encodings[1]; e->name.length != 0; e++) {
if (njs_strstr_eq(&name, &e->name)) {
return e;
}
}
njs_type_error(vm, "Unknown digest encoding: \"%V\"", &name);
return NULL;
}
static njs_int_t
njs_buffer_digest(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
{
return njs_buffer_new(vm, value, src->start, src->length);
}
static njs_int_t
njs_crypto_init(njs_vm_t *vm)
{
njs_int_t ret, proto_id;
njs_mod_t *module;
njs_opaque_value_t value;
njs_crypto_hash_proto_id =
njs_vm_external_prototype(vm, njs_ext_crypto_hash,
njs_nitems(njs_ext_crypto_hash));
if (njs_slow_path(njs_crypto_hash_proto_id < 0)) {
return NJS_ERROR;
}
njs_crypto_hmac_proto_id =
njs_vm_external_prototype(vm, njs_ext_crypto_hmac,
njs_nitems(njs_ext_crypto_hmac));
if (njs_slow_path(njs_crypto_hmac_proto_id < 0)) {
return NJS_ERROR;
}
proto_id = njs_vm_external_prototype(vm, njs_ext_crypto_crypto,
njs_nitems(njs_ext_crypto_crypto));
if (njs_slow_path(proto_id < 0)) {
return NJS_ERROR;
}
ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
module = njs_module_add(vm, &njs_str_value("crypto"));
if (njs_slow_path(module == NULL)) {
return NJS_ERROR;
}
njs_value_assign(&module->value, &value);
module->function.native = 1;
return NJS_OK;
}