如何在使用 React Native 时实现 SSL 证书锁定 [英] How can I implement SSL Certificate Pinning while using React Native

查看:53
本文介绍了如何在使用 React Native 时实现 SSL 证书锁定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在我的 React Native 应用程序中实现 SSL 证书锁定.

我对 SSL/TLS 知之甚少,更不用说固定了.我也不是本地移动开发人员,尽管我了解 Java 并在这个项目中学习了 Objective-C,足以应付.

我开始寻找如何执行这个任务.

React Native 不是已经实现了吗?

不,我最初的搜索使我找到了这个提案自 2016 年 8 月 2 日起未收到任何活动.

从中我了解到 react-native 使用了支持 Pinning 的 OkHttp,但我无法将它从 Javascript 中拉出来,这不是真正的要求,而是一个加分项.

在 Javascript 中实现它.

虽然 react 看起来像是使用 nodejs 运行时,但它更像是一个浏览器而不是 node,这意味着它不支持所有本机模块,特别是 https 模块,我已经在 这篇文章.因此无法将其带入 React Native.

我尝试使用 rn-nodeify 但模块不起作用.从 RN 0.33 到我目前使用的 RN 0.35 以来,情况一直如此.

使用 phonegap 插件实现

我想过使用 phongape-plugin 但是因为我依赖库需要反应 0.32+ 我不能使用 react-native-cordova-plugin

就自己做

虽然我不是本地应用程序开发人员,但我总是可以尝试破解它,只是时间问题.

Android 有证书固定

我了解到 android 支持 SSL Pinning 但是没有成功似乎这种方法在 Android 7 之前不起作用.以及仅适用于 android.

底线

我已经用尽了几个方向,将继续追求更多的原生实现,也许会弄清楚如何配置 OkHttp 和 RNNetworking,然后可能会回到 react-native.

但是是否已经有适用于 IOS 和 android 的实现或指南?

解决方案

在用尽 Javascript 的当前可用选项范围后,我决定简单地在本地实现证书固定,现在我完成了这一切看起来很简单.

<块引用>

如果您不想通读获得解决方案的过程,请跳至标题为 Android 解决方案IOS 解决方案 的标题.

安卓

根据 Kudo 的建议,我想到了使用 okhttp3 实现固定.

client = new OkHttpClient.Builder().certificatePinner(new CertificatePinner.Builder().add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=").add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=").add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=").add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=").建造()).建造();

我首先学习如何创建一个原生的 android 桥与本机反应创建一个吐司模块.然后我用一种发送简单请求的方法对其进行了扩展

