/*
 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */

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

#include "../TestMacroConversions.h"
#include "../TrustEvaluationTestHelpers.h"
#include "TrustFrameworkTestCase.h"
#include "TrustInterfaceTests_data.h"

@interface TrustInterfaceTests : TrustFrameworkTestCase
@end

@implementation TrustInterfaceTests

- (void)testCreateWithCertificates {
    SecTrustRef trust = NULL;
    CFArrayRef certs = NULL;
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL;

    isnt(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)),
         NULL, "create cert0");
    isnt(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)),
         NULL, "create cert1");
    const void *v_certs[] = { cert0, cert1 };

    certs = CFArrayCreate(NULL, v_certs, array_size(v_certs), &kCFTypeArrayCallBacks);
    policy = SecPolicyCreateSSL(false, NULL);

    /* SecTrustCreateWithCertificates failures. */
    is_status(SecTrustCreateWithCertificates(kCFBooleanTrue, policy, &trust),
              errSecParam, "create trust with boolean instead of cert");
    is_status(SecTrustCreateWithCertificates(cert0, kCFBooleanTrue, &trust),
              errSecParam, "create trust with boolean instead of policy");

    NSArray *badValues = @[ [NSData dataWithBytes:_c0 length:sizeof(_c0)], [NSData dataWithBytes:_c1 length:sizeof(_c1)]];
    XCTAssert(errSecParam == SecTrustCreateWithCertificates((__bridge CFArrayRef)badValues, policy, &trust),
              "create trust with array of datas instead of certs");
    XCTAssert(errSecParam == SecTrustCreateWithCertificates(certs, (__bridge CFArrayRef)badValues, &trust),
              "create trust with array of datas instead of policies");

    /* SecTrustCreateWithCertificates using array of certs. */
    ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust");

    CFReleaseNull(trust);
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(certs);
    CFReleaseNull(policy);
}

- (void)testGetCertificate {
    SecTrustRef trust = NULL;
    CFArrayRef certs = NULL;
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL;

    isnt(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)),
         NULL, "create cert0");
    isnt(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)),
         NULL, "create cert1");
    const void *v_certs[] = { cert0, cert1 };

    certs = CFArrayCreate(NULL, v_certs, array_size(v_certs), &kCFTypeArrayCallBacks);
    policy = SecPolicyCreateSSL(false, NULL);

    ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust");

    /* NOTE: prior to <rdar://11810677 SecTrustGetCertificateCount would return 1 at this point.
     * Now, however, we do an implicit SecTrustEvaluate to build the chain if it has not yet been
     * evaluated, so we now expect the full chain length. */
#if !TARGET_OS_BRIDGE
    is(SecTrustGetCertificateCount(trust), 3, "cert count is 3");
#else
    /* bridgeOS has no system anchors, so trustd never finds the root */
    is(SecTrustGetCertificateCount(trust), 2, "cert count is 2");
#endif
    is(SecTrustGetCertificateAtIndex(trust, 0), cert0, "cert 0 is leaf");

    CFReleaseNull(trust);
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(certs);
    CFReleaseNull(policy);
}

- (void)testRestoreOS {
    SecTrustRef trust = NULL;
    CFArrayRef certs = NULL;
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL;
    CFDateRef date = NULL;
    SecTrustResultType trustResult = kSecTrustResultOtherError;

    /* Apr 14 2018. */
    isnt(date = CFDateCreateForGregorianZuluMoment(NULL, 2018, 4, 14, 12, 0, 0),
         NULL, "create verify date");
    if (!date) { goto errOut; }
    
    isnt(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)),
         NULL, "create cert0");
    isnt(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)),
         NULL, "create cert1");
    const void *v_certs[] = { cert0, cert1 };

    certs = CFArrayCreate(NULL, v_certs, array_size(v_certs), &kCFTypeArrayCallBacks);
    policy = SecPolicyCreateSSL(false, NULL);

    ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, date), "set date");

    // Test Restore OS environment
    SecServerSetTrustdMachServiceName("com.apple.security.doesn't-exist");
    ok_status(SecTrustGetTrustResult(trust, &trustResult), "evaluate trust without securityd running");
    is_status(trustResult, kSecTrustResultInvalid, "trustResult is kSecTrustResultInvalid");
    is(SecTrustGetCertificateCount(trust), 1, "cert count is 1 without securityd running");
    SecKeyRef pubKey = NULL;
    ok(pubKey = SecTrustCopyPublicKey(trust), "copy public key without securityd running");
    CFReleaseNull(pubKey);
    SecServerSetTrustdMachServiceName("com.apple.trustd");
    // End of Restore OS environment tests

errOut:
    CFReleaseNull(trust);
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(certs);
    CFReleaseNull(policy);
}

