//
//  TrustEvaluationTestHelpers.m
//  Security
//
//

#include <AssertMacros.h>
#import <Foundation/Foundation.h>
#import <Security/Security.h>

#include <utilities/SecInternalReleasePriv.h>
#include <utilities/SecCFRelease.h>
#include <Security/SecCertificate.h>
#include <Security/SecCertificatePriv.h>
#include <Security/SecPolicyPriv.h>
#include <Security/SecTrust.h>
#include <Security/SecTrustPriv.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>

#include "TrustEvaluationTestHelpers.h"

// Want a class for running trust evaluations
// Want a dictionary-driven test with callback

@interface TestTrustEvaluation ()
@property NSString *directory;
@property NSMutableArray *certificates;
@property NSMutableArray *policies;
@property BOOL enableTestCertificates;
@end

@implementation TestTrustEvaluation
@synthesize ocspResponses = _ocspResponses;
@synthesize presentedSCTs = _presentedSCTs;
@synthesize trustedCTLogs = _trustedCTLogs;


- (instancetype)initWithCertificates:(NSArray *)certs policies:(NSArray *)policies {
    if (self = [super init]) {
        if (errSecSuccess != SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, (__bridge CFArrayRef)policies, &_trust)) {
            return NULL;
        }
    }
    return self;
}

- (void)addAnchor:(SecCertificateRef)certificate
{
    CFArrayRef anchors = NULL;
    SecTrustCopyCustomAnchorCertificates(_trust, &anchors);

    NSMutableArray *newAnchors = [NSMutableArray array];
    if (anchors) {
        [newAnchors addObjectsFromArray:CFBridgingRelease(anchors)];
    }
    [newAnchors addObject:(__bridge id)certificate];
    (void)SecTrustSetAnchorCertificates(_trust, (__bridge CFArrayRef)newAnchors);
}

- (void)setAnchors:(NSArray *)anchorArray {
    (void)SecTrustSetAnchorCertificates(_trust, (__bridge CFArrayRef)anchorArray);
}

- (NSArray *)anchors  {
    CFArrayRef anchors = NULL;
    SecTrustCopyCustomAnchorCertificates(_trust, &anchors);
    return CFBridgingRelease(anchors);
}

- (void)setOcspResponses:(NSArray *)ocspResponsesArray {
    if (ocspResponsesArray != _ocspResponses) {
        _ocspResponses = nil;
        _ocspResponses = ocspResponsesArray;
        (void)SecTrustSetOCSPResponse(_trust, (__bridge CFArrayRef)ocspResponsesArray);
    }
}

- (void)setPresentedSCTs:(NSArray *)presentedSCTsArray {
    if (presentedSCTsArray != _presentedSCTs) {
        _presentedSCTs = nil;
        _presentedSCTs = presentedSCTsArray;
        (void)SecTrustSetSignedCertificateTimestamps(_trust, (__bridge CFArrayRef)presentedSCTsArray);
    }
}

- (void)setTrustedCTLogs:(NSArray *)trustedCTLogsArray {
    if (trustedCTLogsArray != _trustedCTLogs) {
        _trustedCTLogs = nil;
        _trustedCTLogs = trustedCTLogsArray;
        (void)SecTrustSetTrustedLogs(_trust, (__bridge CFArrayRef)trustedCTLogsArray);
    }
}

- (void)setVerifyDate:(NSDate *)aVerifyDate {
    if (aVerifyDate != _verifyDate) {
        _verifyDate = nil;
        _verifyDate = aVerifyDate;
        (void)SecTrustSetVerifyDate(_trust, (__bridge CFDateRef)aVerifyDate);
    }
}

- (void)setNeedsEvaluation {
    SecTrustSetNeedsEvaluation(_trust);
}

- (bool)evaluate:(out NSError * _Nullable __autoreleasing *)outError {
    CFErrorRef localError = nil;
    _trustResult = kSecTrustResultInvalid;
    _resultDictionary = nil;
    bool result = SecTrustEvaluateWithError(_trust, &localError);
    if (outError && localError) {
        *outError = CFBridgingRelease(localError);
    }
    (void)SecTrustGetTrustResult(_trust, &_trustResult);
    _resultDictionary = CFBridgingRelease(SecTrustCopyResult(_trust));
    return result;
}

