2011-01-24 57 views
4

在下面的iOS UIViewController代码中,我连接到使用自签名证书的服务器。我可以通过两种方式验证自签名证书:手动使用信任API,或者通过将自签名证书添加到我的应用程序的钥匙串中自动进行验证。在一个CFReadStream中设置kCFStreamSSLValidatesCertificateChain导致其他CFReadStreams无法验证证书链

不幸的是,我创建了一个CFReadStream并设置kCFStreamSSLValidatesCertificateChain到kBooleanFalse后,我每次创建之后CFReadStream不验证其证书链。我没有清理代码吗?如果是的话,我会很乐意将这个问题重新解释成关于API清理的具体内容。

#import <UIKit/UIKit.h> 
#import <Security/Security.h> 

@interface SecureViewController : UIViewController<NSStreamDelegate> { 

} 

- (id) initWithCertificate: (SecCertificateRef) certificate; 

@end 

#import "SecureViewController.h" 

@interface SecureViewController() 

@property (nonatomic) SecCertificateRef certificate; 

@property (nonatomic, retain) NSInputStream *inputStream; 
@property (nonatomic, retain) NSOutputStream *outputStream; 

@property (nonatomic) BOOL verifyOnHasSpaceAvailable; 

- (void) verifyManually; 
- (void) verifyWithKeychain; 

@end 

@implementation SecureViewController 

@synthesize certificate = _certificate; 

@synthesize inputStream = _inputStream; 
@synthesize outputStream = _outputStream; 

@synthesize verifyOnHasSpaceAvailable = _verifyOnHasSpaceAvailable; 

#pragma mark - 
#pragma mark init/dealloc methods 

- (id) initWithCertificate: (SecCertificateRef) certificate { 
    if (self = [super initWithNibName:nil bundle:nil]) { 
     self.certificate = certificate; 
    } 
    return self; 
} 

- (void)dealloc { 
    self.certificate = NULL; 

self.inputStream = nil; 
self.outputStream = nil; 

    [super dealloc]; 
} 

#pragma mark - 
#pragma mark UIViewController 

- (void)loadView { 
[super loadView]; 

UIButton *manualVerificationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 
[manualVerificationButton addTarget:self 
        action:@selector(verifyManually) 
     forControlEvents:UIControlEventTouchUpInside]; 
manualVerificationButton.frame = CGRectMake(0, 
              0, 
              self.view.bounds.size.width, 
              self.view.bounds.size.height/2); 
[manualVerificationButton setTitle:@"Manual Verification" 
       forState:UIControlStateNormal]; 

[self.view addSubview:manualVerificationButton]; 

UIButton *keychainVerificationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 
[keychainVerificationButton addTarget:self 
           action:@selector(verifyWithKeychain) 
        forControlEvents:UIControlEventTouchUpInside]; 
keychainVerificationButton.frame = CGRectMake(0, 
               self.view.bounds.size.height/2, 
               self.view.bounds.size.width, 
               self.view.bounds.size.height/2); 
[keychainVerificationButton setTitle: 
            @"Keychain Verification\n" 
            @"(Doesn't work after Manual Verification)\n" 
            @"((Don't know why yet.))" 
          forState:UIControlStateNormal]; 
keychainVerificationButton.titleLabel.lineBreakMode = UILineBreakModeWordWrap; 
keychainVerificationButton.titleLabel.numberOfLines = 0; 

[self.view addSubview:keychainVerificationButton]; 
} 

#pragma mark - 
#pragma mark private api 

- (void) verifyWithKeychain { 
self.inputStream = nil; 
self.outputStream = nil; 

self.verifyOnHasSpaceAvailable = NO; 

OSStatus err = SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: 
              (id) kSecClassCertificate, kSecClass, 
              self.certificate, kSecValueRef, 
              nil], 
          NULL); 
assert(err == noErr || err == errSecDuplicateItem); 

CFReadStreamRef readStream; 
CFWriteStreamRef writeStream; 
CFStreamCreatePairWithSocketToHost(NULL, 
            (CFStringRef)@"localhost", 
            8443, 
            &readStream, 
            &writeStream); 

CFReadStreamSetProperty(readStream, 
         kCFStreamPropertySocketSecurityLevel, 
         kCFStreamSocketSecurityLevelTLSv1); 

self.inputStream = (NSInputStream *)readStream; 
self.outputStream = (NSOutputStream *)writeStream; 

CFReadStreamOpen(readStream); 
CFWriteStreamOpen(writeStream); 
} 

- (void) verifyManually { 
self.inputStream = nil; 
self.outputStream = nil; 

// we don't want the keychain to accidentally accept our self-signed cert. 
SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: 
           (id) kSecClassCertificate, kSecClass, 
           self.certificate, kSecValueRef, 
           nil]); 

