/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include "auth.h" #include "class_binder.h" #include "http_connection.h" #include "http_message.h" #include "io.h" #include #include #include #include #include #include #include #include static const char *AWS_NAPI_KEY_ENDPOINT = "endpoint"; static const char *AWS_NAPI_KEY_IDENTITY = "identity"; static const char *AWS_NAPI_KEY_LOGINS = "logins"; static const char *AWS_NAPI_KEY_CUSTOM_ROLE_ARN = "customRoleArn"; static const char *AWS_NAPI_KEY_IDENTITY_PROVIDER_NAME = "identityProviderName"; static const char *AWS_NAPI_KEY_IDENTITY_PROVIDER_TOKEN = "identityProviderToken"; static const char *AWS_NAPI_KEY_THING_NAME = "thingName"; static const char *AWS_NAPI_KEY_ROLE_ALIAS = "roleAlias"; static struct aws_napi_class_info s_creds_provider_class_info; static aws_napi_method_fn s_creds_provider_constructor; static aws_napi_method_fn s_creds_provider_new_default; static aws_napi_method_fn s_creds_provider_new_static; static aws_napi_method_fn s_creds_provider_new_cognito; static aws_napi_method_fn s_creds_provider_new_x509; static aws_napi_method_fn s_aws_sign_request; static aws_napi_method_fn s_aws_verify_sigv4a_signing; napi_status aws_napi_auth_bind(napi_env env, napi_value exports) { static const struct aws_napi_method_info s_creds_provider_constructor_info = { .name = "AwsCredentialsProvider", .method = s_creds_provider_constructor, .num_arguments = 1, .arg_types = {napi_external}, }; static const struct aws_napi_method_info s_creds_provider_methods[] = { { .name = "newDefault", .method = s_creds_provider_new_default, .num_arguments = 1, .arg_types = {napi_undefined}, .attributes = napi_static, }, { .name = "newStatic", .method = s_creds_provider_new_static, .num_arguments = 2, .arg_types = {napi_string, napi_string, napi_string}, .attributes = napi_static, }, { .name = "newCognito", .method = s_creds_provider_new_cognito, .num_arguments = 4, .arg_types = {napi_undefined, napi_undefined, napi_undefined, napi_undefined}, .attributes = napi_static, }, { .name = "newX509", .method = s_creds_provider_new_x509, .num_arguments = 3, .arg_types = {napi_undefined, napi_undefined, napi_undefined}, .attributes = napi_static, }}; AWS_NAPI_CALL( env, aws_napi_define_class( env, exports, &s_creds_provider_constructor_info, NULL, 0, s_creds_provider_methods, AWS_ARRAY_SIZE(s_creds_provider_methods), &s_creds_provider_class_info), { return status; }); static struct aws_napi_method_info s_signer_request_method = { .name = "aws_sign_request", .method = s_aws_sign_request, .num_arguments = 3, .arg_types = {napi_object, napi_object, napi_function}, }; AWS_NAPI_CALL(env, aws_napi_define_function(env, exports, &s_signer_request_method), { return status; }); static struct aws_napi_method_info s_verify_sigv4a_signing_method = { .name = "aws_verify_sigv4a_signing", .method = s_aws_verify_sigv4a_signing, .num_arguments = 6, .arg_types = {napi_object, napi_object, napi_string, napi_string, napi_string, napi_string}, }; AWS_NAPI_CALL(env, aws_napi_define_function(env, exports, &s_verify_sigv4a_signing_method), { return status; }); return napi_ok; } /*********************************************************************************************************************** * Credentials Provider **********************************************************************************************************************/ static void s_napi_creds_provider_finalize(napi_env env, void *finalize_data, void *finalize_hint) { (void)env; (void)finalize_hint; aws_credentials_provider_release(finalize_data); } napi_status aws_napi_credentials_provider_wrap( napi_env env, struct aws_credentials_provider *creds_provider, napi_value *result) { aws_credentials_provider_acquire(creds_provider); return aws_napi_wrap(env, &s_creds_provider_class_info, creds_provider, s_napi_creds_provider_finalize, result); } struct aws_credentials_provider *aws_napi_credentials_provider_unwrap(napi_env env, napi_value js_object) { struct aws_credentials_provider *creds_provider = NULL; AWS_NAPI_CALL(env, napi_unwrap(env, js_object, (void **)&creds_provider), { return NULL; }); aws_credentials_provider_acquire(creds_provider); return creds_provider; } static napi_value s_creds_provider_constructor(napi_env env, const struct aws_napi_callback_info *cb_info) { (void)env; /* Don't do any construction, object should be empty except prototype and wrapped value */ return cb_info->native_this; } static napi_value s_creds_provider_new_default(napi_env env, const struct aws_napi_callback_info *cb_info) { AWS_FATAL_ASSERT(cb_info->num_args == 1); struct aws_allocator *allocator = aws_napi_get_allocator(); const struct aws_napi_argument *arg = NULL; aws_napi_method_next_argument(napi_external, cb_info, &arg); struct aws_credentials_provider_chain_default_options options; AWS_ZERO_STRUCT(options); if (arg->native.external != NULL) { options.bootstrap = aws_napi_get_client_bootstrap(arg->native.external); } else { options.bootstrap = aws_napi_get_default_client_bootstrap(); } struct aws_credentials_provider *provider = aws_credentials_provider_new_chain_default(allocator, &options); napi_value node_this = NULL; AWS_NAPI_CALL(env, aws_napi_credentials_provider_wrap(env, provider, &node_this), { napi_throw_error(env, NULL, "Failed to wrap CredentialsProvider"); return NULL; }); /* Reference is now held by the node object */ aws_credentials_provider_release(provider); return node_this; } static napi_value s_creds_provider_new_static(napi_env env, const struct aws_napi_callback_info *cb_info) { AWS_FATAL_ASSERT(cb_info->num_args >= 2); struct aws_allocator *allocator = aws_napi_get_allocator(); const struct aws_napi_argument *arg = NULL; struct aws_credentials_provider_static_options options; AWS_ZERO_STRUCT(options); aws_napi_method_next_argument(napi_string, cb_info, &arg); options.access_key_id = aws_byte_cursor_from_buf(&arg->native.string); aws_napi_method_next_argument(napi_string, cb_info, &arg); options.secret_access_key = aws_byte_cursor_from_buf(&arg->native.string); if (aws_napi_method_next_argument(napi_string, cb_info, &arg)) { options.session_token = aws_byte_cursor_from_buf(&arg->native.string); } struct aws_credentials_provider *provider = aws_credentials_provider_new_static(allocator, &options); napi_value node_this = NULL; AWS_NAPI_CALL(env, aws_napi_credentials_provider_wrap(env, provider, &node_this), { napi_throw_error(env, NULL, "Failed to wrap CredentialsProvider"); return NULL; }); /* Reference is now held by the node object */ aws_credentials_provider_release(provider); return node_this; } struct aws_cognito_credentials_provider_config { struct aws_byte_buf endpoint; struct aws_byte_buf identity; struct aws_array_list logins; struct aws_array_list login_buffers; struct aws_byte_buf custom_role_arn; }; static void s_aws_cognito_credentials_provider_config_clean_up(struct aws_cognito_credentials_provider_config *config) { aws_byte_buf_clean_up(&config->endpoint); aws_byte_buf_clean_up(&config->identity); aws_array_list_clean_up(&config->logins); for (size_t i = 0; i < aws_array_list_length(&config->login_buffers); ++i) { struct aws_byte_buf buffer; AWS_ZERO_STRUCT(buffer); aws_array_list_get_at(&config->login_buffers, &buffer, i); aws_byte_buf_clean_up(&buffer); } aws_array_list_clean_up(&config->login_buffers); aws_byte_buf_clean_up(&config->custom_role_arn); } static int s_aws_cognito_credentials_provider_config_init( struct aws_cognito_credentials_provider_config *config, napi_env env, napi_value node_config) { if (env == NULL) { return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV); } int result = AWS_OP_ERR; struct aws_byte_buf identity_provider_name_buf; AWS_ZERO_STRUCT(identity_provider_name_buf); struct aws_byte_buf identity_provider_token_buf; AWS_ZERO_STRUCT(identity_provider_token_buf); struct aws_allocator *allocator = aws_napi_get_allocator(); if (aws_array_list_init_dynamic( &config->logins, allocator, 0, sizeof(struct aws_cognito_identity_provider_token_pair)) || aws_array_list_init_dynamic(&config->login_buffers, allocator, 0, sizeof(struct aws_byte_buf))) { return AWS_OP_ERR; } if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, node_config, AWS_NAPI_KEY_ENDPOINT, napi_string, &config->endpoint)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - required property 'endpoint' could not be extracted from " "config"); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, node_config, AWS_NAPI_KEY_IDENTITY, napi_string, &config->identity)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - required property 'identity' could not be extracted from " "config"); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } napi_value napi_logins = NULL; if (AWS_NGNPR_VALID_VALUE == aws_napi_get_named_property(env, node_config, AWS_NAPI_KEY_LOGINS, napi_object, &napi_logins)) { /* how many login entries */ uint32_t login_count = 0; AWS_NAPI_CALL(env, napi_get_array_length(env, napi_logins, &login_count), { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - property 'logins' must be an array"); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); }); for (size_t i = 0; i < login_count; ++i) { napi_value napi_token_pair = NULL; AWS_NAPI_CALL(env, napi_get_element(env, napi_logins, (uint32_t)i, &napi_token_pair), { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - could not access property 'logins' array " "element"); aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); goto done; }); if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, napi_token_pair, AWS_NAPI_KEY_IDENTITY_PROVIDER_NAME, napi_string, &identity_provider_name_buf)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - required property 'identityProviderName' missing " "from login token pair"); aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); goto done; } if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, napi_token_pair, AWS_NAPI_KEY_IDENTITY_PROVIDER_TOKEN, napi_string, &identity_provider_token_buf)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - required property 'identityProviderToken' " "missing from login token pair"); aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); goto done; } struct aws_byte_cursor identity_provider_name_cursor = aws_byte_cursor_from_buf(&identity_provider_name_buf); if (aws_array_list_push_back(&config->login_buffers, &identity_provider_name_buf)) { goto done; } AWS_ZERO_STRUCT(identity_provider_name_buf); struct aws_byte_cursor identity_provider_token_cursor = aws_byte_cursor_from_buf(&identity_provider_token_buf); if (aws_array_list_push_back(&config->login_buffers, &identity_provider_token_buf)) { goto done; } AWS_ZERO_STRUCT(identity_provider_token_buf); struct aws_cognito_identity_provider_token_pair config_token_pair = { .identity_provider_name = identity_provider_name_cursor, .identity_provider_token = identity_provider_token_cursor, }; if (aws_array_list_push_back(&config->logins, &config_token_pair)) { goto done; } } } if (AWS_NGNPR_INVALID_VALUE == aws_napi_get_named_property_as_bytebuf( env, node_config, AWS_NAPI_KEY_CUSTOM_ROLE_ARN, napi_string, &config->custom_role_arn)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_cognito_credentials_provider_config_init - optional property 'customRoleArn' could not be extracted " "from " "config"); aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); goto done; } result = AWS_OP_SUCCESS; done: /* we carefully ensure that we only hit here with buffers that failed to be added to the login_buffers array */ aws_byte_buf_clean_up(&identity_provider_name_buf); aws_byte_buf_clean_up(&identity_provider_token_buf); return result; } static napi_value s_creds_provider_new_cognito(napi_env env, const struct aws_napi_callback_info *cb_info) { AWS_FATAL_ASSERT(cb_info->num_args == 4); napi_value node_provider = NULL; struct aws_allocator *allocator = aws_napi_get_allocator(); struct aws_credentials_provider *provider = NULL; struct aws_cognito_credentials_provider_config provider_config; AWS_ZERO_STRUCT(provider_config); struct aws_credentials_provider_cognito_options options; AWS_ZERO_STRUCT(options); const struct aws_napi_argument *arg = NULL; aws_napi_method_next_argument(napi_undefined, cb_info, &arg); napi_value first_argument = arg->node; if (s_aws_cognito_credentials_provider_config_init(&provider_config, env, first_argument)) { napi_throw_error(env, NULL, "Failed to initialize cognito provider configuration from node config"); goto done; } options.endpoint = aws_byte_cursor_from_buf(&provider_config.endpoint); options.identity = aws_byte_cursor_from_buf(&provider_config.identity); options.login_count = aws_array_list_length(&provider_config.logins); options.logins = provider_config.logins.data; struct aws_byte_cursor custom_role_arn_cursor; AWS_ZERO_STRUCT(custom_role_arn_cursor); if (provider_config.custom_role_arn.len > 0) { custom_role_arn_cursor = aws_byte_cursor_from_buf(&provider_config.custom_role_arn); options.custom_role_arn = &custom_role_arn_cursor; } aws_napi_method_next_argument(napi_external, cb_info, &arg); AWS_NAPI_CALL(env, napi_get_value_external(env, arg->node, (void **)&options.tls_ctx), { napi_throw_error(env, NULL, "Failed to extract tls_ctx from external"); goto done; }); aws_napi_method_next_argument(napi_external, cb_info, &arg); if (arg->native.external != NULL) { options.bootstrap = aws_napi_get_client_bootstrap(arg->native.external); } else { options.bootstrap = aws_napi_get_default_client_bootstrap(); } aws_napi_method_next_argument(napi_external, cb_info, &arg); if (arg->native.external != NULL) { options.http_proxy_options = aws_napi_get_http_proxy_options(arg->native.external); } provider = aws_credentials_provider_new_cognito_caching(allocator, &options); if (provider == NULL) { napi_throw_error(env, NULL, "Failed to create native Cognito Credentials Provider"); goto done; } AWS_NAPI_CALL(env, aws_napi_credentials_provider_wrap(env, provider, &node_provider), { napi_throw_error(env, NULL, "Failed to wrap CognitoCredentialsProvider"); goto done; }); done: s_aws_cognito_credentials_provider_config_clean_up(&provider_config); aws_credentials_provider_release(provider); return node_provider; } struct aws_x509_credentials_provider_config { struct aws_byte_buf endpoint; struct aws_byte_buf thing_name; struct aws_byte_buf role_alias; }; static void s_aws_x509_credentials_provider_config_clean_up(struct aws_x509_credentials_provider_config *config) { aws_byte_buf_clean_up(&config->endpoint); aws_byte_buf_clean_up(&config->thing_name); aws_byte_buf_clean_up(&config->role_alias); } static int s_aws_x509_credentials_provider_config_init( struct aws_x509_credentials_provider_config *config, napi_env env, napi_value node_config) { if (env == NULL) { return aws_raise_error(AWS_CRT_NODEJS_ERROR_THREADSAFE_FUNCTION_NULL_NAPI_ENV); } if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, node_config, AWS_NAPI_KEY_ENDPOINT, napi_string, &config->endpoint)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_x509_credentials_provider_config_init - required property 'endpoint' could not be extracted from " "config"); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, node_config, AWS_NAPI_KEY_THING_NAME, napi_string, &config->thing_name)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_x509_credentials_provider_config_init - required property 'thing_name' could not be extracted from " "config"); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } if (AWS_NGNPR_VALID_VALUE != aws_napi_get_named_property_as_bytebuf( env, node_config, AWS_NAPI_KEY_ROLE_ALIAS, napi_string, &config->role_alias)) { AWS_LOGF_ERROR( AWS_LS_NODEJS_CRT_GENERAL, "s_aws_x509_credentials_provider_config_init - required property 'role_alias' could not be extracted from " "config"); return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } return AWS_OP_SUCCESS; } static napi_value s_creds_provider_new_x509(napi_env env, const struct aws_napi_callback_info *cb_info) { AWS_FATAL_ASSERT(cb_info->num_args == 3); napi_value node_provider = NULL; struct aws_allocator *allocator = aws_napi_get_allocator(); struct aws_credentials_provider *provider = NULL; struct aws_x509_credentials_provider_config provider_config; AWS_ZERO_STRUCT(provider_config); struct aws_tls_connection_options tls_connection_options; AWS_ZERO_STRUCT(tls_connection_options); struct aws_credentials_provider_x509_options options; AWS_ZERO_STRUCT(options); const struct aws_napi_argument *arg = NULL; aws_napi_method_next_argument(napi_undefined, cb_info, &arg); napi_value first_argument = arg->node; if (s_aws_x509_credentials_provider_config_init(&provider_config, env, first_argument)) { napi_throw_error(env, NULL, "Failed to initialize x509 provider configuration from node config"); goto done; } options.endpoint = aws_byte_cursor_from_buf(&provider_config.endpoint); options.thing_name = aws_byte_cursor_from_buf(&provider_config.thing_name); options.role_alias = aws_byte_cursor_from_buf(&provider_config.role_alias); struct aws_tls_ctx *tls_context = NULL; aws_napi_method_next_argument(napi_external, cb_info, &arg); AWS_NAPI_CALL(env, napi_get_value_external(env, arg->node, (void **)&tls_context), { napi_throw_error(env, NULL, "Failed to extract tls_ctx from external"); goto done; }); if (tls_context != NULL) { aws_tls_connection_options_init_from_ctx(&tls_connection_options, tls_context); options.tls_connection_options = &tls_connection_options; } else { napi_throw_error(env, NULL, "Failed to extract and set tls_ctx from external"); goto done; } /* Always use the default bootstrap */ options.bootstrap = aws_napi_get_default_client_bootstrap(); aws_napi_method_next_argument(napi_external, cb_info, &arg); if (arg->native.external != NULL) { options.proxy_options = aws_napi_get_http_proxy_options(arg->native.external); } provider = aws_credentials_provider_new_x509(allocator, &options); if (provider == NULL) { napi_throw_error(env, NULL, "Failed to create native X509 Credentials Provider"); goto done; } AWS_NAPI_CALL(env, aws_napi_credentials_provider_wrap(env, provider, &node_provider), { napi_throw_error(env, NULL, "Failed to wrap X509CredentialsProvider"); goto done; }); done: aws_tls_connection_options_clean_up(&tls_connection_options); s_aws_x509_credentials_provider_config_clean_up(&provider_config); aws_credentials_provider_release(provider); return node_provider; } /*********************************************************************************************************************** * Signing **********************************************************************************************************************/ struct signer_sign_request_state { napi_ref node_request; struct aws_http_message *request; struct aws_signable *signable; /** * aws_string * * this exists so that when should_sign_param is called from off thread, we don't have to hit Node every single * time */ struct aws_array_list header_blacklist; napi_threadsafe_function on_complete; int error_code; }; static bool s_should_sign_header(const struct aws_byte_cursor *name, void *userdata) { struct signer_sign_request_state *state = userdata; /* If there are params in the black_list, check them all */ if (state->header_blacklist.length) { const size_t num_blacklisted = aws_array_list_length(&state->header_blacklist); for (size_t i = 0; i < num_blacklisted; ++i) { struct aws_string *blacklisted = NULL; aws_array_list_get_at(&state->header_blacklist, &blacklisted, i); AWS_ASSUME(blacklisted); if (aws_string_eq_byte_cursor_ignore_case(blacklisted, name)) { return false; } } } return true; } static void s_destroy_signing_binding( napi_env env, struct aws_allocator *allocator, struct signer_sign_request_state *binding) { if (binding == NULL) { return; } /* Release references */ napi_delete_reference(env, binding->node_request); const size_t num_blacklisted = binding->header_blacklist.length; for (size_t i = 0; i < num_blacklisted; ++i) { struct aws_string *blacklisted = NULL; aws_array_list_get_at(&binding->header_blacklist, &blacklisted, i); aws_string_destroy(blacklisted); } aws_array_list_clean_up(&binding->header_blacklist); aws_signable_destroy(binding->signable); AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(binding->on_complete, napi_tsfn_abort)); aws_mem_release(allocator, binding); } static void s_aws_sign_request_complete_call(napi_env env, napi_value on_complete, void *context, void *user_data) { struct signer_sign_request_state *state = context; struct aws_allocator *allocator = user_data; napi_value args[1]; AWS_NAPI_ENSURE(env, napi_create_int32(env, state->error_code, &args[0])); AWS_NAPI_ENSURE( env, aws_napi_dispatch_threadsafe_function(env, state->on_complete, NULL, on_complete, AWS_ARRAY_SIZE(args), args)); s_destroy_signing_binding(env, allocator, state); } static void s_aws_sign_request_complete(struct aws_signing_result *result, int error_code, void *userdata) { struct signer_sign_request_state *state = userdata; struct aws_allocator *allocator = aws_napi_get_allocator(); state->error_code = error_code; if (error_code == AWS_ERROR_SUCCESS) { aws_apply_signing_result_to_http_request(state->request, allocator, result); } AWS_NAPI_ENSURE(NULL, aws_napi_queue_threadsafe_function(state->on_complete, allocator)); } static int s_get_config_from_js_config( napi_env env, struct aws_signing_config_aws *config, napi_value js_config, struct aws_byte_buf *region_buf, struct aws_byte_buf *service_buf, struct aws_byte_buf *signed_body_value_buf, struct signer_sign_request_state *state, struct aws_allocator *allocator) { config->config_type = AWS_SIGNING_CONFIG_AWS; int result = AWS_OP_SUCCESS; napi_value current_value = NULL; /* Get algorithm */ if (aws_napi_get_named_property(env, js_config, "algorithm", napi_number, ¤t_value) == AWS_NGNPR_VALID_VALUE) { int32_t algorithm_int = 0; napi_get_value_int32(env, current_value, &algorithm_int); if (algorithm_int < 0) { napi_throw_error(env, NULL, "Signing algorithm value out of acceptable range"); result = AWS_OP_ERR; goto done; } config->algorithm = (enum aws_signing_algorithm)algorithm_int; } /* Get signature type */ if (aws_napi_get_named_property(env, js_config, "signature_type", napi_number, ¤t_value) == AWS_NGNPR_VALID_VALUE) { int32_t signature_type_int = 0; napi_get_value_int32(env, current_value, &signature_type_int); if (signature_type_int < 0) { napi_throw_error(env, NULL, "Signing signature type value out of acceptable range"); result = AWS_OP_ERR; goto done; } config->signature_type = (enum aws_signature_type)signature_type_int; } /* Get provider */ if (aws_napi_get_named_property(env, js_config, "provider", napi_object, ¤t_value) != AWS_NGNPR_VALID_VALUE || NULL == (config->credentials_provider = aws_napi_credentials_provider_unwrap(env, current_value))) { napi_throw_type_error(env, NULL, "Credentials Provider is required"); result = AWS_OP_ERR; goto done; } /* Get region */ if (aws_napi_get_named_property(env, js_config, "region", napi_string, ¤t_value) != AWS_NGNPR_VALID_VALUE) { napi_throw_type_error(env, NULL, "Region string is required"); result = AWS_OP_ERR; goto done; } if (aws_byte_buf_init_from_napi(region_buf, env, current_value)) { napi_throw_error(env, NULL, "Failed to build region buffer"); result = AWS_OP_ERR; goto done; } config->region = aws_byte_cursor_from_buf(region_buf); /* Get service */ if (aws_napi_get_named_property(env, js_config, "service", napi_string, ¤t_value) == AWS_NGNPR_VALID_VALUE) { if (aws_byte_buf_init_from_napi(service_buf, env, current_value)) { napi_throw_error(env, NULL, "Failed to build service buffer"); result = AWS_OP_ERR; goto done; } config->service = aws_byte_cursor_from_buf(service_buf); } /* Get date */ /* #TODO eventually check for napi_date type (node v11) */ if (aws_napi_get_named_property(env, js_config, "date", napi_object, ¤t_value) == AWS_NGNPR_VALID_VALUE) { napi_value prototype = NULL; AWS_NAPI_CALL(env, napi_get_prototype(env, current_value, &prototype), { napi_throw_type_error(env, NULL, "Date param must be a Date object"); result = AWS_OP_ERR; goto done; }); napi_value valueOfFn = NULL; AWS_NAPI_CALL(env, napi_get_named_property(env, prototype, "getTime", &valueOfFn), { napi_throw_type_error(env, NULL, "Date param must be a Date object"); result = AWS_OP_ERR; goto done; }); napi_value node_result = NULL; AWS_NAPI_CALL(env, napi_call_function(env, current_value, valueOfFn, 0, NULL, &node_result), { napi_throw_type_error(env, NULL, "Date param must be a Date object"); result = AWS_OP_ERR; goto done; }); int64_t ms_since_epoch = 0; AWS_NAPI_CALL(env, napi_get_value_int64(env, node_result, &ms_since_epoch), { napi_throw_type_error(env, NULL, "Date param must be a Date object"); result = AWS_OP_ERR; goto done; }); aws_date_time_init_epoch_millis(&config->date, (uint64_t)ms_since_epoch); } else { aws_date_time_init_now(&config->date); } /* Get param blacklist */ if (aws_napi_get_named_property(env, js_config, "header_blacklist", napi_object, ¤t_value) == AWS_NGNPR_VALID_VALUE) { bool is_array = false; AWS_NAPI_CALL(env, napi_is_array(env, current_value, &is_array), { napi_throw_error(env, NULL, "Failed to check if header blacklist is an array"); result = AWS_OP_ERR; goto done; }); if (!is_array) { napi_throw_type_error(env, NULL, "header blacklist must be an array of strings"); result = AWS_OP_ERR; goto done; } uint32_t blacklist_length = 0; AWS_NAPI_CALL(env, napi_get_array_length(env, current_value, &blacklist_length), { napi_throw_error(env, NULL, "Failed to get the length of node_header_blacklist"); result = AWS_OP_ERR; goto done; }); /* Initialize the string array */ int err = aws_array_list_init_dynamic( &state->header_blacklist, allocator, blacklist_length, sizeof(struct aws_string *)); if (err == AWS_OP_ERR) { aws_napi_throw_last_error(env); result = AWS_OP_ERR; goto done; } /* Start copying the strings */ for (uint32_t i = 0; i < blacklist_length; ++i) { napi_value header = NULL; AWS_NAPI_CALL(env, napi_get_element(env, current_value, i, &header), { napi_throw_error(env, NULL, "Failed to get element from param blacklist"); result = AWS_OP_ERR; goto done; }); struct aws_string *header_name = aws_string_new_from_napi(env, header); if (!header_name) { napi_throw_error(env, NULL, "header blacklist must be array of strings"); result = AWS_OP_ERR; goto done; } if (aws_array_list_push_back(&state->header_blacklist, &header_name)) { aws_string_destroy(header_name); aws_napi_throw_last_error(env); result = AWS_OP_ERR; goto done; } } config->should_sign_header = s_should_sign_header; config->should_sign_header_ud = state; } /* Get bools */ if (aws_napi_get_named_property(env, js_config, "use_double_uri_encode", napi_boolean, ¤t_value) == AWS_NGNPR_VALID_VALUE) { bool property_value = true; napi_get_value_bool(env, current_value, &property_value); config->flags.use_double_uri_encode = property_value; } else { config->flags.use_double_uri_encode = true; } if (aws_napi_get_named_property(env, js_config, "should_normalize_uri_path", napi_boolean, ¤t_value) == AWS_NGNPR_VALID_VALUE) { bool property_value = true; napi_get_value_bool(env, current_value, &property_value); config->flags.should_normalize_uri_path = property_value; } else { config->flags.should_normalize_uri_path = true; } if (aws_napi_get_named_property(env, js_config, "omit_session_token", napi_boolean, ¤t_value) == AWS_NGNPR_VALID_VALUE) { bool property_value = true; napi_get_value_bool(env, current_value, &property_value); config->flags.omit_session_token = property_value; } else { config->flags.omit_session_token = false; } /* Get signed body value */ if (aws_napi_get_named_property(env, js_config, "signed_body_value", napi_string, ¤t_value) == AWS_NGNPR_VALID_VALUE) { if (aws_byte_buf_init_from_napi(signed_body_value_buf, env, current_value)) { napi_throw_error(env, NULL, "Failed to build signed_body_value buffer"); result = AWS_OP_ERR; goto done; } config->signed_body_value = aws_byte_cursor_from_buf(signed_body_value_buf); } /* Get signed body header */ if (aws_napi_get_named_property(env, js_config, "signed_body_header", napi_number, ¤t_value) == AWS_NGNPR_VALID_VALUE) { int32_t signed_body_header = 0; napi_get_value_int32(env, current_value, &signed_body_header); config->signed_body_header = (enum aws_signed_body_header_type)signed_body_header; } else { config->signed_body_header = AWS_SBHT_NONE; } /* Get expiration time */ if (aws_napi_get_named_property(env, js_config, "expiration_in_seconds", napi_number, ¤t_value) == AWS_NGNPR_VALID_VALUE) { int64_t expiration_in_seconds = 0; napi_get_value_int64(env, current_value, &expiration_in_seconds); if (expiration_in_seconds < 0) { napi_throw_error(env, NULL, "Signing expiration time in seconds must be non-negative"); result = AWS_OP_ERR; goto done; } config->expiration_in_seconds = (uint64_t)expiration_in_seconds; } done: return result; } static napi_value s_aws_sign_request(napi_env env, const struct aws_napi_callback_info *cb_info) { struct aws_allocator *allocator = aws_napi_get_allocator(); const struct aws_napi_argument *arg = NULL; struct signer_sign_request_state *state = aws_mem_calloc(allocator, 1, sizeof(struct signer_sign_request_state)); if (!state) { return NULL; } /* Temp buffers */ struct aws_byte_buf region_buf; AWS_ZERO_STRUCT(region_buf); struct aws_byte_buf service_buf; AWS_ZERO_STRUCT(service_buf); struct aws_byte_buf signed_body_value_buf; AWS_ZERO_STRUCT(signed_body_value_buf); /* Get request */ aws_napi_method_next_argument(napi_object, cb_info, &arg); napi_create_reference(env, arg->node, 1, &state->node_request); state->request = aws_napi_http_message_unwrap(env, arg->node); state->signable = aws_signable_new_http_request(allocator, state->request); /* Populate config */ struct aws_signing_config_aws config; AWS_ZERO_STRUCT(config); aws_napi_method_next_argument(napi_object, cb_info, &arg); napi_value js_config = arg->node; if (s_get_config_from_js_config( env, &config, js_config, ®ion_buf, &service_buf, &signed_body_value_buf, state, allocator)) { /* error already raised */ goto error; } aws_napi_method_next_argument(napi_function, cb_info, &arg); AWS_NAPI_CALL( env, aws_napi_create_threadsafe_function( env, arg->node, "aws_signer_on_signing_complete", s_aws_sign_request_complete_call, state, &state->on_complete), { napi_throw_type_error(env, NULL, "on_shutdown must be a valid callback or undefined"); goto error; }); if (aws_sign_request_aws( allocator, state->signable, (struct aws_signing_config_base *)&config, s_aws_sign_request_complete, state)) { aws_napi_throw_last_error(env); AWS_NAPI_ENSURE(env, aws_napi_release_threadsafe_function(state->on_complete, napi_tsfn_abort)); } goto done; error: // Additional cleanup needed when we didn't successfully bind the on_complete function s_destroy_signing_binding(env, allocator, state); done: // Shared cleanup aws_credentials_provider_release(config.credentials_provider); aws_byte_buf_clean_up(®ion_buf); aws_byte_buf_clean_up(&service_buf); aws_byte_buf_clean_up(&signed_body_value_buf); return NULL; } struct sigv4a_credentail_getter_state { struct aws_allocator *allocator; struct aws_condition_variable cvar; struct aws_mutex lock; bool completed; struct aws_signing_config_aws *config; }; static void s_aws_signv4a_on_get_credentials(struct aws_credentials *credentials, int error_code, void *user_data) { (void)error_code; struct sigv4a_credentail_getter_state *state = user_data; aws_mutex_lock(&state->lock); state->completed = true; if (credentials) { state->config->credentials = credentials; } aws_mutex_unlock(&state->lock); aws_condition_variable_notify_one(&state->cvar); } static bool s_get_credential_completed(void *arg) { struct sigv4a_credentail_getter_state *state = arg; return state->completed; } static void s_wait_for_get_credential_to_complete(struct sigv4a_credentail_getter_state *state) { aws_mutex_lock(&state->lock); aws_condition_variable_wait_pred(&state->cvar, &state->lock, s_get_credential_completed, state); aws_mutex_unlock(&state->lock); } /* wrap of the signing verification tests */ static napi_value s_aws_verify_sigv4a_signing(napi_env env, const struct aws_napi_callback_info *cb_info) { napi_value result = NULL; AWS_NAPI_ENSURE(env, napi_get_boolean(env, false, &result)); struct aws_allocator *allocator = aws_napi_get_allocator(); const struct aws_napi_argument *arg = NULL; struct signer_sign_request_state *state = aws_mem_calloc(allocator, 1, sizeof(struct signer_sign_request_state)); if (!state) { return result; } /* Temp buffers */ struct aws_byte_buf region_buf; AWS_ZERO_STRUCT(region_buf); struct aws_byte_buf service_buf; AWS_ZERO_STRUCT(service_buf); struct aws_byte_buf signed_body_value_buf; AWS_ZERO_STRUCT(signed_body_value_buf); struct aws_byte_buf expected_canonical_request_buf; AWS_ZERO_STRUCT(expected_canonical_request_buf); struct aws_byte_buf signature_buf; AWS_ZERO_STRUCT(signature_buf); struct aws_byte_buf ecc_key_pub_x_buf; AWS_ZERO_STRUCT(ecc_key_pub_x_buf); struct aws_byte_buf ecc_key_pub_y_buf; AWS_ZERO_STRUCT(ecc_key_pub_y_buf); /* Get request */ aws_napi_method_next_argument(napi_object, cb_info, &arg); state->request = aws_napi_http_message_unwrap(env, arg->node); state->signable = aws_signable_new_http_request(allocator, state->request); /* Populate config */ struct aws_signing_config_aws config; AWS_ZERO_STRUCT(config); aws_napi_method_next_argument(napi_object, cb_info, &arg); napi_value js_config = arg->node; if (s_get_config_from_js_config( env, &config, js_config, ®ion_buf, &service_buf, &signed_body_value_buf, state, allocator)) { /* error already raised */ goto done; } aws_napi_method_next_argument(napi_string, cb_info, &arg); napi_value node_expected_canonical_request = arg->node; if (aws_byte_buf_init_from_napi(&expected_canonical_request_buf, env, node_expected_canonical_request)) { napi_throw_type_error(env, NULL, "The expected canonical request must be a string"); goto done; } aws_napi_method_next_argument(napi_string, cb_info, &arg); napi_value node_signature = arg->node; if (aws_byte_buf_init_from_napi(&signature_buf, env, node_signature)) { napi_throw_type_error(env, NULL, "The signature must be a string"); goto done; } aws_napi_method_next_argument(napi_string, cb_info, &arg); napi_value node_ecc_key_pub_x = arg->node; if (aws_byte_buf_init_from_napi(&ecc_key_pub_x_buf, env, node_ecc_key_pub_x)) { napi_throw_type_error(env, NULL, "The public ecc key must be a string"); goto done; } aws_napi_method_next_argument(napi_string, cb_info, &arg); napi_value node_ecc_key_pub_y = arg->node; if (aws_byte_buf_init_from_napi(&ecc_key_pub_y_buf, env, node_ecc_key_pub_y)) { napi_throw_type_error(env, NULL, "The public ecc key must be a string"); goto done; } struct sigv4a_credentail_getter_state credential_state; AWS_ZERO_STRUCT(credential_state); credential_state.allocator = allocator; credential_state.config = &config; aws_condition_variable_init(&credential_state.cvar); aws_mutex_init(&credential_state.lock); /* get credential from provider for the verification */ if (aws_credentials_provider_get_credentials( config.credentials_provider, s_aws_signv4a_on_get_credentials, &credential_state)) { goto done; } /* wait for credential provider getting the credential */ s_wait_for_get_credential_to_complete(&credential_state); if (!config.credentials) { napi_throw_type_error(env, NULL, "Failed to get credentials from credential provider"); goto done; } if (aws_verify_sigv4a_signing( allocator, state->signable, (struct aws_signing_config_base *)&config, aws_byte_cursor_from_buf(&expected_canonical_request_buf), aws_byte_cursor_from_buf(&signature_buf), aws_byte_cursor_from_buf(&ecc_key_pub_x_buf), aws_byte_cursor_from_buf(&ecc_key_pub_y_buf))) { /* Verification failed, the signature result is wrong. */ aws_napi_throw_last_error(env); goto done; } /* verification succeed */ AWS_NAPI_ENSURE(env, napi_get_boolean(env, true, &result)); done: s_destroy_signing_binding(env, allocator, state); aws_credentials_provider_release(config.credentials_provider); aws_byte_buf_clean_up(®ion_buf); aws_byte_buf_clean_up(&service_buf); aws_byte_buf_clean_up(&signed_body_value_buf); aws_byte_buf_clean_up(&expected_canonical_request_buf); aws_byte_buf_clean_up(&signature_buf); aws_byte_buf_clean_up(&ecc_key_pub_x_buf); aws_byte_buf_clean_up(&ecc_key_pub_y_buf); return result; }