iOS 安全性参考点

2018-01-23  本文已影响578人  sheepcao

iOS本地数据存储安全

本地存储的方式主要有:Userdefault, 沙盒文件(包括DB文件),Archieve和Keychain。其中,

NSString *multiHomePath = [NSHomeDirectory() stringByAppendingPathComponent:@"multi.archiver"];  
NSMutableData *data = [[NSMutableData alloc]init];  
NSKeyedArchiver *archvier = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];  
  
[archvier encodeCGPoint:point forKey:@"kPoint"];  
[archvier finishEncoding];  
[data writeToFile:multiHomePath atomically:YES];  

以上这些Plist和沙盒路径文件都很容易在越狱设备甚至未越狱设备上被提取导致信息泄露。

这里写图片描述

其中,数字身份=证书+密钥。不同类型的加密属性支持不同的属性类型,如internet password支持设置domainserver等属性。

iOS APP的Keychain数据是存储在应用沙箱外面的,各APP的keychain数据内容为逻辑隔离,由系统进程securityd实施访问控制。为了在多个APP间能够共享Keychain信息,Apple引入了Keychain访问组概念:拥有相同Keychain访问组标识符的应用,可以共享Keychain数据。

随着iOS引入了数据保护机制,存储在Keychain中的数据被另一层与用户密码(passcode)相关联的加密机制保护着。数据保护加密密钥(保护类密钥-protection class keys)是由一个设备硬件密钥和一个由用户密码衍生的密钥共同产生的。可以通过向Keychain接口的SecItemAdd或SecItemUpdate方法的kSecAttrAccessible属性提供一个可访问常量来启用Keychain的数据保护。该可访问常量值决定一个Keychain条目在何时可被应用访问,同时也决定某个Keychain条目是否允许移动到另一台设备上。
以下是Keychain条目的数据保护可访问常量列表:
(1)kSecAttrAccessibleWhenUnlocked:Keychain条目只能在设备被解锁后被访问,此时用于解密Keychain条目的数据保护类密钥只在设备被解锁后才会被加载到内存中,并且当设备锁定后,加密密钥将在10s钟内自动清除。
(2)kSecAttrAccessibleAfterFirstUnlock:Keychain条目可以在设备第一次解锁到重启过程中被访问,此时用于解密Keychain条目的数据保护类密钥只有在用户重启并解锁设备后才会被加载到内存中,并且该密钥将一直保留在内存中,直到下一次设备重启。
(3)kSecAttrAccessibleAlways:Keychain条目即使在设备锁定时也可被访问,此时用于解密Keychain条目的数据保护类密钥一直被加载在内存中。
(4)kSecAttrAccessibleWhenUnlockedThisDeviceOnly:Keychain条目只能在设备被解锁后被访问,并且无法在设备间移动。
(5)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly:Keychain条目可在设备第一次解锁后访问,但无法在设备间移动
(6)kSecAttrAccessibleAlwaysThisDeviceOnly:Keychain条目即使在设备锁定时也可被访问,但无法再设备将移动
Keychain条目的数据保护可访问常量被映射到各Keychain表(genp、inet..)中的pdmn列(protection domain)。下表显示了keychain数据保护可访问常量和pdmn值之间的映射关系。

Keychain_dumper工具可用于导出越狱iOS设备上的所有Keychain条目,所以keychain存储也不是绝对安全的。

综合上述,如果需要本地存储敏感数据,可参考以下原则:

网络通信安全

HTTPS通信链路的安全

https是在http通信协议上加了一层SSL/TLS安全握手过程,以保证通信双方的合法认证保证数据以密文形式传输。本文不赘述SSL的具体过程及原理,以下是iOS端处理SSL安全认证的常见方法:
1.确认server端返回的证书是CA授权的合法证书。

SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
static BOOL serverTrustIsVaild(SecTrustRef trust) {
   BOOL allowConnection = NO;

// 假设验证结果是无效的
SecTrustResultType trustResult = kSecTrustResultInvalid;

// 函数的内部递归地从叶节点证书到根证书的验证
OSStatus statue = SecTrustEvaluate(trust, &trustResult);

    if (statue == noErr) {
    // kSecTrustResultUnspecified: 系统隐式地信任这个证书
    // kSecTrustResultProceed: 用户加入自己的信任锚点,显式地告诉系统这个证书是值得信任的

    allowConnection = (trustResult == kSecTrustResultProceed 
                                || trustResult == kSecTrustResultUnspecified);
    }
    return allowConnection;
}

2.验证域名
可以通过以下的代码获得当前的验证策略:

CFArrayRef policiesRef;
SecTrustCopyPolicies(trust, &policiesRef);

打印 policiesRef 后,你会发现默认的验证策略就包含了域名验证。如果想自定义域名验证过程,可参考:

    NSString *serverHostname = challenge.protectionSpace.host;
    CFRetain(serverTrust);
    
    SecPolicyRef SslPolicy = SecPolicyCreateSSL(YES, (__bridge CFStringRef)(serverHostname));
    SecTrustSetPolicies(serverTrust, SslPolicy);
    CFRelease(SslPolicy);
    serverTrustIsVaild(serverTrust)

