/* * Curl_sasl_create_gssapi_user_message() * * This is used to generate an already encoded GSSAPI (Kerberos V5) user token * message ready for sending to the recipient. * * Parameters: * * data [in] - The session handle. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * service [in] - The service type such as www, smtp, pop or imap. * mutual_auth [in] - Flag specifing whether or not mutual authentication * is enabled. * chlg64 [in] - The optional base64 encoded challenge message. * krb5 [in/out] - The gssapi data struct being used and modified. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_gssapi_user_message(struct SessionHandle *data, const char *userp, const char *passwdp, const char *service, const bool mutual_auth, const char *chlg64, struct kerberos5data *krb5, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; size_t chlglen = 0; unsigned char *chlg = NULL; CtxtHandle context; PSecPkgInfo SecurityPackage; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ if(!krb5->credentials) { /* Query the security package for Kerberos */ status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_KERBEROS), &SecurityPackage); if(status != SEC_E_OK) { return CURLE_NOT_BUILT_IN; } krb5->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ krb5->output_token = malloc(krb5->token_max); if(!krb5->output_token) return CURLE_OUT_OF_MEMORY; /* Generate our SPN */ krb5->spn = Curl_sasl_build_spn(service, data->easy_conn->host.name); if(!krb5->spn) return CURLE_OUT_OF_MEMORY; if(userp && *userp) { /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &krb5->identity); if(result) return result; /* Allow proper cleanup of the identity structure */ krb5->p_identity = &krb5->identity; } else /* Use the current Windows user */ krb5->p_identity = NULL; /* Allocate our credentials handle */ krb5->credentials = malloc(sizeof(CredHandle)); if(!krb5->credentials) return CURLE_OUT_OF_MEMORY; memset(krb5->credentials, 0, sizeof(CredHandle)); /* Acquire our credentials handle */ status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_KERBEROS), SECPKG_CRED_OUTBOUND, NULL, krb5->p_identity, NULL, NULL, krb5->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; /* Allocate our new context handle */ krb5->context = malloc(sizeof(CtxtHandle)); if(!krb5->context) return CURLE_OUT_OF_MEMORY; memset(krb5->context, 0, sizeof(CtxtHandle)); } else { /* Decode the base-64 encoded challenge message */ if(strlen(chlg64) && *chlg64 != '=') { result = Curl_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } /* Ensure we have a valid challenge message */ if(!chlg) { infof(data, "GSSAPI handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Setup the challenge "input" security buffer */ chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; chlg_buf.pvBuffer = chlg; chlg_buf.cbBuffer = curlx_uztoul(chlglen); } /* 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 = krb5->output_token; resp_buf.cbBuffer = curlx_uztoul(krb5->token_max); /* Generate our challenge-response message */ status = s_pSecFn->InitializeSecurityContext(krb5->credentials, chlg ? krb5->context : NULL, krb5->spn, (mutual_auth ? ISC_REQ_MUTUAL_AUTH : 0), 0, SECURITY_NATIVE_DREP, chlg ? &chlg_desc : NULL, 0, &context, &resp_desc, &attrs, &expiry); if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { free(chlg); return CURLE_RECV_ERROR; } if(memcmp(&context, krb5->context, sizeof(context))) { s_pSecFn->DeleteSecurityContext(krb5->context); memcpy(krb5->context, &context, sizeof(context)); } if(resp_buf.cbBuffer) { /* Base64 encode the response */ result = Curl_base64_encode(data, (char *)resp_buf.pvBuffer, resp_buf.cbBuffer, outptr, outlen); } /* Free the decoded challenge */ free(chlg); return result; }
/* * Curl_sasl_create_digest_md5_message() * * This is used to generate an already encoded DIGEST-MD5 response message * ready for sending to the recipient. * * Parameters: * * data [in] - The session handle. * chlg64 [in] - Pointer to the base64 encoded challenge message. * userp [in] - The user name. * passdwp [in] - The user's password. * service [in] - The service type such as www, smtp, pop or imap. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data, const char *chlg64, const char *userp, const char *passwdp, const char *service, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; size_t i; MD5_context *ctxt; char *response = NULL; unsigned char digest[MD5_DIGEST_LEN]; char HA1_hex[2 * MD5_DIGEST_LEN + 1]; char HA2_hex[2 * MD5_DIGEST_LEN + 1]; char resp_hash_hex[2 * MD5_DIGEST_LEN + 1]; char nonce[64]; char realm[128]; char algorithm[64]; char qop_options[64]; int qop_values; char cnonce[33]; unsigned int entropy[4]; char nonceCount[] = "00000001"; char method[] = "AUTHENTICATE"; char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; char *spn = NULL; /* Decode the challange message */ result = sasl_decode_digest_md5_message(chlg64, nonce, sizeof(nonce), realm, sizeof(realm), algorithm, sizeof(algorithm), qop_options, sizeof(qop_options)); if(result) return result; /* We only support md5 sessions */ if(strcmp(algorithm, "md5-sess") != 0) return CURLE_BAD_CONTENT_ENCODING; /* Get the qop-values from the qop-options */ result = sasl_digest_get_qop_values(qop_options, &qop_values); if(result) return result; /* We only support auth quality-of-protection */ if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) return CURLE_BAD_CONTENT_ENCODING; /* Generate 16 bytes of random data */ entropy[0] = Curl_rand(data); entropy[1] = Curl_rand(data); entropy[2] = Curl_rand(data); entropy[3] = Curl_rand(data); /* Convert the random data into a 32 byte hex string */ snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x", entropy[0], entropy[1], entropy[2], entropy[3]); /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ ctxt = Curl_MD5_init(Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; Curl_MD5_update(ctxt, (const unsigned char *) userp, curlx_uztoui(strlen(userp))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) realm, curlx_uztoui(strlen(realm))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) passwdp, curlx_uztoui(strlen(passwdp))); Curl_MD5_final(ctxt, digest); ctxt = Curl_MD5_init(Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) nonce, curlx_uztoui(strlen(nonce))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) cnonce, curlx_uztoui(strlen(cnonce))); Curl_MD5_final(ctxt, digest); /* Convert calculated 16 octet hex into 32 bytes string */ for(i = 0; i < MD5_DIGEST_LEN; i++) snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); /* Generate our SPN */ spn = Curl_sasl_build_spn(service, realm); if(!spn) return CURLE_OUT_OF_MEMORY; /* Calculate H(A2) */ ctxt = Curl_MD5_init(Curl_DIGEST_MD5); if(!ctxt) { Curl_safefree(spn); return CURLE_OUT_OF_MEMORY; } Curl_MD5_update(ctxt, (const unsigned char *) method, curlx_uztoui(strlen(method))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) spn, curlx_uztoui(strlen(spn))); Curl_MD5_final(ctxt, digest); for(i = 0; i < MD5_DIGEST_LEN; i++) snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); /* Now calculate the response hash */ ctxt = Curl_MD5_init(Curl_DIGEST_MD5); if(!ctxt) { Curl_safefree(spn); return CURLE_OUT_OF_MEMORY; } Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) nonce, curlx_uztoui(strlen(nonce))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) nonceCount, curlx_uztoui(strlen(nonceCount))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) cnonce, curlx_uztoui(strlen(cnonce))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) qop, curlx_uztoui(strlen(qop))); Curl_MD5_update(ctxt, (const unsigned char *) ":", 1); Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN); Curl_MD5_final(ctxt, digest); for(i = 0; i < MD5_DIGEST_LEN; i++) snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); /* Generate the response */ response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s," "qop=%s", userp, realm, nonce, cnonce, nonceCount, spn, resp_hash_hex, qop); Curl_safefree(spn); if(!response) return CURLE_OUT_OF_MEMORY; /* Base64 encode the response */ result = Curl_base64_encode(data, response, 0, outptr, outlen); Curl_safefree(response); return result; }
/* * Curl_sasl_create_digest_md5_message() * * This is used to generate an already encoded DIGEST-MD5 response message * ready for sending to the recipient. * * Parameters: * * data [in] - The session handle. * chlg64 [in] - The base64 encoded challenge message. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * service [in] - The service type such as www, smtp, pop or imap. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data, const char *chlg64, const char *userp, const char *passwdp, const char *service, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; TCHAR *spn = NULL; size_t chlglen = 0; size_t token_max = 0; unsigned char *input_token = NULL; unsigned char *output_token = NULL; CredHandle credentials; CtxtHandle context; PSecPkgInfo SecurityPackage; SEC_WINNT_AUTH_IDENTITY identity; SEC_WINNT_AUTH_IDENTITY *p_identity; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ /* Decode the base-64 encoded challenge message */ if(strlen(chlg64) && *chlg64 != '=') { result = Curl_base64_decode(chlg64, &input_token, &chlglen); if(result) return result; } /* Ensure we have a valid challenge message */ if(!input_token) { infof(data, "DIGEST-MD5 handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Query the security package for DigestSSP */ status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), &SecurityPackage); if(status != SEC_E_OK) { free(input_token); return CURLE_NOT_BUILT_IN; } token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ output_token = malloc(token_max); if(!output_token) { free(input_token); return CURLE_OUT_OF_MEMORY; } /* Generate our SPN */ spn = Curl_sasl_build_spn(service, data->easy_conn->host.name); if(!spn) { free(output_token); free(input_token); return CURLE_OUT_OF_MEMORY; } if(userp && *userp) { /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &identity); if(result) { free(spn); free(output_token); free(input_token); return result; } /* 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(spn); free(output_token); free(input_token); return CURLE_LOGIN_DENIED; } /* Setup the challenge "input" security buffer */ chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; chlg_buf.pvBuffer = input_token; chlg_buf.cbBuffer = curlx_uztoul(chlglen); /* 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); /* Generate our response message */ status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, 0, 0, 0, &chlg_desc, 0, &context, &resp_desc, &attrs, &expiry); 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(spn); free(output_token); free(input_token); return CURLE_RECV_ERROR; } /* Base64 encode the response */ result = Curl_base64_encode(data, (char *) output_token, resp_buf.cbBuffer, outptr, outlen); /* Free our handles */ s_pSecFn->DeleteSecurityContext(&context); s_pSecFn->FreeCredentialsHandle(&credentials); /* Free the identity structure */ Curl_sspi_free_identity(p_identity); /* Free the SPN */ free(spn); /* Free the response buffer */ free(output_token); /* Free the decoded challenge message */ free(input_token); return result; }
CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, const char *header) { BYTE *input_token = NULL; SecBufferDesc out_buff_desc; SecBuffer out_sec_buff; SecBufferDesc in_buff_desc; SecBuffer in_sec_buff; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ size_t len = 0, input_token_len = 0; CURLcode result; /* Point to the username and password */ const char *userp; const char *passwdp; /* Point to the correct struct with this */ struct negotiatedata *neg_ctx; if(proxy) { userp = conn->proxyuser; passwdp = conn->proxypasswd; neg_ctx = &conn->data->state.proxyneg; } else { userp = conn->user; passwdp = conn->passwd; neg_ctx = &conn->data->state.negotiate; } /* Not set means empty */ if(!userp) userp = ""; if(!passwdp) passwdp = ""; if(neg_ctx->context && neg_ctx->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server * rejected it (since we're again here). Exit with an error since we * can't invent anything better */ Curl_cleanup_negotiate(conn->data); return CURLE_LOGIN_DENIED; } if(!neg_ctx->server_name) { /* Check proxy auth requested but no given proxy name */ if(proxy && !conn->proxy.name) return CURLE_BAD_FUNCTION_ARGUMENT; /* Generate our SPN */ neg_ctx->server_name = Curl_sasl_build_spn("HTTP", proxy ? conn->proxy.name : conn->host.name); if(!neg_ctx->server_name) return CURLE_OUT_OF_MEMORY; } if(!neg_ctx->output_token) { PSecPkgInfo SecurityPackage; status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NEGOTIATE), &SecurityPackage); if(status != SEC_E_OK) return CURLE_NOT_BUILT_IN; /* Allocate input and output buffers according to the max token size as indicated by the security package */ neg_ctx->token_max = SecurityPackage->cbMaxToken; neg_ctx->output_token = malloc(neg_ctx->token_max); s_pSecFn->FreeContextBuffer(SecurityPackage); } /* Obtain the input token, if any */ header += strlen("Negotiate"); while(*header && ISSPACE(*header)) header++; len = strlen(header); if(!len) { /* Is this the first call in a new negotiation? */ if(neg_ctx->context) { /* The server rejected our authentication and hasn't suppled any more negotiation mechanisms */ return CURLE_LOGIN_DENIED; } /* We have to acquire credentials and allocate memory for the context */ neg_ctx->credentials = malloc(sizeof(CredHandle)); neg_ctx->context = malloc(sizeof(CtxtHandle)); if(!neg_ctx->credentials || !neg_ctx->context) return CURLE_OUT_OF_MEMORY; if(userp && *userp) { /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &neg_ctx->identity); if(result) return result; /* Allow proper cleanup of the identity structure */ neg_ctx->p_identity = &neg_ctx->identity; } else /* Use the current Windows user */ neg_ctx->p_identity = NULL; /* Acquire our credientials handle */ neg_ctx->status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_NEGOTIATE), SECPKG_CRED_OUTBOUND, NULL, neg_ctx->p_identity, NULL, NULL, neg_ctx->credentials, &expiry); if(neg_ctx->status != SEC_E_OK) return CURLE_LOGIN_DENIED; } else { result = Curl_base64_decode(header, (unsigned char **)&input_token, &input_token_len); if(result) return result; if(!input_token_len) { infof(conn->data, "Negotiate handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } } /* Setup the "output" security buffer */ out_buff_desc.ulVersion = SECBUFFER_VERSION; out_buff_desc.cBuffers = 1; out_buff_desc.pBuffers = &out_sec_buff; out_sec_buff.BufferType = SECBUFFER_TOKEN; out_sec_buff.pvBuffer = neg_ctx->output_token; out_sec_buff.cbBuffer = curlx_uztoul(neg_ctx->token_max); /* Setup the "input" security buffer if present */ if(input_token) { in_buff_desc.ulVersion = SECBUFFER_VERSION; in_buff_desc.cBuffers = 1; in_buff_desc.pBuffers = &in_sec_buff; in_sec_buff.BufferType = SECBUFFER_TOKEN; in_sec_buff.pvBuffer = input_token; in_sec_buff.cbBuffer = curlx_uztoul(input_token_len); } /* Generate our message */ neg_ctx->status = s_pSecFn->InitializeSecurityContext( neg_ctx->credentials, input_token ? neg_ctx->context : NULL, neg_ctx->server_name, ISC_REQ_CONFIDENTIALITY, 0, SECURITY_NATIVE_DREP, input_token ? &in_buff_desc : NULL, 0, neg_ctx->context, &out_buff_desc, &attrs, &expiry); free(input_token); if(GSS_ERROR(neg_ctx->status)) return CURLE_OUT_OF_MEMORY; if(neg_ctx->status == SEC_I_COMPLETE_NEEDED || neg_ctx->status == SEC_I_COMPLETE_AND_CONTINUE) { neg_ctx->status = s_pSecFn->CompleteAuthToken(neg_ctx->context, &out_buff_desc); if(GSS_ERROR(neg_ctx->status)) return CURLE_RECV_ERROR; } neg_ctx->output_token_length = out_sec_buff.cbBuffer; return CURLE_OK; }