我想在用户访问网站之前在钥匙串中安装/保存证书。 我有一个HTTPS服务器,我的应用程序在访问https://mysite之前对用户进行了身份验证。 有没有办法可以通过钥匙串中的邮政请求安装/保存证书。 要么 我将该证书(文件)复制到资源包以标记它是受信任的。
感谢
人
答案 0 :(得分:14)
获得服务器证书格式后,您可以尝试以下代码:
+ (void) addCertToKeychain:(NSData*)certInDer
{
OSStatus err = noErr;
SecCertificateRef cert;
cert = SecCertificateCreateWithData(NULL, (CFDataRef) certInDer);
assert(cert != NULL);
CFTypeRef result;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassCertificate, kSecClass,
cert, kSecValueRef,
nil];
err = SecItemAdd((CFDictionaryRef)dict, &result);
assert(err == noErr || err == errSecDuplicateItem);
CFRelease(cert);
}
它会将证书添加到您应用程序的钥匙串沙箱中,即其他任何应用程序都不会信任您的证书。
答案 1 :(得分:7)
您有两种选择:将服务器的证书添加到钥匙串或手动执行验证。无论您采用何种方法,都需要在应用中包含DER编码的X.509公共证书。在下面的示例中,它名为“ios-trusted-cert.der”)并使用它创建SecCertificateRef。 (如果服务器的证书是根证书颁发机构链的一部分,则应安装根证书颁发机构而不是服务器的证书。)
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSData *iosTrustedCertDerData =
[NSData dataWithContentsOfFile:[bundle pathForResource:@"ios-trusted-cert"
ofType:@"der"]];
SecCertificateRef certificate =
SecCertificateCreateWithData(NULL,
(CFDataRef) iosTrustedCertDerData);
请记住,SecCertificateCreateWithData遵循内存所有权的创建规则,因此当您不再需要它时,必须CFRelease它以避免内存泄漏。
接下来,您可以将证书添加到应用的钥匙串中。当您希望iOS为您创建的每个新套接字信任您的证书时,这是合适的。
- (void) useKeychain: (SecCertificateRef) certificate {
OSStatus err =
SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(id) kSecClassCertificate, kSecClass,
certificate, kSecValueRef,
nil],
NULL);
if ((err == noErr) || // success!
(err == errSecDuplicateItem)) { // the cert was already added. Success!
// create your socket normally.
// This is oversimplified. Refer to the CFNetwork Guide for more details.
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)@"localhost",
8443,
&readStream,
&writeStream);
CFReadStreamSetProperty(readStream,
kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelTLSv1);
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
} else {
// handle the error. There is probably something wrong with your cert.
}
}
如果您只想验证正在创建的套接字的证书,并且您的应用中没有其他套接字,则可以手动验证您对该证书的信任。首先,创建一个套接字(假设您的服务器正在侦听与客户端在同一台机器上的端口8443)并在其ssl设置中禁用其证书链验证:
- (void) verifiesManually: (SecCertificateRef) certificate {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)@"localhost",
8443,
&readStream,
&writeStream);
// Set this kCFStreamPropertySocketSecurityLevel before
// setting kCFStreamPropertySSLSettings.
// Setting kCFStreamPropertySocketSecurityLevel
// appears to override previous settings in kCFStreamPropertySSLSettings
CFReadStreamSetProperty(readStream,
kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelTLSv1);
// this disables certificate chain validation in ssl settings.
NSDictionary *sslSettings =
[NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
nil];
CFReadStreamSetProperty(readStream,
kCFStreamPropertySSLSettings,
sslSettings);
NSInputStream *inputStream = (NSInputStream *)readStream;
NSOutputStream *outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
}
然后,当您收到套接字已准备好写入数据的回调时,您应该在将任何数据写入服务器或从服务器读取任何数据之前验证服务器所包含的证书中的信任。首先(1),使用您连接的服务器的主机名创建客户端SSL策略。主机名包含在服务器的证书中,用于验证DNS指示您的服务器是您信任的服务器。接下来(2),您从套接字中获取实际的服务器证书。如果服务器的证书是证书链的一部分,则可能有多个与服务器关联的证书。拥有实际的服务器证书后,您可以(3)创建信任对象。信任对象表示信任评估的本地上下文。它隔离了各个信任评估,而密钥链证书适用于所有可信套接字。拥有信任对象后,您可以(4)设置锚证书,这是您信任的证书。最后(5),您可以评估信任对象并发现服务器是否可信任。
#pragma mark -
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)aStream
handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
break;
case NSStreamEventHasSpaceAvailable:
// #1
// NO for client, YES for server. In this example, we are a client
// replace "localhost" with the name of the server to which you are connecting
SecPolicyRef policy = SecPolicyCreateSSL(NO, CFSTR("localhost"));
SecTrustRef trust = NULL;
// #2
CFArrayRef streamCertificates =
[aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates];
// #3
SecTrustCreateWithCertificates(streamCertificates,
policy,
&trust);
// #4
SecTrustSetAnchorCertificates(trust,
(CFArrayRef) [NSArray arrayWithObject:(id) self.certificate]);
// #5
SecTrustResultType trustResultType = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &trustResultType);
if (status == errSecSuccess) {
// expect trustResultType == kSecTrustResultUnspecified
// until my cert exists in the keychain see technote for more detail.
if (trustResultType == kSecTrustResultUnspecified) {
NSLog(@"We can trust this certificate! TrustResultType: %d", trustResultType);
} else {
NSLog(@"Cannot trust certificate. TrustResultType: %d", trustResultType);
}
} else {
NSLog(@"Creating trust failed: %d", status);
[aStream close];
}
if (trust) {
CFRelease(trust);
}
if (policy) {
CFRelease(policy);
}
break;
case NSStreamEventErrorOccurred:
NSLog(@"unexpected NSStreamEventErrorOccurred: %@", [aStream streamError]);
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}