diff --git a/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.h b/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.h index f2acfdb0..27a27150 100644 --- a/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.h +++ b/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.h @@ -18,7 +18,6 @@ - (NSArray *)convertCertificatesToData; - /** Converts MASSecurityConfiguration's array of certificate array into array of SecCertificateRef diff --git a/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.m b/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.m index a7a49e2f..0e60c424 100644 --- a/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.m +++ b/MASFoundation/Classes/_private_/models/MASSecurityConfiguration+MASPrivate.m @@ -47,6 +47,7 @@ - (NSArray *)convertCertificatesToData } + - (NSArray *)convertCertificatesToSecCertificateRef { NSMutableArray *certAsData = [[self convertCertificatesToData] mutableCopy]; diff --git a/MASFoundation/Classes/_private_/services/network/internal/MASSecurityPolicy.m b/MASFoundation/Classes/_private_/services/network/internal/MASSecurityPolicy.m index cb64f6c6..d3a1cc85 100644 --- a/MASFoundation/Classes/_private_/services/network/internal/MASSecurityPolicy.m +++ b/MASFoundation/Classes/_private_/services/network/internal/MASSecurityPolicy.m @@ -77,7 +77,7 @@ - (BOOL)evaluateSecurityConfigurationsForServerTrust:(SecTrustRef)serverTrust fo // // If trustPublicPKI is set to NO, and there is no pinning information defined, reject connection // - else if (!securityConfiguration.trustPublicPKI && ((([securityConfiguration.certificates isKindOfClass:[NSArray class]] && [securityConfiguration.certificates count] == 0) || securityConfiguration.certificates == nil) && (([securityConfiguration.publicKeyHashes isKindOfClass:[NSArray class]] && [securityConfiguration.publicKeyHashes count] == 0) || securityConfiguration.publicKeyHashes == nil))) + else if (!securityConfiguration.trustPublicPKI && (((([securityConfiguration.certificates isKindOfClass:[NSArray class]] && [securityConfiguration.certificates count] == 0) || securityConfiguration.certificates == nil) && (([securityConfiguration.publicKeyHashes isKindOfClass:[NSArray class]] && [securityConfiguration.publicKeyHashes count] == 0) || securityConfiguration.publicKeyHashes == nil)))) { return NO; } @@ -88,30 +88,43 @@ - (BOOL)evaluateSecurityConfigurationsForServerTrust:(SecTrustRef)serverTrust fo BOOL isPinningVerified = YES; NSArray *certificateChain = [self extractCertificateDataFromServerTrust:serverTrust]; + switch (securityConfiguration.pinningMode) { + case MASSecuritySSLPinningModeCertificate: + { + isPinningVerified = [self validateCertPinning:serverTrust configuration:securityConfiguration certChain:certificateChain]; + } + break; + case MASSecuritySSLPinningModeIntermediateCertifcate: + { + isPinningVerified = [self validateIntermediateCertPinning:serverTrust configuration:securityConfiguration certChain:certificateChain]; + } + break; + case MASSecuritySSLPinningModePublicKeyHash: + { + isPinningVerified = [self validatePublicKeyHash:serverTrust configuration:securityConfiguration]; + } + break; + + } + + return isPinningVerified; +} + + +- (BOOL)validateCertPinning:(SecTrustRef)serverTrust configuration:(MASSecurityConfiguration *)securityConfiguration certChain:(NSArray *)certificateChain +{ // // pinning with certificates // if (securityConfiguration.certificates != nil && [securityConfiguration.certificates isKindOfClass:[NSArray class]] && [securityConfiguration.certificates count] > 0) { - // - // Set anchor cert with pinned certificates - // - NSMutableArray *pinnedCertificates = [NSMutableArray array]; NSMutableArray *pinnedCertificatesData = [[securityConfiguration convertCertificatesToData] mutableCopy]; - for (NSData *certificateData in pinnedCertificatesData) - { - [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; - } - SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); - - // - // Stop proceeding if validation of server trust against anchor (pinned) certificates - // - if (![self validateServerTrust:serverTrust]) + if(![self validateAnchorTrust:serverTrust pinnedCerts:pinnedCertificatesData]) { return NO; } + // // As of this point, if the configuration forces to validate the entire chain, validate entire chain of certificates // @@ -137,6 +150,67 @@ - (BOOL)evaluateSecurityConfigurationsForServerTrust:(SecTrustRef)serverTrust fo } } + return YES; +} + + +//Validate the intermediate certificate pinning + +- (BOOL)validateIntermediateCertPinning:(SecTrustRef)serverTrust configuration:(MASSecurityConfiguration *)securityConfiguration certChain:(NSArray *)certificateChain +{ + if (securityConfiguration.certificates != nil && [securityConfiguration.certificates isKindOfClass:[NSArray class]] && [securityConfiguration.certificates count] > 0) + { + NSMutableArray *pinnedCertificatesData = [[securityConfiguration convertCertificatesToData] mutableCopy]; + if(![self validateAnchorTrust:serverTrust pinnedCerts:pinnedCertificatesData]) + { + return NO; + } + + // + // Since this part is only pinning intermediate certificates no need to validate the entire chain. Only make sure if the intermediate certs are part of the CertificateChain that server presented + // + for (NSData *pinnedCertData in pinnedCertificatesData) + { + if (![certificateChain containsObject:pinnedCertData]) + { + return NO; + } + } + + } + + return YES; +} + + +//Validate server anchor trust based on the certificates that are pinned +- (BOOL)validateAnchorTrust:(SecTrustRef)serverTrust pinnedCerts:(NSArray *)pinnedCertificatesData +{ + // + // Set anchor cert with pinned certificates + // + NSMutableArray *pinnedCertificates = [NSMutableArray array]; + + for (NSData *certificateData in pinnedCertificatesData) + { + [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; + } + SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); + + // + // Stop proceeding if validation of server trust against anchor (pinned) certificates + // + if (![self validateServerTrust:serverTrust]) + { + return NO; + } + + return YES; +} + +//Pinning based on public key hash +- (BOOL)validatePublicKeyHash:(SecTrustRef)serverTrust configuration:(MASSecurityConfiguration *)securityConfiguration +{ // // pinning with public key hashes // @@ -201,8 +275,10 @@ - (BOOL)evaluateSecurityConfigurationsForServerTrust:(SecTrustRef)serverTrust fo } } - return isPinningVerified; + return YES; } + + - (BOOL)validateServerTrust:(SecTrustRef)serverTrust diff --git a/MASFoundation/Classes/models/MASSecurityConfiguration.h b/MASFoundation/Classes/models/MASSecurityConfiguration.h index 5c7455f4..cd90c7cf 100644 --- a/MASFoundation/Classes/models/MASSecurityConfiguration.h +++ b/MASFoundation/Classes/models/MASSecurityConfiguration.h @@ -21,6 +21,27 @@ @interface MASSecurityConfiguration : MASObject +/** + * Different SSL pinning modes that can be opted. + */ +typedef NS_ENUM(NSUInteger, MASSecuritySSLPinningMode) { + /** + * SSL pinning based on Public Key Hash. + */ + MASSecuritySSLPinningModePublicKeyHash, + + /** + * SSL pinning based on Leaf Certificate. + */ + MASSecuritySSLPinningModeCertificate, + + /** + * SSL pinning based on Intermediate Certificate. + */ + MASSecuritySSLPinningModeIntermediateCertifcate, +}; + + ///-------------------------------------- /// @name Properties ///-------------------------------------- @@ -45,6 +66,12 @@ */ @property (assign) BOOL trustPublicPKI; +/** + enum value that determines the SSL pinning mode. The Certifcates array needs to be set accordingly with the certificates that needs to be pinned. If MASSecuritySSLPinningModeIntermediateCertifcate is chosen, then the certificates array should contain intermediate certificate. + @see certificates + */ +@property (assign) MASSecuritySSLPinningMode pinningMode; + /** NSArray value of pinned certificates. Certificates must be in PEM encoded CRT; each line should be an item of the certificate array. diff --git a/MASFoundation/Classes/models/MASSecurityConfiguration.m b/MASFoundation/Classes/models/MASSecurityConfiguration.m index f59f7d11..0b6db956 100644 --- a/MASFoundation/Classes/models/MASSecurityConfiguration.m +++ b/MASFoundation/Classes/models/MASSecurityConfiguration.m @@ -40,6 +40,7 @@ - (instancetype)initWithURL:(NSURL *)url self.trustPublicPKI = NO; self.validateCertificateChain = NO; self.validateDomainName = YES; + self.pinningMode = MASSecuritySSLPinningModeCertificate; } return self; @@ -99,6 +100,11 @@ - (void)setValuesWithConfiguration:(NSDictionary *)configuration self.trustPublicPKI = [[configuration objectForKey:@"trustPublicPKI"] boolValue]; } + if ([configuration.allKeys containsObject:@"pinningMode"]) + { + self.pinningMode = [MASSecurityConfiguration praseStringToPinningMode:[configuration objectForKey:@"pinningMode"]]; + } + if ([configuration.allKeys containsObject:@"certificates"] && [[configuration objectForKey:@"certificates"] isKindOfClass:[NSArray class]]) { self.certificates = [configuration objectForKey:@"certificates"]; @@ -111,6 +117,27 @@ - (void)setValuesWithConfiguration:(NSDictionary *)configuration } ++ (MASSecuritySSLPinningMode)praseStringToPinningMode:(NSString *)pinningMode +{ + MASSecuritySSLPinningMode pinningModeValue = MASSecuritySSLPinningModeCertificate; + + if ([pinningMode isEqualToString:MASSecurityConfigurationPinningModeCertificate]) + { + pinningModeValue = MASSecuritySSLPinningModeCertificate; + } +// else if ([pinningMode isEqualToString:MASSecurityConfigurationPinningModePublicKey]) +// { +// pinningModeValue = MASSecuritySSLPinningModePublicKey; +// } + else if ([pinningMode isEqualToString:MASSecurityConfigurationPinningModePublicKeyHash]) + { + pinningModeValue = MASSecuritySSLPinningModePublicKeyHash; + } + + return pinningModeValue; +} + + - (NSString *)debugDescription { return [NSString stringWithFormat:@"(%@) for %@\n\nisPublic: %@\nvalidateDomainName: %@\ntrustPublicPKI: %@\ncertificates: %@\npublicKeyHashes: %@\n", [self class], [[self host] absoluteString], self.isPublic ? @"YES":@"NO", self.validateDomainName ? @"YES":@"NO", self.trustPublicPKI ? @"YES":@"NO", self.certificates, self.publicKeyHashes];