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;
}