(原創(chuàng)內(nèi)容,轉(zhuǎn)載請(qǐng)注明出處)
本文將通過創(chuàng)建SSL/TLS連接到認(rèn)證的順序來(lái)一一講解SSL API的作用和實(shí)現(xiàn)捆昏。 (TLS是基于SSL的升級(jí)江掩,提升了SSL算法,也可以理解為TLS1.0 = SSL 3.0,下文中通用SSL來(lái)稱呼SSL/TLS)缎除。SSL原理網(wǎng)上一大堆,自行Google总寻。
創(chuàng)建SSL上下文
有SSL連接就需要SSLContext會(huì)話上下文來(lái)做基石器罐。一個(gè)上下文不能重復(fù)用于多個(gè)會(huì)話。
創(chuàng)建
SSLCreateContext(CFAllocatorRef __nullable alloc, SSLProtocolSide protocolSide, SSLConnectionType connectionType)
我們來(lái)看這個(gè)函數(shù)每個(gè)參數(shù)的具體意義:
CFAllocatorRef用于標(biāo)識(shí)初始化方式渐行。
SSLProtocolSide用來(lái)標(biāo)識(shí)這個(gè)SSL協(xié)議用于服務(wù)端還是客戶端轰坊。
SSLConnectionType用來(lái)標(biāo)識(shí)該上下文是基于流還是基于數(shù)據(jù)的通信,kSSLStreamType用于TCP通信祟印,kSSLDatagramType用于UDP通信肴沫。
創(chuàng)建好SSLContext之后,需要設(shè)置它的IO數(shù)據(jù)輸入輸出函數(shù)蕴忆,此函數(shù)也屬于配置SSL Session:
SSLSetIOFuncs (SSLContextRef context, SSLReadFunc readFunc, SSLWriteFunc writeFunc)
一個(gè)輸入輸出的回調(diào)函數(shù)應(yīng)該是這個(gè)樣子:
(*SSLReadFunc)(SSLConnectionRef connection, void *data, size_t *dataLength);
其中connection一般用來(lái)傳遞當(dāng)前使用SSLContext的對(duì)象颤芬,此函數(shù)收到數(shù)據(jù)的時(shí)候,便能通過connection橋接的self來(lái)把數(shù)據(jù)發(fā)送給self。
關(guān)聯(lián)
怎么設(shè)置connection站蝠?這樣:
SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self);
設(shè)置SSL相關(guān)參數(shù)
配置SSL Session
初始化和關(guān)聯(lián)處理數(shù)據(jù)的對(duì)象關(guān)聯(lián)起來(lái)之后汰具,就該為SSL設(shè)置一些常用參數(shù)了!下面這個(gè)方法就是給SSL填充參數(shù)選項(xiàng)了:
SSLSetSessionOption(SSLContextRef context, SSLSessionOption option, Boolean value)
我們來(lái)看看SSLSessionOption都有些什么內(nèi)容菱魔!這些選項(xiàng)決定了SSLHandShake什么時(shí)候會(huì)返回一個(gè)狀態(tài)值留荔。所以,這些參數(shù)是很重要的澜倦!
typedef CF_ENUM(int, SSLSessionOption) {
// 服務(wù)器端認(rèn)證客戶端完成(OSStatus = errSSLServerAuthCompleted)聚蝶,客戶端此時(shí)可以通過手動(dòng)驗(yàn)證服務(wù)端證書來(lái)決定是否繼續(xù)SSL連接
//如果需要手動(dòng)驗(yàn)證服務(wù)器證書,就把這個(gè)選項(xiàng)設(shè)置為YES
kSSLSessionOptionBreakOnServerAuth = 0,
// 服務(wù)端要求客戶端提供證書(OSStatus = errSSLClientCertRequested)
kSSLSessionOptionBreakOnCertRequested = 1,
//客戶端已經(jīng)提供證書藻治,是否允許服務(wù)端對(duì)其進(jìn)行驗(yàn)證
kSSLSessionOptionBreakOnClientAuth = 2,
//如果開啟此選項(xiàng)碘勉,在協(xié)商好充足的密碼套件(cipher-suite)的前提下,才會(huì)開始一個(gè)非正確的SSL連接
kSSLSessionOptionFalseStart = 3,
//是否啟動(dòng)應(yīng)對(duì)BEAST攻擊的(1/n-1)記錄拆分栋艳,開啟后恰聘,TLS1.0會(huì)對(duì)cipher執(zhí)行記錄拆分
kSSLSessionOptionSendOneByteRecord = 4,
//在SSL連接進(jìn)行重新協(xié)商的時(shí)候,是否允許服務(wù)器標(biāo)識(shí)發(fā)生改變吸占。為了避免三重握手攻擊晴叨,默認(rèn)是禁止的
kSSLSessionOptionAllowServerIdentityChange = 5,
//開啟此選項(xiàng)后,SSL連接將使用低版本協(xié)議進(jìn)行連接失敗后的重試
kSSLSessionOptionFallback = 6,
//握手第一步(clientHello)消息時(shí)進(jìn)行打斷以便檢查SNI(Service Node Interface:不同域名使用不同證書)矾屯,該選項(xiàng)一般用在服務(wù)端
kSSLSessionOptionBreakOnClientHello = 7,
//是否重新協(xié)商密碼套件兼蕊,默認(rèn)是不允許的
kSSLSessionOptionAllowRenegotiation = 8,
};
以上選項(xiàng)會(huì)決定SSLHandShake什么時(shí)候會(huì)返回對(duì)應(yīng)握手步驟的狀態(tài)值,光是有這些件蚕,是不夠的孙技,我們還需要設(shè)置一些別的參數(shù)!Let‘s go排作!
SLSetClientSideAuthenticate(SSLContextRef context, SSLAuthenticate auth);
這個(gè)函數(shù)用在服務(wù)端(非必須)牵啦,用于設(shè)定客戶端驗(yàn)證標(biāo)識(shí)。默認(rèn)值是kNeverAuthenticate妄痪,既默認(rèn)不需要客戶端提供證書哈雏。
SSLSetProtocolVersionMin(SSLContextRef context, SSLProtocol minVersion)
SSLSetProtocolVersionMin函數(shù)用來(lái)設(shè)定支持的SSL協(xié)議最小版本
SSLSetProtocolVersionMax(SSLContextRef context, SSLProtocol maxVersion)
這個(gè)函數(shù)用來(lái)設(shè)定支持的SSL協(xié)議最大版本。 設(shè)置最大或者最小的協(xié)議版本號(hào)衫生,合法的版本號(hào)有這么幾個(gè):kSSLProtocol3(SSL v3.0)裳瘪、kTLSProtocol1(TLS v1.0)、kTLSProtocol11(TLS v1.1)罪针、kTLSProtocol12(TLS v1.2)以及kDTLSProtocol1(DTLS v1.0)
SSLSetPeerID(SSLContextRef context, const void *peerID, size_t peerIDLen);
SSLSetPeerID設(shè)置一個(gè)c字符串的data來(lái)作為當(dāng)前會(huì)話的唯一標(biāo)識(shí)符彭羹,比如IP地址+端口,在SSLHandShake握手之前是可選設(shè)置的泪酱。但是要想成功恢復(fù)某個(gè)會(huì)話時(shí)派殷,這個(gè)設(shè)置則是必須的还最,嘗試恢復(fù)SSL會(huì)話的時(shí)候會(huì)檢查上一個(gè)會(huì)話是否使用的是相同的peerID。
SSLSetPeerDomainName(SSLContextRef context, const char *peerName, size_t peerNameLen);
SSLSetPeerDomainName這個(gè)函數(shù)用來(lái)驗(yàn)證另一端的證書通用名稱字段毡惜,比如可以設(shè)置成“store.apple.com”憋活,如果證書中的通用名稱與設(shè)置的PeerDomainName不匹配時(shí),則會(huì)握手失敗虱黄,返回errSSLXCertChainInvalid狀態(tài)值。此功能是可選的吮成,并且只能在會(huì)話沒有開始時(shí)設(shè)置橱乱。(蘋果官方很推薦設(shè)置這個(gè)參數(shù)~)
SSLSetEnabledCiphers(SSLContextRef context,const SSLCipherSuite *ciphers, size_t numCiphers)
SetEnabledCiphers用來(lái)指定SSL會(huì)話使用的密碼套件,會(huì)話活躍的時(shí)候是不能設(shè)置的粱甫!如果要查詢會(huì)話正在使用的密碼套件泳叠,可以調(diào)用函數(shù)SSLGetEnabledCiphers來(lái)查詢到。
SSLSetCertificate(SSLContextRef context, CFArrayRef certRefs);
SSLSetCertificate這個(gè)函數(shù)很重要2柘NH摇!設(shè)置客戶端證書是可選的乌庶,如果服務(wù)端要求客戶端提供證書种蝶,則必須調(diào)用此函數(shù)設(shè)置好證書,設(shè)置證書的函數(shù)必須在SSLHandShake握手開始前或者返回errSSLClientCertRequested后調(diào)用瞒大。這個(gè)證書會(huì)在當(dāng)前會(huì)話有效螃征!certRefs數(shù)組必須在[0]位中放置一個(gè)SecIdentityRef對(duì)象,該對(duì)象標(biāo)識(shí)葉證書及其相應(yīng)的私鑰透敌。
還有很多很多的配置函數(shù)盯滚,不便一一列出。具體可以查看和官方文檔酗电。
接下來(lái)就是重頭戲了魄藕!
握手
都配置好了,就需要執(zhí)行SSL握手了撵术!執(zhí)行握手的方法如下:
OSStatus SSLHandshake(SSLContextRef context)
結(jié)果將會(huì)返回給OSStatus背率!一些比較常見的狀態(tài)值有這些:
errSSLUnknownRootCert(對(duì)方證書鏈的根證書未知)
errSSLCertExpired(證書過期)
errSSLXCertChainInvalid(另一端的證書鏈無(wú)效或者沒有證書)
errSSLClientCertRequested:(服務(wù)器需要客戶端提供證書,客戶端可以選擇檢查服務(wù)器的證書和可分辨名稱列表荷荤,然后根據(jù)驗(yàn)證結(jié)果來(lái)決定是否繼續(xù)握手退渗。如果之前沒有設(shè)置本地證書,則調(diào)用SSLSetCertificate設(shè)置證書蕴纳,然后再次調(diào)用SSLHandshake來(lái)恢復(fù)握手)
errSSLWouldBlock的返回值表示SSLHandshake必須再次調(diào)用(并且一次又一次地会油,直到返回其他的東西)
握手過程中,如果要手動(dòng)驗(yàn)證另一端證書古毛,在對(duì)方已經(jīng)發(fā)送證書的前提下可以通過SSLCopyPeerTrust和SSLCopyPeerCertificates來(lái)得到對(duì)方的證書了翻翩! 驗(yàn)證完對(duì)方證書并且通過后都许,再次調(diào)用SSLHandShake()來(lái)繼續(xù)剛才的握手。 當(dāng)SSLHandShake返回noErr時(shí)嫂冻,就是握手成功完成啦胶征!
讀寫
握手成功以后,就可以通過
SSLWrite(SSLContextRef context, const void * _nullable data, sizet dataLength, size_t*processed)
來(lái)進(jìn)行數(shù)據(jù)寫入了桨仿。 數(shù)據(jù)讀取則是這個(gè)方法:
SSLRead(SSLContextRef context, const void * _nullable data, sizet dataLength, size_t *processed)
還有獲取緩存的數(shù)據(jù)等方法睛低,已經(jīng)不屬于建立SSL連接的范疇,此處不再贅述服傍。 比如:
SSLGetBufferedReadSize