第一次寫簡(jiǎn)書,記錄一下最近開發(fā)的項(xiàng)目關(guān)于使用websocket遇到的問題。
因?yàn)楹笈_(tái)使用的是Websocket協(xié)議,所以我就在GitHub上面找到了用戶量比較多Objecttive-C的框架,F(xiàn)acebook開源的SocketRocket,具體Websocket與Socket的區(qū)別自行百度,網(wǎng)上資料還是比較多的。
(簡(jiǎn)單介紹:WebSocket同HTTP一樣也是應(yīng)用層的協(xié)議,但是它是一種雙向通信協(xié)議,是建立在TCP之上,Socket其實(shí)并不是一個(gè)協(xié)議,而是為了方便使用TCP或UDP而抽象出來的一層,是位于應(yīng)用層和傳輸控制層之間的一組接口)
此處省略一火車的文字...直接說處理方式,希望對(duì)跟我遇到一樣的問題的人有幫助。
從GitHub下載代碼,pod安裝代碼比較舊,所以下載后將SocketRocket文件夾手動(dòng)拖入工程。
需要添加 libicucore.A.tdb 庫。<方法:點(diǎn)擊工程--->Build Phases --->Link Binary With Libraries >
可能會(huì)有一些頭文件的報(bào)未找到文件的錯(cuò),將import <>改成import "" 即可。
一、證書準(zhǔn)備工作
證書需要:服務(wù)器提供三個(gè)證書文件分別為: CA根證書(必須是der編碼格式,后綴有cer、der等,可用OpenSSL做轉(zhuǎn)換)、公鑰、私鑰 ,其中<公鑰、私鑰 使用OpenSSL合并成一個(gè)p12文件,合并的時(shí)候會(huì)提示設(shè)置密碼,需記住密碼,代碼里面會(huì)使用>,下面是我的轉(zhuǎn)換方法,因?yàn)榉?wù)器給的是pem格式,所以具體格式具體自行搜索轉(zhuǎn)換方法:
假設(shè)后臺(tái)給的三個(gè)證書命名為以下:
根證書命名為:CAcert.pem
公鑰證書命名為:client-cert.pem
私鑰證書命名為:client-key.pem
1、將CA根證書pem格式轉(zhuǎn)成der格式:(以下命令都在終端中執(zhí)行,需要cd到存放證書的目錄下)
openssl x509 -inform PEM -outform DER -in CAcert.pem -out CAcert.der
2、將公鑰與私鑰合并成一個(gè) p12 ,合成時(shí)需要記住設(shè)置的密碼
openssl?pkcs12?-export?-clcerts?-in client-cert.pem?-inkey client-key.pem?-out client.p12
OK,到這里證書的準(zhǔn)備工作已經(jīng)完成,后面就是導(dǎo)入工程,在項(xiàng)目里面使用了。
二、將證書導(dǎo)入工程,然后代碼使用
使用子類 SRPinningSecurityPolicy 重寫 父類SRSecurityPolicy 的updateSecurityOptionsInStream方法,注:SRPinningSecurityPolicy 和 SRSecurityPolicy 類都是SocketRocket框架里面的文件,作者提供子類SRPinningSecurityPolicy給開發(fā)者重寫處理證書相關(guān)的操作,如下
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];
}
//分割線內(nèi)的方法可以抽出來寫到類里面,簡(jiǎn)書不知道怎么傳文件,所以把代碼貼出來了。
//思路:例如抽到類SocketSSLSetting,可以將defaultSetting寫成類方法
//+?(nonnullNSDictionary?*)defaultSetting,在SRPinningSecurityPolicy使用的時(shí)候?qū)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文件的時(shí)候設(shè)置的密碼
? ? 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]];
//這里面就是處理證書認(rèn)證
SRPinningSecurityPolicy * cer = [[SRPinningSecurityPolicy alloc] init];
self.socket = [[SRWebSocket alloc] initWithURLRequest:request protocols:@[@"這里是子協(xié)議參數(shù),后臺(tái)沒有定義子協(xié)議參數(shù)的話可以用框架提供的其他方式初始化"] securityPolicy:cer];
self.socket.delegate = self;? //SRWebSocketDelegate 協(xié)議
[self.socketopen];? ? ? ? ? ? //開始連接
到這里就已經(jīng)完成了與后臺(tái)的雙向認(rèn)證了。
關(guān)于雙向認(rèn)證的原理推薦兩個(gè)博客鏈接:
https://blog.csdn.net/oldmtn/article/details/52208747
https://blog.csdn.net/liuchunming033/article/details/48467587
--------------------
第一次寫,可能寫的比較粗糙,忘見諒 -。-!