- (void)testAnchorCerts {
    SecTrustRef trust = NULL;
    CFArrayRef certs = NULL, anchors = NULL;
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL;
    CFDateRef date = NULL;

    /* Apr 14 2018. */
    isnt(date = CFDateCreateForGregorianZuluMoment(NULL, 2018, 4, 14, 12, 0, 0),
         NULL, "create verify date");
    if (!date) { goto errOut; }
    
    isnt(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)),
         NULL, "create cert0");
    isnt(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)),
         NULL, "create cert1");
    const void *v_certs[] = { cert0, cert1 };

    certs = CFArrayCreate(NULL, v_certs, array_size(v_certs), &kCFTypeArrayCallBacks);
    policy = SecPolicyCreateSSL(false, NULL);

    ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "create trust");
    ok_status(SecTrustSetVerifyDate(trust, date), "set date");

    anchors = CFArrayCreate(NULL, (const void **)&cert1, 1, &kCFTypeArrayCallBacks);
    ok_status(SecTrustSetAnchorCertificates(trust, anchors), "set anchors");
    XCTAssert(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    is(SecTrustGetCertificateCount(trust), 2, "cert count is 2");

    CFReleaseNull(anchors);
    anchors = CFArrayCreate(NULL, NULL, 0, NULL);
    ok_status(SecTrustSetAnchorCertificates(trust, anchors), "set empty anchors list");
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    CFReleaseNull(anchors);

    ok_status(SecTrustSetAnchorCertificatesOnly(trust, false), "trust passed in anchors and system anchors");
#if !TARGET_OS_BRIDGE
    XCTAssert(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
#else
    /* BridgeOS has no system anchors */
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
#endif

    ok_status(SecTrustSetAnchorCertificatesOnly(trust, true), "only trust passed in anchors (default)");
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");

    ok_status(SecTrustSetAnchorCertificates(trust, NULL), "reset anchors");
#if !TARGET_OS_BRIDGE
    XCTAssert(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    is(SecTrustGetCertificateCount(trust), 3, "cert count is 3");
#else
    /* bridgeOS has no system anchors */
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    is(SecTrustGetCertificateCount(trust), 2, "cert count is 2");
#endif

    XCTAssert(errSecParam == SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)@[ [NSData dataWithBytes:_c0 length:sizeof(_c0)]]),
              "set anchor with data instead of certificate");
#if !TARGET_OS_BRIDGE
    XCTAssert(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    is(SecTrustGetCertificateCount(trust), 3, "cert count is 3");
#else
    /* bridgeOS has no system anchors */
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");
    is(SecTrustGetCertificateCount(trust), 2, "cert count is 2");
#endif

errOut:
    CFReleaseNull(trust);
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(certs);
    CFReleaseNull(anchors);
    CFReleaseNull(policy);
}

- (void)testInputCertificates {
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    CFArrayRef certificates = NULL;

    require(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)), errOut);
    require(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)), errOut);
    require(policy = SecPolicyCreateBasicX509(), errOut);
    require_noerr(SecTrustCreateWithCertificates(cert0, policy, &trust), errOut);

    ok_status(SecTrustCopyInputCertificates(trust, &certificates), "SecTrustCopyInputCertificates failed");
    is(CFArrayGetCount(certificates), 1, "got too many input certs back");
    is(CFArrayGetValueAtIndex(certificates, 0), cert0, "wrong input cert");
    CFReleaseNull(certificates);

    XCTAssert(errSecParam == SecTrustAddToInputCertificates(trust, (__bridge CFDataRef)[NSData dataWithBytes:_c1 length:sizeof(_c1)]),
              "add data instead of cert");
    XCTAssert(errSecParam == SecTrustAddToInputCertificates(trust, (__bridge CFArrayRef)@[[NSData dataWithBytes:_c1 length:sizeof(_c1)]]),
              "add array of data instead of cert");

    ok_status(SecTrustAddToInputCertificates(trust, cert0), "SecTrustAddToInputCertificates failed");
    ok_status(SecTrustCopyInputCertificates(trust, &certificates), "SecTrustCopyInputCertificates failed");
    is(CFArrayGetCount(certificates), 2, "got wrong number of input certs back");
    is(CFArrayGetValueAtIndex(certificates, 0), cert0, "wrong input cert0");
    is(CFArrayGetValueAtIndex(certificates, 1), cert0, "wrong input cert0");

    ok_status(SecTrustAddToInputCertificates(trust, (__bridge CFArrayRef)@[ (__bridge id)cert1]), "SecTrustAddToInputCertificates failed");
    ok_status(SecTrustCopyInputCertificates(trust, &certificates), "SecTrustCopyInputCertificates failed");
    is(CFArrayGetCount(certificates), 3, "got wrong number of input certs back");
    is(CFArrayGetValueAtIndex(certificates, 0), cert0, "wrong input cert0");
    is(CFArrayGetValueAtIndex(certificates, 1), cert0, "wrong input cert0");
    is(CFArrayGetValueAtIndex(certificates, 2), cert1, "wrong input cert1");
#if !TARGET_OS_BRIDGE
    is(SecTrustGetCertificateCount(trust), 3, "cert count is 3");
#else
    /* bridgeOS has no system anchors, so trustd never finds the root */
    is(SecTrustGetCertificateCount(trust), 2, "cert count is 2");
#endif

errOut:
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(policy);
    CFReleaseNull(trust);
    CFReleaseNull(certificates);
}