- (void) dealloc {
    CFReleaseNull(_trust);
}

/* MARK: -
 * MARK: Dictionary-driven trust objects
 */

/* INSTRUCTIONS FOR ADDING NEW DICTIONARY-DRIVEN TRUSTS:
 *   1. Add the certificates, as DER-encoded files with the 'cer' extension to a directory included in the test Resources
 *      (e.g. OSX/shared_regressions/si-20-sectrust-policies-data/)
 *        NOTE: If your cert needs to be named with "(i[Pp]hone|i[Pp]ad|i[Pp]od)", you need to make two copies -- one named properly
 *        and another named such that it doesn't match that regex. Use the regex trick below for TARGET_OS_TV to make sure your test
 *        works.
 *   2. The input dictionary must include: (see constants below)
 *          MajorTestName
 *          MinorTestName
 *          Policies
 *          Leaf
 *          ExpectedResult
 *          CertDirectory
 *      It is strongly recommended that all test dictionaries include the Anchors and VerifyDate keys.
 *      Addtional optional keys are defined below.
 */

/* Key Constants for Test Dictionaries */
const NSString *kSecTrustTestMajorTestName  = @"MajorTestName";     /* Required; value: string */
const NSString *kSecTrustTestMinorTestName  = @"MinorTestName";     /* Required; value: string */
const NSString *kSecTrustTestPolicies       = @"Policies";          /* Required; value: dictionary or array of dictionaries */
const NSString *kSecTrustTestLeaf           = @"Leaf";              /* Required; value: string */
const NSString *kSecTrustTestIntermediates  = @"Intermediates";     /* Optional; value: string or array of strings */
const NSString *kSecTrustTestAnchors        = @"Anchors";           /* Recommended; value: string or array of strings */
const NSString *kSecTrustTestVerifyDate     = @"VerifyDate";        /* Recommended; value: date */
const NSString *kSecTrustTestExpectedResult = @"ExpectedResult";    /* Required; value: number */
const NSString *kSecTrustTestChainLength    = @"ChainLength";       /* Optional; value: number */
const NSString *kSecTrustTestEnableTestCerts= @"EnableTestCertificates"; /* Optional; value: string */
const NSString *kSecTrustTestDisableBridgeOS= @"BridgeOSDisable";   /* Optional; value: boolean */
const NSString *kSecTrustTestDirectory      = @"CertDirectory";     /* Required; value: string */

/* Key Constants for Policies Dictionaries */
const NSString *kSecTrustTestPolicyOID      = @"PolicyIdentifier";  /* Required; value: string */
const NSString *kSecTrustTestPolicyProperties = @"Properties";      /* Optional; value: dictionary, see Policy Value Constants, SecPolicy.h */

- (void)setMajorTestName:(NSString *)majorTestName minorTestName:(NSString *)minorTestName {
    self.fullTestName = [[majorTestName stringByAppendingString:@"-"] stringByAppendingString:minorTestName];
}

#if TARGET_OS_TV
/* Mastering removes all files named i[Pp]hone, so dynamically replace any i[Pp]hone's with
 * iPh0ne. We have two copies in the resources directory. */
- (NSString *)replaceiPhoneNamedFiles:(NSString *)filename {
    NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"iphone"
                                                                                       options:NSRegularExpressionCaseInsensitive
                                                                                         error:nil];
    NSString *newfilename = [regularExpression stringByReplacingMatchesInString:filename
                                                                        options:0
                                                                          range:NSMakeRange(0, [filename length])
                                                                   withTemplate:@"iPh0ne"];
    return newfilename;
}
#endif

- (bool)addLeafToCertificates:(NSString *)leafName {
    SecCertificateRef cert;
    NSString *path = nil, *filename = nil;
    require_string(leafName, errOut, "%@: failed to get leaf for test");
#if TARGET_OS_TV
    filename = [self replaceiPhoneNamedFiles:leafName];
#else
    filename = leafName;
#endif

    path = [[NSBundle bundleForClass:[self class]]
            pathForResource:filename
            ofType:@"cer"
            inDirectory:self.directory];
    require_string(path, errOut, "failed to get path for leaf");
    cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
    require_string(cert, errOut, "failed to create leaf certificate from path");
    self.certificates = [[NSMutableArray alloc] initWithObjects:(__bridge id)cert, nil];
    CFReleaseNull(cert);
    require_string(self.certificates, errOut, "failed to initialize certificates array");
    return true;

errOut:
    return false;
}

