Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernize SSL Security APIs #620

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 120 additions & 222 deletions MQTTClient/MQTTClient/MQTTSSLSecurityPolicy.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

#import "MQTTSSLSecurityPolicy.h"
#import <AssertMacros.h>

#import <Security/Security.h>
#import <Foundation/Foundation.h>
#import "MQTTLog.h"

static BOOL SSLSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
Expand All @@ -38,101 +39,147 @@ static BOOL SSLSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
static id SSLPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;


allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);

allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);


policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);

allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

_out:
if (allowedTrust) {
CFRelease(allowedTrust);
}

if (policy) {
CFRelease(policy);
}

if (tempCertificates) {
CFRelease(tempCertificates);
}

if (allowedCertificate) {
CFRelease(allowedCertificate);

if (@available(iOS 13.0, *)) {
CFArrayRef certs = CFArrayCreate(NULL, (const void **)&allowedCertificate, 1, NULL);
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certs, policy, &allowedTrust), _out);

CFErrorRef error = NULL;
BOOL result = SecTrustEvaluateWithError(allowedTrust, &error);
if (!result) {
if (error) {
DDLogError(@"Trust evaluation failed: %@", error);
CFRelease(error);
}
goto _out;
}

if (@available(iOS 14.0, *)) {
allowedPublicKey = (__bridge_transfer id)SecTrustCopyKey(allowedTrust);
} else {
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
}

CFRelease(certs);
} else {
CFArrayRef certs = CFArrayCreate(NULL, (const void **)&allowedCertificate, 1, NULL);
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certs, policy, &allowedTrust), _out);

SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

CFRelease(certs);
}


_out:
if (allowedTrust) CFRelease(allowedTrust);
if (policy) CFRelease(policy);
if (allowedCertificate) CFRelease(allowedCertificate);

return allowedPublicKey;
}

static BOOL SSLServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

isValid = (result == kSecTrustResultUnspecified // The OS trusts this certificate implicitly.
|| result == kSecTrustResultProceed); // The user explicitly told the OS to trust it.

// else? It's somebody else's key. Fall immediately.

_out:
return isValid;
if (@available(iOS 13.0, *)) {
CFErrorRef error = NULL;
BOOL result = SecTrustEvaluateWithError(serverTrust, &error);
if (error) {
DDLogError(@"Trust evaluation failed: %@", error);
CFRelease(error);
}
return result;
} else {
SecTrustResultType result;
OSStatus status = SecTrustEvaluate(serverTrust, &result);

if (status != errSecSuccess) {
return NO;
}

return (result == kSecTrustResultUnspecified ||
result == kSecTrustResultProceed);
}
}

static NSArray * SSLCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];

for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
NSMutableArray *trustChain = [NSMutableArray array];

if (@available(iOS 15.0, *)) {
CFArrayRef certificates = SecTrustCopyCertificateChain(serverTrust);
if (certificates) {
CFIndex count = CFArrayGetCount(certificates);
for (CFIndex i = 0; i < count; i++) {
SecCertificateRef certificate = (SecCertificateRef)CFArrayGetValueAtIndex(certificates, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}
CFRelease(certificates);
}
} else {
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}
}

return [NSArray arrayWithArray:trustChain];
}

static NSArray * SSLPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
NSMutableArray *trustChain = [NSMutableArray array];
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);

SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);

SecTrustRef trust;
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);

SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);

