From 03fe473ad79d15b41849db09a39c3c88aa773f06 Mon Sep 17 00:00:00 2001 From: Juan Lang Date: Sun, 13 Dec 2009 16:05:35 -0800 Subject: [PATCH] wininet: Check the certificate in an https connection using the crypto api. --- dlls/wininet/netconnection.c | 198 +++++++++++++++++++++++++++-------- 1 file changed, 153 insertions(+), 45 deletions(-) diff --git a/dlls/wininet/netconnection.c b/dlls/wininet/netconnection.c index e0667ec779d..39ef4640c4b 100644 --- a/dlls/wininet/netconnection.c +++ b/dlls/wininet/netconnection.c @@ -23,6 +23,8 @@ #include "config.h" #include "wine/port.h" +#define NONAMELESSUNION + #if defined(__MINGW32__) || defined (_MSC_VER) #include #endif @@ -152,6 +154,8 @@ MAKE_FUNCPTR(ERR_free_strings); MAKE_FUNCPTR(ERR_get_error); MAKE_FUNCPTR(ERR_error_string); MAKE_FUNCPTR(i2d_X509); +MAKE_FUNCPTR(sk_num); +MAKE_FUNCPTR(sk_value); #undef MAKE_FUNCPTR static CRITICAL_SECTION *ssl_locks; @@ -169,16 +173,159 @@ static void ssl_lock_callback(int mode, int type, const char *file, int line) LeaveCriticalSection(&ssl_locks[type]); } +static PCCERT_CONTEXT X509_to_cert_context(X509 *cert) +{ + unsigned char* buffer,*p; + INT len; + BOOL malloced = FALSE; + PCCERT_CONTEXT ret; + + p = NULL; + len = pi2d_X509(cert,&p); + /* + * SSL 0.9.7 and above malloc the buffer if it is null. + * however earlier version do not and so we would need to alloc the buffer. + * + * see the i2d_X509 man page for more details. + */ + if (!p) + { + buffer = HeapAlloc(GetProcessHeap(),0,len); + p = buffer; + len = pi2d_X509(cert,&p); + } + else + { + buffer = p; + malloced = TRUE; + } + + ret = CertCreateCertificateContext(X509_ASN_ENCODING,buffer,len); + + if (malloced) + free(buffer); + else + HeapFree(GetProcessHeap(),0,buffer); + + return ret; +} + +static BOOL netconn_verify_cert(PCCERT_CONTEXT cert, HCERTSTORE store, + WCHAR *server) +{ + BOOL ret; + CERT_CHAIN_PARA chainPara = { sizeof(chainPara), { 0 } }; + PCCERT_CHAIN_CONTEXT chain; + char oid_server_auth[] = szOID_PKIX_KP_SERVER_AUTH; + char *server_auth[] = { oid_server_auth }; + DWORD err; + + TRACE("verifying %s\n", debugstr_w(server)); + chainPara.RequestedUsage.Usage.cUsageIdentifier = 1; + chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = server_auth; + if ((ret = CertGetCertificateChain(NULL, cert, NULL, store, &chainPara, 0, + NULL, &chain))) + { + if (chain->TrustStatus.dwErrorStatus) + { + if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_TIME_VALID) + err = ERROR_INTERNET_SEC_CERT_DATE_INVALID; + else if (chain->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_UNTRUSTED_ROOT) + err = ERROR_INTERNET_INVALID_CA; + else if ((chain->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_OFFLINE_REVOCATION) || + (chain->TrustStatus.dwErrorStatus & + CERT_TRUST_REVOCATION_STATUS_UNKNOWN)) + err = ERROR_INTERNET_SEC_CERT_NO_REV; + else if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) + err = ERROR_INTERNET_SEC_CERT_REVOKED; + else if (chain->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_NOT_VALID_FOR_USAGE) + err = ERROR_INTERNET_SEC_INVALID_CERT; + else + err = ERROR_INTERNET_SEC_INVALID_CERT; + INTERNET_SetLastError(err); + ret = FALSE; + } + else + { + CERT_CHAIN_POLICY_PARA policyPara; + SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslExtraPolicyPara; + CERT_CHAIN_POLICY_STATUS policyStatus; + + sslExtraPolicyPara.u.cbSize = sizeof(sslExtraPolicyPara); + sslExtraPolicyPara.dwAuthType = AUTHTYPE_SERVER; + sslExtraPolicyPara.pwszServerName = server; + policyPara.cbSize = sizeof(policyPara); + policyPara.dwFlags = 0; + policyPara.pvExtraPolicyPara = &sslExtraPolicyPara; + ret = CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, + chain, &policyPara, &policyStatus); + /* Any error in the policy status indicates that the + * policy couldn't be verified. + */ + if (ret && policyStatus.dwError) + { + if (policyStatus.dwError == CERT_E_CN_NO_MATCH) + err = ERROR_INTERNET_SEC_CERT_CN_INVALID; + else + err = ERROR_INTERNET_SEC_INVALID_CERT; + INTERNET_SetLastError(err); + ret = FALSE; + } + } + CertFreeCertificateChain(chain); + } + TRACE("returning %d\n", ret); + return ret; +} + static int netconn_secure_verify(int preverify_ok, X509_STORE_CTX *ctx) { SSL *ssl; WCHAR *server; + BOOL ret = FALSE; ssl = pX509_STORE_CTX_get_ex_data(ctx, pSSL_get_ex_data_X509_STORE_CTX_idx()); server = pSSL_get_ex_data(ssl, hostname_idx); - FIXME("verify %s\n", debugstr_w(server)); - return preverify_ok; + if (preverify_ok) + { + HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, + CERT_STORE_CREATE_NEW_FLAG, NULL); + + if (store) + { + X509 *cert; + int i; + PCCERT_CONTEXT endCert = NULL; + + ret = TRUE; + for (i = 0; ret && i < psk_num((struct stack_st *)ctx->chain); i++) + { + PCCERT_CONTEXT context; + + cert = (X509 *)psk_value((struct stack_st *)ctx->chain, i); + if ((context = X509_to_cert_context(cert))) + { + if (i == 0) + ret = CertAddCertificateContextToStore(store, context, + CERT_STORE_ADD_ALWAYS, &endCert); + else + ret = CertAddCertificateContextToStore(store, context, + CERT_STORE_ADD_ALWAYS, NULL); + CertFreeCertificateContext(context); + } + } + if (!endCert) ret = FALSE; + if (ret) + ret = netconn_verify_cert(endCert, store, server); + CertFreeCertificateContext(endCert); + CertCloseStore(store, 0); + } + } + return ret; } #endif @@ -268,6 +415,8 @@ DWORD NETCON_init(WININET_NETCONNECTION *connection, BOOL useSSL) DYNCRYPTO(ERR_get_error); DYNCRYPTO(ERR_error_string); DYNCRYPTO(i2d_X509); + DYNCRYPTO(sk_num); + DYNCRYPTO(sk_value); #undef DYNCRYPTO pSSL_library_init(); @@ -459,13 +608,7 @@ DWORD NETCON_close(WININET_NETCONNECTION *connection) return sock_get_error(errno); return ERROR_SUCCESS; } -#ifdef SONAME_LIBSSL -static BOOL check_hostname(X509 *cert, LPCWSTR hostname) -{ - /* FIXME: implement */ - return TRUE; -} -#endif + /****************************************************************************** * NETCON_secure_connect * Initiates a secure connection over an existing plaintext connection. @@ -525,12 +668,6 @@ DWORD NETCON_secure_connect(WININET_NETCONNECTION *connection, LPWSTR hostname) * the moment */ } - if (!check_hostname(cert, hostname)) - { - res = ERROR_INTERNET_SEC_CERT_CN_INVALID; - goto fail; - } - connection->useSSL = TRUE; return ERROR_SUCCESS; @@ -662,42 +799,13 @@ LPCVOID NETCON_GetCert(WININET_NETCONNECTION *connection) { #ifdef SONAME_LIBSSL X509* cert; - unsigned char* buffer,*p; - INT len; - BOOL malloced = FALSE; LPCVOID r = NULL; if (!connection->useSSL) return NULL; cert = pSSL_get_peer_certificate(connection->ssl_s); - p = NULL; - len = pi2d_X509(cert,&p); - /* - * SSL 0.9.7 and above malloc the buffer if it is null. - * however earlier version do not and so we would need to alloc the buffer. - * - * see the i2d_X509 man page for more details. - */ - if (!p) - { - buffer = HeapAlloc(GetProcessHeap(),0,len); - p = buffer; - len = pi2d_X509(cert,&p); - } - else - { - buffer = p; - malloced = TRUE; - } - - r = CertCreateCertificateContext(X509_ASN_ENCODING,buffer,len); - - if (malloced) - free(buffer); - else - HeapFree(GetProcessHeap(),0,buffer); - + r = X509_to_cert_context(cert); return r; #else return NULL;