- (void)testSetPolicies {
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL, replacementPolicy = NULL;
    SecTrustRef trust = NULL;
    CFDateRef date = NULL;
    CFArrayRef anchors = NULL, policies = NULL;

    require(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)), errOut);
    require(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)), errOut);
    require(policy = SecPolicyCreateSSL(true, CFSTR("example.com")), errOut);
    require_noerr(SecTrustCreateWithCertificates(cert0, policy, &trust), errOut);

    isnt(date = CFDateCreateForGregorianZuluMoment(NULL, 2018, 4, 14, 12, 0, 0),
         NULL, "create verify date");
    ok_status(SecTrustSetVerifyDate(trust, date), "set date");
    anchors = CFArrayCreate(NULL, (const void **)&cert1, 1, &kCFTypeArrayCallBacks);
    ok_status(SecTrustSetAnchorCertificates(trust, anchors), "set anchors");
    XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");

    /* replace with one policy */
    require(replacementPolicy = SecPolicyCreateSSL(true, CFSTR("store.apple.com")), errOut);
    ok_status(SecTrustSetPolicies(trust, replacementPolicy));
    XCTAssert(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");

    /* replace with policy array */
    CFReleaseNull(trust);
    require_noerr(SecTrustCreateWithCertificates(cert0, policy, &trust), errOut);
    isnt(date = CFDateCreateForGregorianZuluMoment(NULL, 2018, 4, 14, 12, 0, 0),
         NULL, "create verify date");
    ok_status(SecTrustSetVerifyDate(trust, date), "set date");
    ok_status(SecTrustSetAnchorCertificates(trust, anchors), "set anchors");
    policies = CFArrayCreate(kCFAllocatorDefault, (CFTypeRef*)&replacementPolicy, 1, &kCFTypeArrayCallBacks);
    ok_status(SecTrustSetPolicies(trust, policies));
    XCTAssert(SecTrustEvaluateWithError(trust, NULL), "evaluate trust");

    /* replace with non-policy */
    XCTAssert(errSecParam == SecTrustSetPolicies(trust, cert0), "set with cert instead of policy");
    XCTAssert(errSecParam == SecTrustSetPolicies(trust, anchors), "set with array of certs instead of polciies");

    /* copy policies */
    CFReleaseNull(policies);
    ok_status(SecTrustCopyPolicies(trust, &policies));
    XCTAssertEqual(CFArrayGetCount(policies), 1, "one policy set");
    XCTAssertTrue(CFEqual(CFArrayGetValueAtIndex(policies, 0), replacementPolicy), "set policy is replacement policy");

errOut:
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(policy);
    CFReleaseNull(trust);
    CFReleaseNull(date);
    CFReleaseNull(anchors);
    CFReleaseNull(replacementPolicy);
    CFReleaseNull(policies);
}

- (void)testAsyncTrustEval {
    SecCertificateRef cert0 = NULL, cert1 = NULL;
    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    CFArrayRef certificates = NULL;
    CFDateRef date = NULL;
    dispatch_queue_t queue = dispatch_queue_create("com.apple.trusttests.async", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);

    XCTestExpectation *blockExpectation = [self expectationWithDescription:@"callback occurs"];

    require(cert0 = SecCertificateCreateWithBytes(NULL, _c0, sizeof(_c0)), errOut);
    require(cert1 = SecCertificateCreateWithBytes(NULL, _c1, sizeof(_c1)), errOut);
    const void *v_certs[] = {
        cert0,
        cert1
    };
    certificates = CFArrayCreate(NULL, v_certs,
                                 array_size(v_certs),
                                 &kCFTypeArrayCallBacks);

    require(policy = SecPolicyCreateBasicX509(), errOut);
    require_noerr(SecTrustCreateWithCertificates(certificates, policy, &trust), errOut);

    /* Jul 30 2014. */
    require(date = CFDateCreateForGregorianZuluMoment(NULL, 2014, 7, 30, 12, 0, 0), errOut);
    require_noerr(SecTrustSetVerifyDate(trust, date), errOut);

    /* This shouldn't crash. */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    ok_status(SecTrustEvaluateAsync(trust, queue, ^(SecTrustRef  _Nonnull trustRef, SecTrustResultType trustResult) {
        if ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified)) {
            // Evaluation succeeded!
            SecKeyRef publicKey = SecTrustCopyPublicKey(trustRef);
            CFReleaseSafe(publicKey);
        } else if (trustResult == kSecTrustResultRecoverableTrustFailure) {
            // Evaluation failed, but may be able to recover . . .
        } else {
            // Evaluation failed
        }
        [blockExpectation fulfill];
    }), "evaluate trust asynchronously");
    CFReleaseNull(trust);
#pragma clang diagnostic pop

    [self waitForExpectations:@[blockExpectation] timeout:1.0];

errOut:
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(policy);
    CFReleaseNull(certificates);
    CFReleaseNull(date);
}

