[libsoup] ntlm: Implement NTLMv2 Session Security support



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]