This commit is contained in:
Amaury Chamayou 2021-03-18 09:01:12 +00:00 коммит произвёл GitHub
Родитель b06bc3cd56
Коммит 92f9fe2015
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 1166 добавлений и 1016 удалений

Просмотреть файл

@ -1 +1 @@
Happy daily trigger :))
Happy daily trigger :)))

Просмотреть файл

@ -64,8 +64,8 @@ if("sgx" IN_LIST COMPILE_TARGETS)
# enclave version
add_library(
ccf.enclave STATIC
${CCF_DIR}/src/enclave/main.cpp ${CCF_DIR}/src/enclave/thread_local.cpp
${CCF_GENERATED_DIR}/ccf_t.cpp
${CCF_DIR}/src/enclave/main.cpp ${CCF_DIR}/src/js/wrap.cpp
${CCF_DIR}/src/enclave/thread_local.cpp ${CCF_GENERATED_DIR}/ccf_t.cpp
)
target_compile_definitions(
@ -86,6 +86,7 @@ if("sgx" IN_LIST COMPILE_TARGETS)
ccf.enclave
PUBLIC ${OE_TARGET_ENCLAVE_AND_STD}
-lgcc
quickjs.enclave
ccfcrypto.enclave
http_parser.enclave
lua.enclave
@ -111,8 +112,9 @@ endif()
if("virtual" IN_LIST COMPILE_TARGETS)
# virtual version
add_library(
ccf.virtual STATIC ${CCF_DIR}/src/enclave/main.cpp
${CCF_DIR}/src/enclave/thread_local.cpp
ccf.virtual STATIC
${CCF_DIR}/src/enclave/main.cpp ${CCF_DIR}/src/js/wrap.cpp
${CCF_DIR}/src/enclave/thread_local.cpp
)
target_compile_definitions(
@ -137,6 +139,7 @@ if("virtual" IN_LIST COMPILE_TARGETS)
ccfcrypto.host
http_parser.host
lua.host
quickjs.host
aft.virtual
sss.host
openenclave::oehost
@ -361,12 +364,12 @@ if(BUILD_TESTS)
target_link_libraries(http_test PRIVATE http_parser.host)
add_unit_test(
frontend_test
frontend_test ${CMAKE_CURRENT_SOURCE_DIR}/src/js/wrap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/frontend_test.cpp
)
target_link_libraries(
frontend_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} lua.host http_parser.host
sss.host
sss.host quickjs.host
)
add_unit_test(
@ -375,12 +378,12 @@ if(BUILD_TESTS)
)
add_unit_test(
member_voting_test
member_voting_test ${CMAKE_CURRENT_SOURCE_DIR}/src/js/wrap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/member_voting_test.cpp
)
target_link_libraries(
member_voting_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} lua.host
http_parser.host sss.host
http_parser.host sss.host quickjs.host
)
set_tests_properties(
@ -390,12 +393,12 @@ if(BUILD_TESTS)
)
add_unit_test(
proposal_id_test
proposal_id_test ${CMAKE_CURRENT_SOURCE_DIR}/src/js/wrap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/node/rpc/test/proposal_id_test.cpp
)
target_link_libraries(
proposal_id_test PRIVATE ${CMAKE_THREAD_LIBS_INIT} lua.host
http_parser.host sss.host
http_parser.host sss.host quickjs.host
)
add_unit_test(

Просмотреть файл

@ -35,9 +35,16 @@ if("sgx" IN_LIST COMPILE_TARGETS)
)
target_link_libraries(quickjs.enclave PUBLIC ${OE_TARGET_LIBC})
set_property(TARGET quickjs.enclave PROPERTY POSITION_INDEPENDENT_CODE ON)
target_include_directories(quickjs.enclave PUBLIC ${QUICKJS_INC})
target_include_directories(
quickjs.enclave PUBLIC $<BUILD_INTERFACE:${CCF_DIR}/3rdparty/quickjs>
$<INSTALL_INTERFACE:include/3rdparty/quickjs>
)
install(TARGETS quickjs.enclave DESTINATION lib)
install(
TARGETS quickjs.enclave
EXPORT ccf
DESTINATION lib
)
endif()
add_library(quickjs.host STATIC ${QUICKJS_SRC})
@ -48,6 +55,13 @@ target_compile_options(
)
add_san(quickjs.host)
set_property(TARGET quickjs.host PROPERTY POSITION_INDEPENDENT_CODE ON)
target_include_directories(quickjs.host PUBLIC ${QUICKJS_INC})
target_include_directories(
quickjs.host PUBLIC $<BUILD_INTERFACE:${CCF_DIR}/3rdparty/quickjs>
$<INSTALL_INTERFACE:include/3rdparty/quickjs>
)
install(TARGETS quickjs.host DESTINATION lib)
install(
TARGETS quickjs.host
EXPORT ccf
DESTINATION lib
)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

