我需要在我的反应原生应用程序中实现SSL证书固定。
我对SSL / TLS知之甚少,更不用说固定了。 我也不是一个本地移动开发人员,虽然我了解Java并且在这个项目上学习了Objective-C足以让他们四处游荡。
我开始搜索如何执行此任务。
不,我的初步搜索引导我{2016年8月2日以来未收到任何活动的this proposal。
从中我了解到react-native使用的OkHttp确实支持Pinning,但是我无法将其从Javascript中删除,这不是真正的要求,而是一个加号。
虽然反应似乎使用了nodejs运行时,但它更像是一个浏览器而不是节点,这意味着它不支持所有本机模块,特别是https模块,我已经在this article之后实现了证书锁定。因此无法将其转化为原生的反应。
我尝试使用rn-nodeify,但模块没有用。这是因为我目前正在使用RN 0.33到RN 0.35。
我想过使用phongape-plugin但是因为我依赖于需要反应0.32+的库我不能使用react-native-cordova-plugin
虽然我不是本机应用程序开发人员,但我总是可以解决它,只是时间问题。
我了解到android支持SSL Pinning然而不成功,因为看起来这种方法在Android 7之前不起作用。以及仅适用于Android。
我已经用尽了几个方向,并将继续寻求更多的原生实现,也许可以弄清楚如何配置OkHttp和RNNetworking然后可能会回到本地反应。
但是IOS和android已经有任何实现或指南吗?
答案 0 :(得分:43)
在从Javascript中耗尽当前频谱的可用选项后,我决定简单地实现本地证书固定,现在看来我已经完成了这一切。
如果您不想阅读完成解决方案的过程,请跳至标题为 Android解决方案和 IOS解决方案的标题。
关注Kudo's recommendation我想过使用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();
我首先学习如何创建一个创建toast模块的本地android bridge with react native。然后我用一种发送简单请求的方法扩展它
@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();
}
}
继续发送请求后,我转向发送固定请求。
我在文件中使用了这些包
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的方法并不清楚我将获得公钥或如何生成公钥。幸运的是okhttp3 docs除了提供如何使用CertificatePinner的明确演示之外,我还需要发送一个带有错误引脚的请求来获取公钥,并且错误中会出现正确的引脚消息。
花了一点时间才意识到OkHttpClent.Builder()可以被链接,我可以在构建之前包含CertificatePinner,不像Kudo的提议(可能是旧版本)中的误导性示例我想出了这个方法
@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());
}
}
然后更换我在错误中获得的公共钥匙链产生了页面的正文,表明我已经成功请求,我更改了密钥的一个字母,以确保它正常工作,我知道我在轨道。
我终于在ToastModule.java文件中使用了这个方法
@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());
}
}
了解了如何发送固定的http请求是好的,现在我可以使用我创建的方法,但理想情况下我认为最好扩展现有的客户端,以便立即获得实施的好处。
此解决方案自RN0.35
起有效,我不知道将来会如何公平。
在研究如何扩展用于RN的OkHttpClient时,我遇到了this article,解释了如何通过替换SSLSocketFactory来添加TLS 1.2支持。
读取它我学到了react使用OkHttpClientProvider来创建XMLHttpRequest对象使用的OkHttpClient实例,因此如果我们替换该实例,我们会将pinning应用于所有应用程序。
我在OkHttpCertPin.java
文件夹
android/app/src/main/java/com/dreidev
的文件
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;
}
}
这个包有一个方法扩展,它接受现有的OkHttpClient并重建它,添加了certificatePinner并返回新构建的实例。
然后我通过添加以下方法修改了this answer's advice之后的MainActivity.java文件
.
.
.
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);
}
.
.
.
执行此解决方案有利于完全重新实现OkHttpClientProvider createClient方法,因为检查提供程序我意识到the master version已实现了TLS 1.2支持,但还不是我可以使用的选项,因此重建被发现是扩展客户端的最佳方式。我想知道这种方法在升级时会如何公平,但现在它运作良好。
更新似乎从0.43开始这个技巧不再适用。出于时间原因,我现在将我的项目冻结在0.42,直到为什么重建停止工作的原因是明确的。
对于IOS,我原本以为我需要采用类似的方法,再次以Kudo的提议开始作为我的领导。
检查RCTNetwork模块我了解到使用了NSURLConnection,因此我没有尝试按照提案中的建议创建一个全新的AFNetworking模块{I} {/ 3>
按照其入门指南我只需添加
pod 'TrustKit'
到我的podfile并运行pod install
GettingStartedGuide解释了如何从我的pList.file配置这个pod,但更喜欢使用代码而不是配置文件我将以下行添加到我的AppDelegate.m文件
.
.
.
#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];
.
.
.
我从我的android实现中得到了公钥哈希,它刚刚起作用(我在我的pod中收到的TrustKit的版本是1.3.2)
我很高兴IOS原来是一口气
作为旁注,TrustKit警告说,如果NSURLSession和Connection已经被淘汰,它的Auto-swizzle将无法正常工作。到目前为止它似乎运作良好。
这个答案为Android和IOS提供了解决方案,因为我能够在本机代码中实现这一点。
一个可能的改进可能是实现一个通用平台模块,其中可以在javascript中管理设置公钥和配置android和IOS的网络提供程序。
TrustKit提到简单地将公钥添加到js包可能会暴露漏洞,在某种程度上可以替换捆绑文件。
我不知道攻击向量是如何起作用的,但是当然建议对bundle.js进行签名的额外步骤可以保护js包。
另一种方法可能是简单地将js包编码为64位字符串,并将其作为Kudo's proposal直接包含在本机代码中。这种方法的好处是可以混淆js捆绑到应用程序中,使攻击者无法访问,或者我认为。
如果你读到这里,我希望我能帮助你修复你的虫子并希望你享受阳光灿烂的日子。
答案 1 :(得分:0)
您可以使用此库https://github.com/nlt2390/react-native-pinning-ssl
它使用SHA1密钥而不是证书来验证SSL连接。