/*
思路: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 使用系统组件进行的 HTTP 网络请求都需要是 HTTPS 加密的,否则会导致请求失败而无法通过审核。
HTTPS 的验证过程有很多资料可以查询到,但对于在iOS中如何实现 HTTPS 验证却不是很清楚,偶尔看到的这篇文章,阅读后收获不小,分享给大家。
正文
本文的介绍分为三部分:
- 1,简要分析下对服务器身份验证的完整握手过程。
- 2,是证书链的验证。
- 3,探索下iOS中原生库NSURLConnection或NSURLSession如何支持实现https。
一、关于HTTPS
HTTPS是承载在TLS/SSL之上的HTTP,相较于HTTP明文数据传输方面所暴露出的缺点,HTTPS具有防止信息被窃听、篡改、劫持,提供信息加密,完整性校验及身份验证等优势。TLS/SSL是安全传输层协议,介于TCP和HTTP之间。TLS1.0是建立在SSL3.0规范之上的,可以理解为SSL3.0的升级版本。目前推荐使用的版本是TLS1.2。
TLS/SSL协议通常分为两层:TLS记录协议(TLS Record Protocol)和TLS握手协议(TLS Handshake Protocol)。
- TLS记录协议建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
- TLS握手协议建立在记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
- 除了这俩协议以外,还存在其它三种辅助协议: Changecipher spec 协议用来通知对端从handshake切换到record协议(有点冗余,在TLS1.3里面已经被删掉了)。alert协议,用来通知各种返回码。application data协议,就是把http,smtp等的数据流传入record层做处理并传输。
场景分析:
访问HTTPS://xxx的网站,对于HTTPS而言,在整个发送请求返回数据过程中,除了发送请求-服务器响应请求-结果返回并显示外,还涉及到通讯双方证书验证、数据加密、数据完整性校验等。
下面以登录qq邮箱为例,通过Wireshark抓包可以看到如下图:
在浏览器与服务器进行Application Data传输之前,还经历了Client Hello-Server Hello-Client Key Exchange-Change Cipher Spec等过程。而这些过程正是TLS/SSL提供的服务所决定的:
- 认证服务器身份,确保数据发送到正确的服务器;
- 加密数据以防止数据中途被窃取;
- 维护数据的完整性,确保数据在传输过程中不被改变。
上述单向验证的完整握手过程,总结如下:
-
第一阶段:ClientHello
客户端发起请求,以明文传输请求信息,包含版本信息,加密套件候选列表,压缩算法候选列表,随机数random_C,扩展字段等信息。 -
第二阶段:ServerHello-ServerHelloDone
如上图可以看出这个阶段包含4个过程( 有的服务器是单条发送,有的是合并一起发送)。服务端返回协商的信息结果,包括选择使用的协议版本,选择的加密套件,选择的压缩算法、随机数random_S等,其中随机数用于后续的密钥协商。服务器也会配置并返回对应的证书链Certificate,用于身份验证与密钥交换。然后会发送ServerHelloDone信息用于通知服务器信息发送结束。 -
第三阶段:证书校验
在上图中的5-6之间,客户端这边还需要对服务器返回的证书进行校验。只有证书验证通过后,才能进行后续的通信。(具体分析可参看后续的证书验证过程) -
第四阶段:ClientKeyExchange-Finished
服务器返回的证书验证合法后, 客户端计算产生随机数字Pre-master,并用server证书中公钥加密,发送给服务器。同时客户端会根据已有的三个随机数使用相应的算法生成协商密钥。客户端会通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信。然后客户端发送Finished消息用于通知客户端信息发送结束。 -
第五阶段:服务器端生成协商密钥
服务器也会根据已有的三个随机数使用相应的算法生成协商密钥,会通知客户端后续的通信都采用协商的通信密钥和加密算法进行加密通信。然后发送Finished消息用于通知服务器信息发送结束。 -
第六阶段:握手结束
在握手阶段结束后,客户端和服务器数据传输开始使用协商密钥进行加密通信。
总结
简单来说,HTTPS请求整个过程主要分为两部分:
- 一是握手过程:用于客户端和服务器验证双方身份,协商后续数据传输时使用到的密钥等。
- 二是数据传输过程:身份验证通过并协商好密钥后,通信双方使用协商好的密钥加密数据并进行通信。
在握手过程协商密钥时,使用的是非对称密钥交换算法, 密钥交换算法本身非常复杂,密钥交换过程涉及到随机数生成,模指数运算,空白补齐,加密,签名等操作。在数据传输过程中,客户端和服务器端使用协商好的密钥进行对称加密解密。
二、证书
PKI (Public Key Infrastructure),公开密钥基础设施。它是一个标准,在这个标准之下发展出的为了实现安全基础服务目的的技术统称为PKI。 权威的第三方机构CA(认证中心)是PKI的核心, CA负责核实公钥的拥有者的信息,并颁发认证“证书”,同时能够为使用者提供证书验证服务。 x.509是PKI中最重要的标准,它定义了公钥证书的基本结构。
证书申请过程
- 证书申请者向颁发证书的可信第三方CA提交申请证书相关信息,包括:申请者域名、申请者生成的公钥(私钥自己保存)及证书请求文件.cer等
- CA通过线上、线下等多种手段验证证书申请者提供的信息合法和真实性。
- 当证书申请者提供的信息审核通过后,CA向证书申请者颁发证书,证书内容包括明文信息和签名信息。其中明文信息包括证书颁发机构、证书有效期、域名、申请者相关信息及申请者公钥等,签名信息是使用CA私钥进行加密的明文信息。当证书申请者获取到证书后,可以通过安装的CA证书中的公钥对签名信息进行解密并与明文信息进行对比来验证签名的完整性。
证书验证过程
- 验证证书本身的合法性(验证签名完整性,验证证书有效期等)
- 验证证书颁发者的合法性(查找颁发者的证书并检查其合法性,这个过程是递归的)
证书验证的递归过程最终会成功终止,而成功终止的条件是:证书验证过程中遇到了锚点证书,锚点证书通常指:嵌入到操作系统中的根证书(权威证书颁发机构颁发的自签名证书)。
证书验证失败的原因
- 无法找到证书的颁发者
- 证书过期
- 验证过程中遇到了自签名证书,但该证书不是锚点证书。
- 无法找到锚点证书(即在证书链的顶端没有找到合法的根证书)
- 访问的server的dns地址和证书中的地址不同
三、iOS实现支持HTTPS
在OC中当使用NSURLConnection或NSURLSession建立URL并向服务器发送https请求获取资源时,服务器会使用HTTP状态码401进行响应(即访问拒绝)。此时NSURLConnection或NSURLSession会接收到服务器需要授权的响应,当客户端授权通过后,才能继续从服务器获取数据。如下图所示:
非自建证书验证实现
在接收到服务器返回的状态码为401的响应后,
对于NSURLSession而言,需要代理对象实现URLSession:task:didReceiveChallenge:completionHandler:方法。
对于NSURLConnection而言,需要代理对象实现connection:willSendRequestForAuthenticationChallenge: 方法(OS X v10.7和iOS5及以上)。
对于早期的版本代理对象需要实现代理对象要实现connection:canAuthenticateAgainstProtectionSpace:和connection:didReceiveAuthenticationChallenge:方法。
代码如下:
@property (nonatomic, copy) void (^ myblock)(NSInteger i);
__weak typeof (self) weakSelf = self;
self.myblock = ^(NSInteger i){
[weakSelf view];
};
在其中,我们需要在block中引用self,如果直接引用,也是循环引用了,采用先定义一个weak变量,然后在block中引用weak对象,避免循环引用 你会直接想到如下的方式
__weak typeof (self) wself = self;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:wself
selector:@selector(animationTimerDidFired:)
userInfo:nil
repeats:YES];
是不是瞬间觉得完美了,呵呵,我只能说少年,你没理解两者之间的区别。在block中,block是对变量进行捕获,意思是对使用到的变量进行拷贝操作,注意是拷贝的不是对象,而是变量自身。拿上面的来说,block中只是对变量wself拷贝了一份,也就是说,block中也定义了一个weak对象,相当于,在block的内存区域中,定义了一个__weak blockWeak对象,然后执行了blockWeak = wself;注意到了没,这里并没有引起对象的持有量的变化,所以没有问题,再看timer的方式,虽然你是将wself传入了timer的构造方法中,我们可以查看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 强引用了一个弱应用的变量,结果还是强引用,也就是说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];
}
//当不能识别方法时候,就会调用这个方法,在这个方法中,我们可以将不能识别的传递给其它对象处理
//由于这里对所有的不能处理的都传递给_target了,所以methodSignatureForSelector和forwardInvocation不可能被执行的,所以不用再重载了吧
//其实还是需要重载methodSignatureForSelector和forwardInvocation的,为什么呢?因为_target是弱引用的,所以当_target可能释放了,当它被释放了的情况下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation没实现的话,就直接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方式来解决循环引用
@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];
网友评论