- (void)testExpiredOnly {
    SecCertificateRef cert0 = NULL, cert1 = NULL, cert2 = NULL;
    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    CFArrayRef certificates = NULL, roots = NULL;
    CFDateRef date = NULL;

    require(cert0 = SecCertificateCreateWithBytes(NULL, _expired_badssl, sizeof(_expired_badssl)), errOut);
    require(cert1 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_dvss, sizeof(_comodo_rsa_dvss)), errOut);
    require(cert2 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_root, sizeof(_comodo_rsa_root)), errOut);

    const void *v_certs[] = {cert0, cert1 };
    certificates = CFArrayCreate(NULL, v_certs,
                                 array_size(v_certs),
                                 &kCFTypeArrayCallBacks);

    const void *v_roots[] = { cert2 };
    roots = CFArrayCreate(NULL, v_roots,
                          array_size(v_roots),
                          &kCFTypeArrayCallBacks);

    require(policy = SecPolicyCreateSSL(true, CFSTR("expired.badssl.com")), errOut);
    require_noerr(SecTrustCreateWithCertificates(certificates, policy, &trust), errOut);
    require_noerr(SecTrustSetAnchorCertificates(trust, roots), errOut);

    /* Mar 21 2017 (cert expired in 2015, so this will cause a validity error.) */
    require(date = CFDateCreateForGregorianZuluMoment(NULL, 2017, 3, 21, 12, 0, 0), errOut);
    require_noerr(SecTrustSetVerifyDate(trust, date), errOut);

    /* SecTrustIsExpiredOnly implicitly evaluates the trust */
    ok(SecTrustIsExpiredOnly(trust), "REGRESSION: has new error as well as expiration");

    CFReleaseNull(policy);
    require(policy = SecPolicyCreateSSL(true, CFSTR("expired.terriblessl.com")), errOut);
    require_noerr(SecTrustSetPolicies(trust, policy), errOut);
    /* expect a hostname mismatch as well as expiration */
    ok(!SecTrustIsExpiredOnly(trust), "REGRESSION: should have found multiple errors");

errOut:
    CFReleaseNull(trust);
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(cert2);
    CFReleaseNull(policy);
    CFReleaseNull(certificates);
    CFReleaseNull(roots);
    CFReleaseNull(date);
}

- (void)testEvaluateWithError {
    SecCertificateRef cert0 = NULL, cert1 = NULL, cert2 = NULL;
    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    CFArrayRef certificates = NULL, roots = NULL;
    CFDateRef date = NULL, validDate = NULL;
    CFErrorRef error = NULL;

    require(cert0 = SecCertificateCreateWithBytes(NULL, _expired_badssl, sizeof(_expired_badssl)), errOut);
    require(cert1 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_dvss, sizeof(_comodo_rsa_dvss)), errOut);
    require(cert2 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_root, sizeof(_comodo_rsa_root)), errOut);

    const void *v_certs[] = {
        cert0,
        cert1,
        cert2,
    };
    certificates = CFArrayCreate(NULL, v_certs,
                                 array_size(v_certs),
                                 &kCFTypeArrayCallBacks);

    const void *v_roots[] = {
        cert2
    };
    roots = CFArrayCreate(NULL, v_roots,
                          array_size(v_roots),
                          &kCFTypeArrayCallBacks);

    require(policy = SecPolicyCreateSSL(true, CFSTR("expired.badssl.com")), errOut);
    require_noerr(SecTrustCreateWithCertificates(certificates, policy, &trust), errOut);
    require_noerr(SecTrustSetAnchorCertificates(trust, roots), errOut);

    /* April 10 2015 (cert expired in 2015) */
    require(validDate = CFDateCreateForGregorianZuluMoment(NULL, 2015, 4, 10, 12, 0, 0), errOut);
    require_noerr(SecTrustSetVerifyDate(trust, validDate), errOut);

    is(SecTrustEvaluateWithError(trust, &error), true, "wrong result for valid cert");
    is(error, NULL, "set error for passing trust evaluation");
    CFReleaseNull(error);

    /* Mar 21 2017 (cert expired in 2015, so this will cause a validity error.) */
    require(date = CFDateCreateForGregorianZuluMoment(NULL, 2017, 3, 21, 12, 0, 0), errOut);
    require_noerr(SecTrustSetVerifyDate(trust, date), errOut);

    /* expect expiration error */
    is(SecTrustEvaluateWithError(trust, &error), false, "wrong result for expired cert");
    isnt(error, NULL, "failed to set error for failing trust evaluation");
    is(CFErrorGetCode(error), errSecCertificateExpired, "Got wrong error code for evaluation");
    CFReleaseNull(error);

    CFReleaseNull(policy);
    require(policy = SecPolicyCreateSSL(true, CFSTR("expired.terriblessl.com")), errOut);
    require_noerr(SecTrustSetPolicies(trust, policy), errOut);

    /* expect a hostname mismatch as well as expiration; hostname mismatch must be a higher priority */
    is(SecTrustEvaluateWithError(trust, &error), false, "wrong result for expired cert with hostname mismatch");
    isnt(error, NULL, "failed to set error for failing trust evaluation");
    is(CFErrorGetCode(error), errSecHostNameMismatch, "Got wrong error code for evaluation");
    CFReleaseNull(error);

    /* expect only a hostname mismatch*/
    require_noerr(SecTrustSetVerifyDate(trust, validDate), errOut);
    is(SecTrustEvaluateWithError(trust, &error), false, "wrong result for valid cert with hostname mismatch");
    isnt(error, NULL, "failed to set error for failing trust evaluation");
    is(CFErrorGetCode(error), errSecHostNameMismatch, "Got wrong error code for evaluation");
    CFReleaseNull(error);

    /* pinning failure */
    CFReleaseNull(policy);
    require(policy = SecPolicyCreateAppleSSLPinned(CFSTR("test"), CFSTR("expired.badssl.com"),
                                                   NULL, CFSTR("1.2.840.113635.100.6.27.1")), errOut);
    require_noerr(SecTrustSetPolicies(trust, policy), errOut);

    is(SecTrustEvaluateWithError(trust, &error), false, "wrong result for valid cert with pinning failure");
    isnt(error, NULL, "failed to set error for failing trust evaluation");
    CFIndex errorCode = CFErrorGetCode(error);
    // failed checks: AnchorApple, LeafMarkerOid, or IntermediateMarkerOid
    ok(errorCode == errSecMissingRequiredExtension || errorCode == errSecInvalidRoot, "Got wrong error code for evaluation");
    CFReleaseNull(error);

    /* trust nothing, trust errors higher priority than hostname mismatch */
    CFReleaseNull(policy);
    require(policy = SecPolicyCreateSSL(true, CFSTR("expired.terriblessl.com")), errOut);
    require_noerr(SecTrustSetPolicies(trust, policy), errOut);

    CFReleaseNull(roots);
    roots = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
    require_noerr(SecTrustSetAnchorCertificates(trust, roots), errOut);
    is(SecTrustEvaluateWithError(trust, &error), false, "wrong result for expired cert with hostname mismatch");
    isnt(error, NULL, "failed to set error for failing trust evaluation");
    is(CFErrorGetCode(error), errSecNotTrusted, "Got wrong error code for evaluation");
    CFReleaseNull(error);

