
/*
思路:Time: O(logN),最壞O(N)
二分排除法
mid比右邊大,排除mid本身和mid左邊的部分
mid比右邊小,排除mid右邊的部分
最后返回mid
*/
class Solution {
func findMin(_ nums: [Int]) -> Int {
let numsCount = nums.count
var left = 0
var right = numsCount - 1
while left <= right
{
if (left == right) {
return nums[left]
}
let mid = left + (right - left) / 2
if nums[mid] > nums[right] {
left = mid + 1
}
else {
right = mid
}
}
return nums[left]
}
}
fds
在 WWDC 16 中,Apple 表示, 從 2017年1月1日起(最新消息, 實(shí)施時(shí)間已延期),所有新提交的 App 使用系統(tǒng)組件進(jìn)行的 HTTP 網(wǎng)絡(luò)請(qǐng)求都需要是 HTTPS 加密的,否則會(huì)導(dǎo)致請(qǐng)求失敗而無法通過審核。
HTTPS 的驗(yàn)證過程有很多資料可以查詢到,但對(duì)于在iOS中如何實(shí)現(xiàn) HTTPS 驗(yàn)證卻不是很清楚,偶爾看到的這篇文章,閱讀后收獲不小,分享給大家。
正文
本文的介紹分為三部分:
- 1,簡要分析下對(duì)服務(wù)器身份驗(yàn)證的完整握手過程。
- 2,是證書鏈的驗(yàn)證。
- 3,探索下iOS中原生庫NSURLConnection或NSURLSession如何支持實(shí)現(xiàn)https。
一、關(guān)于HTTPS
HTTPS是承載在TLS/SSL之上的HTTP,相較于HTTP明文數(shù)據(jù)傳輸方面所暴露出的缺點(diǎn),HTTPS具有防止信息被竊聽、篡改、劫持,提供信息加密,完整性校驗(yàn)及身份驗(yàn)證等優(yōu)勢(shì)。TLS/SSL是安全傳輸層協(xié)議,介于TCP和HTTP之間。TLS1.0是建立在SSL3.0規(guī)范之上的,可以理解為SSL3.0的升級(jí)版本。目前推薦使用的版本是TLS1.2。
TLS/SSL協(xié)議通常分為兩層:TLS記錄協(xié)議(TLS Record Protocol)和TLS握手協(xié)議(TLS Handshake Protocol)。
- TLS記錄協(xié)議建立在可靠的傳輸協(xié)議(如TCP)之上,為高層協(xié)議提供數(shù)據(jù)封裝、壓縮、加密等基本功能的支持。
- TLS握手協(xié)議建立在記錄協(xié)議之上,用于在實(shí)際的數(shù)據(jù)傳輸開始前,通訊雙方進(jìn)行身份認(rèn)證、協(xié)商加密算法、交換加密密鑰等。
- 除了這倆協(xié)議以外,還存在其它三種輔助協(xié)議: Changecipher spec 協(xié)議用來通知對(duì)端從handshake切換到record協(xié)議(有點(diǎn)冗余,在TLS1.3里面已經(jīng)被刪掉了)。alert協(xié)議,用來通知各種返回碼。application data協(xié)議,就是把http,smtp等的數(shù)據(jù)流傳入record層做處理并傳輸。
場景分析:
訪問HTTPS://xxx的網(wǎng)站,對(duì)于HTTPS而言,在整個(gè)發(fā)送請(qǐng)求返回?cái)?shù)據(jù)過程中,除了發(fā)送請(qǐng)求-服務(wù)器響應(yīng)請(qǐng)求-結(jié)果返回并顯示外,還涉及到通訊雙方證書驗(yàn)證、數(shù)據(jù)加密、數(shù)據(jù)完整性校驗(yàn)等。
下面以登錄qq郵箱為例,通過Wireshark抓包可以看到如下圖:

