我需要在我的本机应用程序中实现SSL证书固定。
我对SSL / TLS知之甚少,更不用说固定了。我也不是本地移动开发人员,尽管我了解Java,并且在此项目上学到了Objective-C足以解决问题。
我开始搜索如何执行此任务。
不,我的初步搜索使我想到了这个提案,自2016年8月2日以来没有任何活动。
通过它,我了解到react-native使用了确实支持Pinning的OkHttp,但是我无法将其从Javascript中提取出来,这并不是真正的要求,而是一个加号。
虽然react似乎使用了nodejs运行时,但它更像是一个浏览器而不是node,这意味着它不支持所有本机模块,特别是https模块,本文在本文中实现了证书固定。因此无法将其带入本地反应。
我尝试使用rn-nodeify,但是模块不起作用。自从我目前在RN 0.33到RN 0.35以来,这是正确的。
我考虑过使用phongape插件,但是由于我对需要react 0.32+的库有依赖性,所以我不能使用react-native-cordova-plugin
虽然我不是本地应用程序开发人员,但我总是可以尽力解决问题,只是时间问题。
我了解到android支持SSL固定,但是并没有成功,因为这种方法似乎在Android 7之前不起作用,而且仅适用于android。
我已经用尽了几个方向,将继续追求更多的本机实现,也许想出如何配置OkHttp和RNNetworking,然后再桥接回本机。
但是,已经有针对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=")
.build())
.build();
我首先开始学习如何通过响应本机创建Toast模块来创建本机android桥。然后,我使用一种发送简单请求的方法对其进行了扩展
@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;
对于我在哪里可以获取公钥或如何生成公钥,工藤的方法尚不清楚。幸运的是,okhttp3文档除了提供了有关如何使用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的方法时,我遇到了这篇文章,解释了如何通过替换SSLSocketFactory添加TLS 1.2支持。
阅读它,我了解到React使用OkHttpClientProvider创建XMLHttpRequest对象使用的OkHttpClient实例,因此,如果替换该实例,我们将对所有应用程序应用钉扎。
我添加了一个名为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对其进行重建,并返回新建的实例。
然后,我按照此答案的建议修改了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方法,因为检查该提供程序时,我意识到主版本已实现TLS 1.2支持,但尚不可用,因此我们发现重建是扩展客户的最佳方法。我想知道这种方法在我升级时如何公平,但目前行之有效。
更新似乎从0.43开始,此技巧不再起作用。出于时间限制,我暂时将项目冻结在0.42,直到明确了停止重建的原因为止。
对于IOS,我曾以为我需要遵循一种类似的方法,同样从Kudo的建议开始。
检查RCTNetwork模块后,我了解到使用了NSURLConnection,因此,我没有按照提案中的建议使用AFNetworking创建一个全新的模块,而是发现了TrustKit
遵循我的入门指南,我只是添加了
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 [email protected] 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的网络提供程序。
Kudo的建议提到,仅将公共密钥添加到js捆绑包中可能会暴露一个漏洞,在该漏洞中可以以某种方式替换捆绑包文件。
我不知道该攻击媒介如何发挥作用,但可以肯定的是,按照提议的方法对bundle.js进行签名的额外步骤可以保护js捆绑包。
另一种方法可能是将js捆绑包简单地编码为64位字符串,然后将其直接包含在本机代码中,如本期话题所述。这种方法的好处是可以将js捆绑包混淆并硬接线到应用程序中,从而使攻击者无法访问它。
如果您读了这么多文章,希望对您的bug修复工作有所启发,并祝您阳光明媚。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句