react-native-ssl-pinning icon indicating copy to clipboard operation
react-native-ssl-pinning copied to clipboard

Public Key Pinning not working for iOS

Open sdevansh96 opened this issue 2 years ago • 4 comments

Hi,

Is there a way to implement public key pinning in iOS as certificate pinning is working fine but public key pinning is not working. It's working fine in Android but in iOS we have to replace the certificate if it has expired.

Also getting this warning AFNetworking has been deprecated in favor of Alamofire

Thanks in Advance

sdevansh96 avatar Jul 03 '23 10:07 sdevansh96

Yeah, I'm wondering the same. As the readme mentioned, there's no need to put the public key cause AFNetworking will automatically extract it from the certificate. However, one would probably want to use public key pinning without the certificate. Else, we might as well just use certificate pinning instead. Is this possible in iOS?

update

Seems this has been the case for awhile: https://github.com/MaxToyberman/react-native-ssl-pinning/issues/176

Won't bother opening another issue.

idrakimuhamad avatar Jul 04 '23 01:07 idrakimuhamad

Hello! I've discovered a workaround that allows for public key pinning, as opposed to pinning the entire certificate:

  1. Install the TrustKit in your project's PodFile using:
pod 'TrustKit'
  1. Add the dependency in node_modules/react-native-ssl-pinning/iOS/RNSslPinning.podspec:
`s.dependency "TrustKit"
  1. Make modifications to the RNSslPinning.m as follows:
#import "TrustKit.h"
 self.session = [NSURLSession sessionWithConfiguration:self.sessionConfig delegate:self delegateQueue:nil];
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    TSKPinningValidator *pinningValidator = [[TrustKit sharedInstance] pinningValidator];
    // Pass the authentication challenge to the validator; if the validation fails, the connection will be blocked
    if (![pinningValidator handleChallenge:challenge completionHandler:completionHandler])
    {
        // TrustKit did not handle this challenge: perhaps it was not for server trust
        // or the domain was not pinned. Fall back to the default behavior
        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
}
-(void)performRequest:(NSMutableURLRequest*) request obj:(NSDictionary *)obj callback:(RCTResponseSenderBlock) callback {
    
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
        NSString *bodyString = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding];
        NSInteger statusCode = httpResp.statusCode;

        if (!error) {
            NSString * responseType = obj[@"responseType"];
            if ([responseType isEqualToString:@"base64"]){
                NSString* base64String = [data base64EncodedStringWithOptions:0];
                callback(@[[NSNull null], @{
                               @"status": @(statusCode),
                               @"headers": httpResp.allHeaderFields,
                               @"data": base64String
                }]);
            }
            else {
                callback(@[[NSNull null], @{
                               @"status": @(statusCode),
                               @"headers": httpResp.allHeaderFields,
                               @"bodyString": bodyString ? bodyString : @""
                }]);
            }
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                callback(@[error.localizedDescription, [NSNull null]]);
            });
        }
    }];

    [task resume];
}

RCT_EXPORT_METHOD(fetch:(NSString *)url obj:(NSDictionary *)obj callback:(RCTResponseSenderBlock)callback) {
    @try {
    NSURL *u = [NSURL URLWithString:url];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:u];
    NSString *providedHash = nil;
        if (obj[@"sslPinning"] && [obj[@"sslPinning"][@"certs"] isKindOfClass:[NSArray class]] && [obj[@"sslPinning"][@"certs"] count] > 0) {
            NSString *rawCertificateHash = obj[@"sslPinning"][@"certs"][0];

            // Remove the "sha256/" prefix
            providedHash = [rawCertificateHash stringByReplacingOccurrencesOfString:@"sha256/" withString:@""];
        }
        NSLog(@"Provided Hash: %@", providedHash);
        
        NSString *domain = nil;
        if (u && u.host) {
            domain = u.host;
            NSLog(@"Domain: %@", domain);
        } else {
            NSLog(@"Invalid URL or no host (domain) found.");
        }
        // Define the pinning configuration for the domain
        NSDictionary *domainConfig = @{
            kTSKPublicKeyHashes : @[
                providedHash,
                @"provide here the back up public key hash hardcoded or with variable as the providedHash" // Backup 
            ],
            kTSKIncludeSubdomains: @(YES)
        };

        // Create an empty mutable dictionary to store the pinning configuration
        NSMutableDictionary *pinnedDomains = [NSMutableDictionary dictionary];

        // Add the domain configuration to the pinnedDomains dictionary
        if (domain) {
            [pinnedDomains setObject:domainConfig forKey:domain];
        }

        // Final TrustKit configuration
        NSDictionary *trustKitConfig = @{
            kTSKPinnedDomains : pinnedDomains
        };
        
        
        
        

    [TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
    
    if (obj[@"method"]) {
        [request setHTTPMethod:obj[@"method"]];
    }
    if (obj[@"timeoutInterval"]) {
        [request setTimeoutInterval:[obj[@"timeoutInterval"] doubleValue] / 1000];
    }
    
    if(obj[@"headers"]) {
        [self setHeaders:obj request:request];
    }
    
    if (obj) {
        if ([obj objectForKey:@"body"]) {
            NSData *data = [obj[@"body"] dataUsingEncoding:NSUTF8StringEncoding];
            [request setHTTPBody:data];
        }
        [self performRequest:request obj:obj callback:callback];
    }
    }
        @catch (NSException *exception) {
            callback(@[[NSString stringWithFormat:@"Exception: %@", exception.reason], [NSNull null]]);
         }
}

It's important to note that this implementation is specific to public key pinning. I've eliminated the functionality related to full certificate pinning.

Additional Note: For these modifications, I recommend making use of the patch-package to ensure that your changes persist even after module updates. This way, you don't have to redo the changes every time the module gets updated.

I hope this solution proves helpful to some of you. If you have any feedback, suggestions, or run into issues, please don't hesitate to share.

GiorgosKatsinoulas avatar Sep 12 '23 09:09 GiorgosKatsinoulas

need as well add a property declaration for session

@property (nonatomic, strong) NSURLSession *session;

qusyairin avatar Feb 29 '24 15:02 qusyairin