- (bool)addCertsToArray:(id)pathsObj outputArray:(NSMutableArray *)outArray {
    __block SecCertificateRef cert = NULL;
    __block NSString* path = nil, *filename = nil;
    require_string(pathsObj, errOut,
                         "failed to get certificate paths for test");

    if ([pathsObj isKindOfClass:[NSString class]]) {
        /* Only one cert path */
#if TARGET_OS_TV
        filename = [self replaceiPhoneNamedFiles:pathsObj];
#else
        filename = pathsObj;
#endif
        path = [[NSBundle bundleForClass:[self class]]
                pathForResource:filename
                ofType:@"cer"
                inDirectory:self.directory];
        require_string(path, errOut, "failed to get path for cert");
        cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
        require_string(cert, errOut, "failed to create certificate from path");
        [outArray addObject:(__bridge id)cert];
        CFReleaseNull(cert);
    } else if ([pathsObj isKindOfClass:[NSArray class]]) {
        /* Test has more than one intermediate */
        [(NSArray *)pathsObj enumerateObjectsUsingBlock:^(NSString *resource, NSUInteger idx, BOOL *stop) {
#if TARGET_OS_TV
            filename = [self replaceiPhoneNamedFiles:resource];
#else
            filename = resource;
#endif
            path = [[NSBundle bundleForClass:[self class]]
                    pathForResource:filename
                    ofType:@"cer"
                    inDirectory:self.directory];
            require_string(path, blockOut, "failed to get path for cert");
            cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
            require_string(cert, blockOut, "failed to create certificate %ld from path %@");
            [outArray addObject:(__bridge id)cert];

            CFReleaseNull(cert);
            return;

        blockOut:
            CFReleaseNull(cert);
            *stop = YES;
        }];
    } else {
        require_string(false, errOut, "unexpected type for intermediates or anchors value");
    }

    return true;

errOut:
    CFReleaseNull(cert);
    return false;
}

- (bool)addIntermediatesToCertificates:(id)intermediatesObj {
    require_string(intermediatesObj, errOut, "failed to get intermediates for test");

    require_string([self addCertsToArray:intermediatesObj outputArray:self.certificates], errOut,
                   "failed to add intermediates to certificates array");

    if ([intermediatesObj isKindOfClass:[NSString class]]) {
        require_string([self.certificates count] == 2, errOut,
                             "failed to add all intermediates");
    } else if ([intermediatesObj isKindOfClass:[NSArray class]]) {
        require_string([self.certificates count] == [(NSArray *)intermediatesObj count] + 1, errOut,
                            "failed to add all intermediates");
    }

    return true;

errOut:
    return false;
}

- (bool)addPolicy:(NSDictionary *)policyDict
{
    SecPolicyRef policy = NULL;
    NSString *policyIdentifier = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyOID];
    NSDictionary *policyProperties = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyProperties];
    require_string(policyIdentifier, errOut, "failed to get policy OID");

    policy = SecPolicyCreateWithProperties((__bridge CFStringRef)policyIdentifier,
                                           (__bridge CFDictionaryRef)policyProperties);
    require_string(policy, errOut, "failed to create properties for policy OID");
    [self.policies addObject:(__bridge id)policy];
    CFReleaseNull(policy);

    return true;
errOut:
    return false;
}

- (bool)addPolicies:(id)policiesObj {
    require_string(policiesObj, errOut,
                   "failed to get policies for test");

    self.policies = [[NSMutableArray alloc] init];
    require_string(self.policies, errOut,
                   "failed to initialize policies array");
    if ([policiesObj isKindOfClass:[NSDictionary class]]) {
        /* Test has only one policy */
        require_string([self addPolicy:policiesObj], errOut, "failed to add policy");
    } else if ([policiesObj isKindOfClass:[NSArray class]]) {
        /* Test more than one policy */
        [(NSArray *)policiesObj enumerateObjectsUsingBlock:^(NSDictionary *policyDict, NSUInteger idx, BOOL *stop) {
            if (![self addPolicy:policyDict]) {
                *stop = YES;
            }
        }];

        require_string([(NSArray *)policiesObj count] == [self.policies count], errOut, "failed to add all policies");
    } else {
        require_string(false, errOut, "unexpected type for Policies value");
    }

    return true;

errOut:
    return false;
}

