// SslPinningGuard.mm

#import "SslPinningGuard.h"
#import <CommonCrypto/CommonDigest.h>
#import <Security/Security.h>
#import <React/RCTLog.h>

@implementation SslPinningGuard {
  NSDictionary *_config;
  NSArray<NSString *> *_domains;
  NSArray<NSString *> *_hashes;
}

RCT_EXPORT_MODULE()

- (void)configure:(NSDictionary *)config {
  _config = config;
  _domains = config[@"domains"];
  _hashes = config[@"hashes"];
}

- (BOOL)isValidPin:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
  SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
  if (!certificate) return NO;

  SecKeyRef publicKey = SecCertificateCopyKey(certificate);
  if (!publicKey) return NO;

  CFDataRef publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil);
  if (!publicKeyData) return NO;

  unsigned char hash[CC_SHA256_DIGEST_LENGTH];
  CC_SHA256(CFDataGetBytePtr(publicKeyData), (CC_LONG)CFDataGetLength(publicKeyData), hash);

  NSData *hashData = [NSData dataWithBytes:hash length:sizeof(hash)];
  NSString *hashBase64 = [hashData base64EncodedStringWithOptions:0];

  CFRelease(publicKeyData);
  CFRelease(publicKey);

  for (NSString *validHash in _hashes) {
    if ([hashBase64 isEqualToString:validHash]) {
      return YES;
    }
  }

  return NO;
}

- (void)URLSession:(NSURLSession *)session
        didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
        completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

  NSString *host = challenge.protectionSpace.host;

  if ([_domains containsObject:host]) {
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;

    if ([self isValidPin:serverTrust forDomain:host]) {
      NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
      completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    } else {
      completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
  } else {
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  }
}

RCT_EXPORT_METHOD(makePinnedRequest:(NSString *)url
                  method:(NSString *)method
                  expectedHash:(NSString *)expectedHash
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  NSURL *nsUrl = [NSURL URLWithString:url];
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:nsUrl];
  request.HTTPMethod = method;

  NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
  NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];

  NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                          completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error) {
      reject(@"request_error", @"Request failed", error);
    } else {
      NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
      resolve(responseString);
    }
  }];

  [task resume];
}

@end
