[libsoup] ntlm: Implement NTLMv2 Session Security support
- From: Dan Winship <danw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsoup] ntlm: Implement NTLMv2 Session Security support
- Date: Sat, 28 Feb 2015 14:43:40 +0000 (UTC)
commit feca9b3dcc99da94167513d8a43aa56de132f6ef
Author: Adam Seering <aseering gmail com>
Date: Sun Dec 28 21:54:00 2014 -0500
ntlm: Implement NTLMv2 Session Security support
https://bugzilla.gnome.org/show_bug.cgi?id=739192
libsoup/soup-auth-ntlm.c | 76 ++++++++++++++++++++++++++++++++----
tests/ntlm-test.c | 96 +++++++++++++++++++++++++++++++++-------------
2 files changed, 136 insertions(+), 36 deletions(-)
---
diff --git a/libsoup/soup-auth-ntlm.c b/libsoup/soup-auth-ntlm.c
index ca0e4e1..ddb6b2e 100644
--- a/libsoup/soup-auth-ntlm.c
+++ b/libsoup/soup-auth-ntlm.c
@@ -25,13 +25,15 @@ static void soup_ntlm_nt_hash (const char *password,
static char *soup_ntlm_request (void);
static gboolean soup_ntlm_parse_challenge (const char *challenge,
char **nonce,
- char **default_domain);
+ char **default_domain,
+ gboolean *ntlmv2_session);
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);
+ const char *domain,
+ gboolean ntlmv2_session);
typedef enum {
SOUP_NTLM_NEW,
@@ -46,6 +48,7 @@ typedef struct {
SoupNTLMState state;
char *nonce;
char *response_header;
+ gboolean ntlmv2_session;
} SoupNTLMConnectionState;
typedef enum {
@@ -327,7 +330,8 @@ soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
return TRUE;
if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce,
- priv->domain ? NULL : &priv->domain)) {
+ priv->domain ? NULL : &priv->domain,
+ &conn->ntlmv2_session)) {
conn->state = SOUP_NTLM_FAILED;
return FALSE;
}
@@ -502,7 +506,8 @@ soup_auth_ntlm_get_connection_authorization (SoupConnectionAuth *auth,
priv->nt_hash,
priv->lm_hash,
NULL,
- priv->domain);
+ priv->domain,
+ conn->ntlmv2_session);
}
g_clear_pointer (&conn->nonce, g_free);
conn->state = SOUP_NTLM_SENT_RESPONSE;
@@ -631,8 +636,12 @@ typedef struct {
#define NTLM_CHALLENGE_NONCE_LENGTH 8
#define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12
+#define NTLM_CHALLENGE_FLAGS_OFFSET 20
+#define NTLM_FLAGS_NEGOTIATE_NTLMV2 0x00080000
+
#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00"
#define NTLM_RESPONSE_FLAGS 0x8201
+#define NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 0x00080000
typedef struct {
guchar header[12];
@@ -658,17 +667,19 @@ ntlm_set_string (NTLMString *string, int *offset, int len)
static char *
soup_ntlm_request (void)
{
- return g_strdup ("NTLM TlRMTVNTUAABAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA");
+ return g_strdup ("NTLM TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA");
}
static gboolean
soup_ntlm_parse_challenge (const char *challenge,
char **nonce,
- char **default_domain)
+ char **default_domain,
+ gboolean *ntlmv2_session)
{
gsize clen;
NTLMString domain;
guchar *chall;
+ guint32 flags;
chall = g_base64_decode (challenge, &clen);
if (clen < NTLM_CHALLENGE_DOMAIN_STRING_OFFSET ||
@@ -677,6 +688,10 @@ soup_ntlm_parse_challenge (const char *challenge,
return FALSE;
}
+ memcpy (&flags, chall + NTLM_CHALLENGE_FLAGS_OFFSET, sizeof(flags));
+ flags = GUINT_FROM_LE (flags);
+ *ntlmv2_session = (flags & NTLM_FLAGS_NEGOTIATE_NTLMV2) ? TRUE : FALSE;
+
if (default_domain) {
memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain));
domain.length = GUINT16_FROM_LE (domain.length);
@@ -701,13 +716,48 @@ soup_ntlm_parse_challenge (const char *challenge,
return TRUE;
}
+static void
+calc_ntlm2_session_response (const char *nonce,
+ guchar nt_hash[21],
+ guchar lm_hash[21],
+ guchar *lm_resp,
+ gsize lm_resp_sz,
+ guchar *nt_resp)
+{
+ guint32 client_nonce[2];
+ guchar ntlmv2_hash[16];
+ GChecksum *ntlmv2_cksum;
+ gsize ntlmv2_hash_sz = sizeof (ntlmv2_hash);
+
+ /* FIXME: if GLib ever gets a more secure random number
+ * generator, use it here
+ */
+ client_nonce[0] = g_random_int();
+ client_nonce[1] = g_random_int();
+
+ ntlmv2_cksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (ntlmv2_cksum, (const guchar *) nonce, 8);
+ g_checksum_update (ntlmv2_cksum, (const guchar *) client_nonce, sizeof (client_nonce));
+ g_checksum_get_digest (ntlmv2_cksum, ntlmv2_hash, &ntlmv2_hash_sz);
+ g_checksum_free (ntlmv2_cksum);
+
+ /* Send the padded client nonce as a fake lm_resp */
+ memset (lm_resp, 0, lm_resp_sz);
+ memcpy (lm_resp, client_nonce, sizeof (client_nonce));
+
+ /* Compute nt_hash as usual but with a new nonce */
+ calc_response (nt_hash, ntlmv2_hash, nt_resp);
+}
+
+
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)
+ const char *domain,
+ gboolean ntlmv2_session)
{
int offset;
gsize hlen, dlen, ulen;
@@ -717,12 +767,20 @@ soup_ntlm_response (const char *nonce,
char *out, *p;
int state, save;
- calc_response (nt_hash, (guchar *)nonce, nt_resp);
- calc_response (lm_hash, (guchar *)nonce, lm_resp);
+ if (ntlmv2_session) {
+ calc_ntlm2_session_response (nonce, nt_hash, lm_hash,
+ lm_resp, sizeof(lm_resp), nt_resp);
+ } else {
+ /* Compute a regular response */
+ calc_response (nt_hash, (guchar *) nonce, nt_resp);
+ calc_response (lm_hash, (guchar *) nonce, lm_resp);
+ }
memset (&resp, 0, sizeof (resp));
memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header));
resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS);
+ if (ntlmv2_session)
+ resp.flags |= GUINT32_TO_LE (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY);
offset = sizeof (resp);
diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c
index c6d5380..a0e02ab 100644
--- a/tests/ntlm-test.c
+++ b/tests/ntlm-test.c
@@ -11,8 +11,6 @@
#include "test-utils.h"
-static SoupURI *uri;
-
typedef enum {
NTLM_UNAUTHENTICATED,
NTLM_RECEIVED_REQUEST,
@@ -29,9 +27,17 @@ static const char *state_name[] = {
#define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA"
#define NTLM_CHALLENGE
"TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
+#define NTLMSSP_CHALLENGE
"TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
#define NTLM_RESPONSE_USER(response) ((response)[86] == 'E' ? NTLM_AUTHENTICATED_ALICE : ((response)[86] ==
'I' ? NTLM_AUTHENTICATED_BOB : NTLM_UNAUTHENTICATED))
+typedef struct {
+ SoupServer *server;
+ GHashTable *connections;
+ SoupURI *uri;
+ gboolean ntlmssp;
+} TestServer;
+
static void
clear_state (gpointer connections, GObject *ex_connection)
{
@@ -43,7 +49,7 @@ server_callback (SoupServer *server, SoupMessage *msg,
const char *path, GHashTable *query,
SoupClientContext *client, gpointer data)
{
- GHashTable *connections = data;
+ TestServer *ts = data;
GSocket *socket;
const char *auth;
NTLMServerState state, required_user = 0;
@@ -71,7 +77,7 @@ server_callback (SoupServer *server, SoupMessage *msg,
not_found = TRUE;
socket = soup_client_context_get_gsocket (client);
- state = GPOINTER_TO_INT (g_hash_table_lookup (connections, socket));
+ state = GPOINTER_TO_INT (g_hash_table_lookup (ts->connections, socket));
auth = soup_message_headers_get_one (msg->request_headers,
"Authorization");
@@ -121,7 +127,7 @@ server_callback (SoupServer *server, SoupMessage *msg,
if (ntlm_allowed && state == NTLM_RECEIVED_REQUEST) {
soup_message_headers_append (msg->response_headers,
"WWW-Authenticate",
- "NTLM " NTLM_CHALLENGE);
+ ts->ntlmssp ? ("NTLM " NTLMSSP_CHALLENGE) : ("NTLM "
NTLM_CHALLENGE));
state = NTLM_SENT_CHALLENGE;
} else if (ntlm_allowed) {
soup_message_headers_append (msg->response_headers,
@@ -141,8 +147,37 @@ server_callback (SoupServer *server, SoupMessage *msg,
}
debug_printf (2, " (S:%s)", state_name[state]);
- g_hash_table_insert (connections, socket, GINT_TO_POINTER (state));
- g_object_weak_ref (G_OBJECT (socket), clear_state, connections);
+ g_hash_table_insert (ts->connections, socket, GINT_TO_POINTER (state));
+ g_object_weak_ref (G_OBJECT (socket), clear_state, ts->connections);
+}
+
+static void
+setup_server (TestServer *ts,
+ gconstpointer test_data)
+{
+ ts->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+ ts->connections = g_hash_table_new (NULL, NULL);
+ ts->ntlmssp = FALSE;
+ soup_server_add_handler (ts->server, NULL, server_callback, ts, NULL);
+
+ ts->uri = soup_test_server_get_uri (ts->server, "http", NULL);
+}
+
+static void
+setup_ntlmssp_server (TestServer *ts,
+ gconstpointer test_data)
+{
+ setup_server (ts, test_data);
+ ts->ntlmssp = TRUE;
+}
+
+static void
+teardown_server (TestServer *ts,
+ gconstpointer test_data)
+{
+ soup_uri_free (ts->uri);
+ soup_test_server_quit_unref (ts->server);
+ g_hash_table_destroy (ts->connections);
}
static gboolean authenticated_ntlm = FALSE;
@@ -178,8 +213,10 @@ prompt_check (SoupMessage *msg, gpointer user_data)
if (header && strstr (header, "Basic "))
state->got_basic_prompt = TRUE;
if (header && strstr (header, "NTLM") &&
- !strstr (header, NTLM_CHALLENGE))
+ (!strstr (header, NTLM_CHALLENGE) &&
+ !strstr (header, NTLMSSP_CHALLENGE))) {
state->got_ntlm_prompt = TRUE;
+ }
}
static void
@@ -461,8 +498,16 @@ static const NtlmTest ntlm_tests[] = {
{ "/ntlm/fallback/basic", "alice", FALSE, FALLBACK }
};
+static const NtlmTest ntlmssp_tests[] = {
+ { "/ntlm/ssp/none", NULL, FALSE, BUILTIN },
+ { "/ntlm/ssp/alice", "alice", TRUE, BUILTIN },
+ { "/ntlm/ssp/bob", "bob", TRUE, BUILTIN },
+ { "/ntlm/ssp/basic", "alice", FALSE, BUILTIN }
+};
+
static void
-do_ntlm_test (gconstpointer data)
+do_ntlm_test (TestServer *ts,
+ gconstpointer data)
{
const NtlmTest *test = data;
gboolean use_builtin_ntlm = TRUE;
@@ -509,7 +554,7 @@ do_ntlm_test (gconstpointer data)
break;
}
- do_ntlm_round (uri, test->conn_uses_ntlm, test->user, use_builtin_ntlm);
+ do_ntlm_round (ts->uri, test->conn_uses_ntlm, test->user, use_builtin_ntlm);
}
static void
@@ -532,9 +577,9 @@ retry_test_authenticate (SoupSession *session, SoupMessage *msg,
}
static void
-do_retrying_test (gconstpointer data)
+do_retrying_test (TestServer *ts,
+ gconstpointer data)
{
- SoupURI *base_uri = (SoupURI *)data;
SoupSession *session;
SoupMessage *msg;
SoupURI *uri;
@@ -552,7 +597,7 @@ do_retrying_test (gconstpointer data)
g_signal_connect (session, "authenticate",
G_CALLBACK (retry_test_authenticate), &retried);
- uri = soup_uri_new_with_base (base_uri, "/alice");
+ uri = soup_uri_new_with_base (ts->uri, "/alice");
msg = soup_message_new_from_uri ("GET", uri);
soup_uri_free (uri);
@@ -574,7 +619,7 @@ do_retrying_test (gconstpointer data)
G_CALLBACK (retry_test_authenticate), &retried);
retried = FALSE;
- uri = soup_uri_new_with_base (base_uri, "/bob");
+ uri = soup_uri_new_with_base (ts->uri, "/bob");
msg = soup_message_new_from_uri ("GET", uri);
soup_uri_free (uri);
@@ -591,28 +636,25 @@ do_retrying_test (gconstpointer data)
int
main (int argc, char **argv)
{
- SoupServer *server;
- GHashTable *connections;
int i, ret;
test_init (argc, argv, NULL);
- server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
- connections = g_hash_table_new (NULL, NULL);
- soup_server_add_handler (server, NULL,
- server_callback, connections, NULL);
-
- uri = soup_test_server_get_uri (server, "http", NULL);
+ for (i = 0; i < G_N_ELEMENTS (ntlm_tests); i++) {
+ g_test_add (ntlm_tests[i].name, TestServer, &ntlm_tests[i],
+ setup_server, do_ntlm_test, teardown_server);
+ }
+ for (i = 0; i < G_N_ELEMENTS (ntlmssp_tests); i++) {
+ g_test_add (ntlmssp_tests[i].name, TestServer, &ntlmssp_tests[i],
+ setup_ntlmssp_server, do_ntlm_test, teardown_server);
+ }
- for (i = 0; i < G_N_ELEMENTS (ntlm_tests); i++)
- g_test_add_data_func (ntlm_tests[i].name, &ntlm_tests[i], do_ntlm_test);
- g_test_add_data_func ("/ntlm/retry", uri, do_retrying_test);
+ g_test_add ("/ntlm/retry", TestServer, NULL,
+ setup_server, do_retrying_test, teardown_server);
ret = g_test_run ();
- soup_test_server_quit_unref (server);
test_cleanup ();
- g_hash_table_destroy (connections);
return ret;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]