@ReactMethod公共无效showURL(字符串url,int持续时间){尝试 {请求请求 = new Request.Builder().url(网址).建造();响应 response = client.newCall(request).execute();Toast.makeText(getReactApplicationContext(), response.body().string(), duration).show();} catch (IOException e) {Toast.makeText(getReactApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();}}

成功发送请求后,我转向发送固定请求.

我在我的文件中使用了这些包

import com.facebook.react.bridge.NativeModule;导入 com.facebook.react.bridge.ReactApplicationContext;导入 com.facebook.react.bridge.ReactContext;导入 com.facebook.react.bridge.ReactContextBaseJavaModule;导入 com.facebook.react.bridge.ReactMethod;导入 com.facebook.react.bridge.Callback;导入 okhttp3.OkHttpClient;导入 okhttp3.Request;导入 okhttp3.Response;导入 okhttp3.CertificatePinner;导入 java.io.IOException;导入 java.util.Map;导入 java.util.HashMap;

Kudo 的方法并不清楚我从哪里获得公钥或如何生成它们.幸运的是 okhttp3 docs 除了提供一个关于如何使用 CertificatePinner 的清晰演示指出,要获取公钥,我需要做的就是发送一个带有错误 PIN 的请求,正确的 PIN 将出现在错误消息中.

在意识到 OkHttpClent.Builder() 可以链接并且我可以在构建之前包含 CertificatePinner 之后,与 Kudo 的提案(可能是旧版本)中的误导性示例不同,我想出了这个方法.

@ReactMethodpublic void getKeyChainForHost(字符串主机名,回调错误CallbackContainingCorrectKeys,回调成功回调) {尝试 {CertificatePinner certificatePinner = new CertificatePinner.Builder().add(主机名, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAA=").建造();OkHttpClient 客户端 = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();请求请求 = new Request.Builder().url("https://" + 主机名).建造();响应 response =client.newCall(request).execute();successCallback.invoke(response.body().string());} 捕获(异常 e){errorCallbackContainingCorrectKeys.invoke(e.getMessage());}}

然后更换我在错误中得到的公共钥匙串,返回页面的正文,表明我已成功提出请求,我更改了密钥的一个字母以确保它正常工作,并且我知道我正在走上正轨.

>

我的 ToastModule.java 文件中终于有了这个方法

@ReactMethodpublic void getKeyChainForHost(字符串主机名,回调错误CallbackContainingCorrectKeys,回调成功回调) {尝试 {CertificatePinner certificatePinner = new CertificatePinner.Builder().add(主机名, "sha256/+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=").add(主机名, "sha256/aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=").add(主机名, "sha256/HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=").建造();OkHttpClient 客户端 = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();请求请求 = new Request.Builder().url("https://" + 主机名).建造();响应 response =client.newCall(request).execute();successCallback.invoke(response.body().string());} 捕获(异常 e){errorCallbackContainingCorrectKeys.invoke(e.getMessage());}}

Android 方案扩展 React Native 的 OkHttpClient

弄清楚如何发送固定的 http 请求很好,现在我可以使用我创建的方法,但理想情况下我认为最好扩展现有客户端,以便立即获得实施的好处.

>

此解决方案自 RN0.35 起有效,我不知道将来会如何公平.

在研究为 RN 扩展 OkHttpClient 的方法时,我遇到了 这篇文章 解释了如何通过替换 SSLSocketFactory 添加 TLS 1.2 支持.

阅读后我了解到 react 使用 OkHttpClientProvider 来创建由 XMLHttpRequest 对象使用的 OkHttpClient 实例,因此如果我们替换该实例,我们将对所有应用程序应用固定.

我在我的 android/app/src/main/java/com/dreidev 文件夹中添加了一个名为 OkHttpCertPin.java 的文件

package com.dreidev;导入 android.util.Log;导入 com.facebook.react.modules.network.OkHttpClientProvider;导入 com.facebook.react.modules.network.ReactCookieJarContainer;导入 java.util.concurrent.TimeUnit;导入 okhttp3.OkHttpClient;导入 okhttp3.Request;导入 okhttp3.Response;导入 okhttp3.CertificatePinner;公共类 OkHttpCertPin {私有静态字符串主机名 = "*.efghermes.com";private static final String TAG = "OkHttpCertPin";公共静态 OkHttpClient 扩展(OkHttpClient currentClient){尝试 {CertificatePinner certificatePinner = new CertificatePinner.Builder().add(主机名, "sha256/+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=").add(主机名, "sha256/aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=").add(主机名, "sha256/HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=").建造();Log.d(TAG, "扩展客户端");返回 currentClient.newBuilder().certificatePinner(certificatePinner).build();} 捕获(异常 e){Log.e(TAG, e.getMessage());}返回当前客户端;}}

这个包有一个方法extend,它接受一个现有的OkHttpClient并重建它,添加certificatePinner并返回新构建的实例.

然后我按照这个答案的建议修改了我的 MainActivity.java 文件,添加了以下方法

<预><代码>...导入 com.facebook.react.ReactActivity;导入 android.os.Bundle;导入 com.dreidev.OkHttpCertPin;导入 com.facebook.react.modules.network.OkHttpClientProvider;导入 okhttp3.OkHttpClient;公共类 MainActivity 扩展了 ReactActivity {@覆盖public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);重建OKHtttp();}私有无效重建OkHtttp(){OkHttpClient currentClient = OkHttpClientProvider.getOkHttpClient();OkHttpClient 替换Client = OkHttpCertPin.extend(currentClient);OkHttpClientProvider.replaceOkHttpClient(replacementClient);}...

这个解决方案是为了完全重新实现 OkHttpClientProvider createClient 方法,在检查提供程序时我意识到 主版本 已经实现了 TLS 1.2 支持,但还不是我可以使用的选项,因此,重建被认为是扩展客户端的最佳方式.我想知道这种方法在我升级时如何公平,但目前效果很好.

更新 似乎从 0.43 开始这个技巧不再有效.由于时间限制,我现在将我的项目冻结在 0.42,直到重建停止工作的原因明确为止.

解决方案IOS

对于 IOS,我曾认为我需要遵循类似的方法,再次以 Kudo 的提案作为我的领导.

检查 RCTNetwork 模块我了解到使用了 NSURLConnection,所以我发现了 TrustKit

按照我简单添加的入门指南

pod 'TrustKit'

到我的 podfile 并运行 pod install

GettingStartedGuide 解释了我如何从我的 pList.file 配置这个 pod,但我更喜欢使用代码而不是配置文件我在 AppDelegate.m 文件中添加了以下几行

<预><代码>...#import ...@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{//初始化 TrustKitNSDictionary *trustKitConfig =@{//自动调整 NSURLSession 委托以添加固定验证kTSKSwizzleNetworkDelegates:@YES,kTSKPinnedDomains:@{//将无效的 SPKI 哈希固定到 *.yahoo.com 以证明固定失败@efghermes.com":@{kTSKEnforcePinning:@YES,kTSKIncludeSubdomains:@YES,kTSKPublicKeyAlgorithms:@[kTSKAlgorithmRsa2048],//错误的 SPKI 哈希值以证明固定失败kTSKPublicKeyHashes : @[@"+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=",@"aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=",@"HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY="],//发送固定失败报告//如果您需要一个免费的仪表板来查看您的应用程序的报告,请发送电子邮件至 info@datatheorem.comkTSKReportUris:@[@"https://overmind.datatheorem.com/trustkit/report"]},}};[TrustKit initializeWithConfiguration:trustKitConfig];...

我从我的 android 实现中获得了公钥哈希,并且它可以正常工作(我在 pod 中收到的 TrustKit 版本是 1.3.2)

我很高兴IOS竟然是一口气

<块引用>

顺便提一下,TrustKit 警告说,如果 NSURLSession 和 Connection 已经混合,它的自动混合将不起作用.也就是说到目前为止它似乎运行良好.

结论

这个答案提供了适用于 Android 和 IOS 的解决方案,因为我能够在本机代码中实现它.

一种可能的改进可能是实现一个通用平台模块,在该模块中可以在 javascript 中管理设置公钥和配置 android 和 IOS 的网络提供程序.

Kudo 的提议 提到简单地将公钥添加到 js 包然而可能会暴露一个漏洞,可以以某种方式替换包文件.

我不知道该攻击向量如何运作,但当然,按照提议对 bundle.js 进行签名的额外步骤可以保护 js 包.

另一种方法可能是简单地将 js 包编码为 64 位字符串,并将其作为 在本期对话中提到.这种方法的好处是混淆了 js 包并将其硬连接到应用程序中,使攻击者无法访问,我认为.

如果您读到这里,我希望我对您修复错误的探索有所启发,并希望您享受阳光明媚的一天.

I need to implement SSL Certificate Pinning in my react native application.

I know very little about SSL/TLS let alone pinning. I am also not a native mobile developer, though I know Java and learned Objective-C on this project enough to get around.

I started searching for how to execute this task.

Doesn't React Native already implement this?

No, My initial search lead me to this proposal which has received no activity since August 2nd 2016.

From it I learned that react-native uses OkHttp which does support Pinning, but I wouldn't be able to pull it off from Javascript, which is not really a requirement but a plus.

Implement it in Javascript.

While react seems like it uses the nodejs runtime, it is more like a browser than node, meaning it does not support all native modules, specifically the https module, for which I had implemented certificate pinning following this article. Thus could not carry it into react native.

I tried using rn-nodeify but the modules didn't work. This has been true since RN 0.33 to RN 0.35 which I'm currently on.

Implement using phonegap plugin

I thought of using a phongape-plugin however since I have a dependency on libraries that require react 0.32+ I can't use react-native-cordova-plugin

Just do it natively

While I'm not a native app developer I can always take a crack at it, only a matter of time.

Android has certificate pinning

I learned that android supports SSL Pinning however was unsuccessful as it seems that this approach does not work Prior to Android 7. As well as only working for android.

The bottom line

I have exhausted several directions and will continue to pursue more native implementation, maybe figure out how to configure OkHttp and RNNetworking then maybe bridging back to react-native.

But is there already any implementations or guide for IOS and android?

解决方案

After exhausting the current spectrum of available options from Javascript I decided to simply implement certificate pinning natively it all seems so simple now that I'm done.

Skip to headers titled Android Solution and IOS Solution if you don't want to read through the process of reaching the solution.

Android

Following Kudo's recommendation I thought out to implement pinning using okhttp3.

client = new OkHttpClient.Builder()
        .certificatePinner(new CertificatePinner.Builder()
            .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
            .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
            .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
            .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
            .build())
        .build();

I first started by learning how to create a native android bridge with react nativecreating a toast module. I then extended it with a method for sending a simple request

@ReactMethod
public void showURL(String url, int duration) {
    try {
        Request request = new Request.Builder()
        .url(url)
        .build();
        Response response = client.newCall(request).execute();
        Toast.makeText(getReactApplicationContext(), response.body().string(), duration).show();
    } catch (IOException e) {
        Toast.makeText(getReactApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

Succeeding in sending a request I then turned to sending a request pinned.

I used these packages in my file

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.CertificatePinner;
import java.io.IOException;

import java.util.Map;
import java.util.HashMap;

Kudo's approach wasn't clear on where I would get the public keys or how to generate them. luckily okhttp3 docs in addition to providing a clear demonstration of how to use the CertificatePinner stated that to get the public keys all I would need to do is send a request with an incorrect pin, and the correct pins will appear in the error message.

After taking a moment to realise that OkHttpClent.Builder() can be chained and I can include the CertificatePinner before the build, unlike the misleading example in Kudo's proposal (probably and older version) I came up with this method.

@ReactMethod
public void getKeyChainForHost(String hostname, Callback errorCallbackContainingCorrectKeys,
  Callback successCallback) {
    try {
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
             .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
             .build();
        OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();

        Request request = new Request.Builder()
             .url("https://" + hostname)
             .build();
        Response response =client.newCall(request).execute();
        successCallback.invoke(response.body().string());
    } catch (Exception e) {
        errorCallbackContainingCorrectKeys.invoke(e.getMessage());
    }
}

Then replacing the public keychains I got in the error yielded back the page's body, indicating I had made a successful request, I change one letter of the key to make sure it was working and I knew I was on track.

I finally had this method in my ToastModule.java file

@ReactMethod
public void getKeyChainForHost(String hostname, Callback errorCallbackContainingCorrectKeys,
  Callback successCallback) {
    try {
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
             .add(hostname, "sha256/+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=")
             .add(hostname, "sha256/aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=")
             .add(hostname, "sha256/HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=")
             .build();
        OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();

        Request request = new Request.Builder()
             .url("https://" + hostname)
             .build();
        Response response =client.newCall(request).execute();
        successCallback.invoke(response.body().string());
    } catch (Exception e) {
        errorCallbackContainingCorrectKeys.invoke(e.getMessage());
    }
}

Android Solution Extending React Native's OkHttpClient

Having figured out how to send pinned http request was good, now I can use the method I created, but ideally I thought it would be best to extend the existing client, so as to immediately gain the benefit of implementing.

This solution is valid as of RN0.35 and I don't know how it will fair in the future.

While looking into ways of extending the OkHttpClient for RN I came across this article explaining how to add TLS 1.2 support through replacing the SSLSocketFactory.

reading it I learned react uses an OkHttpClientProvider for creating the OkHttpClient instance used by the XMLHttpRequest Object and therefore if we replace that instance we would apply pinning to all the app.

I added a file called OkHttpCertPin.java to my android/app/src/main/java/com/dreidev folder

package com.dreidev;

import android.util.Log;

import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;


import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.CertificatePinner;

public class OkHttpCertPin {
    private static String hostname = "*.efghermes.com";
    private static final String TAG = "OkHttpCertPin";

    public static OkHttpClient extend(OkHttpClient currentClient){
      try {
        CertificatePinner certificatePinner = new CertificatePinner.Builder()
             .add(hostname, "sha256/+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=")
             .add(hostname, "sha256/aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=")
             .add(hostname, "sha256/HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=")
             .build();
        Log.d(TAG, "extending client");
        return currentClient.newBuilder().certificatePinner(certificatePinner).build();
      } catch (Exception e) {
        Log.e(TAG, e.getMessage());
      }
     return currentClient;
   }
}

This package has a method extend which takes an existing OkHttpClient and rebuilds it adding the certificatePinner and returns the newly built instance.

I then modified my MainActivity.java file following this answer's advice by adding the following methods

.
.
.
import com.facebook.react.ReactActivity;
import android.os.Bundle;

import com.dreidev.OkHttpCertPin;
import com.facebook.react.modules.network.OkHttpClientProvider;
import okhttp3.OkHttpClient;

public class MainActivity extends ReactActivity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     rebuildOkHtttp();
  }

  private void rebuildOkHtttp() {
      OkHttpClient currentClient = OkHttpClientProvider.getOkHttpClient();
      OkHttpClient replacementClient = OkHttpCertPin.extend(currentClient);
      OkHttpClientProvider.replaceOkHttpClient(replacementClient);
  }
.
.
.

This solution was carried out in favor of completely reimplementing the OkHttpClientProvider createClient method, as inspecting the provider I realized that the master version had implemented TLS 1.2 support but was not yet an available option for me to use, and so rebuilding was found to be the best means of extending the client. I'm wondering how this approach will fair as I upgrade but for now it works well.

Update It seems that starting 0.43 this trick no longer works. For timebound reasons I will freeze my project at 0.42 for now, until the reason for why rebuilding stopped working is clear.

Solution IOS

For IOS I had thought I would need to follow a similar method, again starting with Kudo's proposal as my lead.

Inspecting the RCTNetwork module I learned that NSURLConnection was used, so instead of trying to create a completely new module with AFNetworking as suggested in the proposal I discovered TrustKit

following its Getting Started Guide I simply added

pod 'TrustKit'

to my podfile and ran pod install

the GettingStartedGuide explained how I can configure this pod from my pList.file but preferring to use code than configuration files I added the following lines to my AppDelegate.m file

.
.
.
#import <TrustKit/TrustKit.h>
.
.
.
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{


  // Initialize TrustKit
  NSDictionary *trustKitConfig =
    @{
    // Auto-swizzle NSURLSession delegates to add pinning validation
    kTSKSwizzleNetworkDelegates: @YES,

    kTSKPinnedDomains: @{

       // Pin invalid SPKI hashes to *.yahoo.com to demonstrate pinning failures
       @"efghermes.com" : @{
           kTSKEnforcePinning:@YES,
           kTSKIncludeSubdomains:@YES,
           kTSKPublicKeyAlgorithms : @[kTSKAlgorithmRsa2048],

           // Wrong SPKI hashes to demonstrate pinning failure
           kTSKPublicKeyHashes : @[
              @"+Jg+cke8HLJNzDJB4qc1Aus14rNb6o+N3IrsZgZKXNQ=",
              @"aR6DUqN8qK4HQGhBpcDLVnkRAvOHH1behpQUU1Xl7fE=",
              @"HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY="
              ],

          // Send reports for pinning failures
          // Email info@datatheorem.com if you need a free dashboard to see your App's reports
          kTSKReportUris: @[@"https://overmind.datatheorem.com/trustkit/report"]
          },

     }
  };

  [TrustKit initializeWithConfiguration:trustKitConfig];
.
.
.

I got the public key hashes from my android implementation and it just worked (the version of TrustKit I received in my pods is 1.3.2)

I was glad IOS turned out to be a breath

As a side note TrustKit warned that it's Auto-swizzle won't work if the NSURLSession and Connection are already swizzled. that said it seems to be working well so far.

Conclusion

This answer presents the solution for both Android and IOS, given I was able to implement this in native code.

One possible improvement may be to implement a common platform module where setting public keys and configuring the Network providers of both android and IOS can be managed in javascript.

Kudo's proposal mentioned simply adding the public keys to the js bundle may however expose a vulnerability, where somehow the bundle file can be replaced.

I don't know how that attack vector can function, but certainly the extra step of signing the bundle.js as proposed may protect the js bundle.

Another approach may be to simply encode the js bundle into a 64 bit string and include it in the native code directly as mentioned in this issue's conversation. This approach has the benefit of obfuscating as well hardwiring the js bundle into the app, making it inaccessible for attackers or so I think.

If you read this far I hope I enlightened you on your quest for fixing your bug and wish you enjoy a sunny day.

这篇关于如何在使用 React Native 时实现 SSL 证书锁定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