diff --git a/SOURCES/0004-ntlmv2.patch b/SOURCES/0004-ntlmv2.patch new file mode 100644 index 0000000..1fad130 --- /dev/null +++ b/SOURCES/0004-ntlmv2.patch @@ -0,0 +1,356 @@ +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; + } diff --git a/SPECS/libsoup.spec b/SPECS/libsoup.spec index 4a288cd..0f462a4 100644 --- a/SPECS/libsoup.spec +++ b/SPECS/libsoup.spec @@ -2,7 +2,7 @@ Name: libsoup Version: 2.62.3 -Release: 2%{?dist} +Release: 3%{?dist} Summary: Soup, an HTTP library implementation License: LGPLv2 @@ -12,6 +12,7 @@ Source0: https://download.gnome.org/sources/%{name}/2.62/%{name}-%{version}.tar. Patch0001: 0001-WebSockets-ignore-any-messages-after-close-has-been-.patch Patch0002: 0002-WebSockets-allow-null-characters-in-text-messages-da.patch Patch0003: 0003-WebSockets-only-poll-IO-stream-when-needed.patch +Patch0004: 0004-ntlmv2.patch BuildRequires: chrpath BuildRequires: glib2-devel >= %{glib2_version} @@ -86,6 +87,9 @@ chrpath --delete $RPM_BUILD_ROOT%{_libdir}/*.so %{_datadir}/vala/vapi/libsoup-2.4.vapi %changelog +* Fri Sep 16 2022 Milan Crha - 2.62.3-3 +- Resolves: #1938011 (Support for NTLMv2 Authentication) + * Thu Aug 27 2020 Martin Pitt - 2.62.3-2 - Some WebSocket fixes to unbreak cockpit-desktop (rhbz#1872270)