self.verifyOnHasSpaceAvailable = YES; 

CFReadStreamRef readStream; 
CFWriteStreamRef writeStream; 
CFStreamCreatePairWithSocketToHost(NULL, 
            (CFStringRef)@"localhost", 
            8443, 
            &readStream, 
            &writeStream); 

NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
          (id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain, 
          nil]; 

CFReadStreamSetProperty(readStream, 
         kCFStreamPropertySSLSettings, 
         sslSettings); 

// Don't set this property. The only settings that work are: 
// kCFStreamSocketSecurityLevelNone or leaving it unset. 
// Leaving it appears to be equivalent to setting it to: 
// kCFStreamSocketSecurityLevelTLSv1 or kCFStreamSocketSecurityLevelSSLv3 
// 
// CFReadStreamSetProperty(readStream, 
//       kCFStreamPropertySocketSecurityLevel, 
//       kCFStreamSocketSecurityLevelTLSv1); 

self.inputStream = (NSInputStream *)readStream; 
self.outputStream = (NSOutputStream *)writeStream; 

CFReadStreamOpen(readStream); 
CFWriteStreamOpen(writeStream); 
} 

#pragma mark - 
#pragma mark private properties 

- (void) setCertificate:(SecCertificateRef) certificate { 
if (_certificate != certificate) { 
    if (_certificate) { 
     CFRelease(_certificate); 
    } 
    _certificate = certificate; 
    if (_certificate) { 
     CFRetain(_certificate); 
    } 
} 
} 

- (void) setInputStream:(NSInputStream *) inputStream { 
if (_inputStream != inputStream) { 
    [_inputStream setDelegate:nil]; 
    [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSDefaultRunLoopMode]; 
    [_inputStream close]; 
    [_inputStream release]; 
    _inputStream = inputStream; 
    [_inputStream retain]; 
    [_inputStream setDelegate:self]; 
    [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSDefaultRunLoopMode]; 
} 
} 

- (void) setOutputStream:(NSOutputStream *) outputStream { 
if (_outputStream != outputStream) { 
    [_outputStream setDelegate:nil]; 
    [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSDefaultRunLoopMode]; 
    [_outputStream close]; 
    [_outputStream release]; 
    _outputStream = outputStream; 
    [_outputStream retain]; 
    [_outputStream setDelegate:self]; 
    [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSDefaultRunLoopMode]; 
} 
} 

#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: 
     NSLog(@"Socket Security Level: %@", [aStream propertyForKey:(NSString *) kCFStreamPropertySocketSecurityLevel]); 
     NSLog(@"SSL settings: %@", [aStream propertyForKey:(NSString *) kCFStreamPropertySSLSettings]); 
     if (self.verifyOnHasSpaceAvailable) { 
      SecPolicyRef policy = SecPolicyCreateSSL(NO, CFSTR("localhost")); 
      SecTrustRef trust = NULL; 

      SecTrustCreateWithCertificates([aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates], 
              policy, 
              &trust); 
      SecTrustSetAnchorCertificates(trust, 
              (CFArrayRef) [NSArray arrayWithObject:(id) self.certificate]); 
      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: http://developer.apple.com/library/mac/#qa/qa2007/qa1360.html 
       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); 
      } 
     } else { 
      NSLog(@"We can trust this server!"); 
     } 
     break; 
    case NSStreamEventErrorOccurred: 
     if ([[aStream streamError] code] == -9807) { // header file with error code symbol isn't present in ios. 
      NSLog(@"We cannot trust this certificate."); 
     } else { 
      NSLog(@"unexpected NSStreamEventErrorOccurred: %@", [aStream streamError]); 
     } 
     break; 
    case NSStreamEventEndEncountered: 
     break; 
    default: 
     break; 
} 
} 

@end 

回答

3

订购CFReadStream setter调用很重要,显然。下面verifyManually方法的工作原理:

- (void) verifyManually { 
self.inputStream = nil; 
self.outputStream = nil; 

// we don't want the keychain to accidentally accept our self-signed cert. 
SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: 
           (id) kSecClassCertificate, kSecClass, 
           self.certificate, kSecValueRef, 
           nil]); 

self.verifyOnHasSpaceAvailable = YES; 

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); 


NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys: 
          (id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain, 
          nil]; 

CFReadStreamSetProperty(readStream, 
         kCFStreamPropertySSLSettings, 
         sslSettings); 

self.inputStream = (NSInputStream *)readStream; 
self.outputStream = (NSOutputStream *)writeStream; 

CFReadStreamOpen(readStream); 
CFWriteStreamOpen(writeStream); 
} 
+0

这完美的作品,谢谢! – J3soon 2016-02-29 05:24:58

相关问题