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

Optionally allow comparison of public keys to validate SSL certificates. #482

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion SocketRocket/Internal/Security/SRPinningSecurityPolicy.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface SRPinningSecurityPolicy : SRSecurityPolicy

- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates;
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys;

@end

Expand Down
34 changes: 32 additions & 2 deletions SocketRocket/Internal/Security/SRPinningSecurityPolicy.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//

#import "SRPinningSecurityPolicy.h"
#import <openssl/x509.h>

#import <Foundation/Foundation.h>

Expand All @@ -18,12 +19,13 @@
@interface SRPinningSecurityPolicy ()

@property (nonatomic, copy, readonly) NSArray *pinnedCertificates;
@property (nonatomic, assign, readonly) BOOL comparesPublicKeys;

@end

@implementation SRPinningSecurityPolicy

- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys
{
// Do not validate certificate chain since we're pinning to specific certificates.
self = [super initWithCertificateChainValidationEnabled:NO];
Expand All @@ -35,6 +37,7 @@ - (instancetype)initWithCertificates:(NSArray *)pinnedCertificates
userInfo:nil];
}
_pinnedCertificates = [pinnedCertificates copy];
_comparesPublicKeys = comparesPublicKeys;

return self;
}
Expand All @@ -53,7 +56,7 @@ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domai
SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
// TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time.
NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));
if ([trustedCertData isEqualToData:data]) {
if ([self isServerCertificateDataValid:data trustedCertData:trustedCertData]) {
validatedCertCount++;
break;
}
Expand All @@ -62,6 +65,33 @@ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domai
return (requiredCertCount == validatedCertCount);
}

- (BOOL)isServerCertificateDataValid:(NSData *)serverCertData trustedCertData:(NSData *)trustedCertData
{
if (_comparesPublicKeys) {
return [[self getPublicKeyStringFromData:trustedCertData] isEqualToString:[self getPublicKeyStringFromData:serverCertData]];
} else {
return [trustedCertData isEqualToData:serverCertData];
}
}

- (NSString *)getPublicKeyStringFromData:(NSData *)data
{
const unsigned char *certificateDataBytes = (const unsigned char *)[data bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [data length]);
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];

for (int i = 0; i < pubKey2->length; i++) {
NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
publicKeyString = [publicKeyString stringByAppendingString:aString];
}

X509_free(certificateX509);

return publicKeyString;
}

@end

NS_ASSUME_NONNULL_END
10 changes: 10 additions & 0 deletions SocketRocket/NSURLRequest+SRWebSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nullable, nonatomic, copy, readonly) NSArray *SR_SSLPinnedCertificates;

/**
A boolean value indicating whether equivalent public keys will be sufficient for validating SSL certificates.
*/
@property (nonatomic, assign, readonly) BOOL SR_comparesPublicKeys;

@end

@interface NSMutableURLRequest (SRWebSocket)
Expand All @@ -29,6 +34,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nullable, nonatomic, copy) NSArray *SR_SSLPinnedCertificates;

/**
A boolean value indicating whether equivalent public keys will be sufficient for validating SSL certificates.
*/
@property (nonatomic, assign) BOOL SR_comparesPublicKeys;

@end

NS_ASSUME_NONNULL_END
16 changes: 16 additions & 0 deletions SocketRocket/NSURLRequest+SRWebSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ void import_NSURLRequest_SRWebSocket() { }
NS_ASSUME_NONNULL_BEGIN

static NSString *const SRSSLPinnnedCertificatesKey = @"SocketRocket_SSLPinnedCertificates";
static NSString *const SRSSLComparesPublicKeysKey = @"SocketRocket_SSLComparesPublicKeys";

@implementation NSURLRequest (SRWebSocket)

Expand All @@ -26,6 +27,11 @@ - (nullable NSArray *)SR_SSLPinnedCertificates
return [NSURLProtocol propertyForKey:SRSSLPinnnedCertificatesKey inRequest:self];
}

- (BOOL)SR_comparesPublicKeys
{
return [[NSURLProtocol propertyForKey:SRSSLComparesPublicKeysKey inRequest:self] boolValue];
}

@end

@implementation NSMutableURLRequest (SRWebSocket)
Expand All @@ -40,6 +46,16 @@ - (void)setSR_SSLPinnedCertificates:(nullable NSArray *)SR_SSLPinnedCertificates
[NSURLProtocol setProperty:[SR_SSLPinnedCertificates copy] forKey:SRSSLPinnnedCertificatesKey inRequest:self];
}

- (BOOL)SR_comparesPublicKeys
{
return [[NSURLProtocol propertyForKey:SRSSLComparesPublicKeysKey inRequest:self] boolValue];
}

- (void)setSR_comparesPublicKeys:(BOOL)SR_comparesPublicKeys
{
[NSURLProtocol setProperty:[NSNumber numberWithBool:SR_comparesPublicKeys] forKey:SRSSLComparesPublicKeysKey inRequest:self];
}

@end

NS_ASSUME_NONNULL_END
3 changes: 2 additions & 1 deletion SocketRocket/SRSecurityPolicy.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ NS_ASSUME_NONNULL_BEGIN
chain validation.

@param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation.
@param comparesPublicKeys Boolean to indicate whether equivalent public keys will be sufficient for validating SSL certificates.
*/
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates;
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys;

/**
Specifies socket security and optional certificate chain validation.
Expand Down
4 changes: 2 additions & 2 deletions SocketRocket/SRSecurityPolicy.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ + (instancetype)defaultPolicy
return [self new];
}

+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys
{
return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates];
return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates comparesPublicKeys:comparesPublicKeys];
}

- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled
Expand Down
3 changes: 2 additions & 1 deletion SocketRocket/SRWebSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,9 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NS
{
SRSecurityPolicy *securityPolicy;
NSArray *pinnedCertificates = request.SR_SSLPinnedCertificates;
BOOL comparesPublicKeys = request.SR_comparesPublicKeys;
if (pinnedCertificates) {
securityPolicy = [SRSecurityPolicy pinnningPolicyWithCertificates:pinnedCertificates];
securityPolicy = [SRSecurityPolicy pinnningPolicyWithCertificates:pinnedCertificates comparesPublicKeys:comparesPublicKeys];
} else {
BOOL certificateChainValidationEnabled = !allowsUntrustedSSLCertificates;
securityPolicy = [[SRSecurityPolicy alloc] initWithCertificateChainValidationEnabled:certificateChainValidationEnabled];
Expand Down