errOut:
    CFReleaseNull(trust);
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(cert2);
    CFReleaseNull(policy);
    CFReleaseNull(certificates);
    CFReleaseNull(roots);
    CFReleaseNull(date);
    CFReleaseNull(validDate);
    CFReleaseNull(error);
}

- (void)testSerialization {
    SecCertificateRef cert0 = NULL, cert1 = NULL, root = NULL;
    SecTrustRef trust = NULL, deserializedTrust = NULL;
    SecPolicyRef policy = NULL;
    CFArrayRef certs = NULL, anchors = NULL, deserializedCerts = NULL;
    CFDateRef date = NULL;
    CFDataRef serializedTrust = NULL;
    CFErrorRef error = NULL;

    require_action(cert0 = SecCertificateCreateWithBytes(NULL, _expired_badssl, sizeof(_expired_badssl)), errOut,
                   fail("unable to create cert"));
    require_action(cert1 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_dvss, sizeof(_comodo_rsa_dvss)), errOut,
                   fail("unable to create cert"));
    require_action(root = SecCertificateCreateWithBytes(NULL, _comodo_rsa_root, sizeof(_comodo_rsa_root)), errOut,
                   fail("unable to create cert"));

    const void *v_certs[] = { cert0, cert1 };
    require_action(certs = CFArrayCreate(NULL, v_certs, array_size(v_certs), &kCFTypeArrayCallBacks), errOut,
                   fail("unable to create array"));
    require_action(anchors = CFArrayCreate(NULL, (const void **)&root, 1, &kCFTypeArrayCallBacks), errOut,
                   fail("unable to create anchors array"));
    require_action(date = CFDateCreateForGregorianZuluMoment(NULL, 2015, 4, 10, 12, 0, 0), errOut, fail("unable to create date"));
    
    require_action(policy = SecPolicyCreateBasicX509(), errOut, fail("unable to create policy"));

    ok_status(SecTrustCreateWithCertificates(certs, policy, &trust), "failed to create trust");
    require_noerr_action(SecTrustSetAnchorCertificates(trust, anchors), errOut,
                         fail("unable to set anchors"));
    require_noerr_action(SecTrustSetVerifyDate(trust, date), errOut, fail("unable to set verify date"));
    
    ok(serializedTrust = SecTrustSerialize(trust, NULL), "failed to serialize trust");
    ok(deserializedTrust = SecTrustDeserialize(serializedTrust, NULL), "Failed to deserialize trust");
    CFReleaseNull(serializedTrust);

    require_noerr_action(SecTrustCopyCustomAnchorCertificates(deserializedTrust, &deserializedCerts), errOut,
                         fail("unable to get anchors from deserialized trust"));
    ok(CFEqual(anchors, deserializedCerts), "Failed to get the same anchors after serialization/deserialization");
    CFReleaseNull(deserializedCerts);
    
    require_noerr_action(SecTrustCopyInputCertificates(trust, &deserializedCerts), errOut,
                         fail("unable to get input certificates from deserialized trust"));
    ok(CFEqual(certs, deserializedCerts), "Failed to get same input certificates after serialization/deserialization");
    CFReleaseNull(deserializedCerts);

    /* correct API behavior */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
    is(SecTrustSerialize(NULL, &error), NULL, "serialize succeeded with null input");
    is(CFErrorGetCode(error), errSecParam, "Incorrect error code for bad serialization input");
    CFReleaseNull(error);
    is(SecTrustDeserialize(NULL, &error), NULL, "deserialize succeeded with null input");
    is(CFErrorGetCode(error), errSecParam, "Incorrect error code for bad deserialization input");
    CFReleaseNull(error);
