From f77dabefd80b05173e602de94865b5cdffb3495e Mon Sep 17 00:00:00 2001 From: Max Khon Date: Mon, 6 Feb 2017 23:40:51 +0600 Subject: digest_sspi: Fix nonce-count generation in HTTP digest - on the first invocation: keep security context returned by InitializeSecurityContext() - on subsequent invocations: use MakeSignature() instead of InitializeSecurityContext() to generate HTTP digest response Bug: https://github.com/curl/curl/issues/870 Reported-by: Andreas Roth Closes https://github.com/curl/curl/pull/1251 --- lib/urldata.h | 1 + lib/vauth/digest_sspi.c | 243 ++++++++++++++++++++++++++++-------------------- tests/data/Makefile.inc | 2 +- tests/data/test1286 | 110 ++++++++++++++++++++++ 4 files changed, 256 insertions(+), 100 deletions(-) create mode 100644 tests/data/test1286 diff --git a/lib/urldata.h b/lib/urldata.h index c17e42cc0..648b3e81d 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -408,6 +408,7 @@ struct digestdata { #if defined(USE_WINDOWS_SSPI) BYTE *input_token; size_t input_token_len; + CtxtHandle *http_context; #else char *nonce; char *cnonce; diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c index 6f0746fc4..f9ee8a8fd 100644 --- a/lib/vauth/digest_sspi.c +++ b/lib/vauth/digest_sspi.c @@ -379,21 +379,13 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, char **outptr, size_t *outlen) { size_t token_max; - CredHandle credentials; - CtxtHandle context; char *resp; BYTE *output_token; + size_t output_token_len; PSecPkgInfo SecurityPackage; - SEC_WINNT_AUTH_IDENTITY identity; - SEC_WINNT_AUTH_IDENTITY *p_identity; - SecBuffer chlg_buf[3]; - SecBuffer resp_buf; + SecBuffer chlg_buf[5]; SecBufferDesc chlg_desc; - SecBufferDesc resp_desc; SECURITY_STATUS status; - unsigned long attrs; - TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ - TCHAR *spn; (void) data; @@ -408,123 +400,170 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); - if(userp && *userp) { - /* Populate our identity structure */ - if(Curl_create_sspi_identity(userp, passwdp, &identity)) - return CURLE_OUT_OF_MEMORY; - - /* Populate our identity domain */ - if(Curl_override_sspi_http_realm((const char *) digest->input_token, - &identity)) - return CURLE_OUT_OF_MEMORY; + /* Allocate the output buffer according to the max token size as indicated + by the security package */ + output_token = malloc(token_max); + if(!output_token) { + return CURLE_OUT_OF_MEMORY; + } - /* Allow proper cleanup of the identity structure */ - p_identity = &identity; + if(digest->http_context) { + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 5; + chlg_desc.pBuffers = chlg_buf; + chlg_buf[0].BufferType = SECBUFFER_TOKEN; + chlg_buf[0].pvBuffer = NULL; + chlg_buf[0].cbBuffer = 0; + chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); + chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[2].pvBuffer = (void *) uripath; + chlg_buf[2].cbBuffer = curlx_uztoul(strlen((const char *) uripath)); + chlg_buf[3].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[3].pvBuffer = NULL; + chlg_buf[3].cbBuffer = 0; + chlg_buf[4].BufferType = SECBUFFER_PADDING; + chlg_buf[4].pvBuffer = output_token; + chlg_buf[4].cbBuffer = curlx_uztoul(token_max); + + status = s_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, 0); + if(status == SEC_E_OK) + output_token_len = chlg_buf[4].cbBuffer; + else { /* delete the context so a new one can be made */ + infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx\n", + (long)status); + s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_safefree(digest->http_context); + } } - else - /* Use the current Windows user */ - p_identity = NULL; - /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_DIGEST), - SECPKG_CRED_OUTBOUND, NULL, - p_identity, NULL, NULL, - &credentials, &expiry); - if(status != SEC_E_OK) { - Curl_sspi_free_identity(p_identity); + if(!digest->http_context) { + CredHandle credentials; + SEC_WINNT_AUTH_IDENTITY identity; + SEC_WINNT_AUTH_IDENTITY *p_identity; + SecBuffer resp_buf; + SecBufferDesc resp_desc; + unsigned long attrs; + TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ + TCHAR *spn; + + if(userp && *userp) { + /* Populate our identity structure */ + if(Curl_create_sspi_identity(userp, passwdp, &identity)) { + free(output_token); + return CURLE_OUT_OF_MEMORY; + } - return CURLE_LOGIN_DENIED; - } + /* Populate our identity domain */ + if(Curl_override_sspi_http_realm((const char *) digest->input_token, + &identity)) { + free(output_token); + return CURLE_OUT_OF_MEMORY; + } - /* Allocate the output buffer according to the max token size as indicated - by the security package */ - output_token = malloc(token_max); - if(!output_token) { - s_pSecFn->FreeCredentialsHandle(&credentials); + /* Allow proper cleanup of the identity structure */ + p_identity = &identity; + } + else + /* Use the current Windows user */ + p_identity = NULL; + + /* Acquire our credentials handle */ + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT(SP_NAME_DIGEST), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); + if(status != SEC_E_OK) { + Curl_sspi_free_identity(p_identity); + free(output_token); - Curl_sspi_free_identity(p_identity); + return CURLE_LOGIN_DENIED; + } - return CURLE_OUT_OF_MEMORY; - } + /* Setup the challenge "input" security buffer if present */ + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 3; + chlg_desc.pBuffers = chlg_buf; + chlg_buf[0].BufferType = SECBUFFER_TOKEN; + chlg_buf[0].pvBuffer = digest->input_token; + chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); + chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); + chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; + chlg_buf[2].pvBuffer = NULL; + chlg_buf[2].cbBuffer = 0; + + /* Setup the response "output" security buffer */ + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = output_token; + resp_buf.cbBuffer = curlx_uztoul(token_max); + + spn = Curl_convert_UTF8_to_tchar((char *) uripath); + if(!spn) { + s_pSecFn->FreeCredentialsHandle(&credentials); + + Curl_sspi_free_identity(p_identity); + free(output_token); - /* Setup the challenge "input" security buffer if present */ - chlg_desc.ulVersion = SECBUFFER_VERSION; - chlg_desc.cBuffers = 3; - chlg_desc.pBuffers = chlg_buf; - chlg_buf[0].BufferType = SECBUFFER_TOKEN; - chlg_buf[0].pvBuffer = digest->input_token; - chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); - chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[1].pvBuffer = (void *) request; - chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); - chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[2].pvBuffer = NULL; - chlg_buf[2].cbBuffer = 0; + return CURLE_OUT_OF_MEMORY; + } - /* Setup the response "output" security buffer */ - resp_desc.ulVersion = SECBUFFER_VERSION; - resp_desc.cBuffers = 1; - resp_desc.pBuffers = &resp_buf; - resp_buf.BufferType = SECBUFFER_TOKEN; - resp_buf.pvBuffer = output_token; - resp_buf.cbBuffer = curlx_uztoul(token_max); + /* Allocate our new context handle */ + digest->http_context = calloc(1, sizeof(CtxtHandle)); + if(!digest->http_context) + return CURLE_OUT_OF_MEMORY; - spn = Curl_convert_UTF8_to_tchar((char *) uripath); - if(!spn) { - s_pSecFn->FreeCredentialsHandle(&credentials); + /* Generate our reponse message */ + status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, + spn, + ISC_REQ_USE_HTTP_STYLE, 0, 0, + &chlg_desc, 0, + digest->http_context, + &resp_desc, &attrs, &expiry); + Curl_unicodefree(spn); + + if(status == SEC_I_COMPLETE_NEEDED || + status == SEC_I_COMPLETE_AND_CONTINUE) + s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { + s_pSecFn->FreeCredentialsHandle(&credentials); + + Curl_sspi_free_identity(p_identity); + free(output_token); - Curl_sspi_free_identity(p_identity); - free(output_token); + Curl_safefree(digest->http_context); - return CURLE_OUT_OF_MEMORY; - } + return CURLE_OUT_OF_MEMORY; + } - /* Generate our reponse message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, - spn, - ISC_REQ_USE_HTTP_STYLE, 0, 0, - &chlg_desc, 0, &context, - &resp_desc, &attrs, &expiry); - Curl_unicodefree(spn); + output_token_len = resp_buf.cbBuffer; - if(status == SEC_I_COMPLETE_NEEDED || - status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); - else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { s_pSecFn->FreeCredentialsHandle(&credentials); - Curl_sspi_free_identity(p_identity); - free(output_token); - - return CURLE_OUT_OF_MEMORY; } - resp = malloc(resp_buf.cbBuffer + 1); + resp = malloc(output_token_len + 1); if(!resp) { - s_pSecFn->DeleteSecurityContext(&context); - s_pSecFn->FreeCredentialsHandle(&credentials); - - Curl_sspi_free_identity(p_identity); free(output_token); + Curl_safefree(digest->http_context); + return CURLE_OUT_OF_MEMORY; } /* Copy the generated reponse */ - memcpy(resp, resp_buf.pvBuffer, resp_buf.cbBuffer); - resp[resp_buf.cbBuffer] = 0x00; + memcpy(resp, output_token, output_token_len); + resp[output_token_len] = 0; /* Return the response */ *outptr = resp; - *outlen = resp_buf.cbBuffer; - - /* Free our handles */ - s_pSecFn->DeleteSecurityContext(&context); - s_pSecFn->FreeCredentialsHandle(&credentials); - - /* Free the identity structure */ - Curl_sspi_free_identity(p_identity); + *outlen = output_token_len; /* Free the response buffer */ free(output_token); @@ -549,6 +588,12 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) /* Reset any variables */ digest->input_token_len = 0; + + /* Delete security context */ + if(digest->http_context) { + s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_safefree(digest->http_context); + } } #endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_CRYPTO_AUTH */ diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 33dce05d8..8251ab9a4 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -130,7 +130,7 @@ test1236 test1237 test1238 test1239 test1240 test1241 test1242 test1243 \ test1244 test1245 test1246 test1247 test1248 test1249 test1250 test1251 \ test1252 test1253 test1254 test1255 test1256 test1257 test1258 test1259 \ \ -test1280 test1281 test1282 test1283 test1284 test1285 \ +test1280 test1281 test1282 test1283 test1284 test1285 test1286 \ \ test1300 test1301 test1302 test1303 test1304 test1305 test1306 test1307 \ test1308 test1309 test1310 test1311 test1312 test1313 test1314 test1315 \ diff --git a/tests/data/test1286 b/tests/data/test1286 new file mode 100644 index 000000000..41782cb0f --- /dev/null +++ b/tests/data/test1286 @@ -0,0 +1,110 @@ + + + +HTTP +HTTP GET +HTTP Digest auth +followlocation + + + +# Server-side + + +HTTP/1.1 401 authentication please swsbounce +Server: Microsoft-IIS/6.0 +WWW-Authenticate: Digest realm="testrealm", nonce="1053604144", qop="auth" +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 0 + + + +HTTP/1.1 302 Thanks for this, but we want to redir you! +Server: Microsoft-IIS/5.0 +Content-Type: text/html; charset=iso-8859-1 +Location: /12860001 +Content-Length: 0 + + + +HTTP/1.1 404 Not Found +Server: Microsoft-IIS/5.0 +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 0 + + + + +HTTP/1.1 401 authentication please swsbounce +Server: Microsoft-IIS/6.0 +WWW-Authenticate: Digest realm="testrealm", nonce="1053604144", qop="auth" +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 0 + +HTTP/1.1 302 Thanks for this, but we want to redir you! +Server: Microsoft-IIS/5.0 +Content-Type: text/html; charset=iso-8859-1 +Location: /12860001 +Content-Length: 0 + +HTTP/1.1 404 Not Found +Server: Microsoft-IIS/5.0 +Content-Type: text/html; charset=iso-8859-1 +Content-Length: 0 + + + + + +# Client-side + +# + +http + + +crypto + + +HTTP GET --digest increasing nonce-count + +# This test is to ensure the nonce-count (nc) increases +# https://github.com/curl/curl/pull/1251 + +-u auser:apasswd --location --digest http://%HOSTIP:%HTTPPORT/1286 + + + +# Verify data after the test has been "shot" + + +^User-Agent:.* + +# Reorder the fields in 'Authorization: Digest' header. +# Since regular and SSPI digest auth header fields may not have the same order +# or whitespace we homogenize so that both may be tested. Also: +# - Remove the unique value from cnonce if in RFC format +# - Remove the unique value from response if in RFC format +# - Remove quotes from qop="auth" used by SSPI +# The if statement is one line because runtests evaluates one line at a time. + +if(s/^(Authorization: Digest )([^\r\n]+)(\r?\n)$//) { $_ = $1 . join(', ', map { s/^(cnonce=)"[a-zA-Z0-9+\/=]+"$/$1REMOVED/; s/^(response=)"[a-f0-9]{32}"$/$1REMOVED/; s/^qop="auth"$/qop=auth/; $_ } sort split(/, */, $2)) . $3; } + + +GET /1286 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + +GET /1286 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Digest cnonce=REMOVED, nc=00000001, nonce="1053604144", qop=auth, realm="testrealm", response=REMOVED, uri="/1286", username="auser" +Accept: */* + +GET /12860001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Digest cnonce=REMOVED, nc=00000002, nonce="1053604144", qop=auth, realm="testrealm", response=REMOVED, uri="/12860001", username="auser" +Accept: */* + + + + -- cgit v1.2.3