第一次写简书,记录一下最近开发的项目关于使用websocket遇到的问题。
因为后台使用的是Websocket协议,所以我就在GitHub上面找到了用户量比较多Objecttive-C的框架,Facebook开源的SocketRocket,具体Websocket与Socket的区别自行百度,网上资料还是比较多的。
(简单介绍:WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上,Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口)
此处省略一火车的文字...直接说处理方式,希望对跟我遇到一样的问题的人有帮助。
从GitHub下载代码,pod安装代码比较旧,所以下载后将SocketRocket文件夹手动拖入工程。
需要添加 libicucore.A.tdb 库。<方法:点击工程--->Build Phases --->Link Binary With Libraries >
可能会有一些头文件的报未找到文件的错,将import <>改成import "" 即可。
一、证书准备工作
证书需要:服务器提供三个证书文件分别为: CA根证书(必须是der编码格式,后缀有cer、der等,可用OpenSSL做转换)、公钥、私钥 ,其中<公钥、私钥 使用OpenSSL合并成一个p12文件,合并的时候会提示设置密码,需记住密码,代码里面会使用>,下面是我的转换方法,因为服务器给的是pem格式,所以具体格式具体自行搜索转换方法:
假设后台给的三个证书命名为以下:
根证书命名为:CAcert.pem
公钥证书命名为:client-cert.pem
私钥证书命名为:client-key.pem
1、将CA根证书pem格式转成der格式:(以下命令都在终端中执行,需要cd到存放证书的目录下)
openssl x509 -inform PEM -outform DER -in CAcert.pem -out CAcert.der
2、将公钥与私钥合并成一个 p12 ,合成时需要记住设置的密码
openssl pkcs12 -export -clcerts -in client-cert.pem -inkey client-key.pem -out client.p12
OK,到这里证书的准备工作已经完成,后面就是导入工程,在项目里面使用了。
二、将证书导入工程,然后代码使用
使用子类 SRPinningSecurityPolicy 重写 父类SRSecurityPolicy 的updateSecurityOptionsInStream方法,注:SRPinningSecurityPolicy 和 SRSecurityPolicy 类都是SocketRocket框架里面的文件,作者提供子类SRPinningSecurityPolicy给开发者重写处理证书相关的操作,如下
SRPinningSecurityPolicy.m:
- (void)updateSecurityOptionsInStream:(NSStream*)stream{
// Enforce TLS 1.2
[streamsetProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
// Validate certificate chain for this stream if enabled.
NSDictionary *sslOptions = [self defaultSetting];
[streamsetProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
}
//分割线内的方法可以抽出来写到类里面,简书不知道怎么传文件,所以把代码贴出来了。
//思路:例如抽到类SocketSSLSetting,可以将defaultSetting写成类方法
//+ (nonnullNSDictionary *)defaultSetting,在SRPinningSecurityPolicy使用的时候将[self defaultSetting]改成[SocketSSLSetting defaultSetting]即可
- (nonnullNSDictionary *)defaultSetting {
NSDictionary*settings =nil;
CFTypeRefcerts[2] = {getIdentityRef(),CFArrayGetValueAtIndex(getSecCertificateRefs(), 0)};
CFArrayRefcertsArray =CFArrayCreate(NULL, (void*)certs,2,NULL);
settings =@{(__bridgeid)kCFStreamSSLCertificates:CFBridgingRelease(certsArray)};
return settings;
}
CFArrayRefgetSecCertificateRefs() {
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"CAcert" ofType:@"der"];
NSData*certData = [[NSDataalloc]initWithContentsOfFile:thePath];
CFDataRefcertCFData = (__bridgeCFDataRef)certData;
SecCertificateRef cert = NULL;
cert =SecCertificateCreateWithData(NULL, certCFData);
SecCertificateRefcertArray[1] = { cert };
CFArrayRefcerts =CFArrayCreate(NULL, (void*)certArray,1,NULL);
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecTrustRefmyTrust =NULL;
OSStatusstatus =SecTrustCreateWithCertificates(certs, myPolicy, &myTrust);
if(myPolicy)
CFRelease(myPolicy);
if(myTrust){
CFRelease(myTrust)
}
return status==noErr? certs :NULL;
}
SecIdentityRefgetIdentityRef() {
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
NSData*PKCS12Data = [[NSDataalloc]initWithContentsOfFile:thePath];
CFDataRefinPKCS12Data = (__bridgeCFDataRef)PKCS12Data;
CFStringRefpassword =CFSTR("123");//合成p12文件的时候设置的密码
SecIdentityRefmyIdentity =NULL;
SecTrustRefmyTrust =NULL;
OSStatusstatus =noErr;
status =extractIdentityAndTrust(inPKCS12Data, &myIdentity, &myTrust, password);
if(myTrust)
CFRelease(myTrust);
return status==noErr? myIdentity :NULL;
}
OSStatusextractIdentityAndTrust(CFDataRefinPKCS12Data,SecIdentityRef*outIdentity,SecTrustRef*outTrust,CFStringRefkeyPassword) {
OSStatussecurityError =errSecSuccess;
const void*keys[] = {kSecImportExportPassphrase };
constvoid*values[] = { keyPassword };
CFDictionaryRefoptionsDictionary =NULL;
optionsDictionary =CFDictionaryCreate(NULL, keys, values, (keyPassword ?1:0),NULL,NULL);
CFArrayRefitems =NULL;
securityError =SecPKCS12Import(inPKCS12Data,optionsDictionary,&items);
if(securityError ==noErr) {
CFDictionaryRefmyIdentityAndTrust =CFArrayGetValueAtIndex(items,0);
constvoid*tempIdentity =NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemIdentity);
*outIdentity = (SecIdentityRef)CFRetain(tempIdentity);
constvoid*tempTrust =NULL;
tempTrust =CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
*outTrust = (SecTrustRef)CFRetain(tempTrust);
}
if(optionsDictionary)
CFRelease(optionsDictionary);
if(items)
CFRelease(items);
return securityError;
}
下面就是初始化Websocket的方法:
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
//这里面就是处理证书认证
SRPinningSecurityPolicy * cer = [[SRPinningSecurityPolicy alloc] init];
self.socket = [[SRWebSocket alloc] initWithURLRequest:request protocols:@[@"这里是子协议参数,后台没有定义子协议参数的话可以用框架提供的其他方式初始化"] securityPolicy:cer];
self.socket.delegate = self; //SRWebSocketDelegate 协议
[self.socketopen]; //开始连接
到这里就已经完成了与后台的双向认证了。
关于双向认证的原理推荐两个博客链接:
https://blog.csdn.net/oldmtn/article/details/52208747
https://blog.csdn.net/liuchunming033/article/details/48467587
--------------------
第一次写,可能写的比较粗糙,忘见谅 -。-!