1 /*
2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "netstack_common_utils.h"
17
18 #ifdef WINDOWS_PLATFORM
19 #include <winsock2.h>
20 #include <ws2tcpip.h>
21 #pragma comment(lib, "ws2_32.lib")
22 #else
23 #include <arpa/inet.h>
24 #endif
25
26 #include <algorithm>
27 #include <cctype>
28 #include <cerrno>
29 #include <regex>
30 #include <string>
31 #include <unistd.h>
32 #include <vector>
33 #include <fstream>
34 #include <sstream>
35 #if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
36 #include <filesystem>
37 #endif
38
39 #if HAS_NETMANAGER_BASE
40 #include <openssl/evp.h>
41 #endif // HAS_NETMANAGER_BASE
42
43 #include "netstack_log.h"
44 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
45 #include "netstack_apipolicy_utils.h"
46 #include "netstack_bundle_utils.h"
47 #endif
48 #if HAS_NETMANAGER_BASE
49 #include "net_conn_client.h"
50 #include "network_security_config.h"
51 #endif // HAS_NETMANAGER_BASE
52
53 constexpr int32_t INET_OPTION_SUC = 1;
54 constexpr size_t MAX_DISPLAY_NUM = 2;
55 #if HAS_NETMANAGER_BASE
56 constexpr unsigned int SHA256_LEN = 32;
57 #endif
58 constexpr int SHA256_BASE64_LEN = 44; // 32-byte base64 -> 44 bytes
59 constexpr int PINNED_PREFIX_LEN = 8; // strlen("sha256//")
60
61 namespace OHOS::NetStack::CommonUtils {
62 const std::regex IP_PATTERN{
63 "((2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)\\.){3}(2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)"};
64 const std::regex IP_MASK_PATTERN{
65 "((2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)\\.){3}(2([0-4]\\d|5[0-5])|1\\d\\d|[1-9]\\d|\\d)/"
66 "(3[0-2]|[1-2]\\d|\\d)"};
67 const std::regex IPV6_PATTERN{"([\\da-fA-F]{0,4}:){2,7}([\\da-fA-F]{0,4})"};
68 const std::regex IPV6_MASK_PATTERN{"([\\da-fA-F]{0,4}:){2,7}([\\da-fA-F]{0,4})/(1[0-2][0-8]|[1-9]\\d|[1-9])"};
69 static const std::string PROTOCOL_WSS = "wss";
70 std::mutex g_commonUtilsMutex;
71
Split(const std::string & str,const std::string & sep)72 std::vector<std::string> Split(const std::string &str, const std::string &sep)
73 {
74 std::string s = str;
75 std::vector<std::string> res;
76 while (!s.empty()) {
77 auto pos = s.find(sep);
78 if (pos == std::string::npos) {
79 res.emplace_back(s);
80 break;
81 }
82 res.emplace_back(s.substr(0, pos));
83 s = s.substr(pos + sep.size());
84 }
85 return res;
86 }
87
Split(const std::string & str,const std::string & sep,size_t size)88 std::vector<std::string> Split(const std::string &str, const std::string &sep, size_t size)
89 {
90 std::string s = str;
91 std::vector<std::string> res;
92 while (!s.empty()) {
93 if (res.size() + 1 == size) {
94 res.emplace_back(s);
95 break;
96 }
97
98 auto pos = s.find(sep);
99 if (pos == std::string::npos) {
100 res.emplace_back(s);
101 break;
102 }
103 res.emplace_back(s.substr(0, pos));
104 s = s.substr(pos + sep.size());
105 }
106 return res;
107 }
108
Strip(const std::string & str,char ch)109 std::string Strip(const std::string &str, char ch)
110 {
111 int64_t i = 0;
112 while (static_cast<size_t>(i) < str.size() && str[i] == ch) {
113 ++i;
114 }
115 int64_t j = static_cast<int64_t>(str.size()) - 1;
116 while (j > 0 && str[j] == ch) {
117 --j;
118 }
119 if (i >= 0 && static_cast<size_t>(i) < str.size() && j >= 0 && static_cast<size_t>(j) < str.size() &&
120 j - i + 1 > 0) {
121 return str.substr(i, j - i + 1);
122 }
123 return "";
124 }
125
ToLower(const std::string & s)126 std::string ToLower(const std::string &s)
127 {
128 std::string res = s;
129 std::transform(res.begin(), res.end(), res.begin(), tolower);
130 return res;
131 }
132
ToString(const std::list<std::string> & lists,char tab)133 std::string ToString(const std::list<std::string> &lists, char tab)
134 {
135 std::string str;
136 for (auto it = lists.begin(); it != lists.end(); ++it) {
137 if (it != lists.begin()) {
138 str.append(1, tab);
139 }
140 str.append(*it);
141 }
142 return str;
143 }
144
HasInternetPermission()145 bool HasInternetPermission()
146 {
147 #ifndef OH_CORE_NETSTACK_PERMISSION_CHECK
148 #ifdef FUZZ_TEST
149 return true;
150 #endif
151 #ifdef DT_TEST
152 return true;
153 #endif
154 int testSock = socket(AF_INET, SOCK_STREAM, 0);
155 if (testSock < 0 && errno == EPERM) {
156 NETSTACK_LOGE("make tcp testSock failed errno is %{public}d %{public}s", errno, strerror(errno));
157 return false;
158 }
159 if (testSock > 0) {
160 close(testSock);
161 }
162 return true;
163 #else
164 constexpr int inetGroup = 40002003; // 3003 in gateway shell.
165 int groupNum = getgroups(0, nullptr);
166 if (groupNum <= 0) {
167 NETSTACK_LOGE("no group of INTERNET permission");
168 return false;
169 }
170 auto groups = (gid_t *)malloc(groupNum * sizeof(gid_t));
171 if (groups == nullptr) {
172 NETSTACK_LOGE("INTERNET permission denied by malloc");
173 return false;
174 }
175 groupNum = getgroups(groupNum, groups);
176 for (int i = 0; i < groupNum; i++) {
177 if (groups[i] == inetGroup) {
178 free(groups);
179 return true;
180 }
181 }
182 free(groups);
183 NETSTACK_LOGE("INTERNET permission denied by group");
184 return false;
185 #endif
186 }
187
IsAtomicService(std::string & bundleName)188 bool IsAtomicService(std::string &bundleName)
189 {
190 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
191 return BundleUtils::IsAtomicService(bundleName);
192 #else
193 return false;
194 #endif
195 }
196
IsAllowedHostname(const std::string & bundleName,const std::string & domainType,const std::string & url)197 bool IsAllowedHostname(const std::string &bundleName, const std::string &domainType, const std::string &url)
198 {
199 #if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
200 if (bundleName.empty()) {
201 NETSTACK_LOGE("isAllowedHostnameForAtomicService bundleName is empty");
202 return true;
203 }
204 auto hostname = GetHostnameWithProtocolAndPortFromURL(url);
205 if (hostname.empty()) {
206 NETSTACK_LOGE("isAllowedHostnameForAtomicService url hostname is empty");
207 return true;
208 }
209 return ApiPolicyUtils::IsAllowedHostname(bundleName, domainType, hostname);
210 #else
211 return true;
212 #endif
213 }
214
EndsWith(const std::string & str,const std::string & suffix)215 bool EndsWith(const std::string &str, const std::string &suffix)
216 {
217 if (str.length() < suffix.length()) {
218 return false;
219 }
220 return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
221 }
222
Trim(std::string str)223 std::string Trim(std::string str)
224 {
225 size_t start = str.find_first_not_of(" \t\n\r");
226 size_t end = str.find_last_not_of(" \t\n\r");
227 if (start == std::string::npos || end == std::string::npos) {
228 return "";
229 } else {
230 return str.substr(start, end - start + 1);
231 }
232 }
233
IsMatch(const std::string & str,const std::string & patternStr)234 bool IsMatch(const std::string &str, const std::string &patternStr)
235 {
236 if (patternStr.empty()) {
237 return false;
238 }
239 if (patternStr == "*") {
240 return true;
241 }
242 if (!IsRegexValid(patternStr)) {
243 NETSTACK_LOGD("Invalid pattern");
244 return patternStr == str;
245 }
246 std::regex pattern(ReplaceCharacters(patternStr));
247 bool isMacth = patternStr != "" && std::regex_match(str, pattern);
248 if (isMacth) {
249 NETSTACK_LOGD("Match patternStr");
250 }
251 return isMacth;
252 }
253
InsertCharBefore(const std::string & input,const char from,const char preChar,const char nextChar)254 std::string InsertCharBefore(const std::string &input, const char from, const char preChar, const char nextChar)
255 {
256 std::string output = input;
257 char arr[] = {preChar, from};
258 unsigned long strSize = sizeof(arr) / sizeof(arr[0]);
259 std::string str(arr, strSize);
260 std::size_t pos = output.find(from);
261 std::size_t length = output.length();
262 while (pos <= length - 1 && pos != std::string::npos) {
263 char nextCharTemp = pos == length - 1 ? '\0' : output[pos + 1];
264 if (nextChar == '\0' || nextCharTemp == '\0' || nextCharTemp != nextChar) {
265 output.replace(pos, 1, str);
266 length += (strSize - 1);
267 }
268 pos = output.find(from, pos + strSize);
269 }
270 return output;
271 }
272
ReplaceCharacters(const std::string & input)273 std::string ReplaceCharacters(const std::string &input)
274 {
275 std::string output = InsertCharBefore(input, '*', '.', '\0');
276 output = InsertCharBefore(output, '.', '\\', '*');
277 return output;
278 }
279
IsRegexValid(const std::string & regex)280 bool IsRegexValid(const std::string ®ex)
281 {
282 if (Trim(regex).empty()) {
283 return false;
284 }
285 return regex_match(regex, std::regex("^[a-zA-Z0-9\\-_\\.*]+$"));
286 }
287
GetProtocolFromURL(const std::string & url)288 std::string GetProtocolFromURL(const std::string &url)
289 {
290 std::string delimiter = "://";
291 size_t pos = url.find(delimiter);
292 if (pos != std::string::npos) {
293 return url.substr(0, pos);
294 }
295 return "";
296 }
297
GetPortFromURL(const std::string & url)298 std::string GetPortFromURL(const std::string &url)
299 {
300 std::string delimiter = "://";
301 std::string protocol = GetProtocolFromURL(url);
302 std::string hostname = GetHostnameFromURL(url);
303 size_t start = protocol.empty() ? hostname.size() : protocol.size() + delimiter.size() + hostname.size();
304 size_t posStart = url.find_first_of(':', start);
305 if (posStart == std::string::npos) {
306 return "";
307 }
308 size_t posEnd = std::min({url.find('/', start), url.find('?', start)});
309 if (posEnd == std::string::npos) {
310 return url.substr(posStart + 1);
311 }
312 if (posStart > posEnd) {
313 return "";
314 }
315 return url.substr(posStart + 1, posEnd - posStart - 1);
316 }
317
GetHostnameFromURL(const std::string & url)318 std::string GetHostnameFromURL(const std::string &url)
319 {
320 if (url.empty()) {
321 return "";
322 }
323 std::string delimiter = "://";
324 std::string tempUrl = url;
325 std::replace(tempUrl.begin(), tempUrl.end(), '\\', '/');
326 size_t posStart = tempUrl.find(delimiter);
327 if (posStart != std::string::npos) {
328 posStart += delimiter.length();
329 } else {
330 posStart = 0;
331 }
332 size_t notSlash = tempUrl.find_first_not_of('/', posStart);
333 if (notSlash != std::string::npos) {
334 posStart = notSlash;
335 }
336 size_t posEnd = std::min({ tempUrl.find(':', posStart),
337 tempUrl.find('/', posStart), tempUrl.find('?', posStart) });
338 if (posEnd != std::string::npos) {
339 return tempUrl.substr(posStart, posEnd - posStart);
340 }
341 return tempUrl.substr(posStart);
342 }
343
GetHostnameWithProtocolAndPortFromURL(const std::string & url)344 std::string GetHostnameWithProtocolAndPortFromURL(const std::string& url)
345 {
346 std::string delimiter = "://";
347 std::string portDelimiter = ":";
348 auto hostname = GetHostnameFromURL(url);
349 if (!hostname.empty()) {
350 std::string protocol = GetProtocolFromURL(url);
351 if (!protocol.empty()) {
352 hostname = protocol + delimiter + hostname;
353 }
354 if (protocol != PROTOCOL_WSS) {
355 std::string port = GetPortFromURL(url);
356 if (!port.empty()) {
357 hostname += portDelimiter + port;
358 }
359 }
360 }
361 return hostname;
362 }
363
IsExcluded(const std::string & str,const std::string & exclusions,const std::string & split)364 bool IsExcluded(const std::string &str, const std::string &exclusions, const std::string &split)
365 {
366 if (Trim(exclusions).empty()) {
367 return false;
368 }
369 std::size_t start = 0;
370 std::size_t end = exclusions.find(split);
371 while (end != std::string::npos) {
372 if (end - start > 0 && IsMatch(str, Trim(exclusions.substr(start, end - start)))) {
373 return true;
374 }
375 start = end + 1;
376 end = exclusions.find(split, start);
377 }
378 return IsMatch(str, Trim(exclusions.substr(start)));
379 }
380
IsHostNameExcluded(const std::string & url,const std::string & exclusions,const std::string & split)381 bool IsHostNameExcluded(const std::string &url, const std::string &exclusions, const std::string &split)
382 {
383 std::string hostName = GetHostnameFromURL(url);
384 return IsExcluded(hostName, exclusions, split);
385 }
386
DetectIPType(const std::string & ip)387 int DetectIPType(const std::string &ip)
388 {
389 if (ip.empty()) {
390 return INVALID_IP_TYPE;
391 }
392 if (IsValidIPV4(ip)) {
393 return AF_INET;
394 }
395 if (IsValidIPV6(ip)) {
396 return AF_INET6;
397 }
398 return INVALID_IP_TYPE;
399 }
400
IsValidIPV4(const std::string & ip)401 bool IsValidIPV4(const std::string &ip)
402 {
403 return IsValidIP(ip, AF_INET);
404 }
405
IsValidIPV6(const std::string & ip)406 bool IsValidIPV6(const std::string &ip)
407 {
408 return IsValidIP(ip, AF_INET6);
409 }
410
IsValidIP(const std::string & ip,int af)411 bool IsValidIP(const std::string& ip, int af)
412 {
413 if (ip.empty()) {
414 return false;
415 }
416 #ifdef WINDOWS_PLATFORM
417 if (af == AF_INET6) {
418 struct sockaddr_in6 sa;
419 return inet_pton(af, ip.c_str(), &(sa.sin6_addr)) == INET_OPTION_SUC;
420 } else {
421 struct sockaddr_in sa;
422 return inet_pton(af, ip.c_str(), &(sa.sin_addr)) == INET_OPTION_SUC;
423 }
424 #else
425 if (af == AF_INET6) {
426 struct in6_addr addr;
427 return inet_pton(af, ip.c_str(), reinterpret_cast<void *>(&addr)) == INET_OPTION_SUC;
428 } else {
429 struct in_addr addr;
430 return inet_pton(af, ip.c_str(), reinterpret_cast<void *>(&addr)) == INET_OPTION_SUC;
431 }
432 #endif
433 }
434
MaskIpv4(std::string & maskedResult)435 std::string MaskIpv4(std::string &maskedResult)
436 {
437 int maxDisplayNum = MAX_DISPLAY_NUM;
438 for (char &i : maskedResult) {
439 if (i == '/') {
440 break;
441 }
442 if (maxDisplayNum > 0) {
443 if (i == '.') {
444 maxDisplayNum--;
445 }
446 } else {
447 if (i != '.') {
448 i = '*';
449 }
450 }
451 }
452 return maskedResult;
453 }
454
MaskIpv6(std::string & maskedResult)455 std::string MaskIpv6(std::string &maskedResult)
456 {
457 size_t colonCount = 0;
458 for (char &i : maskedResult) {
459 if (i == '/') {
460 break;
461 }
462 if (i == ':') {
463 colonCount++;
464 }
465
466 if (colonCount >= MAX_DISPLAY_NUM) {
467 if (i != ':') {
468 i = '*';
469 }
470 }
471 }
472 return maskedResult;
473 }
474
AnonymizeIp(std::string & input)475 std::string AnonymizeIp(std::string &input)
476 {
477 if (input.empty()) {
478 return input;
479 }
480 std::lock_guard<std::mutex> lock(g_commonUtilsMutex);
481 std::string maskedResult{input};
482 if (std::regex_match(maskedResult, IP_PATTERN) || std::regex_match(maskedResult, IP_MASK_PATTERN)) {
483 return MaskIpv4(maskedResult);
484 }
485 if (std::regex_match(maskedResult, IPV6_PATTERN) || std::regex_match(maskedResult, IPV6_MASK_PATTERN)) {
486 return MaskIpv6(maskedResult);
487 }
488 return input;
489 }
490
GetBundleName()491 std::optional<std::string> GetBundleName()
492 {
493 #if HAS_NETMANAGER_BASE
494 return OHOS::NetManagerStandard::NetConnClient::ObtainBundleNameForSelf();
495 #endif
496 return std::nullopt;
497 }
498
GetFileDataFromFilePath(const std::string & filePath,std::string & fileData)499 bool GetFileDataFromFilePath(const std::string& filePath, std::string& fileData)
500 {
501 #if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
502 std::error_code error;
503 auto path = std::filesystem::absolute(filePath, error);
504 if (error) {
505 NETSTACK_LOGE("Failed to obtain the absolute path: %{public}s", error.message().c_str());
506 return false;
507 }
508 std::ifstream file(path);
509 #else
510 std::ifstream file(filePath);
511 #endif
512 if (file.is_open()) {
513 std::stringstream buffer;
514 buffer << file.rdbuf();
515 file.close();
516 fileData = buffer.str();
517 return true;
518 } else {
519 NETSTACK_LOGE("Failed to obtain the file data stream.");
520 return false;
521 }
522 }
523
Sha256sum(unsigned char * buf,size_t buflen,std::string & digestStr)524 bool Sha256sum(unsigned char *buf, size_t buflen, std::string &digestStr)
525 {
526 #ifdef HAS_NETMANAGER_BASE
527 EVP_MD_CTX *mdctx = EVP_MD_CTX_create();
528 unsigned int digestLen = 0;
529 unsigned char digest[SHA256_LEN];
530 unsigned char out[SHA256_BASE64_LEN + 1] = {0};
531 if (!mdctx) {
532 NETSTACK_LOGE("create MD_CTX failed.");
533 return false;
534 }
535 if (!EVP_DigestInit(mdctx, EVP_sha256())) {
536 NETSTACK_LOGE("EVP_DigestInit failed.");
537 return false;
538 }
539 if (!EVP_DigestUpdate(mdctx, buf, buflen)) {
540 NETSTACK_LOGE("EVP_DigestUpdate failed.");
541 return false;
542 }
543 if (!EVP_DigestFinal_ex(mdctx, digest, &digestLen)) {
544 NETSTACK_LOGE("EVP_DigestFinal_ex failed.");
545 return false;
546 }
547 EVP_MD_CTX_free(mdctx);
548 if (digestLen != SHA256_LEN) {
549 NETSTACK_LOGE("SHA256 length invalid");
550 return false;
551 }
552 int base64Len = EVP_EncodeBlock(out, digest, SHA256_LEN);
553 if (base64Len != SHA256_BASE64_LEN) {
554 NETSTACK_LOGE("SHA256-Base64 length invalid.");
555 return false;
556 }
557 digestStr = std::string(reinterpret_cast<const char *>(out), SHA256_BASE64_LEN);
558 return true;
559 #else
560 return false;
561 #endif
562 }
563
IsCertPubKeyInPinned(const std::string & certPubKeyDigest,const std::string & pinnedPubkey)564 bool IsCertPubKeyInPinned(const std::string &certPubKeyDigest, const std::string &pinnedPubkey)
565 {
566 auto begin = pinnedPubkey.find("sha256//");
567 if (begin != 0) {
568 NETSTACK_LOGE("pinnedPubkey format invalid, should start with sha256//");
569 return false;
570 }
571 while (begin < pinnedPubkey.size()) {
572 auto end = pinnedPubkey.find(";", begin);
573 if (end == std::string::npos) {
574 end = pinnedPubkey.size();
575 }
576 if (pinnedPubkey.find("sha256//", begin) != begin) {
577 NETSTACK_LOGE("pinnedPubkey format invalid, should be like sha256//[hash1];sha256//[hash2]");
578 begin = end + 1;
579 continue;
580 }
581 if (end - begin != PINNED_PREFIX_LEN + SHA256_BASE64_LEN) {
582 NETSTACK_LOGE("pinnedPubkey format invalid, hash length not match");
583 begin = end + 1;
584 continue;
585 }
586 std::string candidate = pinnedPubkey.substr(begin + PINNED_PREFIX_LEN, SHA256_BASE64_LEN);
587 if (candidate == certPubKeyDigest) {
588 return true;
589 }
590 begin = end + 1;
591 }
592 return false;
593 }
594
IsCleartextPermitted(const std::string & url,const std::string & protocol)595 bool IsCleartextPermitted(const std::string &url, const std::string &protocol)
596 {
597 bool isCleartextPermitted = true;
598 #if HAS_NETMANAGER_BASE
599 using namespace OHOS::NetManagerStandard;
600 bool isComponetCfg = true;
601 int32_t ret = NetworkSecurityConfig::GetInstance().IsCleartextCfgByComponent("Network Kit", isComponetCfg);
602 if (ret || !isComponetCfg) {
603 NETSTACK_LOGD("Network Kit Component Not Cfg or Cfg False");
604 return isCleartextPermitted;
605 }
606 if (url.find(protocol) != std::string::npos) {
607 std::string hostName = GetHostnameFromURL(url);
608 NetworkSecurityConfig::GetInstance().IsCleartextPermitted(hostName, isCleartextPermitted);
609 }
610 #endif
611 return isCleartextPermitted;
612 }
613
IsValidPort(const uint32_t & port)614 bool IsValidPort(const uint32_t &port)
615 {
616 if (port < 0 || port > MAX_PORT) {
617 return false;
618 }
619 return true;
620 }
621
ToAnonymousIp(const std::string & input)622 std::string ToAnonymousIp(const std::string &input)
623 {
624 std::string maskedResult = input;
625 // Mask ipv4 address.
626 if (std::regex_match(maskedResult, IP_PATTERN) || std::regex_match(maskedResult, IP_MASK_PATTERN)) {
627 MaskIpv4(maskedResult);
628 return maskedResult;
629 }
630 // Mask ipv6 address.
631 if (std::regex_match(maskedResult, IPV6_PATTERN) || std::regex_match(maskedResult, IPV6_MASK_PATTERN)) {
632 MaskIpv6(maskedResult);
633 return maskedResult;
634 }
635 return input;
636 }
637 } // namespace OHOS::NetStack::CommonUtils