#pragma clang diagnostic pop

errOut:
    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(root);
    CFReleaseNull(certs);
    CFReleaseNull(anchors);
    CFReleaseNull(date);
    CFReleaseNull(policy);
    CFReleaseNull(trust);
    CFReleaseNull(deserializedTrust);
}

- (void)testSerializationSCTs {
    SecCertificateRef certA = NULL, certCA_alpha = NULL, certCA_beta = NULL;
    NSURL *trustedLogsURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"CTlogs"
                                                                     withExtension:@"plist"
                                                                      subdirectory:@"si-82-sectrust-ct-data"];
    CFArrayRef trustedLogs= CFBridgingRetain([NSArray arrayWithContentsOfURL:trustedLogsURL]);
    SecTrustRef trust = NULL, deserializedTrust = NULL;
    SecPolicyRef policy = SecPolicyCreateBasicX509(); // <rdar://problem/50066309> Need to generate new certs for CTTests
    NSData *proofA_1 = NULL, *proofA_2 = NULL;
    NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:447450000.0]; // March 7, 2015 at 11:40:00 AM PST
    CFErrorRef error = NULL;

    NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"serverA" withExtension:@".cer" subdirectory:@"si-82-sectrust-ct-data"];
    NSData *certData = [NSData dataWithContentsOfURL:url];
    isnt(certA = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certData),  NULL, "create certA");

    url = [[NSBundle bundleForClass:[self class]] URLForResource:@"CA_beta" withExtension:@".cer" subdirectory:@"si-82-sectrust-ct-data"];
    certData = [NSData dataWithContentsOfURL:url];
    isnt(certCA_alpha  = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certData),  NULL, "create ca-alpha cert");

    NSArray *anchors = @[ (__bridge id)certCA_alpha ];

    url = [[NSBundle bundleForClass:[self class]] URLForResource:@"serverA_proof_Alfa_3" withExtension:@".bin" subdirectory:@"si-82-sectrust-ct-data"];
    isnt(proofA_1 = [NSData dataWithContentsOfURL:url], NULL, "creat proofA_1");
    url = [[NSBundle bundleForClass:[self class]] URLForResource:@"serverA_proof_Bravo_3" withExtension:@".bin" subdirectory:@"si-82-sectrust-ct-data"];
    isnt(proofA_2 = [NSData dataWithContentsOfURL:url], NULL, "creat proofA_2");
    NSArray *scts = @[ proofA_1, proofA_2 ];

    /* Make a SecTrustRef and then serialize it */
    ok_status(SecTrustCreateWithCertificates(certA, policy, &trust), "failed to create trust object");
    ok_status(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)anchors), "failed to set anchors");
    ok_status(SecTrustSetTrustedLogs(trust, trustedLogs), "failed to set trusted logs");
    ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), "failed to set verify date");
    ok_status(SecTrustSetSignedCertificateTimestamps(trust, (__bridge CFArrayRef)scts), "failed to set SCTS");

    NSData *serializedTrust = CFBridgingRelease(SecTrustSerialize(trust, &error));
    isnt(serializedTrust, NULL, "failed to serialize trust: %@", error);

    /* Evaluate it to make sure it's CT */
    ok(SecTrustEvaluateWithError(trust, &error), "failed to evaluate trust: %@", error);
    NSDictionary *results = CFBridgingRelease(SecTrustCopyResult(trust));
    isnt(results[(__bridge NSString*)kSecTrustCertificateTransparency], NULL, "failed get CT result");
    ok([results[(__bridge NSString*)kSecTrustCertificateTransparency] boolValue], "CT failed");

    /* Make a new trust object by deserializing the previous trust object */
    ok(deserializedTrust = SecTrustDeserialize((__bridge CFDataRef)serializedTrust, &error), "failed to deserialize trust: %@", error);

    /* Evaluate the new one to make sure it's CT (because the SCTs were serialized) */
    ok(SecTrustEvaluateWithError(deserializedTrust, &error), "failed to evaluate trust: %@", error);
    results = CFBridgingRelease(SecTrustCopyResult(deserializedTrust));
    isnt(results[(__bridge NSString*)kSecTrustCertificateTransparency], NULL, "failed get CT result");
    ok([results[(__bridge NSString*)kSecTrustCertificateTransparency] boolValue], "CT failed");

    CFReleaseNull(certA);
    CFReleaseNull(certCA_alpha);
    CFReleaseNull(certCA_beta);
    CFReleaseNull(trustedLogs);
    CFReleaseNull(policy);
    CFReleaseNull(trust);
    CFReleaseNull(deserializedTrust);
    CFReleaseNull(error);
}