110
src/js/conv.cpp Normal file
Просмотреть файл

@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
namespace js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
static void js_free_arraybuffer_cstring(JSRuntime*, void* opaque, void* ptr)
{
JS_FreeCString((JSContext*)opaque, (char*)ptr);
}
static JSValue js_str_to_buf(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
if (!JS_IsString(argv[0]))
return JS_ThrowTypeError(ctx, "Argument must be a string");
size_t str_size = 0;
const char* str = JS_ToCStringLen(ctx, &str_size, argv[0]);
if (!str)
{
js_dump_error(ctx);
return JS_EXCEPTION;
}
JSValue buf = JS_NewArrayBuffer(
ctx, (uint8_t*)str, str_size, js_free_arraybuffer_cstring, ctx, false);
if (JS_IsException(buf))
js_dump_error(ctx);
return buf;
}
static JSValue js_buf_to_str(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
size_t buf_size;
uint8_t* buf = JS_GetArrayBuffer(ctx, &buf_size, argv[0]);
if (!buf)
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
JSValue str = JS_NewStringLen(ctx, (char*)buf, buf_size);
if (JS_IsException(str))
js::js_dump_error(ctx);
return str;
}
static JSValue js_json_compatible_to_buf(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
JSValue str = JS_JSONStringify(ctx, argv[0], JS_NULL, JS_NULL);
if (JS_IsException(str))
{
js::js_dump_error(ctx);
return str;
}
JSValue buf = js_str_to_buf(ctx, JS_NULL, 1, &str);
JS_FreeValue(ctx, str);
return buf;
}
static JSValue js_buf_to_json_compatible(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
size_t buf_size;
uint8_t* buf = JS_GetArrayBuffer(ctx, &buf_size, argv[0]);
if (!buf)
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
std::vector<uint8_t> buf_null_terminated(buf_size + 1);
buf_null_terminated[buf_size] = 0;
buf_null_terminated.assign(buf, buf + buf_size);
JSValue obj =
JS_ParseJSON(ctx, (char*)buf_null_terminated.data(), buf_size, "<json>");
if (JS_IsException(obj))
js::js_dump_error(ctx);
return obj;
}
#pragma clang diagnostic pop
}

201
src/js/crypto.cpp Normal file
Просмотреть файл