或者取消域名验证

NSMutableArray *policies = [NSMutableArray array];
    
// BasicX509 不验证域名是否相同
SecPolicyRef policy = SecPolicyCreateBasicX509();
[policies addObject:(__bridge_transfer id)policy];
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
serverTrustIsVaild(serverTrust)

3.验证证书的确实是自己信任的证书。
App可以将证书文件存于bundle中,也可以将证书内的一些需要验证的value存于代码中,如:PublicKey。

      SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, 0);
      NSData* serverCertificateData = (NSData* )SecCertificateCopyData(serverCertificate);
      NSString *serverCertPublicKey = (NSString* )SecCertificateCopyPublicKey(serverCertificate);//通过SecCertificateCopyPublicKey等一系列方法可以取出证书中的所有信息,以备进行比对。

注意使用:

    CFIndex certCount = SecTrustGetCertificateCount(trust);

去获取证书链上的每一级证书,必要时分别进行验证。

4.自签名的证书链验证(optional)
假设你的服务器返回:[你的自签名的根证书] -- [你的二级证书] -- [你的客户端证书],系统是不信任这个三个证书的。
所以你在验证的时候需要将这三个的其中一个设置为锚点证书,当然,多个也行。

比如将 [你的二级证书] 作为锚点后,SecTrustEvaluate() 函数只要验证到 [你的客户端证书] 确实是由 [你的二级证书] 签署的,那么验证结果为 kSecTrustResultUnspecified,表明了 [你的客户端证书] 是可信的。下面是设置锚点证书的做法:

NSMutableArray *certificates = [NSMutableArray array];

NSDate *cerData = /* 在 App Bundle 中你用来做锚点的证书数据,证书是 CER 编码的,常见扩展名有:cer, crt...*/

SecCertificateRef cerRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)cerData);

[certificates addObject:(__bridge_transfer id)cerRef];

// 设置锚点证书。
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)certificates);
serverTrustIsVaild(serverTrust)

只调用 SecTrustSetAnchorCertificates () 这个函数的话,那么就只有作为参数被传入的证书作为锚点证书,连系统本身信任的 CA 证书不能作为锚点验证证书链。要想恢复系统中 CA 证书作为锚点的功能,还要再调用下面这个函数:

// true 代表仅被传入的证书作为锚点,false 允许系统 CA 证书也作为锚点
SecTrustSetAnchorCertificatesOnly(trust, false);

传输数据的安全

原则只有一个,不管是https还是http,不要明文传输敏感数据,防止数据泄露。并且要充分验证传输数据的完整性,保证数据不被篡改。


        NSURLCredential *defaultCredential = [NSURLCredential credentialWithUser:@"username" password:@"password" persistence:NSURLCredentialPersistenceForSession];
        NSURL *APIURL = [NSURL URLWithString:@"www.test.com"];
        NSString *host = [APIURL host];
        NSInteger port = [APIURL port];
        NSString *scheme = [APIURL scheme];
        NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:scheme realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
        NSURLCredentialStorage *credentials = [NSURLCredentialStorage sharedCredentialStorage];
        [credentials setDefaultCredential:defaultCredential forProtectionSpace:protectionSpace];
        
        NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
        [sessionConfiguration setURLCredentialStorage:credentials];
        //sessionConfiguration.HTTPAdditionalHeaders =@{@"Authorization":[NSString stringWithFormat:@"Basic %@=%@",@"username",@"password"]};
        //不需要在手动设置header
        NSURLSession *sesson = [NSURLSession sessionWithConfiguration:sessionConfiguration];

应用快照缓存

当一个应用在后台被挂起时,iOS会生成一个当前屏幕的快照,当应用被重新唤起时,可以快速还原该APP之前的内容,以提高用户的使用体验。
然而,这样做也可能会导致一些应用数据的泄露。应用快照保存在“/var/mobile/Containers/Data/Application/XXXXXXX-XXXXXXXXX-XXXXXXXXX/Library/Caches/Snapshots/”目录下。恶意APP可通过读取该文件并发送至远程服务端,从而获得其快照内容信息。

措施

要防止这种信息泄露途径,屏幕内容就必须在iOS系统进行屏幕快照之前进行隐藏、模糊化或覆盖其他图片处理,而iOS系统也提供了许多回调方法来提示程序将被挂起。例如以下两个方法:

[UIApplication sharedApplication].keyWindow.hidden=YES;

方案二:在两个挂起回调内,在window上覆盖一个新图片或对当前界面模糊化处理。
获取当前视图的参考代码:


 UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    if (window.windowLevel != UIWindowLevelNormal) {
        NSArray *windows = [[UIApplication sharedApplication] windows];
        for(UIWindow *tmpWin in windows) {
            if (tmpWin.windowLevel == UIWindowLevelNormal) {
                window = tmpWin;
                break;
            }
        }
    }
