zoukankan      html  css  js  c++  java
  • AFN里的https 的AFSecurityPolicy

    本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题。

    HTTPS

    HTTPS 连接建立过程大致是,客户端和服务端建立一个连接,服务端返回一个证书,客户端里存有各个受信任的证书机构根证书,用这些根证书对服务端 返回的证书进行验证,经验证如果证书是可信任的,就生成一个pre-master  secret,用这个证书的公钥加密后发送给服务端,服务端用私钥解密后得到pre-master secret,再根据某种算法生成master  secret,客户端也同样根据这种算法从pre-master secret生成master secret,随后双方的通信都用这个master  secret对传输数据进行加密解密。

    以上是简单过程,中间还有很多细节,详细过程和原理已经有很多文章阐述得很好,就不再复述,推荐一些相关文章:
    关于非对称加密算法的原理:RSA算法原理<一><二>
    关于整个流程:HTTPS那些事<一><二><三>
    关于数字证书:浅析数字证书

    这里说下一开始我比较费解的两个问题:

    1.证书是怎样验证的?怎样保证中间人不能伪造证书?

    首先要知道非对称加密算法的特点,非对称加密有一对公钥私钥,用公钥加密的数据只能通过对应的私钥解密,用私钥加密的数据只能通过对应的公钥解密。

    我们来看最简单的情况:一个证书颁发机构(CA),颁发了一个证书A,服务器用这个证书建立https连接。客户端在信任列表里有这个CA机构的根证书。

    首先CA机构颁发的证书A里包含有证书内容F,以及证书加密内容F1,加密内容F1就是用这个证书机构的私钥对内容F加密的结果。(这中间还有一次hash算法,略过。)

    建 立https连接时,服务端返回证书A给客户端,客户端的系统里的CA机构根证书有这个CA机构的公钥,用这个公钥对证书A的加密内容F1解密得 到F2,跟证书A里内容F对比,若相等就通过验证。整个流程大致是:F->CA私钥加密->F1->客户端CA公钥解密->F。 因为中间人不会有CA机构的私钥,客户端无法通过CA公钥解密,所以伪造的证书肯定无法通过验证。

    2.什么是SSL Pinning?

    可 以理解为证书绑定,是指客户端直接保存服务端的证书,建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书 是真的,不再去系统的信任证书机构里寻找验证。这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架 构的像手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。

    为什么直接对比就能保证证书没问题? 如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?确 实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私 钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。

    为什么要用SSL  Pinning?正常的验证方式不够吗?如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用 户 可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证。

    AFSecurityPolicy

    NSURLConnection 已经封装了https连接的建立、数据的加密解密功能,我们直接使用NSURLConnection是可以访问 https网站的,但NSURLConnection并没有验证证书是否合法,无法避免中间人攻击。要做到真正安全通讯,需要我们手动去验证服务端返回的 证书,AFSecurityPolicy封装了证书验证的过程,让用户可以轻易使用,除了去系统信任CA机构列表验证,还支持SSL  Pinning方式的验证。使用方法:

    1
    2
    3
    4
    5
    6
    7
    //把服务端证书(需要转换成cer格式)放到APP项目资源里,AFSecurityPolicy会自动寻找根目录下所有cer文件
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
    securityPolicy.allowInvalidCertificates = YES;
    [AFHTTPRequestOperationManager manager].securityPolicy = securityPolicy;
    [manager GET:@"https://example.com/" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    }];

    AFSecurityPolicy分三种验证模式:

    AFSSLPinningModeNone

    这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。

    AFSSLPinningModeCertificate

    这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。

    这里还没弄明白第一步的验证是怎么进行的,代码上跟去系统信任机构列表里验证一样调用了SecTrustEvaluate,只是这里的列表换成了客户端保存的那些证书列表。若要验证这个,是否应该把服务端证书的颁发机构根证书也放到客户端里?

    AFSSLPinningModePublicKey

    这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。

    整个AFSecurityPolicy就是实现这这几种验证方式,剩下的就是实现细节了,详见源码。

     
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    // AFSecurity.m
    //
    // Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to deal
    // in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    // THE SOFTWARE.
      
    #import "AFSecurityPolicy.h"
      
    // Equivalent of macro in , without causing compiler warning:
    // "'DebugAssert' is deprecated: first deprecated in OS X 10.8"
    //这两个宏方法用于方便地处理调用各种证书方法过程中出现的错误,出现错误后用goto语句直接跳转到结束语
    //关于为什么要用 __builtin_expect (x, 0) 而不直接用 x != 0,是为了CPU执行时的性能优化,见这里:
    #ifndef AF_Require
    #define AF_Require(assertion, exceptionLabel)                
    do {                                                     
    if (__builtin_expect(!(assertion), 0)) {             
    goto exceptionLabel;                             
    }                                                    
    while (0)
    #endif
      
    #ifndef AF_Require_noErr
    #define AF_Require_noErr(errorCode, exceptionLabel)          
    do {                                                     
    if (__builtin_expect(0 != (errorCode), 0)) {         
    goto exceptionLabel;                             
    }                                                    
    while (0)
    #endif
      
    #if !defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
    static NSData * AFSecKeyGetData(SecKeyRef key) {
        CFDataRef data = NULL;
      
        AF_Require_noErr(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);
      
        return (__bridge_transfer NSData *)data;
      
    _out:
        if (data) {
            CFRelease(data);
        }
      
        return nil;
    }
    #endif
      
    static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
    #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
        return [(__bridge id)key1 isEqual:(__bridge id)key2];
    #else
        return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
    #endif
    }
      
    //从证书里取public key
    static id AFPublicKeyForCertificate(NSData *certificate) {
        id allowedPublicKey = nil;
        SecCertificateRef allowedCertificate;
        SecCertificateRef allowedCertificates[1];
        CFArrayRef tempCertificates = nil;
        SecPolicyRef policy = nil;
        SecTrustRef allowedTrust = nil;
        SecTrustResultType result;
      
        //取证书SecCertificateRef -> 生成证书数组 -> 生成SecTrustRef -> 从SecTrustRef取PublicKey
        allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
        AF_Require(allowedCertificate != NULL, _out);
      
        allowedCertificates[0] = allowedCertificate;
        tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
      
        policy = SecPolicyCreateBasicX509();
        AF_Require_noErr(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
        AF_Require_noErr(SecTrustEvaluate(allowedTrust, &result), _out);
      
        allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
      
    _out:
        if (allowedTrust) {
            CFRelease(allowedTrust);
        }
      
        if (policy) {
            CFRelease(policy);
        }
      
        if (tempCertificates) {
            CFRelease(tempCertificates);
        }
      
        if (allowedCertificate) {
            CFRelease(allowedCertificate);
        }
      
        return allowedPublicKey;
    }
      
    static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
        BOOL isValid = NO;
        SecTrustResultType result;
        AF_Require_noErr(SecTrustEvaluate(serverTrust, &result), _out);
      
        //kSecTrustResultUnspecified:证书通过验证,但用户没有设置这些证书是否被信任
        //kSecTrustResultProceed:证书通过验证,用户有操作设置了证书被信任,例如在弹出的是否信任的alert框中选择always trust
        isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
      
    _out:
        return isValid;
    }
      
    //取出服务端返回的所有证书
    static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
        CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
        NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
      
        for (CFIndex i = 0; i < certificateCount; i++) {
            SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
            [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
        }
      
        return [NSArray arrayWithArray:trustChain];
    }
      
    //取出服务端返回证书里所有的public key
    static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
        SecPolicyRef policy = SecPolicyCreateBasicX509();
        CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
        NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
        for (CFIndex i = 0; i  生成证书数组 -> 生成SecTrustRef -> 从SecTrustRef取PublicKey
            SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
      
            SecCertificateRef someCertificates[] = {certificate};
            CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
      
            SecTrustRef trust;
            AF_Require_noErr(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
      
            SecTrustResultType result;
            AF_Require_noErr(SecTrustEvaluate(trust, &result), _out);
      
            [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
      
        _out:
            if (trust) {
                CFRelease(trust);
            }
      
            if (certificates) {
                CFRelease(certificates);
            }
      
            continue;
        }
        CFRelease(policy);
      
        return [NSArray arrayWithArray:trustChain];
    }
      
    #pragma mark -
      
    @interface AFSecurityPolicy()
    @property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys;
    @end
      
    @implementation AFSecurityPolicy
      
    + (NSArray *)defaultPinnedCertificates {
        //默认证书列表,遍历根目录下所有.cer文件
        static NSArray *_defaultPinnedCertificates = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSBundle *bundle = [NSBundle bundleForClass:[self class]];
            NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
      
            NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]];
            for (NSString *path in paths) {
                NSData *certificateData = [NSData dataWithContentsOfFile:path];
                [certificates addObject:certificateData];
            }
      
            _defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates];
        });
      
        return _defaultPinnedCertificates;
    }
      
    + (instancetype)defaultPolicy {
        AFSecurityPolicy *securityPolicy = [[self alloc] init];
        securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
      
        return securityPolicy;
    }
      
    + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
        AFSecurityPolicy *securityPolicy = [[self alloc] init];
        securityPolicy.SSLPinningMode = pinningMode;
        securityPolicy.validatesDomainName = YES;
        [securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]];
      
        return securityPolicy;
    }
      
    - (id)init {
        self = [super init];
        if (!self) {
            return nil;
        }
      
        self.validatesCertificateChain = YES;
      
        return self;
    }
      
    #pragma mark -
      
    - (void)setPinnedCertificates:(NSArray *)pinnedCertificates {
        _pinnedCertificates = pinnedCertificates;
      
        if (self.pinnedCertificates) {
            //预先取出public key,用于AFSSLPinningModePublicKey方式的验证
            NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]];
            for (NSData *certificate in self.pinnedCertificates) {
                id publicKey = AFPublicKeyForCertificate(certificate);
                if (!publicKey) {
                    continue;
                }
                [mutablePinnedPublicKeys addObject:publicKey];
            }
            self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys];
        else {
            self.pinnedPublicKeys = nil;
        }
    }
      
    #pragma mark -
      
    - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust {
        return [self evaluateServerTrust:serverTrust forDomain:nil];
    }
      
    - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                      forDomain:(NSString *)domain
    {
        NSMutableArray *policies = [NSMutableArray array];
        if (self.validatesDomainName) {
            [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
        else {
            [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
        }
      
        SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
      
        //向系统内置的根证书验证服务端返回的证书是否合法
        //若使用自签名证书,这里的验证是会不合法的,需要设allowInvalidCertificates = YES
        if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
            return NO;
        }
      
        //取出服务端返回的证书
        NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
        switch (self.SSLPinningMode) {
            case AFSSLPinningModeNone:
                //两种情况走到这里,
                //一是通过系统证书验证,返回认证成功
                //二是没通过验证,但allowInvalidCertificates = YES,也就是说完全不认证直接返回认证成功
                return YES;
      
                //验证整个证书
            case AFSSLPinningModeCertificate: {
                NSMutableArray *pinnedCertificates = [NSMutableArray array];
                for (NSData *certificateData in self.pinnedCertificates) {
                    [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
                }
                //在本地证书里验证服务端返回的证书的有效性
                SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
                if (!AFServerTrustIsValid(serverTrust)) {
                    return NO;
                }
      
                if (!self.validatesCertificateChain) {
                    return YES;
                }
      
                //整个证书链都跟本地的证书匹配才给过
                NSUInteger trustedCertificateCount = 0;
                for (NSData *trustChainCertificate in serverCertificates) {
                    if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                        trustedCertificateCount++;
                    }
                }
      
                return trustedCertificateCount == [serverCertificates count];
            }
      
                //只验证证书的public key
            case AFSSLPinningModePublicKey: {
                NSUInteger trustedPublicKeyCount = 0;
                NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
      
                //如果不用验证整个证书链,取第一个也就是真正会使用的那个证书验证就行
                if (!self.validatesCertificateChain && [publicKeys count] > 0) {
                    publicKeys = @[[publicKeys firstObject]];
                }
      
                //在本地证书里搜索相等的public key,记录找到个数
                for (id trustChainPublicKey in publicKeys) {
                    for (id pinnedPublicKey in self.pinnedPublicKeys) {
                        if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                            trustedPublicKeyCount += 1;
                        }
                    }
                }
      
                //验证整个证书链的情况:每个public key都在本地找到算验证通过
                //验证单个证书的情况:找到一个算验证通过
                return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain && trustedPublicKeyCount == [serverCertificates count]) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));
            }
        }
      
        return NO;
    }
      
    #pragma mark - NSKeyValueObserving
      
    + (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
        return [NSSet setWithObject:@"pinnedCertificates"];
    }
      
    @end
     
     
  • 相关阅读:
    后序非递归遍历二叉树的应用
    关于驰骋工作流程引擎,工作流程管理系统演示与学习环境发布的通知。
    驰骋工作流程引擎,ccflow,如何把子线程的数据汇总到合流节点表单中去?
    关于取消ccflow abc 级别用户与开放表单设计器源代码的通知
    驰骋工作流程引擎问题解答,武汉朋友。
    ccform 单据打印的规则调整与新增功能发布说明
    发几个傻瓜表单设计器预览图片,以方便大家学习.
    关于工作流程管理系统中的现有版本自由表单设计器的停止升级与新版本将要发布的声明.
    ccflow向流程开始节点表单传输数据方法大全
    利用开源的驰骋工作流程引擎,处理的集团公司流程应用案例之一.
  • 原文地址:https://www.cnblogs.com/canghaixiaoyuer/p/4738453.html
Copyright © 2011-2022 走看看