沙盒
-
.app捆绑目录(.app bundle),Xcode最终构建出来并复制到设备上的包。内容全部经过数字签名,无法修改,包含开发者Resource目录。要修改作为捆绑安装的文件,需要先将它复制到其他地方,通常是Library中。
-
Document,用户可见数据,可以通过设置info.plist的UIFileSharingEnable,将文件可以通过共享出现在桌面上。
-
Library,不能被用户直接看到的文件,会自动备份,可以通过NSURL setResourceValue:forKey:error:方法为文件加上NSURLIsExcluedFromBackupKey.
- Library/Cache,不会被备份,但会在应用升级中保留下来,可以将不想复制到桌面的大部分内容保存在这里。
-
temp,不会备份,也不会在升级的过程中保留。系统可能在程序不运行时删除temp。
iTunes对如何处理和这四个顶级目录同级的其他文件并没有明确说明。说以为了更好的组织,新建的文件应该放在这四个顶级目录下
公钥和私钥
-
服务器生成公钥和私钥,并把公钥公布给客户端
-
客户端通过公钥对数据进行加密,并发送给服务器,服务器拿到数据后通过私钥解密获取信息, 外界即便截获数据,但没有对应的私钥进行解密,那么就无法读取信息。
-
服务器 数字签名:用hash函数生成摘要,并用私钥进行加密。
-
客户端通过私钥对数字签名进行解密,验证服务器。对信息使用hash函数,与摘要信息对比,判断信息是否被修改。
-
数字证书:服务器公钥+证书中心私钥共同生成数字证书,同数字签名一块发送给客户端。客户端通过证书中心公钥对数字证书解密获取服务器公钥。
购买商业证书来加密应用和服务器之间的网络协议并不能提高安全性。商业证书只对由浏览器或其他不可控软件访问的网站有用。自行生成证书并将证书放在应用中甚至比商业证书还要安全。
检验证书有效性
证书包含有关其所含公钥的大量元数据,公钥只是一个很大的数字,自身并没有意义,元数据赋予了数字的实际含义。有效性指的是证书包含的元数据一致且适用请求。
元数据最重要的就是主题(subject),主题就是完全限制域名,有效性的第一步就是做名称匹配。有些服务器带的主题是类似 *.example.com的通配符证书,iOS会接收这些证书,证书使用的是字符串匹配,因此在某些情况下还是需要做处理,比如通过IP地址连接服务器。
/*
通过IP地址连接 encrypted.google.com 服务器,我们收到的证书主题为 *.google.com,下面代码可以接收所有主题名称含google.com的所有受信任证书。
需要加入 Security.framework
*/
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSURLProtectionSpace *protSpace = challenge.protectionSpace;
SecTrustRef trust = protSpace.serverTrust;
SecTrustResultType result = kSecTrustResultFatalTrustFailure;
// 通过 SecTrustEvaluate 得到一个可恢复错误
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess && result == kSecTrustResultRecoverableTrustFailure) {
//证书无效,但开发人员可以接受,可能已过期、不匹配、缺乏可信度(自签证书)
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, 0);
CFStringRef subject = SecCertificateCopySubjectSummary(cert);
NSLog(@"Trying to access %@. Got %@. ",protSpace.host,subject);
// 判断证书主题是否足够接近
CFRange range = CFStringFind(subject, CFSTR(".google.com"), kCFCompareAnchored||kCFCompareBackwards);
if (range.location != kCFNotFound) {
// 将证书当做一个简单的 X.509证书来重新测试,而不是当做SSL握手的一部分(这种情况在测试时一般会忽略主机名)
status = RNSecTrustEvaluateAsX509(trust, &result);
}
CFRelease(subject);
}
if (status == errSecSuccess) {
switch (result) {
case kSecTrustResultInvalid:// 验证过程无法完成,很可能是开发人员代码有问题
case kSecTrustResultDeny:// 证书有效,但用户明确拒绝
case kSecTrustResultRecoverableTrustFailure:
case kSecTrustResultFatalTrustFailure://证书有问题或已损坏
case kSecTrustResultOtherError:// 验证过程无法完成,可能是苹果问题
NSLog(@"failing due to result :%u",result);
[challenge.sender cancelAuthenticationChallenge:challenge];
break;
case kSecTrustResultProceed://证书有效,用户明确接受
//证书有效,但用户没有明确的拒绝或接受,开发人员接受就可以了
case kSecTrustResultUnspecified:{
NSLog(@"successing with result :%u",result);
NSURLCredential *cred;
cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}
break;
default:
NSAssert(NO, @"unexpected result from trust evalution:%u",result);
break;
}
}else {
NSLog(@"Complete Failure with code %d",(int)status);
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
/// 复制并创建一个新的 trust,使用更简单的X.509策略,只检查有效性和可信度,不会像原来的SSL握手一样验证主机名
/// @param trust
/// @param result
static OSStatus RNSecTrustEvaluateAsX509(SecTrustRef trust,SecTrustResultType *result){
OSStatus status = errSecSuccess;
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef newTrust;
CFIndex numOfCerts = SecTrustGetCertificateCount(trust);
NSMutableArray *certs = [NSMutableArray array];
for (int i = 0; i < numOfCerts; i++) {
SecCertificateRef cert;
cert = SecTrustGetCertificateAtIndex(trust, i);
[certs addObject:(__bridge id _Nonnull)(cert)];
}
status = SecTrustCreateWithCertificates((__bridge CFTypeRef _Nonnull)(certs), policy, &newTrust);
if (status == errSecSuccess) {
status = SecTrustEvaluate(newTrust, result);
}
CFRelease(policy);
CFRelease(newTrust);
return status;
}
// 证书如果因过期而无效,可以通过SecTrustSetVerifyDate指定一个任意时间来测试该证书
#pragma mark- NSURLConnectionDelegate
//- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// // 可以决定是否通过该服务器的认证,如果需要通过,则需要提供凭据(credential)
// SecTrustRef trust = challenge.protectionSpace.serverTrust;
// NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
// // 连接到持有非损坏证书的任何服务器来进行验证,不管证书是否有效或受信任
// [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
//}
判断证书的可信度
测试:可以通过钥匙串创建一个自签名的根证书要想证书受信任,它必须是由trust对象的锚证书列表中的某个证书签名。
锚证书
指系统明确信任的证书。证书是自行生成的,你可以将公钥添加到应用中,并配置trust对象只接受该证书或由它签发的证书。
将该证书添加到钥匙串中,再在钥匙串中选中并拖到桌面导出,该文件只包含公钥。默认情况下,钥匙串不会导出私钥。将这个公钥拖入到工程中。测试收到的证书是由你的证书签名的:
// NSURLAuthenticationChallenge *)challenge
NSURLProtectionSpace *protSpace = challenge.protectionSpace;
SecTrustRef trust = protSpace.serverTrust; // 需要验证的 trust对象
NSError *error;
NSString *path = [[NSBundle mainBundle] pathForResource:@"MyCert" ofType:@"cer"];
NSData *cerData = [NSData dataWithContentsOfFile:path options:0 error:&error];
SecCertificateRef certificate;
certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)cerData);
NSArray *certs = @[(__bridge id)certificate];
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)certs);
CFRelease(certificate);
文件保护
NSData *data;
NSString *path;
NSError *writeError;
// 创建一个受保护的文件
[data writeToFile:path options:NSDataWritingFileProtectionComplete error:&writeError] || [data writeToFile:path options:NSDataWritingFileProtectionCompleteUnlessOpen error:&writeError];
/*
NSDataWritingFileProtectionNone:未受保护,可随时读写
NSDataWritingFileProtectionComplete:最高级别保护,锁屏10s后开启,会导致后台运行时无法读写文件
NSDataWritingFileProtectionCompleteUnlessOpen:锁屏10s后开启保护,除非文件处于打开状态,如果处于打开状态允许继续访问
NSDataWritingFileProtectionCompleteUntilFirstUserAuthentication:设备启动、用户首次解锁该设备时受保护,之后文件就不受保护了,直到设备重启
*/
/// 提高文件保护级别
/// @param dir <#dir description#>
/// @param error <#error description#>
- (void)upgradeFilesInDirectory:(NSString*)dir error:(NSError **)error {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:dir];
for (NSString *path in dirEnum) {
// 目录下的文件
NSDictionary *attrs = [dirEnum fileAttributes];
// 如果不是最高保护级别,那么重新设置保护级别
if (![[attrs objectForKey:NSFileProtectionKey] isEqual:NSFileProtectionComplete]) {
// 如果有别的属性,注意迁移
attrs = @{NSFileProtectionKey:NSFileProtectionComplete};
[fileManager setAttributes:attrs ofItemAtPath:path error:error];
}
}
}
// 判断受保护数据是否可用
#pragma mark- UIApplicationDelegate
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
}
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
}
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(fileProtectChange) name:UIApplicationProtectedDataDidBecomeAvailable object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(fileProtectChange) name:UIApplicationProtectedDataWillBecomeUnavailable object:nil];
[[UIApplication sharedApplication] isProtectedDataAvailable];
访问组
跨应用凭据共享方案
// 目标:应用B 通过访问组获取 应用A存储的凭据(如密码)
// 1.应用A中存凭据
/// 在运行时获取应用前缀,确保前缀有效
- (NSString*)bundleSeedID {
// 创建一个钥匙串条目
NSDictionary *query = @{
(__bridge id)kSecClass:(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount:@"BundleSeedIDQuery",
(__bridge id)kSecAttrService:@"",
(__bridge id)kSecReturnAttributes:(id)kCFBooleanTrue
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFTypeRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound) {
status = SecItemAdd((__bridge CFTypeRef)query, (CFTypeRef *)&result);
}
if (status != errSecSuccess) {
return nil;
}
// 检查它被分配到了哪个访问组
NSString *accessGroup = CFDictionaryGetValue(result, kSecAttrAccessGroup);
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
NSString *bundleSeedID = components.firstObject;
CFRelease(result);
return bundleSeedID;
}
// 存
NSString *accessgroup = [NSString stringWithFormat:@"%@.%@",[self bundleSeedID],@"One"];
[SGKeychain setPassword:@"pass" username:@"name" serviceName:@"service" accessGroup:accessgroup updateExisting:YES error:nil];
// B应用中,配置keychain Share
// Signing&Capabilities 添加 Keychain Sharing,加入应用A的访问组
// 取
NSString *accessgroup = [NSString stringWithFormat:@"%@.%@",[self bundleSeedID],@"One"];
NSString *password = [SGKeychain passwordForUsername:@"name" serviceName:@"service" accessGroup:accessgroup error:nil];
网友评论