- (void)testTLSAnalytics {
    xpc_object_t metric = xpc_dictionary_create(NULL, NULL, 0);
    ok(metric != NULL);

    const char *TLS_METRIC_PROCESS_IDENTIFIER = "process";
    const char *TLS_METRIC_CIPHERSUITE = "cipher_name";
    const char *TLS_METRIC_PROTOCOL_VERSION = "version";
    const char *TLS_METRIC_SESSION_RESUMED = "resumed";

    xpc_dictionary_set_string(metric, TLS_METRIC_PROCESS_IDENTIFIER, "super awesome unit tester");
    xpc_dictionary_set_uint64(metric, TLS_METRIC_CIPHERSUITE, 0x0304);
    xpc_dictionary_set_uint64(metric, TLS_METRIC_PROTOCOL_VERSION, 0x0304);
    xpc_dictionary_set_bool(metric, TLS_METRIC_SESSION_RESUMED, false);
    // ... TLS would fill in the rest

    // Invoke the callback
    CFErrorRef error = NULL;
    bool reported = SecTrustReportTLSAnalytics(CFSTR("TLSConnectionEvent"), metric, &error);
    ok(reported, "Failed to report analytics with error %@", error);
}

- (void)testEvaluateFastAsync
{
    SecCertificateRef cert0 = NULL, cert1 = NULL, cert2 = NULL;
    SecPolicyRef policy = NULL;
    __block SecTrustRef trust = NULL;
    NSArray *certificates = nil, *roots = nil;
    NSDate *validDate = nil;
    dispatch_queue_t queue = dispatch_queue_create("com.apple.trusttests.EvalAsync", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
    __block XCTestExpectation *blockExpectation = [self expectationWithDescription:@"callback occurs"];

    cert0 = SecCertificateCreateWithBytes(NULL, _expired_badssl, sizeof(_expired_badssl));
    cert1 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_dvss, sizeof(_comodo_rsa_dvss));
    cert2 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_root, sizeof(_comodo_rsa_root));

    certificates = @[ (__bridge id)cert0, (__bridge id)cert1 ];
    roots = @[ (__bridge id)cert2 ];

    policy = SecPolicyCreateSSL(true, CFSTR("expired.badssl.com"));
    XCTAssert(errSecSuccess == SecTrustCreateWithCertificates((__bridge CFArrayRef)certificates, policy, &trust));
    XCTAssert(errSecSuccess == SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)roots));

    /* April 10 2015 (cert expired in 2015) */
    validDate = CFBridgingRelease(CFDateCreateForGregorianZuluMoment(NULL, 2015, 4, 10, 12, 0, 0));
    XCTAssert(errSecSuccess == SecTrustSetVerifyDate(trust, (__bridge CFDateRef)validDate));

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
    XCTAssert(errSecParam == SecTrustEvaluateFastAsync(trust, NULL, ^(SecTrustRef  _Nonnull trustRef, SecTrustResultType trustResult) {
        XCTAssert(false, "callback called with invalid parameter");
    }));
#pragma clang diagnostic pop

    /* expect success */
    dispatch_async(queue, ^{
        XCTAssert(errSecSuccess == SecTrustEvaluateFastAsync(trust, queue, ^(SecTrustRef  _Nonnull trustRef, SecTrustResultType trustResult) {
            XCTAssert(trustRef != NULL);
            XCTAssert(trustResult == kSecTrustResultUnspecified);
            [blockExpectation fulfill];
        }));
    });

    [self waitForExpectations:@[blockExpectation] timeout:1.0];

    /* Mar 21 2017 (cert expired in 2015, so this will cause a validity error.) */
    validDate = CFBridgingRelease(CFDateCreateForGregorianZuluMoment(NULL, 2017, 3, 21, 12, 0, 0));
    XCTAssert(errSecSuccess == SecTrustSetVerifyDate(trust, (__bridge CFDateRef)validDate));

    /* expect failure */
    blockExpectation = [self expectationWithDescription:@"callback occurs"];
    dispatch_async(queue, ^{
        XCTAssert(errSecSuccess == SecTrustEvaluateFastAsync(trust, queue, ^(SecTrustRef  _Nonnull trustRef, SecTrustResultType trustResult) {
            XCTAssert(trustRef != NULL);
            XCTAssert(trustResult == kSecTrustResultRecoverableTrustFailure);
            [blockExpectation fulfill];
        }));
    });

    [self waitForExpectations:@[blockExpectation] timeout:1.0];

    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(cert2);
    CFReleaseNull(policy);
    CFReleaseNull(trust);
}