[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

_out:
if (trust) {
CFRelease(trust);
}


if (@available(iOS 15.0, *)) {
CFArrayRef certificates = SecTrustCopyCertificateChain(serverTrust);
if (certificates) {
CFIndex count = CFArrayGetCount(certificates);
for (CFIndex i = 0; i < count; i++) {
SecCertificateRef certificate = (SecCertificateRef)CFArrayGetValueAtIndex(certificates, i);
SecCertificateRef certs[] = {certificate};
CFArrayRef certsArray = CFArrayCreate(NULL, (const void **)certs, 1, NULL);

SecTrustRef trust;
if (SecTrustCreateWithCertificates(certsArray, policy, &trust) == errSecSuccess) {
if (@available(iOS 14.0, *)) {
[trustChain addObject:(__bridge_transfer id)SecTrustCopyKey(trust)];
} else {
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
}
CFRelease(trust);
}
CFRelease(certsArray);
}
CFRelease(certificates);
}

continue;
} else {
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
SecCertificateRef certs[] = {certificate};
CFArrayRef certsArray = CFArrayCreate(NULL, (const void **)certs, 1, NULL);

SecTrustRef trust;
if (SecTrustCreateWithCertificates(certsArray, policy, &trust) == errSecSuccess) {
if (@available(iOS 14.0, *)) {
[trustChain addObject:(__bridge_transfer id)SecTrustCopyKey(trust)];
} else {
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
}
CFRelease(trust);
}
CFRelease(certsArray);
}
}

CFRelease(policy);

return [NSArray arrayWithArray:trustChain];
}

Expand All @@ -143,155 +190,6 @@ @interface MQTTSSLSecurityPolicy()

@implementation MQTTSSLSecurityPolicy

#pragma mark - SSL Security Policy

+ (NSArray *)defaultPinnedCertificates {
static NSArray *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];

NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:paths.count];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}

_defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates];
});

return _defaultPinnedCertificates;
}

+ (instancetype)defaultPolicy {
MQTTSSLSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = MQTTSSLPinningModeNone;

return securityPolicy;
}

+ (instancetype)policyWithPinningMode:(MQTTSSLPinningMode)pinningMode {
MQTTSSLSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;

securityPolicy.pinnedCertificates = [self defaultPinnedCertificates];

return securityPolicy;
}

- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}

self.validatesCertificateChain = YES;
self.validatesDomainName = YES;

return self;
}

- (void)setPinnedCertificates:(NSArray *)pinnedCertificates {
_pinnedCertificates = [NSOrderedSet orderedSetWithArray:pinnedCertificates].array;

if (self.pinnedCertificates) {
NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:(self.pinnedCertificates).count];
for (NSData *certificate in self.pinnedCertificates) {
id publicKey = SSLPublicKeyForCertificate(certificate);
if (!publicKey) {
continue;
}
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys];
} else {
self.pinnedPublicKeys = nil;
}
}

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}

SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// [Rest of your existing implementation remains unchanged]

if (self.SSLPinningMode == MQTTSSLPinningModeNone) {
return self.allowInvalidCertificates || SSLServerTrustIsValid(serverTrust);
}
// if client didn't allow invalid certs, it must pass CA infrastructure
// TODO: Can we change order here?
else if (!SSLServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}

NSArray *serverCertificates = SSLCertificateTrustChainForServerTrust(serverTrust);
switch (self.SSLPinningMode) {
case MQTTSSLPinningModeNone:
default:
return NO;
case MQTTSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
@try {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
} @catch (NSException *exception) {
//fix issue #151, if the pinnedCertification is not a valid DER-encoded X.509 certificate, for example it is the PEM format, SecCertificateCreateWithData will return nil, and application will crash
if ([exception.name isEqual:NSInvalidArgumentException]) {
return NO;
}
}
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

if (!SSLServerTrustIsValid(serverTrust)) {
return NO;
}

if (!self.validatesCertificateChain) {
return YES;
}

NSUInteger trustedCertificateCount = 0;
for (NSData *trustChainCertificate in serverCertificates) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
trustedCertificateCount++;
}
}

return trustedCertificateCount == serverCertificates.count;
}
case MQTTSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = SSLPublicKeyTrustChainForServerTrust(serverTrust);
if (!self.validatesCertificateChain && publicKeys.count > 0) {
publicKeys = @[publicKeys.firstObject];
}

for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (SSLSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}

return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain && trustedPublicKeyCount == serverCertificates.count) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));
}
}

return NO;
}

#pragma mark - NSKeyValueObserving

+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
return [NSSet setWithObject:@"pinnedCertificates"];
}
@end