@ -0,0 +1,201 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "crypto/entropy.h"
#include "crypto/key_wrap.h"
#include "crypto/rsa_key_pair.h"
#include <quickjs/quickjs.h>
namespace js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
static JSValue js_generate_aes_key(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
int32_t key_size;
if (JS_ToInt32(ctx, &key_size, argv[0]) < 0)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
// Supported key sizes for AES.
if (key_size != 128 && key_size != 192 && key_size != 256)
{
JS_ThrowRangeError(ctx, "invalid key size");
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
std::vector<uint8_t> key = crypto::create_entropy()->random(key_size / 8);
return JS_NewArrayBufferCopy(ctx, key.data(), key.size());
}
static JSValue js_generate_rsa_key_pair(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 1 && argc != 2)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1 or 2", argc);
uint32_t key_size = 0, key_exponent = 0;
if (JS_ToUint32(ctx, &key_size, argv[0]) < 0)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
if (argc == 2 && JS_ToUint32(ctx, &key_exponent, argv[1]) < 0)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
std::shared_ptr<crypto::RSAKeyPair> k;
if (argc == 1)
{
k = crypto::make_rsa_key_pair(key_size);
}
else
{
k = crypto::make_rsa_key_pair(key_size, key_exponent);
}
crypto::Pem prv = k->private_key_pem();
crypto::Pem pub = k->public_key_pem();
auto r = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx, r, "privateKey", JS_NewString(ctx, (char*)prv.data()));
JS_SetPropertyStr(
ctx, r, "publicKey", JS_NewString(ctx, (char*)pub.data()));
return r;
}
static JSValue js_wrap_key(
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
if (argc != 3)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 3", argc);
// API loosely modeled after
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/wrapKey.
size_t key_size;
uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
if (!key)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
size_t wrapping_key_size;
uint8_t* wrapping_key = JS_GetArrayBuffer(ctx, &wrapping_key_size, argv[1]);
if (!wrapping_key)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
void* auto_free_ptr = JS_GetContextOpaque(ctx);
js::Context& auto_free = *(js::Context*)auto_free_ptr;
auto parameters = argv[2];
JSValue wrap_algo_name_val =
auto_free(JS_GetPropertyStr(ctx, parameters, "name"));
auto wrap_algo_name_cstr = auto_free(JS_ToCString(ctx, wrap_algo_name_val));
if (!wrap_algo_name_cstr)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
try
{
auto algo_name = std::string(wrap_algo_name_cstr);
if (algo_name == "RSA-OAEP")
{
// key can in principle be arbitrary data (see note on maximum size
// in rsa_key_pair.h). wrapping_key is a public RSA key.
auto label_val = auto_free(JS_GetPropertyStr(ctx, parameters, "label"));
size_t label_buf_size = 0;
uint8_t* label_buf = JS_GetArrayBuffer(ctx, &label_buf_size, label_val);
auto wrapped_key = crypto::ckm_rsa_pkcs_oaep_wrap(
crypto::Pem(wrapping_key, wrapping_key_size),
{key, key + key_size},
{label_buf, label_buf + label_buf_size});
return JS_NewArrayBufferCopy(
ctx, wrapped_key.data(), wrapped_key.size());
}
else if (algo_name == "AES-KWP")
{
std::vector<uint8_t> wrapped_key = crypto::ckm_aes_key_wrap_pad(
{wrapping_key, wrapping_key + wrapping_key_size},
{key, key + key_size});
return JS_NewArrayBufferCopy(
ctx, wrapped_key.data(), wrapped_key.size());
}
else if (algo_name == "RSA-OAEP-AES-KWP")
{
auto aes_key_size_value =
auto_free(JS_GetPropertyStr(ctx, parameters, "aesKeySize"));
int32_t aes_key_size = 0;
if (JS_ToInt32(ctx, &aes_key_size, aes_key_size_value) < 0)
{
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
auto label_val = auto_free(JS_GetPropertyStr(ctx, parameters, "label"));
size_t label_buf_size = 0;
uint8_t* label_buf = JS_GetArrayBuffer(ctx, &label_buf_size, label_val);
auto wrapped_key = crypto::ckm_rsa_aes_key_wrap(
aes_key_size,
crypto::Pem(wrapping_key, wrapping_key_size),
{key, key + key_size},
{label_buf, label_buf + label_buf_size});
return JS_NewArrayBufferCopy(
ctx, wrapped_key.data(), wrapped_key.size());
}
else
{
JS_ThrowRangeError(
ctx,
"unsupported key wrapping algorithm, supported: RSA-OAEP, AES-KWP, "
"RSA-OAEP-AES-KWP");
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
}
catch (std::exception& ex)
{
JS_ThrowRangeError(ctx, "%s", ex.what());
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
catch (...)
{
JS_ThrowRangeError(ctx, "caught unknown exception");
js::js_dump_error(ctx);
return JS_EXCEPTION;
}
}
#pragma clang diagnostic pop
}

625
src/js/wrap.cpp Normal file
Просмотреть файл

@ -0,0 +1,625 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "js/wrap.h"
#include "ds/logger.h"
#include "js/conv.cpp"
#include "js/crypto.cpp"
#include "kv/untyped_map.h"
#include "node/rpc/call_types.h"
#include "tls/base64.h"
#include "tx_id.h"
#include <memory>
#include <quickjs/quickjs-exports.h>
#include <quickjs/quickjs.h>
namespace js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
using KVMap = kv::untyped::Map;
JSClassID kv_class_id = 0;
JSClassID kv_map_handle_class_id = 0;
JSClassID body_class_id = 0;
JSClassDef kv_class_def = {};
JSClassExoticMethods kv_exotic_methods = {};
JSClassDef kv_map_handle_class_def = {};
JSClassDef body_class_def = {};
static JSValue js_kv_map_has(
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
{
auto handle = static_cast<KVMap::Handle*>(
JS_GetOpaque(this_val, kv_map_handle_class_id));
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
size_t key_size;
uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
if (!key)
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
auto has = handle->has({key, key + key_size});
return JS_NewBool(ctx, has);
}
static JSValue js_kv_map_get(
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
{
auto handle = static_cast<KVMap::Handle*>(
JS_GetOpaque(this_val, kv_map_handle_class_id));
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
size_t key_size;
uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
if (!key)
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
auto val = handle->get({key, key + key_size});
if (!val.has_value())
return JS_UNDEFINED;
JSValue buf =
JS_NewArrayBufferCopy(ctx, val.value().data(), val.value().size());
if (JS_IsException(buf))
js_dump_error(ctx);
return buf;
}
static JSValue js_kv_map_delete(
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
{
auto handle = static_cast<KVMap::Handle*>(
JS_GetOpaque(this_val, kv_map_handle_class_id));
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
size_t key_size;
uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
if (!key)
return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
auto val = handle->remove({key, key + key_size});
return JS_NewBool(ctx, val);
}
static JSValue js_kv_map_delete_read_only(
JSContext* ctx, JSValueConst, int, JSValueConst*)
{
return JS_ThrowTypeError(ctx, "Cannot call delete on read-only map");
}
static JSValue js_kv_map_set(
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
{
auto handle = static_cast<KVMap::Handle*>(
JS_GetOpaque(this_val, kv_map_handle_class_id));
if (argc != 2)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 2", argc);
size_t key_size;
uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
size_t val_size;
uint8_t* val = JS_GetArrayBuffer(ctx, &val_size, argv[1]);
if (!key || !val)
return JS_ThrowTypeError(ctx, "Arguments must be ArrayBuffers");
handle->put({key, key + key_size}, {val, val + val_size});
return JS_DupValue(ctx, this_val);
}
static JSValue js_kv_map_set_read_only(
JSContext* ctx, JSValueConst, int, JSValueConst*)
{
return JS_ThrowTypeError(ctx, "Cannot call set on read-only map");
}
static JSValue js_kv_map_foreach(
JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
{
auto handle = static_cast<KVMap::Handle*>(
JS_GetOpaque(this_val, kv_map_handle_class_id));
if (argc != 1)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected 1", argc);
JSValue func = argv[0];
if (!JS_IsFunction(ctx, func))
return JS_ThrowTypeError(ctx, "Argument must be a function");
bool failed = false;
handle->foreach(
[ctx, this_val, func, &failed](const auto& k, const auto& v) {
JSValue args[3];
// JS forEach expects (v, k, map) rather than (k, v)
args[0] = JS_NewArrayBufferCopy(ctx, v.data(), v.size());
args[1] = JS_NewArrayBufferCopy(ctx, k.data(), k.size());
args[2] = JS_DupValue(ctx, this_val);
auto val = JS_Call(ctx, func, JS_UNDEFINED, 3, args);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
JS_FreeValue(ctx, args[2]);
if (JS_IsException(val))
{
js_dump_error(ctx);
failed = true;
return false;
}
JS_FreeValue(ctx, val);
return true;
});
if (failed)
{
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}
static int js_kv_lookup(
JSContext* ctx,
JSPropertyDescriptor* desc,
JSValueConst this_val,
JSAtom property)
{
const auto property_name_c = JS_AtomToCString(ctx, property);
const std::string property_name(property_name_c);
JS_FreeCString(ctx, property_name_c);
LOG_TRACE_FMT("Looking for kv map '{}'", property_name);
const auto [security_domain, access_category] =
kv::parse_map_name(property_name);
auto read_only = false;
switch (access_category)
{
case kv::AccessCategory::INTERNAL:
{
if (security_domain == kv::SecurityDomain::PUBLIC)
{
read_only = true;
}
else
{
throw std::runtime_error(fmt::format(
"JS application cannot access private internal CCF table '{}'",
property_name));
}
break;
}
case kv::AccessCategory::GOVERNANCE:
{
read_only = true;
break;
}
case kv::AccessCategory::APPLICATION:
{
break;
}
default:
{
throw std::logic_error(fmt::format(
"Unhandled AccessCategory for table '{}'", property_name));
}
}
auto tx_ptr = static_cast<kv::Tx*>(JS_GetOpaque(this_val, kv_class_id));
auto handle = tx_ptr->rw<KVMap>(property_name);
// This follows the interface of Map:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
// Keys and values are ArrayBuffers. Keys are matched based on their
// contents.
auto view_val = JS_NewObjectClass(ctx, kv_map_handle_class_id);
JS_SetOpaque(view_val, handle);
JS_SetPropertyStr(
ctx, view_val, "has", JS_NewCFunction(ctx, js_kv_map_has, "has", 1));
JS_SetPropertyStr(
ctx, view_val, "get", JS_NewCFunction(ctx, js_kv_map_get, "get", 1));
auto setter = js_kv_map_set;
auto deleter = js_kv_map_delete;
if (read_only)
{
setter = js_kv_map_set_read_only;
deleter = js_kv_map_delete_read_only;
}
JS_SetPropertyStr(
ctx, view_val, "set", JS_NewCFunction(ctx, setter, "set", 2));
JS_SetPropertyStr(
ctx, view_val, "delete", JS_NewCFunction(ctx, deleter, "delete", 1));
JS_SetPropertyStr(
ctx,
view_val,
"forEach",
JS_NewCFunction(ctx, js_kv_map_foreach, "forEach", 1));
desc->flags = 0;
desc->value = view_val;
return true;
}
JSValue js_body_text(
JSContext* ctx,
JSValueConst this_val,
int argc,
[[maybe_unused]] JSValueConst* argv)
{
if (argc != 0)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected none", argc);
auto body = static_cast<const std::vector<uint8_t>*>(
JS_GetOpaque(this_val, body_class_id));
auto body_ = JS_NewStringLen(ctx, (const char*)body->data(), body->size());
return body_;
}
JSValue js_body_json(
JSContext* ctx,
JSValueConst this_val,
int argc,
[[maybe_unused]] JSValueConst* argv)
{
if (argc != 0)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected none", argc);
auto body = static_cast<const std::vector<uint8_t>*>(
JS_GetOpaque(this_val, body_class_id));
std::string body_str(body->begin(), body->end());
auto body_ = JS_ParseJSON(ctx, body_str.c_str(), body->size(), "<body>");
return body_;
}
JSValue js_body_array_buffer(
JSContext* ctx,
JSValueConst this_val,
int argc,
[[maybe_unused]] JSValueConst* argv)
{
if (argc != 0)
return JS_ThrowTypeError(
ctx, "Passed %d arguments, but expected none", argc);
auto body = static_cast<const std::vector<uint8_t>*>(
JS_GetOpaque(this_val, body_class_id));
auto body_ = JS_NewArrayBufferCopy(ctx, body->data(), body->size());
return body_;
}
// Partially replicates https://developer.mozilla.org/en-US/docs/Web/API/Body
// with a synchronous interface.
static const JSCFunctionListEntry js_body_proto_funcs[] = {
JS_CFUNC_DEF("text", 0, js_body_text),
JS_CFUNC_DEF("json", 0, js_body_json),
JS_CFUNC_DEF("arrayBuffer", 0, js_body_array_buffer),
};
// Not thread-safe, must happen exactly once
void register_class_ids()
{
JS_NewClassID(&kv_class_id);
kv_exotic_methods.get_own_property = js_kv_lookup;
kv_class_def.class_name = "KV Tables";
kv_class_def.exotic = &kv_exotic_methods;
JS_NewClassID(&kv_map_handle_class_id);
kv_map_handle_class_def.class_name = "KV Map Handle";
JS_NewClassID(&body_class_id);
body_class_def.class_name = "Body";
}
JSValue js_print(JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
{
int i;
const char* str;
std::stringstream ss;
for (i = 0; i < argc; i++)
{
if (i != 0)
ss << ' ';
if (!JS_IsError(ctx, argv[i]) && JS_IsObject(argv[i]))
{
JSValue rval = JS_JSONStringify(ctx, argv[i], JS_NULL, JS_NULL);
str = JS_ToCString(ctx, rval);
JS_FreeValue(ctx, rval);
}
else
str = JS_ToCString(ctx, argv[i]);
if (!str)
return JS_EXCEPTION;
ss << str;
JS_FreeCString(ctx, str);
}
LOG_INFO << ss.str() << std::endl;
return JS_UNDEFINED;
}
void js_dump_error(JSContext* ctx)
{
JSValue exception_val = JS_GetException(ctx);
JSValue val;
const char* stack;
bool is_error;
is_error = JS_IsError(ctx, exception_val);
if (!is_error)
LOG_INFO_FMT("Throw: ");
js_print(ctx, JS_NULL, 1, (JSValueConst*)&exception_val);
if (is_error)
{
val = JS_GetPropertyStr(ctx, exception_val, "stack");
if (!JS_IsUndefined(val))
{
stack = JS_ToCString(ctx, val);
LOG_INFO_FMT("{}", stack);
JS_FreeCString(ctx, stack);
}
JS_FreeValue(ctx, val);
}
JS_FreeValue(ctx, exception_val);
}
JSValue Context::function(const std::string& code, const std::string& path)
{
JSValue module = JS_Eval(
ctx,
code.c_str(),
code.size(),
path.c_str(),
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(module))
{
js_dump_error(ctx);
throw std::runtime_error(fmt::format("Failed to compile {}", path));
}
auto eval_val = JS_EvalFunction(ctx, module);
if (JS_IsException(eval_val))
{
js_dump_error(ctx);
JS_FreeValue(ctx, eval_val);
throw std::runtime_error(fmt::format("Failed to execute {}", path));
}
JS_FreeValue(ctx, eval_val);
// Get exported function from module
assert(JS_VALUE_GET_TAG(module) == JS_TAG_MODULE);
auto module_def = (JSModuleDef*)JS_VALUE_GET_PTR(module);
if (JS_GetModuleExportEntriesCount(module_def) != 1)
{
throw std::runtime_error(
"Endpoint module exports more than one function");
}
auto export_func = JS_GetModuleExportEntry(ctx, module_def, 0);
if (!JS_IsFunction(ctx, export_func))
{
JS_FreeValue(ctx, export_func);
throw std::runtime_error(
"Endpoint module exports something that is not a function");
}
return export_func;
}
void register_request_body_class(JSContext* ctx)
{
// Set prototype for request body class
JSValue body_proto = JS_NewObject(ctx);
size_t func_count =
sizeof(js_body_proto_funcs) / sizeof(js_body_proto_funcs[0]);
JS_SetPropertyFunctionList(
ctx, body_proto, js_body_proto_funcs, func_count);
JS_SetClassProto(ctx, body_class_id, body_proto);
}
static JSValue create_console_obj(JSContext* ctx)
{
auto console = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx, console, "log", JS_NewCFunction(ctx, js_print, "log", 1));
return console;
}
void populate_global_console(JSContext* ctx)
{
auto global_obj = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global_obj, "console", create_console_obj(ctx));
JS_FreeValue(ctx, global_obj);
}
JSValue create_ccf_obj(
kv::Tx* tx,
const std::optional<kv::TxID>& transaction_id,
ccf::historical::TxReceiptPtr receipt,
JSContext* ctx)
{
auto ccf = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx, ccf, "strToBuf", JS_NewCFunction(ctx, js_str_to_buf, "strToBuf", 1));
JS_SetPropertyStr(
ctx, ccf, "bufToStr", JS_NewCFunction(ctx, js_buf_to_str, "bufToStr", 1));
JS_SetPropertyStr(
ctx,
ccf,
"jsonCompatibleToBuf",
JS_NewCFunction(
ctx, js_json_compatible_to_buf, "jsonCompatibleToBuf", 1));
JS_SetPropertyStr(
ctx,
ccf,
"bufToJsonCompatible",
JS_NewCFunction(
ctx, js_buf_to_json_compatible, "bufToJsonCompatible", 1));
JS_SetPropertyStr(
ctx,
ccf,
"generateAesKey",
JS_NewCFunction(ctx, js_generate_aes_key, "generateAesKey", 1));
JS_SetPropertyStr(
ctx,
ccf,
"generateRsaKeyPair",
JS_NewCFunction(ctx, js_generate_rsa_key_pair, "generateRsaKeyPair", 1));
JS_SetPropertyStr(
ctx, ccf, "wrapKey", JS_NewCFunction(ctx, js_wrap_key, "wrapKey", 3));
if (tx != nullptr)
{
auto kv = JS_NewObjectClass(ctx, kv_class_id);
JS_SetOpaque(kv, tx);
JS_SetPropertyStr(ctx, ccf, "kv", kv);
}
// Historical queries
if (receipt)
{
auto state = JS_NewObject(ctx);
ccf::TxID tx_id;
tx_id.seqno = static_cast<ccf::SeqNo>(transaction_id.value().version);
tx_id.view = static_cast<ccf::View>(transaction_id.value().term);
JS_SetPropertyStr(
ctx, state, "transactionId", JS_NewString(ctx, tx_id.to_str().c_str()));
ccf::GetReceipt::Out receipt_out;
receipt_out.from_receipt(receipt);
auto js_receipt = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
js_receipt,
"signature",
JS_NewString(ctx, receipt_out.signature.c_str()));
JS_SetPropertyStr(
ctx, js_receipt, "root", JS_NewString(ctx, receipt_out.root.c_str()));
JS_SetPropertyStr(
ctx, js_receipt, "leaf", JS_NewString(ctx, receipt_out.leaf.c_str()));
JS_SetPropertyStr(
ctx,
js_receipt,
"nodeId",
JS_NewString(ctx, receipt_out.node_id.value().c_str()));
auto proof = JS_NewArray(ctx);
uint32_t i = 0;
for (auto& element : receipt_out.proof)
{
auto js_element = JS_NewObject(ctx);
auto is_left = element.left.has_value();
JS_SetPropertyStr(
ctx,
js_element,
is_left ? "left" : "right",
JS_NewString(
ctx, (is_left ? element.left : element.right).value().c_str()));
JS_DefinePropertyValueUint32(
ctx, proof, i++, js_element, JS_PROP_C_W_E);
}
JS_SetPropertyStr(ctx, js_receipt, "proof", proof);
JS_SetPropertyStr(ctx, state, "receipt", js_receipt);
JS_SetPropertyStr(ctx, ccf, "historicalState", state);
}
return ccf;
}
void populate_global_ccf(
kv::Tx* tx,
const std::optional<kv::TxID>& transaction_id,
ccf::historical::TxReceiptPtr receipt,
JSContext* ctx)
{
auto global_obj = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(
ctx, global_obj, "ccf", create_ccf_obj(tx, transaction_id, receipt, ctx));
JS_FreeValue(ctx, global_obj);
}
void Runtime::add_ccf_classdefs()
{
// Register class for KV
{
auto ret = JS_NewClass(rt, kv_class_id, &kv_class_def);
if (ret != 0)
{
throw std::logic_error("Failed to register JS class definition for KV");
}
}
// Register class for KV map views
{
auto ret =
JS_NewClass(rt, kv_map_handle_class_id, &kv_map_handle_class_def);
if (ret != 0)
{
throw std::logic_error(
"Failed to register JS class definition for KVMap");
}
}
// Register class for request body
{
auto ret = JS_NewClass(rt, body_class_id, &body_class_def);
if (ret != 0)
{
throw std::logic_error(
"Failed to register JS class definition for Body");
}
}
}
#pragma clang diagnostic pop
}

173
src/js/wrap.h Normal file
Просмотреть файл

@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "ds/logger.h"
#include "historical_queries_interface.h"
#include "kv/kv_types.h"
#include "kv/tx.h"
#include <memory>
#include <quickjs/quickjs-exports.h>
#include <quickjs/quickjs.h>
namespace js
{
extern JSClassID kv_class_id;
extern JSClassID kv_map_handle_class_id;
extern JSClassID body_class_id;
extern JSClassDef kv_class_def;
extern JSClassExoticMethods kv_exotic_methods;
extern JSClassDef kv_map_handle_class_def;
extern JSClassDef body_class_def;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
void register_class_ids();
void register_request_body_class(JSContext* ctx);
void populate_global_console(JSContext* ctx);
void populate_global_ccf(
kv::Tx* tx,
const std::optional<kv::TxID>& transaction_id,
ccf::historical::TxReceiptPtr receipt,
JSContext* ctx);
JSValue js_print(JSContext* ctx, JSValueConst, int argc, JSValueConst* argv);
void js_dump_error(JSContext* ctx);
JSValue js_body_text(
JSContext* ctx,
JSValueConst this_val,
int argc,
[[maybe_unused]] JSValueConst* argv);
JSValue js_body_json(
JSContext* ctx,
JSValueConst this_val,
int argc,
[[maybe_unused]] JSValueConst* argv);
JSValue js_body_array_buffer(
JSContext* ctx,
JSValueConst this_val,
int argc,
[[maybe_unused]] JSValueConst* argv);
class Runtime
{
JSRuntime* rt;
public:
inline Runtime(
size_t max_stack_size = 1024 * 1024,
size_t max_heap_size = 100 * 1024 * 1024)
{
rt = JS_NewRuntime();
if (rt == nullptr)
{
throw std::runtime_error("Failed to initialise QuickJS runtime");
}
JS_SetMaxStackSize(rt, max_stack_size);
JS_SetMemoryLimit(rt, max_heap_size);
}
inline ~Runtime()
{
JS_FreeRuntime(rt);
}
inline operator JSRuntime*() const
{
return rt;
}
void add_ccf_classdefs();
};
class Context
{
JSContext* ctx;
public:
inline Context(JSRuntime* rt)
{
ctx = JS_NewContext(rt);
if (ctx == nullptr)
{
throw std::runtime_error("Failed to initialise QuickJS context");
}
JS_SetContextOpaque(ctx, this);
}
inline ~Context()
{
JS_FreeContext(ctx);
}
inline operator JSContext*() const
{
return ctx;
}
struct JSWrappedValue
{
inline JSWrappedValue(JSContext* ctx, JSValue&& val) :
ctx(ctx),
val(std::move(val))
{}
inline ~JSWrappedValue()
{
JS_FreeValue(ctx, val);
}
inline operator const JSValue&() const
{
return val;
}
JSContext* ctx;
JSValue val;
};
struct JSWrappedCString
{
inline JSWrappedCString(JSContext* ctx, const char* cstr) :
ctx(ctx),
cstr(cstr)
{}
inline ~JSWrappedCString()
{
JS_FreeCString(ctx, cstr);
}
inline operator const char*() const
{
return cstr;
}
inline operator std::string() const
{
return std::string(cstr);
}
inline operator std::string_view() const
{
return std::string_view(cstr);
}
JSContext* ctx;
const char* cstr;
};
inline JSWrappedValue operator()(JSValue&& val)
{
return JSWrappedValue(ctx, std::move(val));
};
inline JSWrappedCString operator()(const char* cstr)
{
return JSWrappedCString(ctx, cstr);
};
JSValue function(const std::string& code, const std::string& path);
};
#pragma clang diagnostic pop
}

Просмотреть файл

@ -7,6 +7,7 @@
#include "node/ledger_secrets.h"
#include "node/members.h"
#include "node/node_info_network.h"
#include "tls/base64.h"
#include <nlohmann/json.hpp>
#include <openenclave/advanced/mallinfo.h>

Просмотреть файл

@ -66,7 +66,7 @@ namespace ccf
constexpr int64_t VIEW_UNKNOWN = std::numeric_limits<int64_t>::min();
static TxStatus evaluate_tx_status(
[[maybe_unused]] static TxStatus evaluate_tx_status(
int64_t target_view,
int64_t target_seqno,
int64_t local_view,