- (void)testEvaluateAsyncWithError
{
    SecCertificateRef cert0 = NULL, cert1 = NULL, cert2 = NULL;
    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    NSArray *certificates = nil, *roots = nil;
    NSDate *validDate = nil;
    dispatch_queue_t queue = dispatch_queue_create("com.apple.trusttests.EvalAsync", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
    __block XCTestExpectation *blockExpectation = [self expectationWithDescription:@"callback occurs"];

    cert0 = SecCertificateCreateWithBytes(NULL, _expired_badssl, sizeof(_expired_badssl));
    cert1 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_dvss, sizeof(_comodo_rsa_dvss));
    cert2 = SecCertificateCreateWithBytes(NULL, _comodo_rsa_root, sizeof(_comodo_rsa_root));

    certificates = @[ (__bridge id)cert0, (__bridge id)cert1 ];
    roots = @[ (__bridge id)cert2 ];

    policy = SecPolicyCreateSSL(true, CFSTR("expired.badssl.com"));
    XCTAssert(errSecSuccess == SecTrustCreateWithCertificates((__bridge CFArrayRef)certificates, policy, &trust));
    XCTAssert(errSecSuccess == SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)roots));

    /* April 10 2015 (cert expired in 2015) */
    validDate = CFBridgingRelease(CFDateCreateForGregorianZuluMoment(NULL, 2015, 4, 10, 12, 0, 0));
    XCTAssert(errSecSuccess == SecTrustSetVerifyDate(trust, (__bridge CFDateRef)validDate));

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
    XCTAssert(errSecParam == SecTrustEvaluateAsyncWithError(trust, NULL, ^(SecTrustRef  _Nonnull trustRef, bool result, CFErrorRef  _Nullable error) {
        XCTAssert(false, "callback called with invalid parameter");
    }));
#pragma clang diagnostic pop

    /* expect success */
    dispatch_async(queue, ^{
        XCTAssert(errSecSuccess == SecTrustEvaluateAsyncWithError(trust, queue, ^(SecTrustRef  _Nonnull trustRef, bool result, CFErrorRef  _Nullable error) {
            XCTAssert(trust != NULL);
            XCTAssertTrue(result);
            XCTAssert(error == NULL);
            [blockExpectation fulfill];
        }));
    });

    [self waitForExpectations:@[blockExpectation] timeout:1.0];

    /* Mar 21 2017 (cert expired in 2015, so this will cause a validity error.) */
    validDate = CFBridgingRelease(CFDateCreateForGregorianZuluMoment(NULL, 2017, 3, 21, 12, 0, 0));
    XCTAssert(errSecSuccess == SecTrustSetVerifyDate(trust, (__bridge CFDateRef)validDate));

    /* expect expiration error */
    blockExpectation = [self expectationWithDescription:@"callback occurs"];
    dispatch_async(queue, ^{
        XCTAssert(errSecSuccess == SecTrustEvaluateAsyncWithError(trust, queue, ^(SecTrustRef  _Nonnull trustRef, bool result, CFErrorRef  _Nullable error) {
            XCTAssert(trust != NULL);
            XCTAssertFalse(result);
            XCTAssert(error != NULL);
            XCTAssert(CFErrorGetCode(error) == errSecCertificateExpired);
            [blockExpectation fulfill];
        }));
    });

    [self waitForExpectations:@[blockExpectation] timeout:1.0];

    CFReleaseNull(cert0);
    CFReleaseNull(cert1);
    CFReleaseNull(cert2);
    CFReleaseNull(policy);
    CFReleaseNull(trust);
}

- (void)testCopyProperties_ios
{
    /* Test null input */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
#if TARGET_OS_IPHONE
    XCTAssertEqual(NULL, SecTrustCopyProperties(NULL));
#else
    XCTAssertEqual(NULL, SecTrustCopyProperties_ios(NULL));
#endif
#pragma clang diagnostic pop

    NSURL *testPlist = nil;
    NSArray *testsArray = nil;

    testPlist = [[NSBundle bundleForClass:[self class]] URLForResource:@"debugging" withExtension:@"plist"
                                                          subdirectory:@"TestCopyProperties_ios-data"];
    if (!testPlist) {
        testPlist = [[NSBundle bundleForClass:[self class]] URLForResource:nil withExtension:@"plist"
                                                              subdirectory:(NSString *)@"TestCopyProperties_ios-data"];
    }
    if (!testPlist) {
        fail("Failed to get tests plist from TestCopyProperties-data");
        return;
    }

    testsArray = [NSArray arrayWithContentsOfURL: testPlist];
    if (!testsArray) {
        fail("Failed to create array from plist");
        return;
    }

    [testsArray enumerateObjectsUsingBlock:^(NSDictionary *testDict, NSUInteger idx, BOOL * _Nonnull stop) {
        TestTrustEvaluation *testObj = [[TestTrustEvaluation alloc] initWithTrustDictionary:testDict];
        XCTAssertNotNil(testObj, "failed to create test object for %lu", (unsigned long)idx);

#if TARGET_OS_BRIDGE
        // Skip disabled bridgeOS tests on bridgeOS
        if (testObj.bridgeOSDisabled) {
            return;
        }
#endif

#if TARGET_OS_IPHONE
        NSArray *properties = CFBridgingRelease(SecTrustCopyProperties(testObj.trust));
#else
        NSArray *properties = CFBridgingRelease(SecTrustCopyProperties_ios(testObj.trust));
#endif

        if (testDict[@"ExpectedProperties"]) {
            XCTAssertEqualObjects(testDict[@"ExpectedProperties"], properties, @"%@ test failed", testObj.fullTestName);
        } else {
            XCTAssertNil(properties, @"%@ test failed", testObj.fullTestName);
        }
    }];
}

@end