[window addSubview:blurView];

键盘缓存

为提供自动填充和纠正的功能,iOS系统的自带键盘会缓存用户的输入信息。其会保存一个接近600个单词的列表,存放在“Library/Keyboard/ en_GB-dynamic-text.dat”或“/private/var/mobile/Library/Keyboard/dynamic-text.dat”文件中(iOS版本不同,位置及文件名会略有不同),该文件存储的是明文信息,有存入敏感信息的可能性。

措施

1, 首先,对于以下位置或方式的输入,iOS不会对其输入内容进行缓存:

2, 可以在不需要缓存的文本框处禁用自动纠正功能。如下代码所示:

UITextField *textField = [[UITextField alloc] initWithFrame: frame ]; 
textField.autocorrectionType = UITextAutocorrectionTypeNo;

3, 输入框也可以被标记为密码输入类型,使得输入变得更加安全,防止缓存。如:

textField.secureTextEntry = YES;

4,在所有敏感信息输入处均使用自定义键盘(尤其是银行App和Hybird App)
下面是一段自定义键盘收起和展开的事件响应:

@interface WQSafeKeyboard : UIWindow  

@property (nonatomic, weak, setter = focusOnTextFiled:) UITextField *textFiled;  
+ (WQSafeKeyboard *)deploySafeKeyboard;  
@end  

@interface WQSafeKeyboard()  

@property (nonatomic, strong)WQInterKeyboard *keyboard;  
@end  

@implementation WQSafeKeyboard  

+ (WQSafeKeyboard *)deploySafeKeyboard  
{  
    WQSafeKeyboard *kb = [[WQSafeKeyboard alloc]init];  
    [kb addObserver];  
    return kb;  
}  

- (instancetype)init  
{  
    if (self = [super init]) {  
        self.windowLevel = UIWindowLevelAlert;  
        self.frame = CGRectZero;  
        self.rootViewController = self.keyboard;  
    }  
    return self;  
}  

- (void)dealloc  
{  
    [[NSNotificationCenter defaultCenter] removeObserver:self];  
}  

- (WQInterKeyboard *)keyboard  
{  
    if (!_keyboard) {  
        _keyboard = [[WQInterKeyboard alloc]init];  
    }  
    return _keyboard;  
}  

- (void)focusOnTextFiled:(UITextField *)textFiled  
{  
    _textFiled = textFiled;  
    self.keyboard.textField = _textFiled;  
}  

- (void)addObserver  
{  
    [[NSNotificationCenter defaultCenter]addObserver:self  
                                            selector:@selector(keyboardWillShow:)  
                                                name:UIKeyboardWillShowNotification  
                                              object:nil];  
    [[NSNotificationCenter defaultCenter]addObserver:self  
                                            selector:@selector(keyboardWillHide:)  
                                                name:UIKeyboardWillHideNotification  
                                              object:nil];  
}  

- (void)keyboardWillShow:(NSNotification *)notification  
{  
    if (![self.textFiled isFirstResponder]) {  
        return;  
    }  
    [self keyboardAnimationWithNotification:notification];  
}  

- (void)keyboardWillHide:(NSNotification *)notification  
{  
    if (![self.textFiled isFirstResponder]) {  
        return;  
    }  
    [self keyboardAnimationWithNotification:notification];  
}  

- (void)keyboardAnimationWithNotification:(NSNotification *)notification  
{  
    [self makeKeyAndVisible];  
    NSDictionary *userInfo = [notification userInfo];  
    CGRect kbFrame_end,kbFrame_begin;  
    NSTimeInterval animationDuration;  
    UIViewAnimationCurve animationCurve;  
    [userInfo[UIKeyboardFrameEndUserInfoKey] getValue:&kbFrame_end];  
    [userInfo[UIKeyboardFrameBeginUserInfoKey] getValue:&kbFrame_begin];  
    [userInfo[UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];  
    [userInfo[UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];  

    self.frame = [self resizeFrameToAdjust:kbFrame_begin];  
    [UIView animateWithDuration:animationDuration  
                          delay:0  
                        options:(animationCurve<<16)  
                     animations:^{  
                         self.frame = [self resizeFrameToAdjust:kbFrame_end];  
                     }completion:^(BOOL finished) {  

                     }];  
    if ([notification.name isEqualToString:UIKeyboardWillHideNotification]) {  
        [self resignKeyWindow];  
    }  
}  

- (CGRect)resizeFrameToAdjust:(CGRect)frame  
{  
    if ([[UIApplication sharedApplication] isStatusBarHidden] )  
        return frame;  

    if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {  
        frame = CGRectMake(frame.origin.x,  
                           frame.origin.y - STATUSBAR_HEIGHT,  
                           frame.size.width,  
                           frame.size.height);  
    }  
    return frame;  
}  

@end

参考:
iOS本地数据存储安全
iOS 中对 HTTPS 证书链的验证

上一篇 下一篇

猜你喜欢

热点阅读