在从Javascript中用尽了当前可用的选项之后,我决定简单地实现本机的证书固定,这一切看起来都很简单,现在我已经完成了.
如果不想通读获取解决方案的过程,请跳到标题为Android Solution和IOS Solution的标题.
Android
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模块.然后,我用一种发送简单请求的方法对其进行了扩展
@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除了提供如何使用证书的清晰演示外,Pinner还表示,要获得公钥,我需要做的就是使用错误的pin发送请求,正确的pin将出现在错误消息中.
花了一点时间才意识到.Builder()可以链接,我可以在构建之前包含CertificatePaner,这与我提出的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());
}
}
然后,替换错误中的公钥链,返回页面正文,表明我已成功提出请求,我更改了密钥的一个字母,以确保它正常工作,并且我知道我已经走上正轨.
我终于在我的演讲模块中使用了这种方法.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());
}
}
Android解决方案扩展React Native的OkHttpClient
在了解了如何发送固定http请求之后,现在我可以使用我创建的方法,但理想情况下,我认为最好扩展现有客户机,以便立即获得实现的好处.
这个解决方案从RN0.35
年前开始有效,我不知道将来它会如何公平.
在研究扩展OkHttpClient for RN的方法时,我遇到了this article个例子,解释了如何通过替换SSLSocketFactory来添加TLS 1.2支持.
阅读本文后,我了解到react使用OkHttpClientProvider创建XMLHttpRequest对象使用的OkHttpClient实例,因此,如果替换该实例,我们将对所有应用程序应用钉住.
我在android/app/src/main/java/com/dreidev
文件夹中添加了一个名为OkHttpCertPin.java
的文件
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;
}
}
这个包有一个方法extend,它接受现有的OkHttpClient并通过添加CertificatePaner来重建它,然后返回新构建的实例.
然后我修改了我的主要活动.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.42,直到重建停止工作的原因明确为止.
Solution IOS
对于IOS,我曾认为我需要遵循类似的方法,再次从工藤的提议开始.
在判断RCTNetwork模块时,我了解到使用了NSURLConnection,因此我没有像提案中建议的那样try 使用AFNetworking创建一个全新的模块,而是发现了TrustKit个
在《入门指南》之后,我简单地添加了
pod 'TrustKit'
到我的播客文件,跑了pod install
GettingStartedGuide解释了如何从pList配置这个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,
kTSKPublicKey算法rithms : @[kTSK算法rithmRsa2048],
// 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实现中获得了公钥散列,它刚刚起作用(我在播客中收到的TrustKit版本是1.3.2)
我很高兴IOS被证明是一种呼吸
作为旁注,TrustKit警告说,如果NSURLSession和连接已被swizzle,它的自动swizzle将无法工作.也就是说,到目前为止,它似乎运行良好.
Conclusion
这个答案为Android和IOS提供了解决方案,因为我能够用本机代码实现这一点.
一个可能的改进可能是实现一个通用平台模块,在该模块中,可以用javascript管理安卓和IOS的公钥设置和网络Provider 配置.
不过,简单地将公钥添加到jsBundle 包可能会expose 漏洞,以某种方式可以替换Bundle 包文件.
我不知道攻击向量是如何运作的,但肯定是额外的步骤.提议的js可能会保护js包.
另一种方法可能是简单地将js包编码为64位字符串,并将其作为mentioned in this issue's conversation直接包含在本机代码中.这种方法的好处是混淆js包,并将其硬连接到应用程序中,使攻击者无法访问它,我认为是这样.
如果你读到这篇文章,我希望我能启发你修复bug的方法,并祝你度过一个阳光灿烂的日子.