/*
思路: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日起(最新消息, 實施時間已延期),所有新提交的 App 使用系統(tǒng)組件進行的 HTTP 網(wǎng)絡請求都需要是 HTTPS 加密的煤搜,否則會導致請求失敗而無法通過審核免绿。
HTTPS 的驗證過程有很多資料可以查詢到,但對于在iOS中如何實現(xiàn) HTTPS 驗證卻不是很清楚擦盾,偶爾看到的這篇文章嘲驾,閱讀后收獲不小,分享給大家迹卢。
正文
本文的介紹分為三部分:
- 1辽故,簡要分析下對服務器身份驗證的完整握手過程。
- 2婶希,是證書鏈的驗證。
- 3蓬衡,探索下iOS中原生庫NSURLConnection或NSURLSession如何支持實現(xiàn)https喻杈。
一、關(guān)于HTTPS
HTTPS是承載在TLS/SSL之上的HTTP狰晚,相較于HTTP明文數(shù)據(jù)傳輸方面所暴露出的缺點筒饰,HTTPS具有防止信息被竊聽、篡改壁晒、劫持瓷们,提供信息加密,完整性校驗及身份驗證等優(yōu)勢秒咐。TLS/SSL是安全傳輸層協(xié)議谬晕,介于TCP和HTTP之間。TLS1.0是建立在SSL3.0規(guī)范之上的携取,可以理解為SSL3.0的升級版本攒钳。目前推薦使用的版本是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ù)據(jù)傳輸開始前,通訊雙方進行身份認證实愚、協(xié)商加密算法兼呵、交換加密密鑰等。
- 除了這倆協(xié)議以外爆侣,還存在其它三種輔助協(xié)議: Changecipher spec 協(xié)議用來通知對端從handshake切換到record協(xié)議(有點冗余萍程,在TLS1.3里面已經(jīng)被刪掉了)。alert協(xié)議兔仰,用來通知各種返回碼茫负。application data協(xié)議,就是把http乎赴,smtp等的數(shù)據(jù)流傳入record層做處理并傳輸忍法。
場景分析:
訪問HTTPS://xxx的網(wǎng)站,對于HTTPS而言榕吼,在整個發(fā)送請求返回數(shù)據(jù)過程中饿序,除了發(fā)送請求-服務器響應請求-結(jié)果返回并顯示外,還涉及到通訊雙方證書驗證羹蚣、數(shù)據(jù)加密借嗽、數(shù)據(jù)完整性校驗等。
下面以登錄qq郵箱為例莫矗,通過Wireshark抓包可以看到如下圖:
在瀏覽器與服務器進行Application Data傳輸之前烛占,還經(jīng)歷了Client Hello-Server Hello-Client Key Exchange-Change Cipher Spec等過程。而這些過程正是TLS/SSL提供的服務所決定的:
- 認證服務器身份胁出,確保數(shù)據(jù)發(fā)送到正確的服務器型型;
- 加密數(shù)據(jù)以防止數(shù)據(jù)中途被竊取全蝶;
- 維護數(shù)據(jù)的完整性闹蒜,確保數(shù)據(jù)在傳輸過程中不被改變。
上述單向驗證的完整握手過程抑淫,總結(jié)如下:
-
第一階段:ClientHello
客戶端發(fā)起請求绷落,以明文傳輸請求信息,包含版本信息始苇,加密套件候選列表嘱函,壓縮算法候選列表,隨機數(shù)random_C埂蕊,擴展字段等信息往弓。 -
第二階段:ServerHello-ServerHelloDone
如上圖可以看出這個階段包含4個過程( 有的服務器是單條發(fā)送疏唾,有的是合并一起發(fā)送)。服務端返回協(xié)商的信息結(jié)果函似,包括選擇使用的協(xié)議版本槐脏,選擇的加密套件,選擇的壓縮算法撇寞、隨機數(shù)random_S等顿天,其中隨機數(shù)用于后續(xù)的密鑰協(xié)商。服務器也會配置并返回對應的證書鏈Certificate蔑担,用于身份驗證與密鑰交換牌废。然后會發(fā)送ServerHelloDone信息用于通知服務器信息發(fā)送結(jié)束。 -
第三階段:證書校驗
在上圖中的5-6之間啤握,客戶端這邊還需要對服務器返回的證書進行校驗鸟缕。只有證書驗證通過后,才能進行后續(xù)的通信排抬。(具體分析可參看后續(xù)的證書驗證過程) -
第四階段:ClientKeyExchange-Finished
服務器返回的證書驗證合法后懂从, 客戶端計算產(chǎn)生隨機數(shù)字Pre-master,并用server證書中公鑰加密蹲蒲,發(fā)送給服務器番甩。同時客戶端會根據(jù)已有的三個隨機數(shù)使用相應的算法生成協(xié)商密鑰〗旄椋客戶端會通知服務器后續(xù)的通信都采用協(xié)商的通信密鑰和加密算法進行加密通信缘薛。然后客戶端發(fā)送Finished消息用于通知客戶端信息發(fā)送結(jié)束。 -
第五階段:服務器端生成協(xié)商密鑰
服務器也會根據(jù)已有的三個隨機數(shù)使用相應的算法生成協(xié)商密鑰卡睦,會通知客戶端后續(xù)的通信都采用協(xié)商的通信密鑰和加密算法進行加密通信宴胧。然后發(fā)送Finished消息用于通知服務器信息發(fā)送結(jié)束。 -
第六階段:握手結(jié)束
在握手階段結(jié)束后么翰,客戶端和服務器數(shù)據(jù)傳輸開始使用協(xié)商密鑰進行加密通信牺汤。
總結(jié)
簡單來說辽旋,HTTPS請求整個過程主要分為兩部分:
- 一是握手過程:用于客戶端和服務器驗證雙方身份浩嫌,協(xié)商后續(xù)數(shù)據(jù)傳輸時使用到的密鑰等。
- 二是數(shù)據(jù)傳輸過程:身份驗證通過并協(xié)商好密鑰后补胚,通信雙方使用協(xié)商好的密鑰加密數(shù)據(jù)并進行通信码耐。
在握手過程協(xié)商密鑰時,使用的是非對稱密鑰交換算法溶其, 密鑰交換算法本身非常復雜骚腥,密鑰交換過程涉及到隨機數(shù)生成,模指數(shù)運算瓶逃,空白補齊束铭,加密廓块,簽名等操作。在數(shù)據(jù)傳輸過程中契沫,客戶端和服務器端使用協(xié)商好的密鑰進行對稱加密解密带猴。
二、證書
PKI (Public Key Infrastructure)懈万,公開密鑰基礎(chǔ)設(shè)施拴清。它是一個標準,在這個標準之下發(fā)展出的為了實現(xiàn)安全基礎(chǔ)服務目的的技術(shù)統(tǒng)稱為PKI会通。 權(quán)威的第三方機構(gòu)CA(認證中心)是PKI的核心口予, CA負責核實公鑰的擁有者的信息,并頒發(fā)認證“證書”涕侈,同時能夠為使用者提供證書驗證服務沪停。 x.509是PKI中最重要的標準,它定義了公鑰證書的基本結(jié)構(gòu)驾凶。
證書申請過程
- 證書申請者向頒發(fā)證書的可信第三方CA提交申請證書相關(guān)信息牙甫,包括:申請者域名、申請者生成的公鑰(私鑰自己保存)及證書請求文件.cer等
- CA通過線上调违、線下等多種手段驗證證書申請者提供的信息合法和真實性窟哺。
- 當證書申請者提供的信息審核通過后,CA向證書申請者頒發(fā)證書技肩,證書內(nèi)容包括明文信息和簽名信息且轨。其中明文信息包括證書頒發(fā)機構(gòu)、證書有效期虚婿、域名旋奢、申請者相關(guān)信息及申請者公鑰等,簽名信息是使用CA私鑰進行加密的明文信息然痊。當證書申請者獲取到證書后至朗,可以通過安裝的CA證書中的公鑰對簽名信息進行解密并與明文信息進行對比來驗證簽名的完整性。
證書驗證過程
- 驗證證書本身的合法性(驗證簽名完整性剧浸,驗證證書有效期等)
- 驗證證書頒發(fā)者的合法性(查找頒發(fā)者的證書并檢查其合法性锹引,這個過程是遞歸的)
證書驗證的遞歸過程最終會成功終止,而成功終止的條件是:證書驗證過程中遇到了錨點證書唆香,錨點證書通常指:嵌入到操作系統(tǒng)中的根證書(權(quán)威證書頒發(fā)機構(gòu)頒發(fā)的自簽名證書)嫌变。
證書驗證失敗的原因
- 無法找到證書的頒發(fā)者
- 證書過期
- 驗證過程中遇到了自簽名證書,但該證書不是錨點證書躬它。
- 無法找到錨點證書(即在證書鏈的頂端沒有找到合法的根證書)
- 訪問的server的dns地址和證書中的地址不同
三腾啥、iOS實現(xiàn)支持HTTPS
在OC中當使用NSURLConnection或NSURLSession建立URL并向服務器發(fā)送https請求獲取資源時,服務器會使用HTTP狀態(tài)碼401進行響應(即訪問拒絕)。此時NSURLConnection或NSURLSession會接收到服務器需要授權(quán)的響應倘待,當客戶端授權(quán)通過后疮跑,才能繼續(xù)從服務器獲取數(shù)據(jù)。如下圖所示:
非自建證書驗證實現(xiàn)
在接收到服務器返回的狀態(tài)碼為401的響應后凸舵,
對于NSURLSession而言祸挪,需要代理對象實現(xiàn)URLSession:task:didReceiveChallenge:completionHandler:方法。
對于NSURLConnection而言贞间,需要代理對象實現(xiàn)connection:willSendRequestForAuthenticationChallenge: 方法(OS X v10.7和iOS5及以上)贿条。
對于早期的版本代理對象需要實現(xiàn)代理對象要實現(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)引用了峻仇,采用先定義一個weak變量公黑,然后在block中引用weak對象,避免循環(huán)引用 你會直接想到如下的方式
__weak typeof (self) wself = self;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:wself
selector:@selector(animationTimerDidFired:)
userInfo:nil
repeats:YES];
是不是瞬間覺得完美了摄咆,呵呵凡蚜,我只能說少年,你沒理解兩者之間的區(qū)別吭从。在block中朝蜘,block是對變量進行捕獲,意思是對使用到的變量進行拷貝操作涩金,注意是拷貝的不是對象谱醇,而是變量自身。拿上面的來說步做,block中只是對變量wself拷貝了一份副渴,也就是說,block中也定義了一個weak對象全度,相當于煮剧,在block的內(nèi)存區(qū)域中,定義了一個__weak blockWeak對象,然后執(zhí)行了blockWeak = wself将鸵;注意到了沒勉盅,這里并沒有引起對象的持有量的變化,所以沒有問題,再看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,是要強應用這個變量的 也就是說,大概是這樣的漩符,__strong strongSelf = wself 強引用了一個弱應用的變量一喘,結(jié)果還是強引用,也就是說strongSelf持有了wself所指向的對象(也即是self所只有的對象),這和你直接傳self進來是一樣的效果凸克,并不能達到解除強引用的作用议蟆!看來只能換個思路了,我直接生成一個臨時對象萎战,讓Timer強用用這個臨時對象咐容,在這個臨時對象中弱引用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];
}
//當不能識別方法時候,就會調(diào)用這個方法,在這個方法中,我們可以將不能識別的傳遞給其它對象處理
//由于這里對所有的不能處理的都傳遞給_target了,所以methodSignatureForSelector和forwardInvocation不可能被執(zhí)行的,所以不用再重載了吧
//其實還是需要重載methodSignatureForSelector和forwardInvocation的,為什么呢?因為_target是弱引用的,所以當_target可能釋放了,當它被釋放了的情況下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation沒實現(xiàn)的話,就直接crash了!!!
//這也是為什么這兩個方法中隨便寫的!!!
- (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
使用的時候,將原來的替換為:
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];