2011-01-24 4 views
4

아래의 iOS UIViewController 코드에서 자체 서명 된 인증서를 사용하는 서버에 연결합니다. 이 자체 서명 된 인증서를 두 가지 방법으로 확인할 수 있습니다. 수동으로 신뢰 API를 사용하거나 자동으로 자체 서명 된 인증서를 내 앱의 키 체인에 추가합니다.하나의 CFReadStream에 kCFStreamSSLValidatesCertificateChain을 설정하면 다른 CFReadStream에서 cert 체인의 유효성을 검사하지 않습니다.

불행히도 CFReadStream을 만들고 kCFStreamSSLValidatesCertificateChain을 kBooleanFalse로 설정하면 나중에 작성한 모든 CFReadStream에서 해당 cert 체인을 확인하지 않습니다. 어딘가에서 코드를 정리하지 못하고 있습니까? 그렇다면이 질문을 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

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