在一个 CFReadStream 中设置 kCFStreamSSLValidatesCertificateChain 会导致其他 CFReadStream 无法验证证书链

发布于 2024-10-14 02:07:55 字数 10129 浏览 6 评论 0原文

在下面的 iOS UIViewController 代码中,我连接到使用自签名证书的服务器。我可以通过两种方式验证此自签名证书:使用信任 API 手动验证,或通过将自签名证书添加到我的应用程序的钥匙串中自动验证。

不幸的是,在我创建 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

In the iOS UIViewController code below, I connect to a server that uses a self-signed cert. I can verify this self-signed cert two ways: manually with the trust APIs, or automatically by adding the self-signed cert into my app's keychain.

Unfortunately, after I create a CFReadStream and set kCFStreamSSLValidatesCertificateChain to kBooleanFalse, every CFReadStream I create afterwards doesn't verify its cert chain. Am I failing to clean up code somewhere? I'll happily reformulate this question into something specific about API cleanup if so.

#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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

山田美奈子 2024-10-21 02:07:55

显然,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);
}

The order of CFReadStream setter calls is important, apparently. The following verifyManually method works:

- (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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文