static int s_manually_verify_peer_cert()

in source/windows/secure_channel_tls_handler.c [180:349]


static int s_manually_verify_peer_cert(struct aws_channel_handler *handler) {
    AWS_LOGF_DEBUG(
        AWS_LS_IO_TLS,
        "id=%p: manually verifying certifcate chain because a custom CA is configured.",
        (void *)handler);
    struct secure_channel_handler *sc_handler = handler->impl;

    int result = AWS_OP_ERR;
    CERT_CONTEXT *peer_certificate = NULL;
    HCERTCHAINENGINE engine = NULL;
    CERT_CHAIN_CONTEXT *cert_chain_ctx = NULL;

    /* get the peer's certificate so we can validate it.*/
    SECURITY_STATUS status =
        QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_certificate);

    if (status != SEC_E_OK || !peer_certificate) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: failed to load peer's certificate with SECURITY_STATUS %d",
            (void *)handler,
            (int)status);
        return AWS_OP_ERR;
    }

    /* this next bit scours the custom trust store to try and load a chain to verify
       the leaf certificate against. */
    CERT_CHAIN_ENGINE_CONFIG engine_config;
    AWS_ZERO_STRUCT(engine_config);
    engine_config.cbSize = sizeof(engine_config);
    engine_config.hExclusiveRoot = sc_handler->custom_ca_store;

    if (!CertCreateCertificateChainEngine(&engine_config, &engine)) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: failed to load a certificate chain engine with SECURITY_STATUS %d. "
            "Most likely, the configured CA is corrupted.",
            (void *)handler,
            (int)status);
        goto done;
    }

    /*
     * TODO: Investigate CRL options further on a per-platform basis.  Add control APIs if appropriate.
     */
    DWORD get_chain_flags = 0;

    /* mimic chromium here since we intend for this to be used generally */
    const LPCSTR usage_identifiers[] = {
        szOID_PKIX_KP_SERVER_AUTH,
        szOID_SERVER_GATED_CRYPTO,
        szOID_SGC_NETSCAPE,
    };

    CERT_CHAIN_PARA chain_params;
    AWS_ZERO_STRUCT(chain_params);
    chain_params.cbSize = sizeof(chain_params);
    chain_params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
    chain_params.RequestedUsage.Usage.cUsageIdentifier = AWS_ARRAY_SIZE(usage_identifiers);
    chain_params.RequestedUsage.Usage.rgpszUsageIdentifier = (LPSTR *)usage_identifiers;

    if (!CertGetCertificateChain(
            engine,
            peer_certificate,
            NULL,
            peer_certificate->hCertStore,
            &chain_params,
            get_chain_flags,
            NULL,
            &cert_chain_ctx)) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: unable to find certificate in chain with SECURITY_STATUS %d.",
            (void *)handler,
            (int)status);
        goto done;
    }

    struct aws_byte_buf host = aws_tls_handler_server_name(handler);
    if (host.len > MAX_HOST_LENGTH) {
        AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: host name too long (%d).", (void *)handler, (int)host.len);
        goto done;
    }

    wchar_t whost[MAX_HOST_LENGTH + 1];
    AWS_ZERO_ARRAY(whost);

    int converted = MultiByteToWideChar(
        CP_UTF8, MB_ERR_INVALID_CHARS, (const char *)host.buffer, (int)host.len, whost, AWS_ARRAY_SIZE(whost));
    if ((size_t)converted != host.len) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: unable to convert host to wstr, %d -> %d, with last error 0x%x.",
            (void *)handler,
            (int)host.len,
            (int)converted,
            (int)GetLastError());
        goto done;
    }

    /* check if the chain was trusted */
    LPCSTR policyiod = CERT_CHAIN_POLICY_SSL;

    SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslpolicy;
    AWS_ZERO_STRUCT(sslpolicy);
    sslpolicy.cbSize = sizeof(sslpolicy);
    sslpolicy.dwAuthType = AUTHTYPE_SERVER;
    sslpolicy.fdwChecks = 0;
    sslpolicy.pwszServerName = whost;

    CERT_CHAIN_POLICY_PARA policypara;
    AWS_ZERO_STRUCT(policypara);
    policypara.cbSize = sizeof(policypara);
    policypara.dwFlags = 0;
    policypara.pvExtraPolicyPara = &sslpolicy;

    CERT_CHAIN_POLICY_STATUS policystatus;
    AWS_ZERO_STRUCT(policystatus);
    policystatus.cbSize = sizeof(policystatus);

    if (!CertVerifyCertificateChainPolicy(policyiod, cert_chain_ctx, &policypara, &policystatus)) {
        int error = GetLastError();
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS, "id=%p: CertVerifyCertificateChainPolicy() failed, error 0x%x", (void *)handler, (int)error);
        goto done;
    }

    if (policystatus.dwError) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: certificate verification failed, error 0x%x",
            (void *)handler,
            (int)policystatus.dwError);
        goto done;
    }

    /* if the chain was trusted, then we're good to go, if it was not
       we bail out. */
    CERT_SIMPLE_CHAIN *simple_chain = cert_chain_ctx->rgpChain[0];
    DWORD trust_mask = ~(DWORD)CERT_TRUST_IS_NOT_TIME_NESTED;
    trust_mask &= simple_chain->TrustStatus.dwErrorStatus;

    if (trust_mask != 0) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: peer certificate is un-trusted with SECURITY_STATUS %d.",
            (void *)handler,
            (int)trust_mask);
        goto done;
    }

    AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: peer certificate is trusted.", (void *)handler);
    result = AWS_OP_SUCCESS;

done:

    if (cert_chain_ctx != NULL) {
        CertFreeCertificateChain(cert_chain_ctx);
    }

    if (engine != NULL) {
        CertFreeCertificateChainEngine(engine);
    }

    if (peer_certificate != NULL) {
        CertFreeCertificateContext(peer_certificate);
    }

    return result;
}