/*
* Copyright (c) 2006-2010,2012-2019 Apple Inc. All Rights Reserved.
*/

#include <AssertMacros.h>
#import <XCTest/XCTest.h>
#import <Security/SecCertificatePriv.h>
#include <Security/SecTrustPriv.h>
#include <Security/SecPolicyPriv.h>
#include "OSX/utilities/array_size.h"
#include "OSX/utilities/SecCFWrappers.h"

#import "../TestMacroConversions.h"
#import "TrustEvaluationTestCase.h"

#import "ExceptionTests_data.h"

@interface TrustExceptionTests : TrustEvaluationTestCase
@end

@implementation TrustExceptionTests

static NSArray *certs = nil;
static NSDate *date = nil;

+ (void)setUp
{
    [super setUp];
    SecCertificateRef cert0 = SecCertificateCreateWithBytes(NULL, _exception_cert0, sizeof(_exception_cert0));
    SecCertificateRef cert1 = SecCertificateCreateWithBytes(NULL, _exception_cert1, sizeof(_exception_cert1));
    certs = @[ (__bridge id)cert0, (__bridge id)cert1 ];
    date = [NSDate dateWithTimeIntervalSinceReferenceDate:545000000.0]; /* April 9, 2018 at 1:53:20 PM PDT */

    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
}

#if !TARGET_OS_BRIDGE
// bridgeOS doesn't have a system root store
- (void)testPassingTrust
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("store.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    ok(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultUnspecified,
            "trust is kSecTrustResultUnspecified");
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}
#endif

- (void)testFailingTrust
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}

- (void)testNewTrustObjectSameFailure
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    /* new trust with the same failing policy and certs should pass */
    CFReleaseNull(trust);
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}

#if !TARGET_OS_BRIDGE
// bridgeOS always has an AnchorTrusted error due to lack of a system root store
- (void)testIntroduceNewAnchorTrustedFailure
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    // new AnchorTrusted failure
    ok_status(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)@[]), "set empty anchor list");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure");

    // fix AnchorTrusted failure
    ok_status(SecTrustSetAnchorCertificatesOnly(trust, false), "trust passed in anchors and system anchors");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}
#endif

- (void)testIntroduceNewExpiredFailure
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    // new expiry failure
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)[NSDate dateWithTimeIntervalSinceReferenceDate:667680000.0]),
              "set date to far future so certs are expired");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}

- (void)testNewTrustObjectNewHostnameFailure
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("store.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust with hostname mismatch");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    /* exceptions from the old trust evaluation should fail */
    SecTrustResultType trustResult;
    ok(SecTrustSetExceptions(trust, exceptions), "set old exceptions");
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);

    /* we should be able to get new exceptions and pass */
    CFReleaseNull(exceptions);
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}

- (void)testClearExceptions
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    XCTAssertFalse(SecTrustSetExceptions(trust, NULL));
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}

- (void)testWrongCertForExceptions
{
    SecTrustRef trust = NULL;
    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");

    SecTrustResultType trustResult;
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    ok_status(SecTrustGetTrustResult(trust, &trustResult));
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);
    CFDataRef exceptions;
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    /* new trust with the same failing policy and certs should pass */
    CFReleaseNull(trust);
    CFReleaseNull(policy);
    SecCertificateRef sscert0 = SecCertificateCreateWithBytes(NULL, _exception_self_signed, sizeof(_exception_self_signed));
    policy = SecPolicyCreateSSL(false, CFSTR("self-signed.ssltest.apple.com"));
    ok_status(SecTrustCreateWithCertificates(sscert0, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");
    XCTAssertFalse(SecTrustSetExceptions(trust, exceptions), "set exceptions fails for other cert");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure);

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}

#if TARGET_OS_IPHONE
- (void)testExtensionsEpoch
{
    SecTrustRef trust = NULL;
    SecTrustResultType trustResult;
    CFDataRef exceptions = NULL;

    SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("badstore.apple.com"));
    ok_status(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "set date");
    ok(exceptions = SecTrustCopyExceptions(trust), "create exceptions");

    /* Test the uninitialized extensions epoch. */
    CFErrorRef exceptionResetCountError = NULL;
    uint64_t exceptionResetCount = SecTrustGetExceptionResetCount(&exceptionResetCountError);
    ok(exceptionResetCount == 0, "exception reset count is uninitialized");
    is(SecTrustGetExceptionResetCount(&exceptionResetCountError), exceptionResetCount, "SecTrustGetExceptionResetCount is idempotent");
    ok(SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultProceed, "trust is kSecTrustResultProceed");

    /* Test increasing the extensions epoch. */
    exceptionResetCountError = NULL;
    ok_status(SecTrustIncrementExceptionResetCount(&exceptionResetCountError), "increase exception reset count");
    exceptionResetCountError = NULL;
    is(SecTrustGetExceptionResetCount(&exceptionResetCountError), 1 + exceptionResetCount, "exception reset count is 1 + previous count");

    /* Test trust evaluation under a future extensions epoch. */
    ok(!SecTrustSetExceptions(trust, exceptions), "set exceptions");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust");
    is_status(trustResult, kSecTrustResultRecoverableTrustFailure, "trust is kSecTrustResultRecoverableTrustFailure");

    CFReleaseNull(trust);
    CFReleaseNull(policy);
    CFReleaseNull(exceptions);
}
#endif

#if !TARGET_OS_BRIDGE
// bridgeOS doesn't support Valid
- (void)testFatalResultsNonOverride
{
    id root = [self SecCertificateCreateFromPEMResource:@"ca-ki" subdirectory:@"si-88-sectrust-valid-data"];
    id revokedLeaf = [self SecCertificateCreateFromPEMResource:@"leaf-ki-revoked1" subdirectory:@"si-88-sectrust-valid-data"];
    TestTrustEvaluation *eval = [[TestTrustEvaluation alloc] initWithCertificates:@[revokedLeaf, root] policies:nil];
    [eval setVerifyDate:[NSDate dateWithTimeIntervalSinceReferenceDate:542400000.0]]; // March 10, 2018 at 10:40:00 AM PST
    [eval setAnchors:@[root]];
    XCTAssertFalse([eval evaluate:nil]);
    XCTAssertEqual(eval.trustResult, kSecTrustResultFatalTrustFailure);

    /* try to set exceptions on the trust and ensure it still fails */
    NSData *exceptions = CFBridgingRelease(SecTrustCopyExceptions(eval.trust));
    XCTAssertNotNil(exceptions);
    XCTAssert(SecTrustSetExceptions(eval.trust, (__bridge CFDataRef)exceptions));
    XCTAssertFalse([eval evaluate:nil]);
    XCTAssertEqual(eval.trustResult, kSecTrustResultFatalTrustFailure);
}
#endif

@end
