From 1e4c57fa6471c0010f66d82a83f190a07fa37309 Mon Sep 17 00:00:00 2001 From: Mark Salisbury Date: Tue, 19 Jun 2012 04:15:02 +0200 Subject: [PATCH] schannel SSL: certificate validation on WinCE curl_schannel.c - auto certificate validation doesn't seem to work right on CE. I added a method to perform the certificate validation which uses CertGetCertificateChain and manually handles the result. --- lib/curl_schannel.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/lib/curl_schannel.c b/lib/curl_schannel.c index 5e2a0b3..3ea6b4d 100644 --- a/lib/curl_schannel.c +++ b/lib/curl_schannel.c @@ -6,6 +6,7 @@ * \___|\___/|_| \_\_____| * * Copyright (C) 2012, Marc Hoersken, , et al. + * Copyright (C) 2012, Mark Salisbury, * Copyright (C) 2012, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which @@ -86,6 +87,10 @@ static Curl_recv schannel_recv; static Curl_send schannel_send; +#ifdef _WIN32_WCE +static CURLcode verify_certificate(struct connectdata *conn, int sockindex); +#endif + static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, void *BufDataPtr, unsigned long BufByteSize) { @@ -133,8 +138,16 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; if(data->set.ssl.verifypeer) { +#ifdef _WIN32_WCE + /* certificate validation on CE doesn't seem to work right; we'll + do it following a more manual process. */ + schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | + SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + SCH_CRED_IGNORE_REVOCATION_OFFLINE; +#else schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_REVOCATION_CHECK_CHAIN; +#endif infof(data, "schannel: checking server certificate revocation\n"); } else { @@ -426,6 +439,13 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) infof(data, "schannel: handshake complete\n"); } +#ifdef _WIN32_WCE + /* Windows CE doesn't do any server certificate validation. + We have to do it manually. */ + if(data->set.ssl.verifypeer) + return verify_certificate(conn, sockindex); +#endif + return CURLE_OK; } @@ -990,4 +1010,111 @@ size_t Curl_schannel_version(char *buffer, size_t size) return size; } +#ifdef _WIN32_WCE +static CURLcode verify_certificate(struct connectdata *conn, int sockindex) +{ + SECURITY_STATUS status; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + CURLcode result = CURLE_OK; + CERT_CONTEXT *pCertContextServer = NULL; + CCERT_CHAIN_CONTEXT *pChainContext = NULL; + + status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); + + if((status != SEC_E_OK) || (pCertContextServer == NULL)) { + failf(data, "schannel: Failed to read remote certificate context: %s", + Curl_sspi_strerror(conn, status)); + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(result == CURLE_OK) { + CERT_CHAIN_PARA ChainPara; + memset(&ChainPara, 0, sizeof(ChainPara)); + ChainPara.cbSize = sizeof(ChainPara); + + if(!CertGetCertificateChain(NULL, + pCertContextServer, + NULL, + pCertContextServer->hCertStore, + &ChainPara, + 0, + NULL, + &pChainContext)) { + failf(data, "schannel: CertGetCertificateChain failed: %s", + Curl_sspi_strerror(conn, GetLastError())); + pChainContext = NULL; + result = CURLE_PEER_FAILED_VERIFICATION; + } + + if(result == CURLE_OK) { + CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0]; + DWORD dwTrustErrorMask = ~(CERT_TRUST_IS_NOT_TIME_NESTED| + CERT_TRUST_REVOCATION_STATUS_UNKNOWN); + dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; + if(dwTrustErrorMask) { + if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_PARTIAL_CHAIN"); + if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_UNTRUSTED_ROOT"); + if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID) + failf(data, "schannel: CertGetCertificateChain trust error" + " CERT_TRUST_IS_NOT_TIME_VALID"); + failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", + dwTrustErrorMask); + result = CURLE_PEER_FAILED_VERIFICATION; + } + } + } + + if(result == CURLE_OK) { + if(data->set.ssl.verifyhost == 1) { + infof(data, "warning: ignoring unsupported value (1) ssl.verifyhost\n"); + } + else if(data->set.ssl.verifyhost == 2) { + WCHAR cert_hostname[128]; + WCHAR *hostname = Curl_convert_UTF8_to_wchar(conn->host.name); + DWORD len; + + len = CertGetNameStringW(pCertContextServer, + CERT_NAME_DNS_TYPE, + 0, + NULL, + cert_hostname, + 128); + if(len > 0 && cert_hostname[0] == '*') { + /* this is a wildcard cert. try matching the last len - 1 chars */ + int hostname_len = strlen(conn->host.name); + if(wcsicmp(cert_hostname + 1, hostname + hostname_len - len + 2) != 0) + result = CURLE_PEER_FAILED_VERIFICATION; + } + else if(len == 0 || wcsicmp(hostname, cert_hostname) != 0) { + result = CURLE_PEER_FAILED_VERIFICATION; + } + if(result == CURLE_PEER_FAILED_VERIFICATION) { + const char *_cert_hostname; + _cert_hostname = Curl_convert_wchar_to_UTF8(cert_hostname); + failf(data, "schannel: CertGetNameString() certificate hostname " + "(%s) did not match connection (%s)", + _cert_hostname, conn->host.name); + free(_cert_hostname); + } + free(hostname); + } + } + + if(pChainContext) + CertFreeCertificateChain(pChainContext); + + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); + + return result; +} +#endif /* _WIN32_WCE */ + #endif /* USE_SCHANNEL */ -- 2.7.4