- (bool)setAnchorsFromPlist:(id)anchorsObj {
    NSMutableArray *anchors = [NSMutableArray array];
    require_string(anchorsObj, errOut, "failed to get anchors for test");
    require_string([self addCertsToArray:anchorsObj outputArray:anchors], errOut, "failed to add anchors to anchors array");

    if ([anchorsObj isKindOfClass:[NSString class]]) {
        require_string([anchors count] == 1, errOut, "failed to add all anchors");
    } else if ([anchorsObj isKindOfClass:[NSArray class]]) {
        require_string([anchors count] == [(NSArray *)anchorsObj count], errOut, "failed to add all anchors");
    }

    // set the anchors in the SecTrustRef
    self.anchors = anchors;
    return true;

errOut:
    return false;
}

- (instancetype _Nullable) initWithTrustDictionary:(NSDictionary *)testDict
{
    if (!(self = [super init])) {
        return self;
    }

    NSString *majorTestName = nil, *minorTestName = nil;
    SecTrustRef trust = NULL;

#if TARGET_OS_BRIDGE
    /* Some of the tests don't work on bridgeOS because there is no Certificates bundle. Skip them. */
    if([testDict[kSecTrustTestDisableBridgeOS] boolValue]) {
        self.bridgeOSDisabled =  YES;
    }
#endif

    /* Test certificates work by default on internal builds. We still need this to
     * determine whether to expect failure for production devices. */
    self.enableTestCertificates = [testDict[kSecTrustTestEnableTestCerts] boolValue];

    /* Test name, for documentation purposes */
    majorTestName = testDict[kSecTrustTestMajorTestName];
    minorTestName = testDict[kSecTrustTestMinorTestName];
    require_string(majorTestName && minorTestName, errOut, "Failed to create test names for test");
    [self setMajorTestName:majorTestName minorTestName:minorTestName];

#if DEBUG
    fprintf(stderr, "BEGIN trust creation for %s", [self.fullTestName cStringUsingEncoding:NSUTF8StringEncoding]);
#endif

    /* Cert directory */
    self.directory = testDict[kSecTrustTestDirectory];
    require_string(self.directory, errOut, "No directory for test!");

    /* Populate the certificates array */
    require_quiet([self addLeafToCertificates:testDict[kSecTrustTestLeaf]], errOut);

    /* Add optional intermediates to certificates array */
    if (testDict[kSecTrustTestIntermediates]) {
        require_quiet([self addIntermediatesToCertificates:testDict[kSecTrustTestIntermediates]], errOut);
    }

    /* Create the policies */
#if !TARGET_OS_BRIDGE
    require_quiet([self addPolicies:testDict[kSecTrustTestPolicies]], errOut);
#else // TARGET_OS_BRIDGE
    if (![self addPolicies:testDict[kSecTrustTestPolicies]]) {
        /* Some policies aren't available on bridgeOS (because there is no Certificates bundle on bridgeOS).
         * If we fail to add the policies for a disabled test, let SecTrustCreate fall back to the Basic policy.
         * We'll skip the evaluation and other tests by honoring bridgeOSDisabled, but we need to return a
         * TestTrustEvaluation object so that the test continues. */
        if (self.bridgeOSDisabled) {
            self.policies = nil;
        } else {
            goto errOut;
        }
    }
#endif // TARGET_OS_BRIDGE


    /* Create the trust object */
    require_noerr_string(SecTrustCreateWithCertificates((__bridge CFArrayRef)self.certificates,
                                                              (__bridge CFArrayRef)self.policies,
                                                              &trust),
                               errOut,
                               "failed to create trust ref");
    self.trust = trust;

    /* Optionally set anchors in trust object */
    if (testDict[kSecTrustTestAnchors]) {
        require_quiet([self setAnchorsFromPlist:testDict[kSecTrustTestAnchors]], errOut);
    }

    /* Set optional date in trust object */
    if (testDict[kSecTrustTestVerifyDate]) {
        self.verifyDate = testDict[kSecTrustTestVerifyDate];
    }

    /* Set expected results */
    self.expectedResult = testDict[kSecTrustTestExpectedResult];
    self.expectedChainLength = testDict[kSecTrustTestChainLength];

#if DEBUG
    fprintf(stderr, "END trust creation for %s", [self.fullTestName cStringUsingEncoding:NSUTF8StringEncoding]);
#endif

    return self;

errOut:
    return nil;
}

