diff -up libsoup-2.62.3/libsoup/soup-auth-ntlm.c.4 libsoup-2.62.3/libsoup/soup-auth-ntlm.c --- libsoup-2.62.3/libsoup/soup-auth-ntlm.c.4 2021-03-12 07:30:03.366088932 +0100 +++ libsoup-2.62.3/libsoup/soup-auth-ntlm.c 2021-03-12 07:30:25.296043405 +0100 @@ -12,6 +12,8 @@ #include #include #include +#include + #include #include "soup-auth-ntlm.h" @@ -26,14 +28,20 @@ static char *soup_ntlm_request static gboolean soup_ntlm_parse_challenge (const char *challenge, char **nonce, char **default_domain, - gboolean *ntlmv2_session); -static char *soup_ntlm_response (const char *nonce, + gboolean *ntlmv2_session, + gboolean *negotiate_target, + char **target_info, + size_t *target_info_sz); +static char *soup_ntlm_response (const char *nonce, const char *user, guchar nt_hash[21], guchar lm_hash[21], const char *host, const char *domain, - gboolean ntlmv2_session); + gboolean ntlmv2_session, + gboolean negotiate_target, + const char *target_info, + size_t target_info_sz); typedef enum { SOUP_NTLM_NEW, @@ -49,6 +57,9 @@ typedef struct { char *nonce; char *response_header; gboolean ntlmv2_session; + gboolean negotiate_target; + char *target_info; + size_t target_info_sz; } SoupNTLMConnectionState; typedef enum { @@ -280,6 +291,7 @@ soup_auth_ntlm_free_connection_state (So g_free (conn->nonce); g_free (conn->response_header); + g_free (conn->target_info); g_slice_free (SoupNTLMConnectionState, conn); } @@ -339,7 +351,8 @@ soup_auth_ntlm_update_connection (SoupCo if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce, priv->domain ? NULL : &priv->domain, - &conn->ntlmv2_session)) { + &conn->ntlmv2_session, &conn->negotiate_target, + &conn->target_info, &conn->target_info_sz)) { conn->state = SOUP_NTLM_FAILED; return FALSE; } @@ -521,7 +534,10 @@ soup_auth_ntlm_get_connection_authorizat priv->lm_hash, NULL, priv->domain, - conn->ntlmv2_session); + conn->ntlmv2_session, + conn->negotiate_target, + conn->target_info, + conn->target_info_sz); } g_clear_pointer (&conn->nonce, g_free); conn->state = SOUP_NTLM_SENT_RESPONSE; @@ -647,14 +663,20 @@ typedef struct { #define NTLM_CHALLENGE_NONCE_OFFSET 24 #define NTLM_CHALLENGE_NONCE_LENGTH 8 #define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12 +#define NTLM_CHALLENGE_TARGET_INFORMATION_OFFSET 40 #define NTLM_CHALLENGE_FLAGS_OFFSET 20 #define NTLM_FLAGS_NEGOTIATE_NTLMV2 0x00080000 +#define NTLM_FLAGS_NEGOTIATE_TARGET_INFORMATION 0x00800000 +#define NTLM_FLAGS_REQUEST_TARGET 0x00000004 #define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00" #define NTLM_RESPONSE_FLAGS 0x8201 +#define NTLM_RESPONSE_TARGET_INFORMATION_OFFSET 44 #define NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 0x00080000 +#define HMAC_MD5_LENGTH 16 + typedef struct { guchar header[12]; @@ -686,10 +708,14 @@ static gboolean soup_ntlm_parse_challenge (const char *challenge, char **nonce, char **default_domain, - gboolean *ntlmv2_session) + gboolean *ntlmv2_session, + gboolean *negotiate_target, + char **target_info, + size_t *target_info_sz) { gsize clen; NTLMString domain; + NTLMString target; guchar *chall; guint32 flags; @@ -703,6 +729,14 @@ soup_ntlm_parse_challenge (const char *c memcpy (&flags, chall + NTLM_CHALLENGE_FLAGS_OFFSET, sizeof(flags)); flags = GUINT_FROM_LE (flags); *ntlmv2_session = (flags & NTLM_FLAGS_NEGOTIATE_NTLMV2) ? TRUE : FALSE; + /* To know if NTLMv2 responses should be calculated */ + *negotiate_target = (flags & NTLM_FLAGS_NEGOTIATE_TARGET_INFORMATION ) ? TRUE : FALSE; + if (*negotiate_target) { + if (clen < NTLM_CHALLENGE_TARGET_INFORMATION_OFFSET + sizeof (target)) { + g_free (chall); + return FALSE; + } + } if (default_domain) { memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain)); @@ -723,6 +757,19 @@ soup_ntlm_parse_challenge (const char *c *nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_OFFSET, NTLM_CHALLENGE_NONCE_LENGTH); } + /* For NTLMv2 response */ + if (*negotiate_target && target_info) { + memcpy (&target, chall + NTLM_CHALLENGE_TARGET_INFORMATION_OFFSET, sizeof (target)); + target.length = GUINT16_FROM_LE (target.length); + target.offset = GUINT16_FROM_LE (target.offset); + + if (clen < target.length + target.offset) { + g_free (chall); + return FALSE; + } + *target_info = g_memdup (chall + target.offset, target.length); + *target_info_sz = target.length; + } g_free (chall); return TRUE; @@ -761,6 +808,115 @@ calc_ntlm2_session_response (const char calc_response (nt_hash, ntlmv2_hash, nt_resp); } +/* Compute HMAC-MD5 with Glib function*/ +static void +calc_hmac_md5 (unsigned char *hmac, const guchar *key, gsize key_sz, const guchar *data, gsize data_sz) +{ + char *hmac_hex, *hex_pos; + size_t count; + + hmac_hex = g_compute_hmac_for_data(G_CHECKSUM_MD5, key, key_sz, data, data_sz); + hex_pos = hmac_hex; + for (count = 0; count < HMAC_MD5_LENGTH; count++) + { + /* The 'hh' sscanf format modifier is C99, so we enable it on + * non-Windows or if __USE_MINGW_ANSI_STDIO is enabled or` + * if we are building on Visual Studio 2015 or later + */ +#if !defined (G_OS_WIN32) || (__USE_MINGW_ANSI_STDIO == 1) || (_MSC_VER >= 1900) + sscanf(hex_pos, "%2hhx", &hmac[count]); +#else + unsigned int tmp_hmac; + sscanf(hex_pos, "%2x", &tmp_hmac); + hmac[count] = (guint8)tmp_hmac; +#endif + + hex_pos += 2; + } + g_free(hmac_hex); +} + +static void +calc_ntlmv2_response (const char *user, const char *domain, + const guchar *nt_hash, const gsize nt_hash_sz, + const guchar *nonce, + const char *target_info, size_t target_info_sz, + guchar *lm_resp, size_t lm_resp_sz, + guchar *nt_resp, size_t nt_resp_sz) +{ + const unsigned char blob_signature[] = {0x01,0x01,0x00,0x00}; + const unsigned char blob_reserved[] = {0x00,0x00,0x00,0x00}; + gint64 blob_timestamp; + unsigned char client_nonce[8]; + const unsigned char blob_unknown[] = {0x00,0x00,0x00,0x00}; + + unsigned char ntv2_hash[HMAC_MD5_LENGTH]; + guchar *nonce_blob, *blob, *p_blob; + unsigned char nonce_blob_hash[HMAC_MD5_LENGTH]; + unsigned char nonce_client_nonce[16], nonce_client_nonce_hash[HMAC_MD5_LENGTH]; + gchar *user_uppercase, *user_domain, *user_domain_conv; + gsize user_domain_conv_sz; + size_t blob_sz; + int i; + + /* create HMAC-MD5 hash of Unicode uppercase username and Unicode domain */ + user_uppercase = g_utf8_strup (user, strlen (user)); + user_domain = g_strconcat (user_uppercase, domain, NULL); + user_domain_conv = g_convert (user_domain, -1, "UCS-2LE", "UTF-8", NULL, &user_domain_conv_sz, NULL); + calc_hmac_md5 (ntv2_hash, nt_hash, nt_hash_sz, (const guchar *)user_domain_conv, user_domain_conv_sz); + g_free (user_uppercase); + g_free (user_domain); + g_free (user_domain_conv); + + /* create random client nonce */ + for (i = 0; i < sizeof (client_nonce); i++) + { + client_nonce[i] = g_random_int(); + } + + /* create timestamp for blob + * LE, 64-bit signed value, number of tenths of a ms since January 1, 1601.*/ + blob_timestamp = GINT64_TO_LE(((unsigned long)time(NULL) + 11644473600) * 10000000); + + /* create blob */ + blob_sz = sizeof (blob_signature) + sizeof (blob_reserved) + + sizeof (blob_timestamp) + sizeof (client_nonce) + + sizeof (blob_unknown) + target_info_sz; + p_blob = blob = g_malloc (blob_sz); + memset (blob, 0, blob_sz); + memcpy (p_blob, blob_signature, sizeof (blob_signature)); + memcpy (p_blob += sizeof (blob_signature), blob_reserved, sizeof (blob_reserved)); + memcpy (p_blob += sizeof (blob_reserved), &blob_timestamp, sizeof (blob_timestamp)); + memcpy (p_blob += sizeof (blob_timestamp), client_nonce, sizeof (client_nonce)); + memcpy (p_blob += sizeof (client_nonce), blob_unknown, sizeof (blob_unknown)); + memcpy (p_blob += sizeof (blob_unknown), target_info, target_info_sz); + + /* create HMAC-MD5 hash of concatenated nonce and blob */ + nonce_blob = g_malloc (NTLM_CHALLENGE_NONCE_LENGTH + blob_sz); + memcpy (nonce_blob, nonce, NTLM_CHALLENGE_NONCE_LENGTH); + memcpy (nonce_blob + NTLM_CHALLENGE_NONCE_LENGTH, blob, blob_sz); + calc_hmac_md5 (nonce_blob_hash, (const guchar *)ntv2_hash, (gsize) sizeof (ntv2_hash), (const guchar *) nonce_blob, (gsize) NTLM_CHALLENGE_NONCE_LENGTH + blob_sz); + g_free (nonce_blob); + + /* create NTv2 response */ + memset (nt_resp, 0, nt_resp_sz); + memcpy (nt_resp, nonce_blob_hash, sizeof (nonce_blob_hash)); + memcpy (nt_resp + sizeof (nonce_blob_hash), blob, blob_sz); + + g_free (blob); + + /* LMv2 + * create HMAC-MD5 hash of concatenated nonce and client nonce + */ + memcpy (nonce_client_nonce, nonce, NTLM_CHALLENGE_NONCE_LENGTH); + memcpy (nonce_client_nonce + NTLM_CHALLENGE_NONCE_LENGTH, client_nonce, sizeof (client_nonce)); + calc_hmac_md5 (nonce_client_nonce_hash, (const guchar *) ntv2_hash, (gsize) sizeof (ntv2_hash), (const guchar *) nonce_client_nonce, (gsize) NTLM_CHALLENGE_NONCE_LENGTH + sizeof (client_nonce)); + + /* create LMv2 response */ + memset (lm_resp, 0, lm_resp_sz); + memcpy (lm_resp, nonce_client_nonce_hash, sizeof (nonce_client_nonce_hash)); + memcpy (lm_resp + sizeof (nonce_client_nonce_hash), client_nonce, sizeof (client_nonce)); +} static char * soup_ntlm_response (const char *nonce, @@ -769,23 +925,45 @@ soup_ntlm_response (const char *nonce, guchar lm_hash[21], const char *host, const char *domain, - gboolean ntlmv2_session) + gboolean ntlmv2_session, + gboolean negotiate_target, + const char *target_info, + size_t target_info_sz) { + int offset; - gsize hlen, dlen, ulen; - guchar lm_resp[24], nt_resp[24]; + gsize hlen, dlen, ulen, nt_resp_sz; + guchar lm_resp[24], *nt_resp; char *user_conv, *host_conv, *domain_conv; NTLMResponse resp; char *out, *p; int state, save; - if (ntlmv2_session) { + if (negotiate_target) + { + /* nonce_blob_hash 16 + blob_signature 4 + blob_reserved 4 + + * blob_timestamp 8 + client_nonce 8 + blob_unknown 4 + + * target_info*/ + nt_resp_sz = NTLM_RESPONSE_TARGET_INFORMATION_OFFSET + target_info_sz; + } else { + nt_resp_sz = 24; + } + nt_resp = g_malloc (nt_resp_sz); + + if (ntlmv2_session && !negotiate_target) { calc_ntlm2_session_response (nonce, nt_hash, lm_hash, lm_resp, sizeof(lm_resp), nt_resp); - } else { - /* Compute a regular response */ + } else if (!negotiate_target){ + /* Compute a regular NTLMv1 response */ calc_response (nt_hash, (guchar *) nonce, nt_resp); calc_response (lm_hash, (guchar *) nonce, lm_resp); + } else { + calc_ntlmv2_response (user, domain, + nt_hash, 21, + (guchar *) nonce, + target_info, target_info_sz, + lm_resp, sizeof (lm_resp), + nt_resp, (size_t) nt_resp_sz); } memset (&resp, 0, sizeof (resp)); @@ -793,7 +971,8 @@ soup_ntlm_response (const char *nonce, resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS); if (ntlmv2_session) resp.flags |= GUINT32_TO_LE (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY); - + if (negotiate_target) + resp.flags |= GUINT32_TO_LE (NTLM_FLAGS_REQUEST_TARGET); offset = sizeof (resp); if (!host) @@ -807,10 +986,10 @@ soup_ntlm_response (const char *nonce, ntlm_set_string (&resp.user, &offset, ulen); ntlm_set_string (&resp.host, &offset, hlen); ntlm_set_string (&resp.lm_resp, &offset, sizeof (lm_resp)); - ntlm_set_string (&resp.nt_resp, &offset, sizeof (nt_resp)); + ntlm_set_string (&resp.nt_resp, &offset, nt_resp_sz); out = g_malloc (((offset + 3) * 4) / 3 + 6); - strncpy (out, "NTLM ", 5); + memcpy (out, "NTLM ", 5); p = out + 5; state = save = 0; @@ -825,7 +1004,7 @@ soup_ntlm_response (const char *nonce, FALSE, p, &state, &save); p += g_base64_encode_step (lm_resp, sizeof (lm_resp), FALSE, p, &state, &save); - p += g_base64_encode_step (nt_resp, sizeof (nt_resp), + p += g_base64_encode_step (nt_resp, nt_resp_sz, FALSE, p, &state, &save); p += g_base64_encode_close (FALSE, p, &state, &save); *p = '\0'; @@ -833,6 +1012,7 @@ soup_ntlm_response (const char *nonce, g_free (domain_conv); g_free (user_conv); g_free (host_conv); + g_free (nt_resp); return out; }