static HRESULT verify_peer()

in c/src/ssl/schannel.cpp [2117:2351]


static HRESULT verify_peer(pni_ssl_t *ssl, HCERTSTORE root_store, const char *server_name, bool tracing)
{
  // Free/release the following before return:
  PCCERT_CONTEXT peer_cc = 0;
  PCCERT_CONTEXT trust_anchor = 0;
  PCCERT_CHAIN_CONTEXT chain_context = 0;
  wchar_t *nameUCS2 = 0;

  if (server_name && strlen(server_name) > 255) {
    ssl_log_error("invalid server name: %s", server_name);
    return WSAENAMETOOLONG;
  }

  // Get peer's certificate.
  SECURITY_STATUS status;
  status = QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_cc);
  if (status != SEC_E_OK) {
    ssl_log_error_status(status, "can't obtain remote peer certificate information");
    return status;
  }

  // Build the peer's certificate chain.  Multiple chains may be built but we
  // care about rgpChain[0], which is the best.  Custom root stores are not
  // allowed until W8/server 2012: see CERT_CHAIN_ENGINE_CONFIG.  For now, we
  // manually override to taste.

  // Chain verification functions give false reports for CRL if the trust anchor
  // is not in the official root store.  We ignore CRL completely if it doesn't
  // apply to any untrusted certs in the chain, and defer to SChannel's veto
  // otherwise.  To rely on CRL, the CA must be in both the official system
  // trusted root store and the Proton cred->trust_store.  To defeat CRL, the
  // most distal cert with CRL must be placed in the Proton cred->trust_store.
  // Similarly, certificate usage checking is overly strict at times.

  CERT_CHAIN_PARA desc;
  memset(&desc, 0, sizeof(desc));
  desc.cbSize = sizeof(desc);

  LPSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
  DWORD n_usages = sizeof(usages) / sizeof(LPSTR);
  desc.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
  desc.RequestedUsage.Usage.cUsageIdentifier = n_usages;
  desc.RequestedUsage.Usage.rgpszUsageIdentifier = usages;

  if(!CertGetCertificateChain(0, peer_cc, 0, peer_cc->hCertStore, &desc,
                             CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT |
                             CERT_CHAIN_CACHE_END_CERT,
                             0, &chain_context)){
    HRESULT st = GetLastError();
    ssl_log_error_status(st, "Basic certificate chain check failed");
    CertFreeCertificateContext(peer_cc);
    return st;
  }
  if (chain_context->cChain < 1 || chain_context->rgpChain[0]->cElement < 1) {
    ssl_log_error("empty chain with status %x %x", chain_context->TrustStatus.dwErrorStatus,
                 chain_context->TrustStatus.dwInfoStatus);
    return SEC_E_CERT_UNKNOWN;
  }

  int chain_len = chain_context->rgpChain[0]->cElement;
  PCCERT_CONTEXT leaf_cert = chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
  PCCERT_CONTEXT trunk_cert = chain_context->rgpChain[0]->rgpElement[chain_len - 1]->pCertContext;
  if (tracing)
    // See doc for CERT_CHAIN_POLICY_STATUS for bit field error and info status values
    ssl_log_error("status for complete chain: error bits %x info bits %x",
                  chain_context->TrustStatus.dwErrorStatus, chain_context->TrustStatus.dwInfoStatus);

  // Supplement with checks against Proton's trusted_ca_db, custom revocation and usage.
  HRESULT error = 0;
  do {
    // Do not return from this do loop.  Set error = SEC_E_XXX and break.
    bool revocable = false;  // unless we see any untrusted certs that could be
    for (int i = 0; i < chain_len; i++) {
      CERT_CHAIN_ELEMENT *ce = chain_context->rgpChain[0]->rgpElement[i];
      PCCERT_CONTEXT cc = ce->pCertContext;
      if (cc->pCertInfo->dwVersion != CERT_V3) {
        if (tracing)
          ssl_log_error("certificate chain element %d is not version 3", i);
        error = SEC_E_CERT_WRONG_USAGE; // A fossil
        break;
      }

      if (!trust_anchor && store_contains(root_store, cc))
        trust_anchor = CertDuplicateCertificateContext(cc);

      int n_ext = cc->pCertInfo->cExtension;
      for (int ii = 0; ii < n_ext && !revocable && !trust_anchor; ii++) {
        CERT_EXTENSION *p = &cc->pCertInfo->rgExtension[ii];
        // rfc 5280 extensions for revocation
        if (!strcmp(p->pszObjId, szOID_AUTHORITY_INFO_ACCESS) ||
            !strcmp(p->pszObjId, szOID_CRL_DIST_POINTS) ||
            !strcmp(p->pszObjId, szOID_FRESHEST_CRL)) {
          revocable = true;
        }
      }

      if (tracing) {
        char name[512];
        const char *is_anchor = (cc == trust_anchor) ? " trust anchor" : "";
        if (!CertNameToStr(cc->dwCertEncodingType, &cc->pCertInfo->Subject,
                           CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, name, sizeof(name)))
          strcpy(name, "[too long]");
        ssl_log_error("element %d (name: %s)%s error bits %x info bits %x", i, name, is_anchor,
                      ce->TrustStatus.dwErrorStatus, ce->TrustStatus.dwInfoStatus);
      }
    }
    if (error)
      break;

    if (!trust_anchor) {
      // We don't trust any of the certs in the chain, see if the last cert
      // is issued by a Proton trusted CA.
      DWORD flags = CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG;
      trust_anchor = CertGetIssuerCertificateFromStore(root_store, trunk_cert, 0, &flags);
      if (trust_anchor) {
        if (tracing) {
          if (flags & CERT_STORE_NO_ISSUER_FLAG)
            ssl_log_error("certificate no issuer");
          if (flags & CERT_STORE_SIGNATURE_FLAG)
            ssl_log_error("certificate signature failure");
          if (flags & CERT_STORE_TIME_VALIDITY_FLAG)
            ssl_log_error("certificate time validity failure");
        }
        if (flags) {
          CertFreeCertificateContext(trust_anchor);
          trust_anchor = 0;
        }
      }
    }
    if (!trust_anchor) {
      error = SEC_E_UNTRUSTED_ROOT;
      break;
    }

    bool strict_usage = false;
    CERT_EXTENSION *leaf_alt_names = 0;
    if (leaf_cert != trust_anchor) {
      int n_ext = leaf_cert->pCertInfo->cExtension;
      for (int ii = 0; ii < n_ext; ii++) {
        CERT_EXTENSION *p = &leaf_cert->pCertInfo->rgExtension[ii];
        if (!strcmp(p->pszObjId, szOID_ENHANCED_KEY_USAGE))
          strict_usage = true;
        if (!strcmp(p->pszObjId, szOID_SUBJECT_ALT_NAME2))
          if (p->Value.pbData)
            leaf_alt_names = p;
      }
    }

    if (server_name) {
      int len = strlen(server_name);
      nameUCS2 = (wchar_t *) calloc(len + 1, sizeof(wchar_t));
      int nwc = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, server_name, len, &nameUCS2[0], len);
      if (!nwc) {
        error = GetLastError();
        ssl_log_error_status(error, "Error converting server name from UTF8");
        break;
      }
    }

    // SSL-specific parameters (ExtraPolicy below)
    SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_desc;
    memset(&ssl_desc, 0, sizeof(ssl_desc));
    ssl_desc.cbSize = sizeof(ssl_desc);
    ssl_desc.pwszServerName = nameUCS2;
    ssl_desc.dwAuthType = nameUCS2 ? AUTHTYPE_SERVER : AUTHTYPE_CLIENT;
    ssl_desc.fdwChecks = SECURITY_FLAG_IGNORE_UNKNOWN_CA;
    if (server_name)
      ssl_desc.fdwChecks |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
    if (!revocable)
      ssl_desc.fdwChecks |= SECURITY_FLAG_IGNORE_REVOCATION;
    if (!strict_usage)
      ssl_desc.fdwChecks |= SECURITY_FLAG_IGNORE_WRONG_USAGE;

    // General certificate chain parameters
    CERT_CHAIN_POLICY_PARA chain_desc;
    memset(&chain_desc, 0, sizeof(chain_desc));
    chain_desc.cbSize = sizeof(chain_desc);
    chain_desc.dwFlags = CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG;
    if (!revocable)
      chain_desc.dwFlags |= CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
    if (!strict_usage)
      chain_desc.dwFlags |= CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG;
    chain_desc.pvExtraPolicyPara = &ssl_desc;

    CERT_CHAIN_POLICY_STATUS chain_status;
    memset(&chain_status, 0, sizeof(chain_status));
    chain_status.cbSize = sizeof(chain_status);

    if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
                                          &chain_desc, &chain_status)) {
      error = GetLastError();
      // Failure to complete the check, does not (in)validate the cert.
      ssl_log_error_status(error, "Supplemental certificate chain check failed");
      break;
    }

    if (chain_status.dwError) {
      error = chain_status.dwError;
      if (tracing) {
        ssl_log_error_status(chain_status.dwError, "Certificate chain verification error");
        if (chain_status.lChainIndex == 0 && chain_status.lElementIndex != -1) {
          int idx = chain_status.lElementIndex;
          CERT_CHAIN_ELEMENT *ce = chain_context->rgpChain[0]->rgpElement[idx];
          ssl_log_error("  chain failure at %d error/info: %x %x", idx,
                        ce->TrustStatus.dwErrorStatus, ce->TrustStatus.dwInfoStatus);
        }
      }
      break;
    }

    if (server_name && ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME &&
        !server_name_matches(server_name, leaf_alt_names, leaf_cert)) {
      error = SEC_E_WRONG_PRINCIPAL;
      break;
    }
    else if (ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME && !server_name) {
      ssl_log_error("Error: configuration error: PN_SSL_VERIFY_PEER_NAME configured, but no peer hostname set!");
      error = SEC_E_WRONG_PRINCIPAL;
      break;
    }
  } while (0);

  if (tracing && !error)
    ssl_log_error("peer certificate authenticated");

  // Lots to clean up.
  if (peer_cc)
    CertFreeCertificateContext(peer_cc);
  if (trust_anchor)
    CertFreeCertificateContext(trust_anchor);
  if (chain_context)
    CertFreeCertificateChain(chain_context);
  free(nameUCS2);
  return error;
}