在瀏覽器與服務(wù)器進(jìn)行Application Data傳輸之前,還經(jīng)歷了Client Hello-Server Hello-Client Key Exchange-Change Cipher Spec等過程。而這些過程正是TLS/SSL提供的服務(wù)所決定的:
- 認(rèn)證服務(wù)器身份,確保數(shù)據(jù)發(fā)送到正確的服務(wù)器;
- 加密數(shù)據(jù)以防止數(shù)據(jù)中途被竊?。?/li>
- 維護(hù)數(shù)據(jù)的完整性,確保數(shù)據(jù)在傳輸過程中不被改變。
上述單向驗(yàn)證的完整握手過程,總結(jié)如下:

-
第一階段:ClientHello
客戶端發(fā)起請(qǐng)求,以明文傳輸請(qǐng)求信息,包含版本信息,加密套件候選列表,壓縮算法候選列表,隨機(jī)數(shù)random_C,擴(kuò)展字段等信息。 -
第二階段:ServerHello-ServerHelloDone
如上圖可以看出這個(gè)階段包含4個(gè)過程( 有的服務(wù)器是單條發(fā)送,有的是合并一起發(fā)送)。服務(wù)端返回協(xié)商的信息結(jié)果,包括選擇使用的協(xié)議版本,選擇的加密套件,選擇的壓縮算法、隨機(jī)數(shù)random_S等,其中隨機(jī)數(shù)用于后續(xù)的密鑰協(xié)商。服務(wù)器也會(huì)配置并返回對(duì)應(yīng)的證書鏈Certificate,用于身份驗(yàn)證與密鑰交換。然后會(huì)發(fā)送ServerHelloDone信息用于通知服務(wù)器信息發(fā)送結(jié)束。 -
第三階段:證書校驗(yàn)
在上圖中的5-6之間,客戶端這邊還需要對(duì)服務(wù)器返回的證書進(jìn)行校驗(yàn)。只有證書驗(yàn)證通過后,才能進(jìn)行后續(xù)的通信。(具體分析可參看后續(xù)的證書驗(yàn)證過程) -
第四階段:ClientKeyExchange-Finished
服務(wù)器返回的證書驗(yàn)證合法后, 客戶端計(jì)算產(chǎn)生隨機(jī)數(shù)字Pre-master,并用server證書中公鑰加密,發(fā)送給服務(wù)器。同時(shí)客戶端會(huì)根據(jù)已有的三個(gè)隨機(jī)數(shù)使用相應(yīng)的算法生成協(xié)商密鑰??蛻舳藭?huì)通知服務(wù)器后續(xù)的通信都采用協(xié)商的通信密鑰和加密算法進(jìn)行加密通信。然后客戶端發(fā)送Finished消息用于通知客戶端信息發(fā)送結(jié)束。 -
第五階段:服務(wù)器端生成協(xié)商密鑰
服務(wù)器也會(huì)根據(jù)已有的三個(gè)隨機(jī)數(shù)使用相應(yīng)的算法生成協(xié)商密鑰,會(huì)通知客戶端后續(xù)的通信都采用協(xié)商的通信密鑰和加密算法進(jìn)行加密通信。然后發(fā)送Finished消息用于通知服務(wù)器信息發(fā)送結(jié)束。 -
第六階段:握手結(jié)束
在握手階段結(jié)束后,客戶端和服務(wù)器數(shù)據(jù)傳輸開始使用協(xié)商密鑰進(jìn)行加密通信。
總結(jié)
簡單來說,HTTPS請(qǐng)求整個(gè)過程主要分為兩部分:
- 一是握手過程:用于客戶端和服務(wù)器驗(yàn)證雙方身份,協(xié)商后續(xù)數(shù)據(jù)傳輸時(shí)使用到的密鑰等。
- 二是數(shù)據(jù)傳輸過程:身份驗(yàn)證通過并協(xié)商好密鑰后,通信雙方使用協(xié)商好的密鑰加密數(shù)據(jù)并進(jìn)行通信。
在握手過程協(xié)商密鑰時(shí),使用的是非對(duì)稱密鑰交換算法, 密鑰交換算法本身非常復(fù)雜,密鑰交換過程涉及到隨機(jī)數(shù)生成,模指數(shù)運(yùn)算,空白補(bǔ)齊,加密,簽名等操作。在數(shù)據(jù)傳輸過程中,客戶端和服務(wù)器端使用協(xié)商好的密鑰進(jìn)行對(duì)稱加密解密。
二、證書
PKI (Public Key Infrastructure),公開密鑰基礎(chǔ)設(shè)施。它是一個(gè)標(biāo)準(zhǔn),在這個(gè)標(biāo)準(zhǔn)之下發(fā)展出的為了實(shí)現(xiàn)安全基礎(chǔ)服務(wù)目的的技術(shù)統(tǒng)稱為PKI。 權(quán)威的第三方機(jī)構(gòu)CA(認(rèn)證中心)是PKI的核心, CA負(fù)責(zé)核實(shí)公鑰的擁有者的信息,并頒發(fā)認(rèn)證“證書”,同時(shí)能夠?yàn)槭褂谜咛峁┳C書驗(yàn)證服務(wù)。 x.509是PKI中最重要的標(biāo)準(zhǔn),它定義了公鑰證書的基本結(jié)構(gòu)。
證書申請(qǐng)過程
- 證書申請(qǐng)者向頒發(fā)證書的可信第三方CA提交申請(qǐng)證書相關(guān)信息,包括:申請(qǐng)者域名、申請(qǐng)者生成的公鑰(私鑰自己保存)及證書請(qǐng)求文件.cer等
- CA通過線上、線下等多種手段驗(yàn)證證書申請(qǐng)者提供的信息合法和真實(shí)性。
- 當(dāng)證書申請(qǐng)者提供的信息審核通過后,CA向證書申請(qǐng)者頒發(fā)證書,證書內(nèi)容包括明文信息和簽名信息。其中明文信息包括證書頒發(fā)機(jī)構(gòu)、證書有效期、域名、申請(qǐng)者相關(guān)信息及申請(qǐng)者公鑰等,簽名信息是使用CA私鑰進(jìn)行加密的明文信息。當(dāng)證書申請(qǐng)者獲取到證書后,可以通過安裝的CA證書中的公鑰對(duì)簽名信息進(jìn)行解密并與明文信息進(jìn)行對(duì)比來驗(yàn)證簽名的完整性。
證書驗(yàn)證過程
- 驗(yàn)證證書本身的合法性(驗(yàn)證簽名完整性,驗(yàn)證證書有效期等)
- 驗(yàn)證證書頒發(fā)者的合法性(查找頒發(fā)者的證書并檢查其合法性,這個(gè)過程是遞歸的)
證書驗(yàn)證的遞歸過程最終會(huì)成功終止,而成功終止的條件是:證書驗(yàn)證過程中遇到了錨點(diǎn)證書,錨點(diǎn)證書通常指:嵌入到操作系統(tǒng)中的根證書(權(quán)威證書頒發(fā)機(jī)構(gòu)頒發(fā)的自簽名證書)。
證書驗(yàn)證失敗的原因
- 無法找到證書的頒發(fā)者
- 證書過期
- 驗(yàn)證過程中遇到了自簽名證書,但該證書不是錨點(diǎn)證書。
- 無法找到錨點(diǎn)證書(即在證書鏈的頂端沒有找到合法的根證書)
- 訪問的server的dns地址和證書中的地址不同
三、iOS實(shí)現(xiàn)支持HTTPS
在OC中當(dāng)使用NSURLConnection或NSURLSession建立URL并向服務(wù)器發(fā)送https請(qǐng)求獲取資源時(shí),服務(wù)器會(huì)使用HTTP狀態(tài)碼401進(jìn)行響應(yīng)(即訪問拒絕)。此時(shí)NSURLConnection或NSURLSession會(huì)接收到服務(wù)器需要授權(quán)的響應(yīng),當(dāng)客戶端授權(quán)通過后,才能繼續(xù)從服務(wù)器獲取數(shù)據(jù)。如下圖所示:

