summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/http/http_auth_handler_ntlm.cc39
-rw-r--r--net/http/http_auth_handler_ntlm.h14
-rw-r--r--net/http/http_network_transaction_unittest.cc262
3 files changed, 295 insertions, 20 deletions
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index 5dbec24..533b484 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -434,6 +434,25 @@ static int ParseType2Msg(const void* in_buf, uint32 in_len, Type2Msg* msg) {
return OK;
}
+static void GenerateRandom(uint8* output, size_t n) {
+ for (size_t i = 0; i < n; ++i)
+ output[i] = base::RandInt(0, 255);
+}
+
+static void GetHostName(char* name, size_t namelen) {
+ if (gethostname(name, namelen) != 0)
+ name[0] = '\0';
+}
+
+// TODO(wtc): these two function pointers should become static members of
+// HttpAuthHandlerNTLM. They are file-scope static variables now so that
+// GenerateType3Msg can use them without being a friend function. We should
+// have HttpAuthHandlerNTLM absorb NTLMAuthModule and pass the host name and
+// random bytes as input arguments to GenerateType3Msg.
+static HttpAuthHandlerNTLM::GenerateRandomProc generate_random_proc_ =
+ GenerateRandom;
+static HttpAuthHandlerNTLM::HostNameProc get_host_name_proc_ = GetHostName;
+
// Returns OK or a network error code.
static int GenerateType3Msg(const string16& domain,
const string16& username,
@@ -511,9 +530,10 @@ static int GenerateType3Msg(const string16& domain,
// Get workstation name (use local machine's hostname).
//
char host_buf[256]; // Host names are limited to 255 bytes.
- if (gethostname(host_buf, sizeof(host_buf)) != 0)
- return ERR_UNEXPECTED;
+ get_host_name_proc_(host_buf, sizeof(host_buf));
host_len = strlen(host_buf);
+ if (host_len == 0)
+ return ERR_UNEXPECTED;
if (unicode) {
// hostname is ASCII, so we can do a simple zero-pad expansion:
ucs_host_buf.assign(host_buf, host_buf + host_len);
@@ -547,10 +567,7 @@ static int GenerateType3Msg(const string16& domain,
MD5Digest session_hash;
uint8 temp[16];
- // TODO(wtc): Add a function that generates random bytes so we can say:
- // GenerateRandom(lm_resp, 8);
- for (int i = 0; i < 8; ++i)
- lm_resp[i] = base::RandInt(0, 255);
+ generate_random_proc_(lm_resp, 8);
memset(lm_resp + 8, 0, LM_RESP_LEN - 8);
memcpy(temp, msg.challenge, 8);
@@ -757,6 +774,16 @@ std::string HttpAuthHandlerNTLM::GenerateCredentials(
return std::string("NTLM ") + encode_output;
}
+// static
+void HttpAuthHandlerNTLM::SetGenerateRandomProc(GenerateRandomProc proc) {
+ generate_random_proc_ = proc;
+}
+
+// static
+void HttpAuthHandlerNTLM::SetHostNameProc(HostNameProc proc) {
+ get_host_name_proc_ = proc;
+}
+
// The NTLM challenge header looks like:
// WWW-Authenticate: NTLM auth-data
bool HttpAuthHandlerNTLM::ParseChallenge(
diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h
index 05ce06b..00c246a 100644
--- a/net/http/http_auth_handler_ntlm.h
+++ b/net/http/http_auth_handler_ntlm.h
@@ -7,6 +7,7 @@
#include <string>
+#include "base/basictypes.h"
#include "base/scoped_ptr.h"
#include "net/http/http_auth_handler.h"
@@ -17,6 +18,15 @@ class NTLMAuthModule;
// Code for handling HTTP NTLM authentication.
class HttpAuthHandlerNTLM : public HttpAuthHandler {
public:
+ // A function that generates n random bytes in the output buffer.
+ typedef void (*GenerateRandomProc)(uint8* output, size_t n);
+
+ // A function that returns the local host name as a null-terminated string
+ // in the output buffer. Returns an empty string if the local host name is
+ // not available.
+ // TODO(wtc): return a std::string instead.
+ typedef void (*HostNameProc)(char* name, size_t namelen);
+
HttpAuthHandlerNTLM();
virtual ~HttpAuthHandlerNTLM();
@@ -28,6 +38,10 @@ class HttpAuthHandlerNTLM : public HttpAuthHandler {
const HttpRequestInfo* request,
const ProxyInfo* proxy);
+ // For unit tests to override the GenerateRandom and GetHostName functions.
+ static void SetGenerateRandomProc(GenerateRandomProc proc);
+ static void SetHostNameProc(HostNameProc proc);
+
protected:
virtual bool Init(std::string::const_iterator challenge_begin,
std::string::const_iterator challenge_end) {
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 08eab15..337f6c7 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -8,6 +8,7 @@
#include "net/base/client_socket_factory.h"
#include "net/base/test_completion_callback.h"
#include "net/base/upload_data.h"
+#include "net/http/http_auth_handler_ntlm.h"
#include "net/http/http_network_session.h"
#include "net/http/http_network_transaction.h"
#include "net/http/http_transaction_unittest.h"
@@ -295,6 +296,40 @@ void FillLargeHeadersString(std::string* str, int size) {
str->append(row, sizeof_row);
}
+// Alternative functions that eliminate randomness and dependency on the local
+// host name so that the generated NTLM messages are reproducible.
+void MyGenerateRandom1(uint8* output, size_t n) {
+ static const uint8 bytes[] = {
+ 0x55, 0x29, 0x66, 0x26, 0x6b, 0x9c, 0x73, 0x54
+ };
+ static size_t current_byte = 0;
+ for (size_t i = 0; i < n; ++i) {
+ output[i] = bytes[current_byte++];
+ current_byte %= arraysize(bytes);
+ }
+}
+
+void MyGenerateRandom2(uint8* output, size_t n) {
+ static const uint8 bytes[] = {
+ 0x96, 0x79, 0x85, 0xe7, 0x49, 0x93, 0x70, 0xa1,
+ 0x4e, 0xe7, 0x87, 0x45, 0x31, 0x5b, 0xd3, 0x1f
+ };
+ static size_t current_byte = 0;
+ for (size_t i = 0; i < n; ++i) {
+ output[i] = bytes[current_byte++];
+ current_byte %= arraysize(bytes);
+ }
+}
+
+void MyGetHostName(char* name, size_t namelen) {
+ static const char hostname[] = "WTC-WIN7";
+ if (namelen >= arraysize(hostname)) {
+ memcpy(name, hostname, arraysize(hostname));
+ } else {
+ name[0] = '\0';
+ }
+}
+
//-----------------------------------------------------------------------------
TEST_F(HttpNetworkTransactionTest, Basic) {
@@ -1535,10 +1570,15 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyThenServer) {
EXPECT_EQ(100, response->headers->GetContentLength());
}
-// Test NTLM authentication.
-// TODO(wtc): This test doesn't work because we need to control the 8 random
-// bytes and the "workstation name" for a deterministic expected result.
-TEST_F(HttpNetworkTransactionTest, DISABLED_NTLMAuth) {
+// The NTLM authentication unit tests were generated by capturing the HTTP
+// requests and responses using Fiddler 2 and inspecting the generated random
+// bytes in the debugger.
+
+// Enter the correct password and authenticate successfully.
+TEST_F(HttpNetworkTransactionTest, NTLMAuth1) {
+ net::HttpAuthHandlerNTLM::SetGenerateRandomProc(MyGenerateRandom1);
+ net::HttpAuthHandlerNTLM::SetHostNameProc(MyGetHostName);
+
scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService());
scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction(
CreateSession(proxy_service.get()), &mock_socket_factory));
@@ -1583,18 +1623,18 @@ TEST_F(HttpNetworkTransactionTest, DISABLED_NTLMAuth) {
MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
"Host: 172.22.68.17\r\n"
"Connection: keep-alive\r\n"
- "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHAAAAAYABgAiA"
- "AAAAAAAABAAAAAGAAYAEAAAAAYABgAWAAAAAAAAAAAAAAABYIIAHQA"
- "ZQBzAHQAaQBuAGcALQBuAHQAbABtAHcAdABjAGgAYQBuAGcALQBjAG"
- "8AcgBwAMertjYHfqUhAAAAAAAAAAAAAAAAAAAAAEP3kddZKtMDMssm"
- "KYA6SCllVGUeyoQppQ==\r\n\r\n"),
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBVKW"
+ "Yma5xzVAAAAAAAAAAAAAAAAAAAAACH+gWcm+YsP9Tqb9zCR3WAeZZX"
+ "ahlhx5I=\r\n\r\n"),
};
MockRead data_reads2[] = {
// The origin server responds with a Type 2 message.
MockRead("HTTP/1.1 401 Access Denied\r\n"),
MockRead("WWW-Authenticate: NTLM "
- "TlRMTVNTUAACAAAADAAMADgAAAAFgokCTroKF1e/DRcAAAAAAAAAALo"
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCjGpMpPGlYKkAAAAAAAAAALo"
"AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
"UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
"HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
@@ -1643,10 +1683,6 @@ TEST_F(HttpNetworkTransactionTest, DISABLED_NTLMAuth) {
EXPECT_EQ(L"", response->auth_challenge->realm);
EXPECT_EQ(L"ntlm", response->auth_challenge->scheme);
- // Pass a null identity to the first RestartWithAuth.
- // TODO(wtc): In the future we may pass the actual identity to the first
- // RestartWithAuth.
-
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback2);
@@ -1660,6 +1696,204 @@ TEST_F(HttpNetworkTransactionTest, DISABLED_NTLMAuth) {
EXPECT_EQ(13, response->headers->GetContentLength());
}
+// Enter a wrong password, and then the correct one.
+TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
+ net::HttpAuthHandlerNTLM::SetGenerateRandomProc(MyGenerateRandom2);
+ net::HttpAuthHandlerNTLM::SetHostNameProc(MyGetHostName);
+
+ scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService());
+ scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction(
+ CreateSession(proxy_service.get()), &mock_socket_factory));
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://172.22.68.17/kids/login.aspx");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ // Negotiate and NTLM are often requested together. We only support NTLM.
+ MockRead("WWW-Authenticate: Negotiate\r\n"),
+ MockRead("WWW-Authenticate: NTLM\r\n"),
+ MockRead("Connection: close\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n"),
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"),
+ // Missing content -- won't matter, as connection will be reset.
+ MockRead(false, net::ERR_UNEXPECTED),
+ };
+
+ MockWrite data_writes2[] = {
+ // After automatically restarting with a null identity, this is the
+ // request we should be issuing -- the final header line contains a Type
+ // 1 message.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM "
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message
+ // (the credentials for the origin server). The second request continues
+ // on the same connection.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwCWeY"
+ "XnSZNwoQAAAAAAAAAAAAAAAAAAAADLa34/phTTKzNTWdub+uyFleOj"
+ "4Ww7b7E=\r\n\r\n"),
+ };
+
+ MockRead data_reads2[] = {
+ // The origin server responds with a Type 2 message.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: NTLM "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCbVWUZezVGpAAAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n"),
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"),
+ MockRead("You are not authorized to view this page\r\n"),
+
+ // Wrong password.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: Negotiate\r\n"),
+ MockRead("WWW-Authenticate: NTLM\r\n"),
+ MockRead("Connection: close\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n"),
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"),
+ // Missing content -- won't matter, as connection will be reset.
+ MockRead(false, net::ERR_UNEXPECTED),
+ };
+
+ MockWrite data_writes3[] = {
+ // After automatically restarting with a null identity, this is the
+ // request we should be issuing -- the final header line contains a Type
+ // 1 message.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM "
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message
+ // (the credentials for the origin server). The second request continues
+ // on the same connection.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBO54"
+ "dFMVvTHwAAAAAAAAAAAAAAAAAAAACS7sT6Uzw7L0L//WUqlIaVWpbI"
+ "+4MUm7c=\r\n\r\n"),
+ };
+
+ MockRead data_reads3[] = {
+ // The origin server responds with a Type 2 message.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: NTLM "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCL24VN8dgOR8AAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n"),
+ MockRead("Proxy-Support: Session-Based-Authentication\r\n\r\n"),
+ MockRead("You are not authorized to view this page\r\n"),
+
+ // Lastly we get the desired content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=utf-8\r\n"),
+ MockRead("Content-Length: 13\r\n\r\n"),
+ MockRead("Please Login\r\n"),
+ MockRead(false, net::OK),
+ };
+
+ MockSocket data1;
+ data1.reads = data_reads1;
+ data1.writes = data_writes1;
+ MockSocket data2;
+ data2.reads = data_reads2;
+ data2.writes = data_writes2;
+ MockSocket data3;
+ data3.reads = data_reads3;
+ data3.writes = data_writes3;
+ mock_sockets[0] = &data1;
+ mock_sockets[1] = &data2;
+ mock_sockets[2] = &data3;
+ mock_sockets[3] = NULL;
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"172.22.68.17", response->auth_challenge->host);
+ EXPECT_EQ(L"", response->auth_challenge->realm);
+ EXPECT_EQ(L"ntlm", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ // Enter the wrong password.
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"wrongpassword", &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"172.22.68.17", response->auth_challenge->host);
+ EXPECT_EQ(L"", response->auth_challenge->realm);
+ EXPECT_EQ(L"ntlm", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback3;
+
+ // Now enter the right password.
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback3);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(13, response->headers->GetContentLength());
+}
+
// Test reading a server response which has only headers, and no body.
// After some maximum number of bytes is consumed, the transaction should
// fail with ERR_RESPONSE_HEADERS_TOO_BIG.