- (bool)evaluateForExpectedResults:(out NSError * _Nullable __autoreleasing *)outError
{
#if TARGET_OS_BRIDGE
    // Artificially skip tests for bridgeOS. To prevent test errors these need to be reported as a pass.
    if (self.bridgeOSDisabled) {
        return true;
    }
#endif

    if (!self.expectedResult) {
        if (outError) {
            NSString *errorDescription = [NSString stringWithFormat:@"Test %@: no expected results set",
                                          self.fullTestName];
            *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-1)
                                        userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
        }
        return false;
    }

    SecTrustResultType trustResult = kSecTrustResultInvalid;
    if (errSecSuccess != SecTrustGetTrustResult(self.trust, &trustResult)) {
        if (outError) {
            NSString *errorDescription = [NSString stringWithFormat:@"Test %@: Failed to get trust result",
                                          self.fullTestName];
            *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-2)
                                        userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
        }
        return false;
    }

    bool result = false;

    /* If we enabled test certificates on a non-internal device, expect a failure instead of success. */
    if (self.enableTestCertificates && !SecIsInternalRelease() && ([self.expectedResult unsignedIntValue] == 4)) {
        if (trustResult == kSecTrustResultRecoverableTrustFailure) {
            result = true;
        }
    } else if (trustResult == [self.expectedResult unsignedIntValue]) {
        result = true;
    }

    if (!result) {
        if (outError) {
            NSString *errorDescription = [NSString stringWithFormat:@"Test %@: Expected result %@ %s does not match actual result %u %s",
                                          self.fullTestName, self.expectedResult,
                                          (self.enableTestCertificates ? "for test cert" : ""),
                                          trustResult,
                                          SecIsInternalRelease() ? "" : "on prod device"];
            *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-3)
                                        userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
        }
        return result;
    }

    /* expected chain length is optional, but if we have it, verify */
    if (self.expectedChainLength && (SecTrustGetCertificateCount(self.trust) != [self.expectedChainLength longValue])) {
        if (outError) {
            NSString *errorDescription = [NSString stringWithFormat:@"Test %@: Expected chain length %@ does not match actual chain length %ld",
                                          self.fullTestName, self.expectedChainLength, SecTrustGetCertificateCount(self.trust)];
            *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-4)
                                        userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
        }
        return false;
    }
    return true;
}

@end

int ping_host(char *host_name)
{
    struct sockaddr_in pin;
    struct hostent *nlp_host;
    struct in_addr addr;
    int sd = 0;
    int port = 80;
    int retries = 5; // we try 5 times, then give up
    char **h_addr_list = NULL;

    while ((nlp_host=gethostbyname(host_name)) == 0 && retries--) {
        printf("Resolve Error! (%s) %d\n", host_name, h_errno);
        sleep(1);
    }
    if (nlp_host == 0) {
        return 0;
    }

    bzero(&pin,sizeof(pin));
    pin.sin_family=AF_INET;
    pin.sin_addr.s_addr=htonl(INADDR_ANY);
    h_addr_list = malloc(nlp_host->h_length * sizeof(char *));
    memcpy(h_addr_list, nlp_host->h_addr_list, nlp_host->h_length * sizeof(char *));
    memcpy(&addr, h_addr_list[0], sizeof(struct in_addr));
    pin.sin_addr.s_addr=addr.s_addr;
    pin.sin_port=htons(port);

    sd=socket(AF_INET,SOCK_STREAM,0);

    if (connect(sd,(struct sockaddr*)&pin,sizeof(pin)) == -1) {
        printf("connect error! (%s) %d\n", host_name, errno);
        close(sd);
        free(h_addr_list);
        return 0;
    }
    close(sd);
    free(h_addr_list);
    return 1;
}
