diff --git a/frameworks/js/napi/http/BUILD.gn b/frameworks/js/napi/http/BUILD.gn index 3c72c0f6894b8709a4c3391fc7542be06256710a..81fa9f8ffa665a5c2cdfcb4b82395e7cd7fea493 100644 --- a/frameworks/js/napi/http/BUILD.gn +++ b/frameworks/js/napi/http/BUILD.gn @@ -103,6 +103,7 @@ ohos_shared_library("http") { "cache/lru_cache/src/lru_cache_disk_handler.cpp", "constant/src/constant.cpp", "http_exec/src/http_exec.cpp", + "http_exec/src/http_tls_config.cpp", "http_module/src/http_module.cpp", "options/src/http_request_options.cpp", "options/src/http_response.cpp", diff --git a/frameworks/js/napi/http/async_context/include/request_context.h b/frameworks/js/napi/http/async_context/include/request_context.h index e965b618375d5789b55b85459cea905fd171ea65..8eb161a59145cb2d670400512aa438fb1762e69d 100644 --- a/frameworks/js/napi/http/async_context/include/request_context.h +++ b/frameworks/js/napi/http/async_context/include/request_context.h @@ -104,6 +104,12 @@ public: void ParseClientCert(napi_value optionsValue); + void ParseRemoteValidationMode(napi_value optionsValue); + + void ParseTlsOption(napi_value optionsValue); + + void ParseServerAuthentication(napi_value optionsValue); + void CachePerformanceTimingItem(const std::string &key, double value); void StopAndCacheNapiPerformanceTiming(const char *key); diff --git a/frameworks/js/napi/http/async_context/src/request_context.cpp b/frameworks/js/napi/http/async_context/src/request_context.cpp index f1b3769956844a15b7dbc4008953370350af2358..a1f239ee0a5a36bdc3e3fa1c403951ada925d67f 100755 --- a/frameworks/js/napi/http/async_context/src/request_context.cpp +++ b/frameworks/js/napi/http/async_context/src/request_context.cpp @@ -234,6 +234,97 @@ void RequestContext::ParseNumberOptions(napi_value optionsValue) } } +void RequestContext::ParseRemoteValidationMode(napi_value optionsValue) +{ + if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_REMOTE_VALIDATION)) { + NETSTACK_LOGD("no remote validation mode config"); + return; + } + napi_value value = NapiUtils::GetNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_REMOTE_VALIDATION); + if (NapiUtils::GetValueType(GetEnv(), value) == napi_string) { + auto remoteValidationMode = NapiUtils::GetStringFromValueUtf8(GetEnv(), value); + if (remoteValidationMode == "skip") { + NETSTACK_LOGI("ParseRemoteValidationMode remoteValidationMode skip"); + options.SetCanSkipCertVerifyFlag(true); + } else if (remoteValidationMode != "system") { + NETSTACK_LOGE("RemoteValidationMode config error"); + } + } +} + +void RequestContext::ParseTlsOption(napi_value optionsValue) +{ + if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_TLS_OPTION)) { + NETSTACK_LOGD("no tls config"); + return; + } + napi_value tlsVersionValue = NapiUtils::GetNamedProperty( + GetEnv(), optionsValue, HttpConstant::PARAM_KEY_TLS_OPTION); + napi_valuetype type = NapiUtils::GetValueType(GetEnv(), tlsVersionValue); + if (type != napi_object && type != napi_string) { + NETSTACK_LOGE("tlsVersionValue type error"); + return; + } + uint32_t tlsVersionMin = NapiUtils::GetUint32Property(GetEnv(), tlsVersionValue, "tlsVersionMin"); + uint32_t tlsVersionMax = NapiUtils::GetUint32Property(GetEnv(), tlsVersionValue, "tlsVersionMax"); + NETSTACK_LOGD("tlsVersionMin = %{public}d, tlsVersionMax = %{public}d", tlsVersionMin, tlsVersionMax); + TlsOption tlsOption; + tlsOption.tlsVersionMin = static_cast(tlsVersionMin); + tlsOption.tlsVersionMax = static_cast(tlsVersionMax); + if (!NapiUtils::HasNamedProperty(GetEnv(), tlsVersionValue, "cipherSuites")) { + NETSTACK_LOGD("no cipherSuites"); + options.SetTlsOption(tlsOption); + return; + } + auto cipherSuiteNapi = NapiUtils::GetNamedProperty(GetEnv(), tlsVersionValue, "cipherSuites"); + if (!NapiUtils::IsArray(GetEnv(), cipherSuiteNapi)) { + options.SetTlsOption(tlsOption); + return; + } + auto length = NapiUtils::GetArrayLength(GetEnv(), cipherSuiteNapi); + for (uint32_t i = 0; i < length; ++i) { + auto standardNameNapi = NapiUtils::GetArrayElement(GetEnv(), cipherSuiteNapi, i); + auto cipherSuite = GetCipherSuiteFromStandardName( + NapiUtils::GetStringFromValueUtf8(GetEnv(), standardNameNapi)); + if (cipherSuite != CipherSuite::INVALID) { + tlsOption.cipherSuite.emplace(cipherSuite); + } + } + + options.SetTlsOption(tlsOption); +} + +void RequestContext::ParseServerAuthentication(napi_value optionsValue) +{ + if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_SERVER_AUTH)) { + NETSTACK_LOGD("no server authentication config"); + return; + } + napi_value serverAuthenticationValue = NapiUtils::GetNamedProperty( + GetEnv(), optionsValue, HttpConstant::PARAM_KEY_SERVER_AUTH); + napi_valuetype type = NapiUtils::GetValueType(GetEnv(), serverAuthenticationValue); + if (type != napi_object) { + NETSTACK_LOGE("server authentication type error"); + return; + } + ServerAuthentication serverAuthentication; + auto credentialNapi = NapiUtils::GetNamedProperty(GetEnv(), serverAuthenticationValue, "credential"); + NapiUtils::GetSecureDataPropertyUtf8(GetEnv(), + credentialNapi, "username", serverAuthentication.credential.username); + NapiUtils::GetSecureDataPropertyUtf8(GetEnv(), + credentialNapi, "password", serverAuthentication.credential.password); + auto authenticationType = NapiUtils::GetStringPropertyUtf8(GetEnv(), + serverAuthenticationValue, "authenticationType"); + if (authenticationType == "basic") { + serverAuthentication.authenticationType = AuthenticationType::BASIC; + } else if (authenticationType == "ntlm") { + serverAuthentication.authenticationType = AuthenticationType::NTLM; + } else if (authenticationType == "digest") { + serverAuthentication.authenticationType = AuthenticationType::DIGEST; + } + options.SetServerAuthentication(serverAuthentication); +} + void RequestContext::ParseHeader(napi_value optionsValue) { if (!NapiUtils::HasNamedProperty(GetEnv(), optionsValue, HttpConstant::PARAM_KEY_HEADER)) { @@ -449,6 +540,9 @@ void RequestContext::UrlAndOptions(napi_value urlValue, napi_value optionsValue) ParseDnsServers(optionsValue); ParseMultiFormData(optionsValue); ParseCertificatePinning(optionsValue); + ParseRemoteValidationMode(optionsValue); + ParseTlsOption(optionsValue); + ParseServerAuthentication(optionsValue); SetParseOK(true); } diff --git a/frameworks/js/napi/http/constant/include/constant.h b/frameworks/js/napi/http/constant/include/constant.h index 38aef4c6cf1b095da9ae7c00a4dc9493db7a7086..16020b7aee372706ab11e41471e521f9bd71e95d 100644 --- a/frameworks/js/napi/http/constant/include/constant.h +++ b/frameworks/js/napi/http/constant/include/constant.h @@ -156,6 +156,9 @@ public: static const char *const PARAM_KEY_CLIENT_CERT; static const char *const PARAM_KEY_MULTI_FORM_DATA_LIST; static const char *const PARAM_KEY_CERTIFICATE_PINNING; + static const char *const PARAM_KEY_REMOTE_VALIDATION; + static const char *const PARAM_KEY_TLS_OPTION; + static const char *const PARAM_KEY_SERVER_AUTH; static const char *const HTTP_PROXY_KEY_HOST; static const char *const HTTP_PROXY_KEY_PORT; @@ -174,6 +177,11 @@ public: static const char *const HTTP_CERT_TYPE_DER; static const char *const HTTP_CERT_TYPE_P12; + static const char *const TLS_VERSION_1_0; + static const char *const TLS_VERSION_1_1; + static const char *const TLS_VERSION_1_2; + static const char *const TLS_VERSION_1_3; + static const char *const HTTP_MULTI_FORM_DATA_NAME; static const char *const HTTP_MULTI_FORM_DATA_CONTENT_TYPE; static const char *const HTTP_MULTI_FORM_DATA_REMOTE_FILE_NAME; diff --git a/frameworks/js/napi/http/constant/src/constant.cpp b/frameworks/js/napi/http/constant/src/constant.cpp index 670ae027fc8252a4cd5b662050cb0f25cd3d70d0..7f9536f1664cde0c5576765af3ac28b33e593ccf 100644 --- a/frameworks/js/napi/http/constant/src/constant.cpp +++ b/frameworks/js/napi/http/constant/src/constant.cpp @@ -54,6 +54,10 @@ const char *const HttpConstant::PARAM_KEY_MULTI_FORM_DATA_LIST = "multiFormDataL const char *const HttpConstant::PARAM_KEY_CERTIFICATE_PINNING = "certificatePinning"; +const char *const HttpConstant::PARAM_KEY_REMOTE_VALIDATION = "remoteValidation"; +const char *const HttpConstant::PARAM_KEY_TLS_OPTION = "tlsOptions"; +const char *const HttpConstant::PARAM_KEY_SERVER_AUTH = "serverAuthentication"; + const char *const HttpConstant::HTTP_PROXY_KEY_HOST = "host"; const char *const HttpConstant::HTTP_PROXY_KEY_PORT = "port"; const char *const HttpConstant::HTTP_PROXY_KEY_EXCLUSION_LIST = "exclusionList"; @@ -70,6 +74,11 @@ const char *const HttpConstant::HTTP_CERT_TYPE_PEM = "PEM"; const char *const HttpConstant::HTTP_CERT_TYPE_DER = "DER"; const char *const HttpConstant::HTTP_CERT_TYPE_P12 = "P12"; +const char *const HttpConstant::TLS_VERSION_1_0 = "TLS_V_1_0"; +const char *const HttpConstant::TLS_VERSION_1_1 = "TLS_V_1_1"; +const char *const HttpConstant::TLS_VERSION_1_2 = "TLS_V_1_2"; +const char *const HttpConstant::TLS_VERSION_1_3 = "TLS_V_1_3"; + const char *const HttpConstant::HTTP_PROXY_EXCLUSIONS_SEPARATOR = ","; const char *const HttpConstant::RESPONSE_KEY_RESULT = "result"; diff --git a/frameworks/js/napi/http/http_exec/include/http_exec.h b/frameworks/js/napi/http/http_exec/include/http_exec.h index 59035eef4707ee2c3bafafa394f8c83469101b19..6d6e1a5f74d8767f18c7fe59aab2cd3cf21f0c10 100644 --- a/frameworks/js/napi/http/http_exec/include/http_exec.h +++ b/frameworks/js/napi/http/http_exec/include/http_exec.h @@ -92,6 +92,8 @@ private: static bool SetOtherOption(CURL *curl, RequestContext *context); + static bool SetAuthOptions(CURL *curl, OHOS::NetStack::Http::RequestContext *context); + static bool SetRequestOption(void *curl, RequestContext *context); static bool SetSSLCertOption(CURL *curl, RequestContext *context); @@ -158,6 +160,8 @@ private: static void SetFormDataOption(MultiFormData &multiFormData, curl_mimepart *part, void *curl, RequestContext *context); + static bool IsBuiltWithOpenSSL(); + #if !HAS_NETMANAGER_BASE static void AddRequestInfo(); #endif diff --git a/frameworks/js/napi/http/http_exec/include/http_tls_config.h b/frameworks/js/napi/http/http_exec/include/http_tls_config.h new file mode 100644 index 0000000000000000000000000000000000000000..f6d128b8a206806e17c5b505056e2bdf54ef4090 --- /dev/null +++ b/frameworks/js/napi/http/http_exec/include/http_tls_config.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NETSTACK_HTTP_TLS_CONFIG_H +#define NETSTACK_HTTP_TLS_CONFIG_H + +#include +#include +#include +#include +#include +#include +#include + +#include "securec.h" + +namespace OHOS::NetStack::Http { +enum class CipherSuite { + INVALID = -1, + TLS_AES_128_GCM_SHA256 = 0x1301, + TLS_AES_256_GCM_SHA384 = 0x1302, + TLS_CHACHA20_POLY1305_SHA256 = 0x1303, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xc02b, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xc02f, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xc02c, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xc030, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca9, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0x009c, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0x009d, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc009, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc013, + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0xc00a, + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0xc014, + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f, + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035, + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a, +}; + +enum class TlsVersion { + DEFAULT = 0, + TLSv1_0 = 4, + TLSv1_1 = 5, + TLSv1_2 = 6, + TLSv1_3 = 7, +}; + +struct TlsCipherString { + std::string ciperSuiteString; + std::string tlsV13CiperSuiteString; +}; + +[[nodiscard]] CipherSuite GetCipherSuiteFromStandardName(const std::string &standardName); +[[nodiscard]] std::string GetInnerNameFromCipherSuite(CipherSuite cipherSuite); +[[nodiscard]] TlsCipherString ConvertCipherSuiteToCipherString(const std::unordered_set &cipherSuite); + +} // namespace OHOS::NetStack::Http +#endif // NETSTACK_HTTP_TLS_CONFIG_H diff --git a/frameworks/js/napi/http/http_exec/src/http_exec.cpp b/frameworks/js/napi/http/http_exec/src/http_exec.cpp index 663607b3af71646e29ddfbdb6010d2802ed44160..cab09f2b693d57edc9cf4d06a8968e66b2dc6765 100755 --- a/frameworks/js/napi/http/http_exec/src/http_exec.cpp +++ b/frameworks/js/napi/http/http_exec/src/http_exec.cpp @@ -891,6 +891,49 @@ bool HttpExec::Initialize() } #endif +bool HttpExec::IsBuiltWithOpenSSL() +{ + const auto data = curl_version_info(CURLVERSION_NOW); + if (!data || !data->ssl_version) { + return false; + } + + const auto sslVersion = CommonUtils::ToLower(data->ssl_version); + return sslVersion.find("openssl") != std::string::npos; +} + +unsigned long GetTlsVersion(TlsVersion tlsVersionMin, TlsVersion tlsVersionMax) +{ + unsigned long tlsVersion = CURL_SSLVERSION_DEFAULT; + if (tlsVersionMin == TlsVersion::DEFAULT || tlsVersionMax == TlsVersion::DEFAULT) { + return tlsVersion; + } + if (tlsVersionMin > tlsVersionMax) { + return tlsVersion; + } + if (tlsVersionMin == TlsVersion::TLSv1_0) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_0); + } else if (tlsVersionMin == TlsVersion::TLSv1_1) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_1); + } else if (tlsVersionMin == TlsVersion::TLSv1_2) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_2); + } else if (tlsVersionMin == TlsVersion::TLSv1_3) { + tlsVersion |= static_cast(CURL_SSLVERSION_TLSv1_3); + } + + if (tlsVersionMax == TlsVersion::TLSv1_0) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_0); + } else if (tlsVersionMax == TlsVersion::TLSv1_1) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_1); + } else if (tlsVersionMax == TlsVersion::TLSv1_2) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_2); + } else if (tlsVersionMax == TlsVersion::TLSv1_3) { + tlsVersion |= static_cast(CURL_SSLVERSION_MAX_TLSv1_3); + } + + return tlsVersion; +} + bool HttpExec::SetOtherOption(CURL *curl, OHOS::NetStack::Http::RequestContext *context) { std::string url = context->options.GetUrl(); @@ -905,8 +948,29 @@ bool HttpExec::SetOtherOption(CURL *curl, OHOS::NetStack::Http::RequestContext * auto proxyType = (host.find("https://") != std::string::npos) ? CURLPROXY_HTTPS : CURLPROXY_HTTP; NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYTYPE, proxyType, context); } - NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2, context); - NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CIPHER_LIST, TLS12_SECURITY_CIPHER_SUITE, context); + const auto &tlsOption = context->options.GetTlsOption(); + unsigned long tlsVersion = GetTlsVersion(tlsOption.tlsVersionMin, tlsOption.tlsVersionMax); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLVERSION, static_cast(tlsVersion), context); + const auto &cipherSuite = tlsOption.cipherSuite; + const auto &cipherSuiteString = ConvertCipherSuiteToCipherString(cipherSuite); + const auto &normalString = cipherSuiteString.ciperSuiteString; + const auto &tlsV13String = cipherSuiteString.tlsV13CiperSuiteString; + if (tlsVersion == CURL_SSLVERSION_DEFAULT) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2, context); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CIPHER_LIST, TLS12_SECURITY_CIPHER_SUITE, context); + } else if (normalString.empty() && tlsV13String.empty()) { + NETSTACK_LOGD("no cipherSuite config"); + } else if (!normalString.empty()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CIPHER_LIST, normalString.c_str(), context); + if (!tlsV13String.empty() && IsBuiltWithOpenSSL()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_TLS13_CIPHERS, tlsV13String.c_str(), context); + } + } else if (!tlsV13String.empty() && IsBuiltWithOpenSSL()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_TLS13_CIPHERS, tlsV13String.c_str(), context); + } else { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2, context); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_CIPHER_LIST, TLS12_SECURITY_CIPHER_SUITE, context); + } #ifdef NETSTACK_PROXY_PASS NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PROXYUSERPWD, NETSTACK_PROXY_PASS, context); @@ -922,6 +986,37 @@ bool HttpExec::SetOtherOption(CURL *curl, OHOS::NetStack::Http::RequestContext * return true; } +bool HttpExec::SetAuthOptions(CURL *curl, OHOS::NetStack::Http::RequestContext *context) +{ + long authType = CURLAUTH_ANY; + auto authentication = context->options.GetServerAuthentication();; + switch (authentication.authenticationType) { + case AuthenticationType::BASIC: + authType = CURLAUTH_BASIC; + break; + case AuthenticationType::NTLM: + authType = CURLAUTH_NTLM; + break; + case AuthenticationType::DIGEST: + authType = CURLAUTH_DIGEST; + break; + case AuthenticationType::AUTO: + default: + break; + } + auto username = authentication.credential.username; + auto password = authentication.credential.password; + if (!username.empty()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_HTTPAUTH, authType, context); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_USERNAME, username.c_str(), context); + } + if (!password.empty()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_PASSWORD, password.c_str(), context); + } + + return true; +} + bool HttpExec::SetSSLCertOption(CURL *curl, OHOS::NetStack::Http::RequestContext *context) { std::string cert; @@ -1092,13 +1187,18 @@ bool HttpExec::SetServerSSLCertOption(CURL *curl, OHOS::NetStack::Http::RequestC NETSTACK_LOGE("GetTrustAnchorsForHostName error. ret [%{public}d]", ret); } #ifdef HTTP_MULTIPATH_CERT_ENABLE - // add user cert path - TrustUser0AndUserCa(certs); - // add system cert path - certs.emplace_back(HttpConstant::HTTP_PREPARE_CA_PATH); - context->SetCertsPath(std::move(certs), context->options.GetCaPath()); - NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 1L, context); - NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 2L, context); + if (context->options.GetCanSkipCertVerifyFlag()) { + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 0L, context); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 0L, context); + } else { + // add user cert path + TrustUser0AndUserCa(certs); + // add system cert path + certs.emplace_back(HttpConstant::HTTP_PREPARE_CA_PATH); + context->SetCertsPath(std::move(certs), context->options.GetCaPath()); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYPEER, 1L, context); + NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_SSL_VERIFYHOST, 2L, context); + } NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, nullptr, context); #else NETSTACK_CURL_EASY_SET_OPTION(curl, CURLOPT_CAINFO, nullptr, context); @@ -1325,6 +1425,10 @@ bool HttpExec::SetOption(CURL *curl, RequestContext *context, struct curl_slist if (!SetOtherOption(curl, context)) { return false; } + + if (!SetAuthOptions(curl, context)) { + return false; + } return true; } diff --git a/frameworks/js/napi/http/http_exec/src/http_tls_config.cpp b/frameworks/js/napi/http/http_exec/src/http_tls_config.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aa1eb6401263e7827f66a255678e35fb4dbd7ea9 --- /dev/null +++ b/frameworks/js/napi/http/http_exec/src/http_tls_config.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_tls_config.h" + +namespace OHOS::NetStack::Http { +struct CipherSuiteConvertor { + CipherSuite cipherSuite = CipherSuite::INVALID; + const char *innerName = nullptr; + const char *standardName = nullptr; +}; + +static constexpr const CipherSuiteConvertor CIPHER_SUITE_CONVERTOR[] = { + { + .cipherSuite = CipherSuite::TLS_AES_128_GCM_SHA256, + .innerName = "TLS_AES_128_GCM_SHA256", + .standardName = "TLS_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_AES_256_GCM_SHA384, + .innerName = "TLS_AES_256_GCM_SHA384", + .standardName = "TLS_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_CHACHA20_POLY1305_SHA256, + .innerName = "TLS_CHACHA20_POLY1305_SHA256", + .standardName = "TLS_CHACHA20_POLY1305_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + .innerName = "ECDHE-ECDSA-AES128-GCM-SHA256", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + .innerName = "ECDHE-RSA-AES128-GCM-SHA256", + .standardName = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + .innerName = "ECDHE-ECDSA-AES256-GCM-SHA384", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + .innerName = "ECDHE-RSA-AES256-GCM-SHA384", + .standardName = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + .innerName = "ECDHE-ECDSA-CHACHA20-POLY1305", + .standardName = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + .innerName = "ECDHE-RSA-CHACHA20-POLY1305", + .standardName = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + .innerName = "AES128-GCM-SHA256", + .standardName = "TLS_RSA_WITH_AES_128_GCM_SHA256", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + .innerName = "AES256-GCM-SHA384", + .standardName = "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + .innerName = "ECDHE-ECDSA-AES128-SHA", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + .innerName = "ECDHE-RSA-AES128-SHA", + .standardName = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + .innerName = "ECDHE-ECDSA-AES256-SHA", + .standardName = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + .innerName = "ECDHE-RSA-AES256-SHA", + .standardName = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + .innerName = "AES128-SHA", + .standardName = "TLS_RSA_WITH_AES_128_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + .innerName = "AES256-SHA", + .standardName = "TLS_RSA_WITH_AES_256_CBC_SHA", + }, + { + .cipherSuite = CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA, + .innerName = "DES-CBC3-SHA", + .standardName = "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + }, +}; + +CipherSuite GetCipherSuiteFromStandardName(const std::string &standardName) +{ + for (const auto &suite : CIPHER_SUITE_CONVERTOR) { + if (suite.standardName == standardName) { + return suite.cipherSuite; + } + } + return CipherSuite::INVALID; +} + +std::string GetInnerNameFromCipherSuite(CipherSuite cipherSuite) +{ + for (const auto &suite : CIPHER_SUITE_CONVERTOR) { + if (suite.cipherSuite == cipherSuite) { + return suite.innerName; + } + } + return {}; +} + +static bool IsTlsV13Cipher(const std::string &innerName) +{ + return innerName == "TLS_AES_128_GCM_SHA256" || innerName == "TLS_AES_256_GCM_SHA384" || + innerName == "TLS_CHACHA20_POLY1305_SHA256"; +} + +TlsCipherString ConvertCipherSuiteToCipherString(const std::unordered_set &cipherSuite) +{ + TlsCipherString cipherString; + for (const auto &cipher : cipherSuite) { + auto innerName = GetInnerNameFromCipherSuite(cipher); + if (innerName.empty()) { + continue; + } + if (IsTlsV13Cipher(innerName)) { + cipherString.tlsV13CiperSuiteString.append(innerName).append(":"); + } else { + cipherString.ciperSuiteString.append(innerName).append(":"); + } + } + if (!cipherString.tlsV13CiperSuiteString.empty()) { + cipherString.tlsV13CiperSuiteString.pop_back(); + } + if (!cipherString.ciperSuiteString.empty()) { + cipherString.ciperSuiteString.pop_back(); + } + return cipherString; +} + +} // namespace OHOS::NetStack::Http \ No newline at end of file diff --git a/frameworks/js/napi/http/http_module/include/http_module.h b/frameworks/js/napi/http/http_module/include/http_module.h index f1b28be8275f83980a4375e6cf0b148944317d52..532b5a2b1951188cc71c12f52c729c9686fcdb11 100644 --- a/frameworks/js/napi/http/http_module/include/http_module.h +++ b/frameworks/js/napi/http/http_module/include/http_module.h @@ -56,6 +56,7 @@ public: static constexpr const char *INTERFACE_CERT_TYPE = "CertType"; static constexpr const char *INTERFACE_HTTP_RESPONSE_CACHE = "OHOS_NET_HTTP_HttpResponseCache"; static constexpr const char *INTERFACE_HTTP_DATA_TYPE = "HttpDataType"; + static constexpr const char *INTERFACE_TLS_VERSION = "TlsVersion"; static napi_value InitHttpModule(napi_env env, napi_value exports); @@ -74,6 +75,8 @@ private: static void InitResponseCode(napi_env env, napi_value exports); + static void InitTlsVersion(napi_env env, napi_value exports); + static void InitHttpProtocol(napi_env env, napi_value exports); static void InitCertType(napi_env env, napi_value exports); diff --git a/frameworks/js/napi/http/http_module/src/http_module.cpp b/frameworks/js/napi/http/http_module/src/http_module.cpp index 0ef2d9d844846012f153ff0f7967491036dd692e..d9e5f3b2ab606c89a3bd94089507afc78f6f72db 100644 --- a/frameworks/js/napi/http/http_module/src/http_module.cpp +++ b/frameworks/js/napi/http/http_module/src/http_module.cpp @@ -134,6 +134,7 @@ void HttpModuleExports::InitHttpProperties(napi_env env, napi_value exports) InitCertType(env, exports); InitHttpProtocol(env, exports); InitHttpDataType(env, exports); + InitTlsVersion(env, exports); } void HttpModuleExports::InitRequestMethod(napi_env env, napi_value exports) @@ -198,6 +199,25 @@ void HttpModuleExports::InitResponseCode(napi_env env, napi_value exports) NapiUtils::SetNamedProperty(env, exports, INTERFACE_RESPONSE_CODE, responseCode); } +void HttpModuleExports::InitTlsVersion(napi_env env, napi_value exports) +{ + std::initializer_list properties = { + DECLARE_NAPI_STATIC_PROPERTY(HttpConstant::TLS_VERSION_1_0, + NapiUtils::CreateUint32(env, static_cast(TlsVersion::TLSv1_0))), + DECLARE_NAPI_STATIC_PROPERTY(HttpConstant::TLS_VERSION_1_1, + NapiUtils::CreateUint32(env, static_cast(TlsVersion::TLSv1_1))), + DECLARE_NAPI_STATIC_PROPERTY(HttpConstant::TLS_VERSION_1_2, + NapiUtils::CreateUint32(env, static_cast(TlsVersion::TLSv1_2))), + DECLARE_NAPI_STATIC_PROPERTY(HttpConstant::TLS_VERSION_1_3, + NapiUtils::CreateUint32(env, static_cast(TlsVersion::TLSv1_3))), + }; + + napi_value tlsVersion = NapiUtils::CreateObject(env); + NapiUtils::DefineProperties(env, tlsVersion, properties); + + NapiUtils::SetNamedProperty(env, exports, INTERFACE_TLS_VERSION, tlsVersion); +} + void HttpModuleExports::InitHttpProtocol(napi_env env, napi_value exports) { std::initializer_list properties = { diff --git a/frameworks/js/napi/http/options/include/http_request_options.h b/frameworks/js/napi/http/options/include/http_request_options.h index 1f90fa6cdbe5021df23b7c4a4f68641b9ecbe311..58b2291dcb20a5678562ed3f4c03201acb5994f6 100644 --- a/frameworks/js/napi/http/options/include/http_request_options.h +++ b/frameworks/js/napi/http/options/include/http_request_options.h @@ -22,6 +22,8 @@ #include "constant.h" #include "secure_char.h" +#include "http_tls_config.h" +#include "napi_utils.h" namespace OHOS::NetStack::Http { enum class HttpProtocol { @@ -52,6 +54,29 @@ enum class HashAlgorithm { INVALID, }; +enum class AuthenticationType { + AUTO, + BASIC, + NTLM, + DIGEST, +}; + +struct Credential { + NapiUtils::SecureData username; + NapiUtils::SecureData password; +}; + +struct ServerAuthentication { + Credential credential; + AuthenticationType authenticationType = AuthenticationType::AUTO; +}; + +struct TlsOption { + std::unordered_set cipherSuite; + TlsVersion tlsVersionMin = TlsVersion::DEFAULT; + TlsVersion tlsVersionMax = TlsVersion::DEFAULT; +}; + struct CertificatePinning { HashAlgorithm hashAlgorithm = HashAlgorithm::SHA256; std::string publicKeyHash; @@ -95,8 +120,14 @@ public: void AddMultiFormData(const MultiFormData &multiFormData); + void SetTlsOption(const TlsOption &tlsOption); + + void SetServerAuthentication(const ServerAuthentication &serverAuthentication); + void SetCertificatePinning(std::string certPIN); + void SetCanSkipCertVerifyFlag(bool canCertVerify); + [[nodiscard]] std::string GetCertificatePinning() const; [[nodiscard]] const std::string &GetUrl() const; @@ -137,9 +168,15 @@ public: [[nodiscard]] const std::vector &GetDnsServers() const; + [[nodiscard]] bool GetCanSkipCertVerifyFlag() const; + void GetClientCert(std::string &cert, std::string &certType, std::string &key, Secure::SecureChar &keyPasswd); std::vector GetMultiPartDataList(); + + TlsOption GetTlsOption(); + + ServerAuthentication GetServerAuthentication(); private: std::string url_; @@ -189,9 +226,15 @@ private: Secure::SecureChar keyPasswd_; + bool canSkipCertVerify_ = false; + std::vector multiFormDataList_; std::string certificatePinning_; + + TlsOption tlsOption_; + + ServerAuthentication serverAuthentication_; }; } // namespace OHOS::NetStack::Http diff --git a/frameworks/js/napi/http/options/src/http_request_options.cpp b/frameworks/js/napi/http/options/src/http_request_options.cpp index 4db0164055f2bd1938a830068df5c0f289a0a12f..98220a13ff01cc34fa96bbd3307328a2f4f74828 100644 --- a/frameworks/js/napi/http/options/src/http_request_options.cpp +++ b/frameworks/js/napi/http/options/src/http_request_options.cpp @@ -177,6 +177,15 @@ uint32_t HttpRequestOptions::GetPriority() const return priority_; } +void HttpRequestOptions::SetCanSkipCertVerifyFlag(bool canCertVerify) +{ + canSkipCertVerify_ = canCertVerify; +} + +bool HttpRequestOptions::GetCanSkipCertVerifyFlag() const +{ + return canSkipCertVerify_; +} void HttpRequestOptions::SetUsingHttpProxyType(UsingHttpProxyType type) { usingHttpProxyType_ = type; @@ -238,6 +247,30 @@ const std::string &HttpRequestOptions::GetCaPath() const return caPath_; } +void HttpRequestOptions::SetTlsOption(const TlsOption &tlsOption) +{ + tlsOption_.tlsVersionMax = tlsOption.tlsVersionMax; + tlsOption_.tlsVersionMin = tlsOption.tlsVersionMin; + tlsOption_.cipherSuite = tlsOption.cipherSuite; +} + +TlsOption HttpRequestOptions::GetTlsOption() +{ + return tlsOption_; +} + +void HttpRequestOptions::SetServerAuthentication(const ServerAuthentication &serverAuthentication) +{ + serverAuthentication_.authenticationType = serverAuthentication.authenticationType; + serverAuthentication_.credential.password = serverAuthentication.credential.password; + serverAuthentication_.credential.username = serverAuthentication.credential.username; +} + +ServerAuthentication HttpRequestOptions::GetServerAuthentication() +{ + return serverAuthentication_; +} + void HttpRequestOptions::SetDohUrl(const std::string &dohUrl) { if (dohUrl.empty()) { diff --git a/utils/napi_utils/include/napi_utils.h b/utils/napi_utils/include/napi_utils.h index c325765f3bbfe9739b4c282592ca454e563be88c..ea99529ae47612d2a7b8fbe226fe58bbe390d793 100644 --- a/utils/napi_utils/include/napi_utils.h +++ b/utils/napi_utils/include/napi_utils.h @@ -28,6 +28,8 @@ #include "napi/native_api.h" #include "uv.h" +#include "securec.h" + namespace OHOS::NetStack::NapiUtils { static constexpr int NETSTACK_NAPI_INTERNAL_ERROR = 2300002; using UvHandler = std::function; @@ -38,6 +40,14 @@ private: std::mutex mutex; }; +struct SecureData : public std::string { + ~SecureData() + { + // Clear Data, to keep the memory safe + (void)memset_s(data(), size(), 0, size()); + } +}; + napi_valuetype GetValueType(napi_env env, napi_value value); bool IsInstanceOf(napi_env env, napi_value object, const std::string &name); @@ -86,6 +96,11 @@ std::string GetStringFromValueUtf8(napi_env env, napi_value value); std::string GetStringPropertyUtf8(napi_env env, napi_value object, const std::string &propertyName); +void GetSecureDataFromValueUtf8(napi_env env, napi_value value, SecureData &data); + +void GetSecureDataPropertyUtf8( + napi_env env, napi_value object, const std::string &propertyName, SecureData &data); + std::string NapiValueToString(napi_env env, napi_value value); void SetStringPropertyUtf8(napi_env env, napi_value object, const std::string &name, const std::string &value); diff --git a/utils/napi_utils/src/napi_utils.cpp b/utils/napi_utils/src/napi_utils.cpp index 820448f3ddfdfec309a1f600c83d61e03dcc23c1..195b259e909020f5f2d1af4976851892365ed55e 100644 --- a/utils/napi_utils/src/napi_utils.cpp +++ b/utils/napi_utils/src/napi_utils.cpp @@ -32,7 +32,6 @@ #include "napi/native_common.h" #include "netstack_log.h" #include "node_api.h" -#include "securec.h" namespace OHOS::NetStack::NapiUtils { static constexpr const char *GLOBAL_JSON = "JSON"; @@ -291,6 +290,44 @@ napi_value CreateStringUtf8(napi_env env, const std::string &str) return value; } +void GetSecureDataPropertyUtf8(napi_env env, napi_value object, const std::string &propertyName, + SecureData &data) +{ + if (!HasNamedProperty(env, object, propertyName)) { + return; + } + napi_value value = GetNamedProperty(env, object, propertyName); + GetSecureDataFromValueUtf8(env, value, data); +} + +void GetSecureDataFromValueUtf8(napi_env env, napi_value value, SecureData &data) +{ + if (GetValueType(env, value) != napi_string) { + return; + } + + size_t stringLength = 0; + NAPI_CALL_RETURN_VOID(env, napi_get_value_string_utf8(env, value, nullptr, 0, &stringLength)); + if (stringLength == 0 || stringLength > SIZE_MAX - 1) { + return; + } + + auto deleter = [](char *s) { free(reinterpret_cast(s)); }; + auto mem = malloc(stringLength + 1); + if (mem == nullptr) { + return; + } + std::unique_ptr str(static_cast(mem), deleter); + if (memset_s(str.get(), stringLength + 1, 0, stringLength + 1) != EOK) { + return; + } + size_t length = 0; + NAPI_CALL_RETURN_VOID(env, napi_get_value_string_utf8(env, value, str.get(), stringLength + 1, &length)); + if (length > 0) { + data.append(str.get(), length); + } +} + std::string GetStringFromValueUtf8(napi_env env, napi_value value) { if (GetValueType(env, value) != napi_string) {