非自建證書驗(yàn)證實(shí)現(xiàn)
在接收到服務(wù)器返回的狀態(tài)碼為401的響應(yīng)后,
對(duì)于NSURLSession而言,需要代理對(duì)象實(shí)現(xiàn)URLSession:task:didReceiveChallenge:completionHandler:方法。
對(duì)于NSURLConnection而言,需要代理對(duì)象實(shí)現(xiàn)connection:willSendRequestForAuthenticationChallenge: 方法(OS X v10.7和iOS5及以上)。
對(duì)于早期的版本代理對(duì)象需要實(shí)現(xiàn)代理對(duì)象要實(shí)現(xiàn)connection:canAuthenticateAgainstProtectionSpace:和connection:didReceiveAuthenticationChallenge:方法。
代碼如下:
@property (nonatomic, copy) void (^ myblock)(NSInteger i);
__weak typeof (self) weakSelf = self;
self.myblock = ^(NSInteger i){
[weakSelf view];
};
在其中,我們需要在block中引用self,如果直接引用,也是循環(huán)引用了,采用先定義一個(gè)weak變量,然后在block中引用weak對(duì)象,避免循環(huán)引用 你會(huì)直接想到如下的方式
__weak typeof (self) wself = self;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:wself
selector:@selector(animationTimerDidFired:)
userInfo:nil
repeats:YES];
是不是瞬間覺得完美了,呵呵,我只能說少年,你沒理解兩者之間的區(qū)別。在block中,block是對(duì)變量進(jìn)行捕獲,意思是對(duì)使用到的變量進(jìn)行拷貝操作,注意是拷貝的不是對(duì)象,而是變量自身。拿上面的來說,block中只是對(duì)變量wself拷貝了一份,也就是說,block中也定義了一個(gè)weak對(duì)象,相當(dāng)于,在block的內(nèi)存區(qū)域中,定義了一個(gè)__weak blockWeak對(duì)象,然后執(zhí)行了blockWeak = wself;注意到了沒,這里并沒有引起對(duì)象的持有量的變化,所以沒有問題,再看timer的方式,雖然你是將wself傳入了timer的構(gòu)造方法中,我們可以查看NSTimer的
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
定義,其target的說明The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated,是要強(qiáng)應(yīng)用這個(gè)變量的 也就是說,大概是這樣的,__strong strongSelf = wself 強(qiáng)引用了一個(gè)弱應(yīng)用的變量,結(jié)果還是強(qiáng)引用,也就是說strongSelf持有了wself所指向的對(duì)象(也即是self所只有的對(duì)象),這和你直接傳self進(jìn)來是一樣的效果,并不能達(dá)到解除強(qiáng)引用的作用!看來只能換個(gè)思路了,我直接生成一個(gè)臨時(shí)對(duì)象,讓Timer強(qiáng)用用這個(gè)臨時(shí)對(duì)象,在這個(gè)臨時(shí)對(duì)象中弱引用self
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
//當(dāng)不能識(shí)別方法時(shí)候,就會(huì)調(diào)用這個(gè)方法,在這個(gè)方法中,我們可以將不能識(shí)別的傳遞給其它對(duì)象處理
//由于這里對(duì)所有的不能處理的都傳遞給_target了,所以methodSignatureForSelector和forwardInvocation不可能被執(zhí)行的,所以不用再重載了吧
//其實(shí)還是需要重載methodSignatureForSelector和forwardInvocation的,為什么呢?因?yàn)開target是弱引用的,所以當(dāng)_target可能釋放了,當(dāng)它被釋放了的情況下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation沒實(shí)現(xiàn)的話,就直接crash了!!!
//這也是為什么這兩個(gè)方法中隨便寫的!!!
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
使用的時(shí)候,將原來的替換為:
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:[YYWeakProxy proxyWithTarget:self ]
selector:@selector(animationTimerDidFired:)
userInfo:nil
repeats:YES];
block方式來解決循環(huán)引用
@interface NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
block:(void(^)())block
repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:timeInterval
target:self
selector:@selector(jq_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)jq_blockInvoke:(NSTimer *)timer{
void(^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
__weak typeof(self)weakSelf = self;
_timer = [NSTimer jq_scheduledTimerWithTimeInterval:self.autoScrollInterval
block:^{
__strong typeof(self)strongSelf = weakSelf;
[strongSelf timerAutoScroll:nil];
}
repeats:YES];