summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-28 01:29:24 +0000
committerwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-28 01:29:24 +0000
commit3f918787e1073e17439e55ed34f23ffdc31f891f (patch)
tree3e591f7dc3c54e8859815486057725366532ca22 /net
parent0a5f0a187c73e47417511ea2ed988c5b3876f563 (diff)
downloadchromium_src-3f918787e1073e17439e55ed34f23ffdc31f891f.zip
chromium_src-3f918787e1073e17439e55ed34f23ffdc31f891f.tar.gz
chromium_src-3f918787e1073e17439e55ed34f23ffdc31f891f.tar.bz2
Implement the NTLM authentication scheme by porting
Mozilla's implementation. R=darin,eroman BUG=6567,6824 Review URL: http://codereview.chromium.org/28144 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10667 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/build/net.vcproj24
-rw-r--r--net/build/net_unittests.vcproj8
-rw-r--r--net/http/des.cc220
-rw-r--r--net/http/des.h27
-rw-r--r--net/http/des_unittest.cc50
-rw-r--r--net/http/http_auth.cc33
-rw-r--r--net/http/http_auth.h11
-rw-r--r--net/http/http_auth_cache_unittest.cc19
-rw-r--r--net/http/http_auth_handler.cc10
-rw-r--r--net/http/http_auth_handler.h38
-rw-r--r--net/http/http_auth_handler_basic.cc1
-rw-r--r--net/http/http_auth_handler_digest.cc1
-rw-r--r--net/http/http_auth_handler_ntlm.cc787
-rw-r--r--net/http/http_auth_handler_ntlm.h60
-rw-r--r--net/http/http_auth_unittest.cc86
-rw-r--r--net/http/http_network_transaction.cc41
-rw-r--r--net/http/http_network_transaction_unittest.cc125
-rw-r--r--net/http/md4.cc184
-rw-r--r--net/http/md4.h74
-rw-r--r--net/net.xcodeproj/project.pbxproj22
-rw-r--r--net/net_lib.scons6
-rw-r--r--net/net_unittests.scons1
22 files changed, 1786 insertions, 42 deletions
diff --git a/net/build/net.vcproj b/net/build/net.vcproj
index ed3a769..a24ebda 100644
--- a/net/build/net.vcproj
+++ b/net/build/net.vcproj
@@ -694,6 +694,14 @@
Name="http"
>
<File
+ RelativePath="..\http\des.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\des.h"
+ >
+ </File>
+ <File
RelativePath="..\http\http_atom_list.h"
>
</File>
@@ -738,6 +746,14 @@
>
</File>
<File
+ RelativePath="..\http\http_auth_handler_ntlm.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\http_auth_handler_ntlm.h"
+ >
+ </File>
+ <File
RelativePath="..\http\http_cache.cc"
>
</File>
@@ -817,6 +833,14 @@
RelativePath="..\http\http_vary_data.h"
>
</File>
+ <File
+ RelativePath="..\http\md4.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\http\md4.h"
+ >
+ </File>
</Filter>
<Filter
Name="disk_cache"
diff --git a/net/build/net_unittests.vcproj b/net/build/net_unittests.vcproj
index 60d5742..36624d7 100644
--- a/net/build/net_unittests.vcproj
+++ b/net/build/net_unittests.vcproj
@@ -227,6 +227,10 @@
Name="http"
>
<File
+ RelativePath="..\http\des_unittest.cc"
+ >
+ </File>
+ <File
RelativePath="..\http\http_auth_cache_unittest.cc"
>
</File>
@@ -367,11 +371,11 @@
>
</File>
<File
- RelativePath="..\base\tcp_pinger_unittest.cc"
+ RelativePath="..\base\tcp_client_socket_unittest.cc"
>
</File>
<File
- RelativePath="..\base\tcp_client_socket_unittest.cc"
+ RelativePath="..\base\tcp_pinger_unittest.cc"
>
</File>
<File
diff --git a/net/http/des.cc b/net/http/des.cc
new file mode 100644
index 0000000..0f89029
--- /dev/null
+++ b/net/http/des.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http/des.h"
+
+#if defined(OS_LINUX)
+#include <nss.h>
+#include <pk11pub.h>
+#elif defined(OS_MACOSX)
+#include <CommonCrypto/CommonCryptor.h>
+#elif defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+#endif
+
+#include "base/logging.h"
+#if defined(OS_LINUX)
+#include "base/nss_init.h"
+#endif
+
+// The Mac and Windows (CryptoAPI) versions of DESEncrypt are our own code.
+// DESSetKeyParity, DESMakeKey, and the Linux (NSS) version of DESEncrypt are
+// based on mozilla/security/manager/ssl/src/nsNTLMAuthModule.cpp,
+// CVS rev. 1.14.
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Set odd parity bit (in least significant bit position).
+static uint8 DESSetKeyParity(uint8 x) {
+ if ((((x >> 7) ^ (x >> 6) ^ (x >> 5) ^
+ (x >> 4) ^ (x >> 3) ^ (x >> 2) ^
+ (x >> 1)) & 0x01) == 0) {
+ x |= 0x01;
+ } else {
+ x &= 0xfe;
+ }
+ return x;
+}
+
+namespace net {
+
+void DESMakeKey(const uint8* raw, uint8* key) {
+ key[0] = DESSetKeyParity(raw[0]);
+ key[1] = DESSetKeyParity((raw[0] << 7) | (raw[1] >> 1));
+ key[2] = DESSetKeyParity((raw[1] << 6) | (raw[2] >> 2));
+ key[3] = DESSetKeyParity((raw[2] << 5) | (raw[3] >> 3));
+ key[4] = DESSetKeyParity((raw[3] << 4) | (raw[4] >> 4));
+ key[5] = DESSetKeyParity((raw[4] << 3) | (raw[5] >> 5));
+ key[6] = DESSetKeyParity((raw[5] << 2) | (raw[6] >> 6));
+ key[7] = DESSetKeyParity((raw[6] << 1));
+}
+
+#if defined(OS_LINUX)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ CK_MECHANISM_TYPE cipher_mech = CKM_DES_ECB;
+ PK11SlotInfo* slot = NULL;
+ PK11SymKey* symkey = NULL;
+ PK11Context* ctxt = NULL;
+ SECItem key_item;
+ SECItem* param = NULL;
+ SECStatus rv;
+ unsigned int n;
+
+ base::EnsureNSSInit();
+
+ slot = PK11_GetBestSlot(cipher_mech, NULL);
+ if (!slot)
+ goto done;
+
+ key_item.data = const_cast<uint8*>(key);
+ key_item.len = 8;
+ symkey = PK11_ImportSymKey(slot, cipher_mech,
+ PK11_OriginUnwrap, CKA_ENCRYPT,
+ &key_item, NULL);
+ if (!symkey)
+ goto done;
+
+ // No initialization vector required.
+ param = PK11_ParamFromIV(cipher_mech, NULL);
+ if (!param)
+ goto done;
+
+ ctxt = PK11_CreateContextBySymKey(cipher_mech, CKA_ENCRYPT,
+ symkey, param);
+ if (!ctxt)
+ goto done;
+
+ rv = PK11_CipherOp(ctxt, hash, reinterpret_cast<int*>(&n), 8,
+ const_cast<uint8*>(src), 8);
+ if (rv != SECSuccess)
+ goto done;
+
+ // TODO(wtc): Should this be PK11_Finalize?
+ rv = PK11_DigestFinal(ctxt, hash+8, &n, 0);
+ if (rv != SECSuccess)
+ goto done;
+
+ done:
+ if (ctxt)
+ PK11_DestroyContext(ctxt, PR_TRUE);
+ if (symkey)
+ PK11_FreeSymKey(symkey);
+ if (param)
+ SECITEM_FreeItem(param, PR_TRUE);
+ if (slot)
+ PK11_FreeSlot(slot);
+}
+
+#elif defined(OS_MACOSX)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ CCCryptorStatus status;
+ size_t data_out_moved = 0;
+ status = CCCrypt(kCCEncrypt, kCCAlgorithmDES, kCCOptionECBMode,
+ key, 8, NULL, src, 8, hash, 8, &data_out_moved);
+ DCHECK(status == kCCSuccess);
+ DCHECK(data_out_moved == 8);
+}
+
+#elif defined(OS_WIN)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ HCRYPTPROV provider;
+ HCRYPTKEY hkey = NULL;
+
+ if (!CryptAcquireContext(&provider, NULL, NULL,
+ PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+ provider = NULL;
+ goto done;
+ }
+
+ // Import the DES key.
+ // This code doesn't work on Win2k because PLAINTEXTKEYBLOB is not supported
+ // on Windows 2000. PLAINTEXTKEYBLOB allows the import of an unencrypted
+ // key. For Win2k support, a cubmbersome exponent-of-one key procedure must
+ // be used:
+ // http://support.microsoft.com/kb/228786/en-us
+
+ struct KeyBlob {
+ BLOBHEADER header;
+ DWORD key_size;
+ BYTE key_data[8];
+ };
+ KeyBlob key_blob;
+ key_blob.header.bType = PLAINTEXTKEYBLOB;
+ key_blob.header.bVersion = CUR_BLOB_VERSION;
+ key_blob.header.reserved = 0;
+ key_blob.header.aiKeyAlg = CALG_DES;
+ key_blob.key_size = 8; // 64 bits
+ memcpy(key_blob.key_data, key, 8);
+
+ if (!CryptImportKey(provider, reinterpret_cast<BYTE*>(&key_blob),
+ sizeof(key_blob), 0, 0, &hkey)) {
+ hkey = NULL;
+ goto done;
+ }
+
+ // Destroy the copy of the key.
+ SecureZeroMemory(key_blob.key_data, sizeof(key_blob.key_data));
+
+ // No initialization vector required.
+ DWORD cipher_mode = CRYPT_MODE_ECB;
+ if (!CryptSetKeyParam(hkey, KP_MODE,
+ reinterpret_cast<BYTE*>(&cipher_mode), 0))
+ goto done;
+
+ // CryptoAPI requires us to copy the plaintext to the output buffer first.
+ CopyMemory(hash, src, 8);
+ // Pass a 'Final' of FALSE, otherwise CryptEncrypt appends one additional
+ // block of padding to the data.
+ DWORD hash_len = 8;
+ if (!CryptEncrypt(hkey, NULL, FALSE, 0, hash, &hash_len, 8))
+ goto done;
+
+ done:
+ if (hkey)
+ CryptDestroyKey(hkey);
+ if (provider)
+ CryptReleaseContext(provider, 0);
+}
+
+#endif
+
+} // namespace net
diff --git a/net/http/des.h b/net/http/des.h
new file mode 100644
index 0000000..7b1469b
--- /dev/null
+++ b/net/http/des.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_HTTP_DES_H_
+#define NET_HTTP_DES_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DES support code for NTLM authentication.
+//
+// TODO(wtc): Turn this into a C++ API and move it to the base module.
+
+// Build a 64-bit DES key from a 56-bit raw key.
+void DESMakeKey(const uint8* raw, uint8* key);
+
+// Run the DES encryption algorithm in ECB mode on one block (8 bytes) of
+// data. |key| is a DES key (8 bytes), |src| is the input plaintext (8
+// bytes), and |hash| is an 8-byte buffer receiving the output ciphertext.
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash);
+
+} // namespace net
+
+#endif // NET_HTTP_DES_H_
diff --git a/net/http/des_unittest.cc b/net/http/des_unittest.cc
new file mode 100644
index 0000000..0aefd42
--- /dev/null
+++ b/net/http/des_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+
+#include "net/http/des.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// This test vector comes from the NSS FIPS power-up self-test.
+TEST(DESTest, KnownAnswerTest1) {
+ // DES known key (56-bits).
+ static const uint8 des_known_key[] = { "ANSI DES" };
+
+ // DES known plaintext (64-bits).
+ static const uint8 des_ecb_known_plaintext[] = { "Netscape" };
+
+ // DES known ciphertext (64-bits).
+ static const uint8 des_ecb_known_ciphertext[] = {
+ 0x26, 0x14, 0xe9, 0xc3, 0x28, 0x80, 0x50, 0xb0
+ };
+
+ uint8 ciphertext[8];
+ memset(ciphertext, 0xaf, sizeof(ciphertext));
+
+ DESEncrypt(des_known_key, des_ecb_known_plaintext, ciphertext);
+ EXPECT_EQ(0, memcmp(ciphertext, des_ecb_known_ciphertext, 8));
+}
+
+// This test vector comes from NIST Special Publication 800-17, Modes of
+// Operation Validation System (MOVS): Requirements and Procedures, Appendix
+// A, page 124.
+TEST(DESTest, KnownAnswerTest2) {
+ static const uint8 key[] = {
+ 0x10, 0x31, 0x6e, 0x02, 0x8c, 0x8f, 0x3b, 0x4a
+ };
+ static const uint8 plaintext[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ static const uint8 known_ciphertext[] = {
+ 0x82, 0xdc, 0xba, 0xfb, 0xde, 0xab, 0x66, 0x02
+ };
+ uint8 ciphertext[8];
+ memset(ciphertext, 0xaf, sizeof(ciphertext));
+
+ DESEncrypt(key, plaintext, ciphertext);
+ EXPECT_EQ(0, memcmp(ciphertext, known_ciphertext, 8));
+}
+
+} // namespace net
diff --git a/net/http/http_auth.cc b/net/http/http_auth.cc
index c3764cc..d65fb4e 100644
--- a/net/http/http_auth.cc
+++ b/net/http/http_auth.cc
@@ -10,6 +10,7 @@
#include "base/string_util.h"
#include "net/http/http_auth_handler_basic.h"
#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_auth_handler_ntlm.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
@@ -19,6 +20,21 @@ namespace net {
void HttpAuth::ChooseBestChallenge(const HttpResponseHeaders* headers,
Target target,
scoped_refptr<HttpAuthHandler>* handler) {
+ // A connection-based authentication scheme must continue to use the
+ // existing handler object in |*handler|.
+ if (*handler && (*handler)->is_connection_based()) {
+ const std::string header_name = GetChallengeHeaderName(target);
+ std::string challenge;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, header_name, &challenge)) {
+ ChallengeTokenizer props(challenge.begin(), challenge.end());
+ if (LowerCaseEqualsASCII(props.scheme(), (*handler)->scheme().c_str()) &&
+ (*handler)->InitFromChallenge(challenge.begin(), challenge.end(),
+ target))
+ return;
+ }
+ }
+
// Choose the challenge whose authentication handler gives the maximum score.
scoped_refptr<HttpAuthHandler> best;
const std::string header_name = GetChallengeHeaderName(target);
@@ -45,6 +61,8 @@ void HttpAuth::CreateAuthHandler(const std::string& challenge,
tmp_handler = new HttpAuthHandlerBasic();
} else if (LowerCaseEqualsASCII(props.scheme(), "digest")) {
tmp_handler = new HttpAuthHandlerDigest();
+ } else if (LowerCaseEqualsASCII(props.scheme(), "ntlm")) {
+ tmp_handler = new HttpAuthHandlerNTLM();
}
if (tmp_handler) {
if (!tmp_handler->InitFromChallenge(challenge.begin(), challenge.end(),
@@ -72,8 +90,7 @@ void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
scheme_end_ = tok.token_end();
// Everything past scheme_end_ is a (comma separated) value list.
- if (scheme_end_ != end)
- props_ = HttpUtil::ValuesIterator(scheme_end_ + 1, end, ',');
+ props_ = HttpUtil::ValuesIterator(scheme_end_, end, ',');
}
// We expect properties to be formatted as one of:
@@ -92,22 +109,22 @@ bool HttpAuth::ChallengeTokenizer::GetNext() {
// Scan for the equals sign.
std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
if (equals == value_end_ || equals == value_begin_)
- return valid_ = false; // Malformed
+ return valid_ = false; // Malformed
// Verify that the equals sign we found wasn't inside of quote marks.
for (std::string::const_iterator it = value_begin_; it != equals; ++it) {
if (HttpUtil::IsQuote(*it))
- return valid_ = false; // Malformed
+ return valid_ = false; // Malformed
}
name_begin_ = value_begin_;
name_end_ = equals;
value_begin_ = equals + 1;
-
+
if (value_begin_ != value_end_ && HttpUtil::IsQuote(*value_begin_)) {
// Trim surrounding quotemarks off the value
if (*value_begin_ != *(value_end_ - 1))
- return valid_ = false; // Malformed -- mismatching quotes.
+ return valid_ = false; // Malformed -- mismatching quotes.
value_is_quoted_ = true;
} else {
value_is_quoted_ = false;
@@ -122,7 +139,7 @@ std::string HttpAuth::ChallengeTokenizer::unquoted_value() const {
// static
std::string HttpAuth::GetChallengeHeaderName(Target target) {
- switch(target) {
+ switch (target) {
case AUTH_PROXY:
return "Proxy-Authenticate";
case AUTH_SERVER:
@@ -135,7 +152,7 @@ std::string HttpAuth::GetChallengeHeaderName(Target target) {
// static
std::string HttpAuth::GetAuthorizationHeaderName(Target target) {
- switch(target) {
+ switch (target) {
case AUTH_PROXY:
return "Proxy-Authorization";
case AUTH_SERVER:
diff --git a/net/http/http_auth.h b/net/http/http_auth.h
index 3bb2a86..7442898 100644
--- a/net/http/http_auth.h
+++ b/net/http/http_auth.h
@@ -53,6 +53,7 @@ class HttpAuth {
IdentitySource source;
bool invalid;
+ // TODO(wtc): |username| and |password| should be string16.
std::wstring username;
std::wstring password;
};
@@ -74,8 +75,14 @@ class HttpAuth {
// Iterate through the challenge headers, and pick the best one that
// we support. Obtains the implementation class for handling the challenge,
- // and passes it back in |*handler|. If no supported challenge was found
- // |*handler| is set to NULL.
+ // and passes it back in |*handler|. If the existing handler in |*handler|
+ // should continue to be used (such as for the NTLM authentication scheme),
+ // |*handler| is unchanged. If no supported challenge was found, |*handler|
+ // is set to NULL.
+ //
+ // TODO(wtc): Continuing to use the existing handler in |*handler| (for
+ // NTLM) is new behavior. Rename ChooseBestChallenge to fully encompass
+ // what it does now.
static void ChooseBestChallenge(const HttpResponseHeaders* headers,
Target target,
scoped_refptr<HttpAuthHandler>* handler);
diff --git a/net/http/http_auth_cache_unittest.cc b/net/http/http_auth_cache_unittest.cc
index bb6219e..28bde95 100644
--- a/net/http/http_auth_cache_unittest.cc
+++ b/net/http/http_auth_cache_unittest.cc
@@ -20,9 +20,10 @@ class MockAuthHandler : public HttpAuthHandler {
realm_ = realm;
score_ = 1;
target_ = target;
+ properties_ = 0;
}
-
- virtual std::string GenerateCredentials(const std::wstring&,
+
+ virtual std::string GenerateCredentials(const std::wstring&,
const std::wstring&,
const HttpRequestInfo*,
const ProxyInfo*) {
@@ -35,14 +36,14 @@ class MockAuthHandler : public HttpAuthHandler {
}
};
-} // namespace
+} // namespace
// Test adding and looking-up cache entries (both by realm and by path).
TEST(HttpAuthCacheTest, Basic) {
GURL origin("http://www.google.com");
HttpAuthCache cache;
HttpAuthCache::Entry* entry;
-
+
// Add cache entries for 3 realms: "Realm1", "Realm2", "Realm3"
scoped_refptr<HttpAuthHandler> realm1_handler =
@@ -80,7 +81,7 @@ TEST(HttpAuthCacheTest, Basic) {
EXPECT_TRUE(entry->handler() == realm2_handler.get());
EXPECT_EQ(L"realm2-user", entry->username());
EXPECT_EQ(L"realm2-password", entry->password());
-
+
// Check that subpaths are recognized.
HttpAuthCache::Entry* realm2Entry = cache.LookupByRealm(origin, "Realm2");
EXPECT_FALSE(NULL == realm2Entry);
@@ -161,7 +162,7 @@ TEST(HttpAuthCacheTest, AddToExistingEntry) {
EXPECT_TRUE(entry == orig_entry);
EXPECT_EQ(L"user3", entry->username());
EXPECT_EQ(L"password3", entry->password());
-
+
EXPECT_EQ(2U, entry->paths_.size());
EXPECT_EQ("/z/", entry->paths_.front());
EXPECT_EQ("/x/y/z/", entry->paths_.back());
@@ -183,7 +184,7 @@ TEST(HttpAuthCacheTest, Remove) {
cache.Add(origin, realm1_handler, L"alice", L"123", "/");
cache.Add(origin, realm2_handler, L"bob", L"princess", "/");
cache.Add(origin, realm3_handler, L"admin", L"password", "/");
-
+
// Fails, because there is no realm "Realm4".
EXPECT_FALSE(cache.Remove(origin, "Realm4", L"alice", L"123"));
@@ -270,7 +271,7 @@ TEST_F(HttpAuthCacheEvictionTest, RealmEntryEviction) {
for (int i = 0; i < 3; ++i)
AddRealm(i + kMaxRealms);
-
+
for (int i = 0; i < 3; ++i)
CheckRealmExistence(i, false);
@@ -301,4 +302,4 @@ TEST_F(HttpAuthCacheEvictionTest, RealmPathEviction) {
CheckRealmExistence(i, true);
}
-} // namespace net
+} // namespace net
diff --git a/net/http/http_auth_handler.cc b/net/http/http_auth_handler.cc
index dd9482f..6dac1f0 100644
--- a/net/http/http_auth_handler.cc
+++ b/net/http/http_auth_handler.cc
@@ -12,15 +12,17 @@ bool HttpAuthHandler::InitFromChallenge(std::string::const_iterator begin,
HttpAuth::Target target) {
target_ = target;
score_ = -1;
+ properties_ = -1;
bool ok = Init(begin, end);
- // Init() is expected to set the scheme, realm, and score.
+ // Init() is expected to set the scheme, realm, score, and properties. The
+ // realm may be empty.
DCHECK(!ok || !scheme().empty());
- DCHECK(!ok || !realm().empty());
DCHECK(!ok || score_ != -1);
-
+ DCHECK(!ok || properties_ != -1);
+
return ok;
}
-} // namespace net
+} // namespace net
diff --git a/net/http/http_auth_handler.h b/net/http/http_auth_handler.h
index ef3b102..3b32e18 100644
--- a/net/http/http_auth_handler.h
+++ b/net/http/http_auth_handler.h
@@ -17,7 +17,7 @@ class ProxyInfo;
// HttpAuthHandler is the interface for the authentication schemes
// (basic, digest, ...)
-// The registry mapping auth-schemes to implementations is hardcoded in
+// The registry mapping auth-schemes to implementations is hardcoded in
// HttpAuth::CreateAuthHandler().
class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
public:
@@ -47,7 +47,27 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
HttpAuth::Target target() const {
return target_;
}
-
+
+ // Returns true if the authentication scheme does not send the username and
+ // password in the clear.
+ bool encrypts_identity() const {
+ return (properties_ & ENCRYPTS_IDENTITY) != 0;
+ }
+
+ // Returns true if the authentication scheme is connection-based, for
+ // example, NTLM. A connection-based authentication scheme does not support
+ // preemptive authentication, and must use the same handler object
+ // throughout the life of an HTTP transaction.
+ bool is_connection_based() const {
+ return (properties_ & IS_CONNECTION_BASED) != 0;
+ }
+
+ // Returns true if the response to the current authentication challenge
+ // requires an identity.
+ // TODO(wtc): Find a better way to handle a multi-round challenge-response
+ // sequence used by a connection-based authentication scheme.
+ virtual bool NeedsIdentity() { return true; }
+
// Generate the Authorization header value.
virtual std::string GenerateCredentials(const std::wstring& username,
const std::wstring& password,
@@ -55,9 +75,14 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
const ProxyInfo* proxy) = 0;
protected:
- // Initialize the handler by parsing a challenge string.
- // Implementations are expcted to initialize the following members:
- // score_, realm_, scheme_
+ enum Property {
+ ENCRYPTS_IDENTITY = 1 << 0,
+ IS_CONNECTION_BASED = 1 << 1,
+ };
+
+ // Initialize the handler by parsing a challenge string.
+ // Implementations are expcted to initialize the following members:
+ // scheme_, realm_, score_, properties_
virtual bool Init(std::string::const_iterator challenge_begin,
std::string::const_iterator challenge_end) = 0;
@@ -73,6 +98,9 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
// Whether this authentication request is for a proxy server, or an
// origin server.
HttpAuth::Target target_;
+
+ // A bitmask of the properties of the authentication scheme.
+ int properties_;
};
} // namespace net
diff --git a/net/http/http_auth_handler_basic.cc b/net/http/http_auth_handler_basic.cc
index 51c165c..698b0ab 100644
--- a/net/http/http_auth_handler_basic.cc
+++ b/net/http/http_auth_handler_basic.cc
@@ -14,6 +14,7 @@ bool HttpAuthHandlerBasic::Init(std::string::const_iterator challenge_begin,
std::string::const_iterator challenge_end) {
scheme_ = "basic";
score_ = 1;
+ properties_ = 0;
// Verify the challenge's auth-scheme.
HttpAuth::ChallengeTokenizer challenge_tok(challenge_begin, challenge_end);
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
index e797296..683c268 100644
--- a/net/http/http_auth_handler_digest.cc
+++ b/net/http/http_auth_handler_digest.cc
@@ -200,6 +200,7 @@ bool HttpAuthHandlerDigest::ParseChallenge(
std::string::const_iterator challenge_end) {
scheme_ = "digest";
score_ = 2;
+ properties_ = ENCRYPTS_IDENTITY;
// Initialize to defaults.
stale_ = false;
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
new file mode 100644
index 0000000..5dbec24
--- /dev/null
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -0,0 +1,787 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http/http_auth_handler_ntlm.h"
+
+#include <stdlib.h>
+// For gethostname
+#if defined(OS_POSIX)
+#include <unistd.h>
+#elif defined(OS_WIN)
+#include <winsock2.h>
+#endif
+
+#include "base/md5.h"
+#include "base/rand_util.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "net/base/base64.h"
+#include "net/base/net_errors.h"
+#include "net/http/des.h"
+#include "net/http/md4.h"
+
+namespace net {
+
+// Based on mozilla/security/manager/ssl/src/nsNTLMAuthModule.cpp,
+// CVS rev. 1.14.
+//
+// TODO(wtc):
+// - The IS_BIG_ENDIAN code is not tested.
+// - Enable the logging code or just delete it.
+// - Delete or comment out the LM code, which hasn't been tested and isn't
+// being used.
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Discover the endianness by testing processor architecture.
+#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_X86_64)
+#define IS_LITTLE_ENDIAN 1
+#undef IS_BIG_ENDIAN
+#else
+#error "Unknown endianness"
+#endif
+
+#define NTLM_LOG(x) ((void) 0)
+
+//-----------------------------------------------------------------------------
+// This file contains a cross-platform NTLM authentication implementation. It
+// is based on documentation from: http://davenport.sourceforge.net/ntlm.html
+//-----------------------------------------------------------------------------
+
+enum {
+ NTLM_NegotiateUnicode = 0x00000001,
+ NTLM_NegotiateOEM = 0x00000002,
+ NTLM_RequestTarget = 0x00000004,
+ NTLM_Unknown1 = 0x00000008,
+ NTLM_NegotiateSign = 0x00000010,
+ NTLM_NegotiateSeal = 0x00000020,
+ NTLM_NegotiateDatagramStyle = 0x00000040,
+ NTLM_NegotiateLanManagerKey = 0x00000080,
+ NTLM_NegotiateNetware = 0x00000100,
+ NTLM_NegotiateNTLMKey = 0x00000200,
+ NTLM_Unknown2 = 0x00000400,
+ NTLM_Unknown3 = 0x00000800,
+ NTLM_NegotiateDomainSupplied = 0x00001000,
+ NTLM_NegotiateWorkstationSupplied = 0x00002000,
+ NTLM_NegotiateLocalCall = 0x00004000,
+ NTLM_NegotiateAlwaysSign = 0x00008000,
+ NTLM_TargetTypeDomain = 0x00010000,
+ NTLM_TargetTypeServer = 0x00020000,
+ NTLM_TargetTypeShare = 0x00040000,
+ NTLM_NegotiateNTLM2Key = 0x00080000,
+ NTLM_RequestInitResponse = 0x00100000,
+ NTLM_RequestAcceptResponse = 0x00200000,
+ NTLM_RequestNonNTSessionKey = 0x00400000,
+ NTLM_NegotiateTargetInfo = 0x00800000,
+ NTLM_Unknown4 = 0x01000000,
+ NTLM_Unknown5 = 0x02000000,
+ NTLM_Unknown6 = 0x04000000,
+ NTLM_Unknown7 = 0x08000000,
+ NTLM_Unknown8 = 0x10000000,
+ NTLM_Negotiate128 = 0x20000000,
+ NTLM_NegotiateKeyExchange = 0x40000000,
+ NTLM_Negotiate56 = 0x80000000
+};
+
+// We send these flags with our type 1 message.
+enum {
+ NTLM_TYPE1_FLAGS =
+ NTLM_NegotiateUnicode |
+ NTLM_NegotiateOEM |
+ NTLM_RequestTarget |
+ NTLM_NegotiateNTLMKey |
+ NTLM_NegotiateAlwaysSign |
+ NTLM_NegotiateNTLM2Key
+};
+
+static const char NTLM_SIGNATURE[] = "NTLMSSP";
+static const char NTLM_TYPE1_MARKER[] = { 0x01, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE2_MARKER[] = { 0x02, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE3_MARKER[] = { 0x03, 0x00, 0x00, 0x00 };
+
+enum {
+ NTLM_TYPE1_HEADER_LEN = 32,
+ NTLM_TYPE2_HEADER_LEN = 32,
+ NTLM_TYPE3_HEADER_LEN = 64,
+
+ LM_HASH_LEN = 16,
+ LM_RESP_LEN = 24,
+
+ NTLM_HASH_LEN = 16,
+ NTLM_RESP_LEN = 24
+};
+
+//-----------------------------------------------------------------------------
+
+// The return value of this function controls whether or not the LM hash will
+// be included in response to a NTLM challenge.
+//
+// In Mozilla, this function returns the value of the boolean preference
+// "network.ntlm.send-lm-response". By default, the preference is disabled
+// since servers should almost never need the LM hash, and the LM hash is what
+// makes NTLM authentication less secure. See
+// https://bugzilla.mozilla.org/show_bug.cgi?id=250691 for further details.
+//
+// We just return a hardcoded false.
+static bool SendLM() {
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+#define LogFlags(x) ((void) 0)
+#define LogBuf(a, b, c) ((void) 0)
+#define LogToken(a, b, c) ((void) 0)
+
+//-----------------------------------------------------------------------------
+
+// Byte order swapping.
+#define SWAP16(x) ((((x) & 0xff) << 8) | (((x) >> 8) & 0xff))
+#define SWAP32(x) ((SWAP16((x) & 0xffff) << 16) | (SWAP16((x) >> 16)))
+
+static void* WriteBytes(void* buf, const void* data, uint32 data_len) {
+ memcpy(buf, data, data_len);
+ return static_cast<char*>(buf) + data_len;
+}
+
+static void* WriteDWORD(void* buf, uint32 dword) {
+#ifdef IS_BIG_ENDIAN
+ // NTLM uses little endian on the wire.
+ dword = SWAP32(dword);
+#endif
+ return WriteBytes(buf, &dword, sizeof(dword));
+}
+
+static void* WriteSecBuf(void* buf, uint16 length, uint32 offset) {
+#ifdef IS_BIG_ENDIAN
+ length = SWAP16(length);
+ offset = SWAP32(offset);
+#endif
+ buf = WriteBytes(buf, &length, sizeof(length));
+ buf = WriteBytes(buf, &length, sizeof(length));
+ buf = WriteBytes(buf, &offset, sizeof(offset));
+ return buf;
+}
+
+#ifdef IS_BIG_ENDIAN
+/**
+ * WriteUnicodeLE copies a unicode string from one buffer to another. The
+ * resulting unicode string is in little-endian format. The input string is
+ * assumed to be in the native endianness of the local machine. It is safe
+ * to pass the same buffer as both input and output, which is a handy way to
+ * convert the unicode buffer to little-endian on big-endian platforms.
+ */
+static void* WriteUnicodeLE(void* buf, const char16* str, uint32 str_len) {
+ // Convert input string from BE to LE.
+ uint8* cursor = static_cast<uint8*>(buf);
+ const uint8* input = reinterpret_cast<const uint8*>(str);
+ for (uint32 i = 0; i < str_len; ++i, input += 2, cursor += 2) {
+ // Allow for the case where |buf == str|.
+ uint8 temp = input[0];
+ cursor[0] = input[1];
+ cursor[1] = temp;
+ }
+ return buf;
+}
+#endif
+
+static uint16 ReadUint16(const uint8*& buf) {
+ uint16 x = (static_cast<uint16>(buf[0])) |
+ (static_cast<uint16>(buf[1]) << 8);
+ buf += sizeof(x);
+ return x;
+}
+
+static uint32 ReadUint32(const uint8*& buf) {
+ uint32 x = (static_cast<uint32>(buf[0])) |
+ (static_cast<uint32>(buf[1]) << 8) |
+ (static_cast<uint32>(buf[2]) << 16) |
+ (static_cast<uint32>(buf[3]) << 24);
+ buf += sizeof(x);
+ return x;
+}
+
+//-----------------------------------------------------------------------------
+
+static void ZapBuf(void* buf, size_t buf_len) {
+ memset(buf, 0, buf_len);
+}
+
+// TODO(wtc): Can we implement ZapString as
+// s.replace(0, s.size(), s.size(), '\0)?
+static void ZapString(std::string* s) {
+ ZapBuf(&(*s)[0], s->length());
+}
+
+static void ZapString(string16* s) {
+ ZapBuf(&(*s)[0], s->length() * 2);
+}
+
+// LM_Hash computes the LM hash of the given password.
+//
+// param password
+// unicode password.
+// param hash
+// 16-byte result buffer
+//
+// Note: This function is not being used because our SendLM() function always
+// returns false.
+static void LM_Hash(const string16& password, uint8* hash) {
+ static const uint8 LM_MAGIC[] = "KGS!@#$%";
+
+ // Convert password to OEM character set. We'll just use the native
+ // filesystem charset.
+ std::string passbuf = base::SysWideToNativeMB(UTF16ToWide(password));
+ StringToUpperASCII(&passbuf);
+ passbuf.resize(14, '\0');
+
+ uint8 k1[8], k2[8];
+ DESMakeKey(reinterpret_cast<const uint8*>(passbuf.data()) , k1);
+ DESMakeKey(reinterpret_cast<const uint8*>(passbuf.data()) + 7, k2);
+ ZapString(&passbuf);
+
+ // Use password keys to hash LM magic string twice.
+ DESEncrypt(k1, LM_MAGIC, hash);
+ DESEncrypt(k2, LM_MAGIC, hash + 8);
+}
+
+// NTLM_Hash computes the NTLM hash of the given password.
+//
+// param password
+// null-terminated unicode password.
+// param hash
+// 16-byte result buffer
+static void NTLM_Hash(const string16& password, uint8* hash) {
+#ifdef IS_BIG_ENDIAN
+ uint32 len = password.length();
+ uint8* passbuf;
+
+ passbuf = static_cast<uint8*>(malloc(len * 2));
+ WriteUnicodeLE(passbuf, password.data(), len);
+ weak_crypto::MD4Sum(passbuf, len * 2, hash);
+
+ ZapBuf(passbuf, len * 2);
+ free(passbuf);
+#else
+ weak_crypto::MD4Sum(reinterpret_cast<const uint8*>(password.data()),
+ password.length() * 2, hash);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+// LM_Response generates the LM response given a 16-byte password hash and the
+// challenge from the Type-2 message.
+//
+// param hash
+// 16-byte password hash
+// param challenge
+// 8-byte challenge from Type-2 message
+// param response
+// 24-byte buffer to contain the LM response upon return
+static void LM_Response(const uint8* hash,
+ const uint8* challenge,
+ uint8* response) {
+ uint8 keybytes[21], k1[8], k2[8], k3[8];
+
+ memcpy(keybytes, hash, 16);
+ ZapBuf(keybytes + 16, 5);
+
+ DESMakeKey(keybytes , k1);
+ DESMakeKey(keybytes + 7, k2);
+ DESMakeKey(keybytes + 14, k3);
+
+ DESEncrypt(k1, challenge, response);
+ DESEncrypt(k2, challenge, response + 8);
+ DESEncrypt(k3, challenge, response + 16);
+}
+
+//-----------------------------------------------------------------------------
+
+// Returns OK or a network error code.
+static int GenerateType1Msg(void** out_buf, uint32* out_len) {
+ //
+ // Verify that buf_len is sufficient.
+ //
+ *out_len = NTLM_TYPE1_HEADER_LEN;
+ *out_buf = malloc(*out_len);
+ if (!*out_buf)
+ return ERR_OUT_OF_MEMORY;
+
+ //
+ // Write out type 1 message.
+ //
+ void* cursor = *out_buf;
+
+ // 0 : signature
+ cursor = WriteBytes(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+
+ // 8 : marker
+ cursor = WriteBytes(cursor, NTLM_TYPE1_MARKER, sizeof(NTLM_TYPE1_MARKER));
+
+ // 12 : flags
+ cursor = WriteDWORD(cursor, NTLM_TYPE1_FLAGS);
+
+ //
+ // NOTE: It is common for the domain and workstation fields to be empty.
+ // This is true of Win2k clients, and my guess is that there is
+ // little utility to sending these strings before the charset has
+ // been negotiated. We follow suite -- anyways, it doesn't hurt
+ // to save some bytes on the wire ;-)
+ //
+
+ // 16 : supplied domain security buffer (empty)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ // 24 : supplied workstation security buffer (empty)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ return OK;
+}
+
+struct Type2Msg {
+ uint32 flags; // NTLM_Xxx bitwise combination
+ uint8 challenge[8]; // 8 byte challenge
+ const void* target; // target string (type depends on flags)
+ uint32 target_len; // target length in bytes
+};
+
+// Returns OK or a network error code.
+// TODO(wtc): This function returns ERR_UNEXPECTED when the input message is
+// invalid. We should return a better error code.
+static int ParseType2Msg(const void* in_buf, uint32 in_len, Type2Msg* msg) {
+ // Make sure in_buf is long enough to contain a meaningful type2 msg.
+ //
+ // 0 NTLMSSP Signature
+ // 8 NTLM Message Type
+ // 12 Target Name
+ // 20 Flags
+ // 24 Challenge
+ // 32 end of header, start of optional data blocks
+ //
+ if (in_len < NTLM_TYPE2_HEADER_LEN)
+ return ERR_UNEXPECTED;
+
+ const uint8* cursor = (const uint8*) in_buf;
+
+ // verify NTLMSSP signature
+ if (memcmp(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) != 0)
+ return ERR_UNEXPECTED;
+ cursor += sizeof(NTLM_SIGNATURE);
+
+ // verify Type-2 marker
+ if (memcmp(cursor, NTLM_TYPE2_MARKER, sizeof(NTLM_TYPE2_MARKER)) != 0)
+ return ERR_UNEXPECTED;
+ cursor += sizeof(NTLM_TYPE2_MARKER);
+
+ // read target name security buffer
+ msg->target_len = ReadUint16(cursor);
+ ReadUint16(cursor); // discard next 16-bit value
+ uint32 offset = ReadUint32(cursor); // get offset from in_buf
+ msg->target = ((const uint8*) in_buf) + offset;
+
+ // read flags
+ msg->flags = ReadUint32(cursor);
+
+ // read challenge
+ memcpy(msg->challenge, cursor, sizeof(msg->challenge));
+ cursor += sizeof(msg->challenge);
+
+ NTLM_LOG(("NTLM type 2 message:\n"));
+ LogBuf("target", (const uint8*) msg->target, msg->target_len);
+ LogBuf("flags", (const uint8*) &msg->flags, 4);
+ LogFlags(msg->flags);
+ LogBuf("challenge", msg->challenge, sizeof(msg->challenge));
+
+ // We currently do not implement LMv2/NTLMv2 or NTLM2 responses,
+ // so we can ignore target information. We may want to enable
+ // support for these alternate mechanisms in the future.
+ return OK;
+}
+
+// Returns OK or a network error code.
+static int GenerateType3Msg(const string16& domain,
+ const string16& username,
+ const string16& password,
+ const void* in_buf,
+ uint32 in_len,
+ void** out_buf,
+ uint32* out_len) {
+ // in_buf contains Type-2 msg (the challenge) from server.
+
+ int rv;
+ Type2Msg msg;
+
+ rv = ParseType2Msg(in_buf, in_len, &msg);
+ if (rv != OK)
+ return rv;
+
+ bool unicode = (msg.flags & NTLM_NegotiateUnicode) != 0;
+
+ // Temporary buffers for unicode strings
+#ifdef IS_BIG_ENDIAN
+ string16 ucs_domain_buf, ucs_user_buf;
+#endif
+ string16 ucs_host_buf;
+ // Temporary buffers for oem strings
+ std::string oem_domain_buf, oem_user_buf, oem_host_buf;
+ // Pointers and lengths for the string buffers; encoding is unicode if
+ // the "negotiate unicode" flag was set in the Type-2 message.
+ const void* domain_ptr;
+ const void* user_ptr;
+ const void* host_ptr;
+ uint32 domain_len, user_len, host_len;
+
+ //
+ // Get domain name.
+ //
+ if (unicode) {
+#ifdef IS_BIG_ENDIAN
+ ucs_domain_buf = domain;
+ domain_ptr = ucs_domain_buf.data();
+ domain_len = ucs_domain_buf.length() * 2;
+ WriteUnicodeLE(const_cast<void*>(domain_ptr), (const char16*) domain_ptr,
+ ucs_domain_buf.length());
+#else
+ domain_ptr = domain.data();
+ domain_len = domain.length() * 2;
+#endif
+ } else {
+ oem_domain_buf = base::SysWideToNativeMB(UTF16ToWide(domain));
+ domain_ptr = oem_domain_buf.data();
+ domain_len = oem_domain_buf.length();
+ }
+
+ //
+ // Get user name.
+ //
+ if (unicode) {
+#ifdef IS_BIG_ENDIAN
+ ucs_user_buf = username;
+ user_ptr = ucs_user_buf.data();
+ user_len = ucs_user_buf.length() * 2;
+ WriteUnicodeLE(const_cast<void*>(user_ptr), (const char16*) user_ptr,
+ ucs_user_buf.length());
+#else
+ user_ptr = username.data();
+ user_len = username.length() * 2;
+#endif
+ } else {
+ oem_user_buf = base::SysWideToNativeMB(UTF16ToWide(username));
+ user_ptr = oem_user_buf.data();
+ user_len = oem_user_buf.length();
+ }
+
+ //
+ // 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;
+ host_len = strlen(host_buf);
+ if (unicode) {
+ // hostname is ASCII, so we can do a simple zero-pad expansion:
+ ucs_host_buf.assign(host_buf, host_buf + host_len);
+ host_ptr = ucs_host_buf.data();
+ host_len = ucs_host_buf.length() * 2;
+#ifdef IS_BIG_ENDIAN
+ WriteUnicodeLE(const_cast<void*>(host_ptr), (const char16*) host_ptr,
+ ucs_host_buf.length());
+#endif
+ } else {
+ host_ptr = host_buf;
+ }
+
+ //
+ // Now that we have generated all of the strings, we can allocate out_buf.
+ //
+ *out_len = NTLM_TYPE3_HEADER_LEN + host_len + domain_len + user_len +
+ LM_RESP_LEN + NTLM_RESP_LEN;
+ *out_buf = malloc(*out_len);
+ if (!*out_buf)
+ return ERR_OUT_OF_MEMORY;
+
+ //
+ // Next, we compute the LM and NTLM responses.
+ //
+ uint8 lm_resp[LM_RESP_LEN];
+ uint8 ntlm_resp[NTLM_RESP_LEN];
+ uint8 ntlm_hash[NTLM_HASH_LEN];
+ if (msg.flags & NTLM_NegotiateNTLM2Key) {
+ // compute NTLM2 session response
+ 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);
+ memset(lm_resp + 8, 0, LM_RESP_LEN - 8);
+
+ memcpy(temp, msg.challenge, 8);
+ memcpy(temp + 8, lm_resp, 8);
+ MD5Sum(temp, 16, &session_hash);
+
+ NTLM_Hash(password, ntlm_hash);
+ LM_Response(ntlm_hash, session_hash.a, ntlm_resp);
+ } else {
+ NTLM_Hash(password, ntlm_hash);
+ LM_Response(ntlm_hash, msg.challenge, ntlm_resp);
+
+ if (SendLM()) {
+ uint8 lm_hash[LM_HASH_LEN];
+ LM_Hash(password, lm_hash);
+ LM_Response(lm_hash, msg.challenge, lm_resp);
+ } else {
+ // According to http://davenport.sourceforge.net/ntlm.html#ntlmVersion2,
+ // the correct way to not send the LM hash is to send the NTLM hash twice
+ // in both the LM and NTLM response fields.
+ LM_Response(ntlm_hash, msg.challenge, lm_resp);
+ }
+ }
+
+ //
+ // Finally, we assemble the Type-3 msg :-)
+ //
+ void* cursor = *out_buf;
+ uint32 offset;
+
+ // 0 : signature
+ cursor = WriteBytes(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+
+ // 8 : marker
+ cursor = WriteBytes(cursor, NTLM_TYPE3_MARKER, sizeof(NTLM_TYPE3_MARKER));
+
+ // 12 : LM response sec buf
+ offset = NTLM_TYPE3_HEADER_LEN + domain_len + user_len + host_len;
+ cursor = WriteSecBuf(cursor, LM_RESP_LEN, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, lm_resp, LM_RESP_LEN);
+
+ // 20 : NTLM response sec buf
+ offset += LM_RESP_LEN;
+ cursor = WriteSecBuf(cursor, NTLM_RESP_LEN, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, ntlm_resp, NTLM_RESP_LEN);
+
+ // 28 : domain name sec buf
+ offset = NTLM_TYPE3_HEADER_LEN;
+ cursor = WriteSecBuf(cursor, domain_len, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, domain_ptr, domain_len);
+
+ // 36 : user name sec buf
+ offset += domain_len;
+ cursor = WriteSecBuf(cursor, user_len, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, user_ptr, user_len);
+
+ // 44 : workstation (host) name sec buf
+ offset += user_len;
+ cursor = WriteSecBuf(cursor, host_len, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, host_ptr, host_len);
+
+ // 52 : session key sec buf (not used)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ // 60 : negotiated flags
+ cursor = WriteDWORD(cursor, msg.flags & NTLM_TYPE1_FLAGS);
+
+ return OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class NTLMAuthModule {
+ public:
+ NTLMAuthModule() {}
+
+ ~NTLMAuthModule();
+
+ int Init(const string16& domain,
+ const string16& username,
+ const string16& password);
+
+ int GetNextToken(const void* in_token,
+ uint32 in_token_len,
+ void** out_token,
+ uint32* out_token_len);
+
+ private:
+ string16 domain_;
+ string16 username_;
+ string16 password_;
+};
+
+NTLMAuthModule::~NTLMAuthModule() {
+ ZapString(&password_);
+}
+
+int NTLMAuthModule::Init(const string16& domain,
+ const string16& username,
+ const string16& password) {
+ domain_ = domain;
+ username_ = username;
+ password_ = password;
+ return OK;
+}
+
+int NTLMAuthModule::GetNextToken(const void* in_token,
+ uint32 in_token_len,
+ void** out_token,
+ uint32* out_token_len) {
+ int rv;
+
+ // If in_token is non-null, then assume it contains a type 2 message...
+ if (in_token) {
+ LogToken("in-token", in_token, in_token_len);
+ rv = GenerateType3Msg(domain_, username_, password_, in_token,
+ in_token_len, out_token, out_token_len);
+ } else {
+ rv = GenerateType1Msg(out_token, out_token_len);
+ }
+
+ if (rv == OK)
+ LogToken("out-token", *out_token, *out_token_len);
+
+ return rv;
+}
+
+// NTLM authentication is specified in "NTLM Over HTTP Protocol Specification"
+// [MS-NTHT].
+
+HttpAuthHandlerNTLM::HttpAuthHandlerNTLM()
+ : ntlm_module_(new NTLMAuthModule) {
+}
+
+HttpAuthHandlerNTLM::~HttpAuthHandlerNTLM() {
+}
+
+bool HttpAuthHandlerNTLM::NeedsIdentity() {
+ return !auth_data_.empty();
+}
+
+std::string HttpAuthHandlerNTLM::GenerateCredentials(
+ const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy) {
+ int rv;
+ // TODO(wtc): See if we can use char* instead of void* for in_buf and
+ // out_buf. This change will need to propagate to GetNextToken,
+ // GenerateType1Msg, and GenerateType3Msg, and perhaps further.
+ const void* in_buf;
+ void* out_buf;
+ uint32 in_buf_len, out_buf_len;
+ std::string decoded_auth_data;
+
+ // |username| may be in the form "DOMAIN\user". Parse it into the two
+ // components.
+ std::wstring domain;
+ std::wstring user;
+ size_t backslash_idx = username.find(L'\\');
+ if (backslash_idx == std::wstring::npos) {
+ user = username;
+ } else {
+ domain = username.substr(0, backslash_idx);
+ user = username.substr(backslash_idx + 1);
+ }
+ rv = ntlm_module_->Init(WideToUTF16(domain), WideToUTF16(user),
+ WideToUTF16(password));
+
+ // Initial challenge.
+ if (auth_data_.empty()) {
+ in_buf_len = 0;
+ in_buf = NULL;
+ } else {
+ // Decode |auth_data_| into the input buffer.
+ int len = auth_data_.length();
+
+ // Strip off any padding.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
+ //
+ // Our base64 decoder requires that the length be a multiple of 4.
+ while (len > 0 && len % 4 != 0 && auth_data_[len - 1] == '=')
+ len--;
+ auth_data_.erase(len);
+
+ if (!Base64Decode(auth_data_, &decoded_auth_data))
+ return std::string(); // Improper base64 encoding
+ in_buf_len = decoded_auth_data.length();
+ in_buf = decoded_auth_data.data();
+ }
+
+ rv = ntlm_module_->GetNextToken(in_buf, in_buf_len, &out_buf, &out_buf_len);
+ if (rv != OK)
+ return std::string();
+
+ // Base64 encode data in output buffer and prepend "NTLM ".
+ std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
+ std::string encode_output;
+ bool ok = Base64Encode(encode_input, &encode_output);
+ // OK, we are done with |out_buf|
+ free(out_buf);
+ if (!ok)
+ return std::string();
+ return std::string("NTLM ") + encode_output;
+}
+
+// The NTLM challenge header looks like:
+// WWW-Authenticate: NTLM auth-data
+bool HttpAuthHandlerNTLM::ParseChallenge(
+ std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) {
+ scheme_ = "ntlm";
+ score_ = 3;
+ properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
+ auth_data_.clear();
+
+ // Verify the challenge's auth-scheme.
+ HttpAuth::ChallengeTokenizer challenge_tok(challenge_begin, challenge_end);
+ if (!challenge_tok.valid() ||
+ !LowerCaseEqualsASCII(challenge_tok.scheme(), "ntlm"))
+ return false;
+
+ // Extract the auth-data. We can't use challenge_tok.GetNext() because
+ // auth-data is base64-encoded and may contain '=' padding at the end,
+ // which would be mistaken for a name=value pair.
+ challenge_begin += 4; // Skip over "NTLM".
+ HttpUtil::TrimLWS(&challenge_begin, &challenge_end);
+
+ auth_data_.assign(challenge_begin, challenge_end);
+
+ return true;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h
new file mode 100644
index 0000000..05ce06b
--- /dev/null
+++ b/net/http/http_auth_handler_ntlm.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_HTTP_HTTP_AUTH_HANDLER_NTLM_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_NTLM_H_
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "net/http/http_auth_handler.h"
+
+namespace net {
+
+class NTLMAuthModule;
+
+// Code for handling HTTP NTLM authentication.
+class HttpAuthHandlerNTLM : public HttpAuthHandler {
+ public:
+ HttpAuthHandlerNTLM();
+
+ virtual ~HttpAuthHandlerNTLM();
+
+ virtual bool NeedsIdentity();
+
+ virtual std::string GenerateCredentials(const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy);
+
+ protected:
+ virtual bool Init(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) {
+ return ParseChallenge(challenge_begin, challenge_end);
+ }
+
+ private:
+ // Parse the challenge, saving the results into this instance.
+ // Returns true on success.
+ bool ParseChallenge(std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end);
+
+ // The actual implementation of NTLM.
+ //
+ // TODO(wtc): This artificial separation of the NTLM auth module from the
+ // NTLM auth handler comes from the Mozilla code. It is due to an
+ // architecture constraint of Mozilla's (all crypto code must reside in the
+ // "PSM" component), so that the NTLM code, which does crypto, must be
+ // separated from the "netwerk" component. Our source tree doesn't have
+ // this constraint, so we may want to merge NTLMAuthModule into this class.
+ scoped_ptr<NTLMAuthModule> ntlm_module_;
+
+ // The base64-encoded string following "NTLM" in the "WWW-Authenticate" or
+ // "Proxy-Authenticate" response header.
+ std::string auth_data_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_NTLM_H_
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
index d591501..0599246 100644
--- a/net/http/http_auth_unittest.cc
+++ b/net/http/http_auth_unittest.cc
@@ -44,9 +44,9 @@ TEST(HttpAuthTest, ChooseBestChallenge) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
// Make a HttpResponseHeaders object.
- std::string headers_with_status_line("HTTP/1.1 401 OK\n");
+ std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
headers_with_status_line += tests[i].headers;
- scoped_refptr<net::HttpResponseHeaders> headers(
+ scoped_refptr<net::HttpResponseHeaders> headers(
new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(
headers_with_status_line.c_str(),
@@ -65,6 +65,60 @@ TEST(HttpAuthTest, ChooseBestChallenge) {
}
}
+TEST(HttpAuthTest, ChooseBestChallengeConnectionBased) {
+ static const struct {
+ const char* headers;
+ const char* challenge_realm;
+ } tests[] = {
+ {
+ "WWW-Authenticate: Negotiate\r\n"
+ "WWW-Authenticate: NTLM\r\n",
+
+ // We don't support Negotiate, so pick NTLM. Either way, realm is
+ // empty.
+ "",
+ },
+ {
+ "WWW-Authenticate: NTLM "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCTroKF1e/DRcAAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n",
+
+ // Realm is empty.
+ "",
+ }
+ };
+
+ scoped_refptr<HttpAuthHandler> handler;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ // Make a HttpResponseHeaders object.
+ std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
+ headers_with_status_line += tests[i].headers;
+ scoped_refptr<net::HttpResponseHeaders> headers(
+ new net::HttpResponseHeaders(
+ net::HttpUtil::AssembleRawHeaders(
+ headers_with_status_line.c_str(),
+ headers_with_status_line.length())));
+
+ scoped_refptr<HttpAuthHandler> old_handler = handler;
+ HttpAuth::ChooseBestChallenge(headers.get(),
+ HttpAuth::AUTH_SERVER,
+ &handler);
+
+ EXPECT_TRUE(handler != NULL);
+ // Since NTLM is connection-based, we should continue to use the existing
+ // handler rather than creating a new one.
+ if (i != 0)
+ EXPECT_EQ(old_handler, handler);
+
+ EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
+ }
+}
+
TEST(HttpAuthTest, ChallengeTokenizer) {
std::string challenge_str = "Basic realm=\"foobar\"";
HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
@@ -137,6 +191,16 @@ TEST(HttpAuthTest, ChallengeTokenizerMultiple) {
EXPECT_FALSE(challenge.GetNext());
}
+// Use a challenge which has no property.
+TEST(HttpAuthTest, ChallengeTokenizerNoProperty) {
+ std::string challenge_str = "NTLM";
+ HttpAuth::ChallengeTokenizer challenge(
+ challenge_str.begin(), challenge_str.end());
+ EXPECT_TRUE(challenge.valid());
+ EXPECT_EQ(std::string("NTLM"), challenge.scheme());
+ EXPECT_FALSE(challenge.GetNext());
+}
+
TEST(HttpAuthTest, GetChallengeHeaderName) {
std::string name;
@@ -167,6 +231,8 @@ TEST(HttpAuthTest, CreateAuthHandler) {
EXPECT_STREQ("basic", handler->scheme().c_str());
EXPECT_STREQ("FooBar", handler->realm().c_str());
EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_FALSE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
}
{
scoped_refptr<HttpAuthHandler> handler;
@@ -184,7 +250,21 @@ TEST(HttpAuthTest, CreateAuthHandler) {
EXPECT_STREQ("digest", handler->scheme().c_str());
EXPECT_STREQ("FooBar", handler->realm().c_str());
EXPECT_EQ(HttpAuth::AUTH_PROXY, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
+ }
+ {
+ scoped_refptr<HttpAuthHandler> handler;
+ HttpAuth::CreateAuthHandler("NTLM",
+ HttpAuth::AUTH_SERVER,
+ &handler);
+ EXPECT_FALSE(handler.get() == NULL);
+ EXPECT_STREQ("ntlm", handler->scheme().c_str());
+ EXPECT_STREQ("", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_TRUE(handler->is_connection_based());
}
}
-} // namespace net
+} // namespace net
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 7969e06..ac9502a 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -123,9 +123,15 @@ void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) {
// the identity is valid yet, but if it is valid we want other transactions
// to know about it. If an entry for (origin, handler->realm()) already
// exists, we update it.
- session_->auth_cache()->Add(AuthOrigin(target), auth_handler_[target],
- auth_identity_[target].username, auth_identity_[target].password,
- AuthPath(target));
+ //
+ // If auth_identity_[target].source is HttpAuth::IDENT_SRC_NONE,
+ // auth_identity_[target] contains no identity because identity is not
+ // required yet.
+ if (auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE) {
+ session_->auth_cache()->Add(AuthOrigin(target), auth_handler_[target],
+ auth_identity_[target].username, auth_identity_[target].password,
+ AuthPath(target));
+ }
bool keep_alive = false;
if (response_.headers->IsKeepAlive()) {
@@ -1262,7 +1268,10 @@ bool HttpNetworkTransaction::SelectPreemptiveAuth(HttpAuth::Target target) {
HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath(
AuthOrigin(target), AuthPath(target));
- if (entry) {
+ // We don't support preemptive authentication for connection-based
+ // authentication schemes because they can't reuse entry->handler().
+ // Hopefully we can remove this limitation in the future.
+ if (entry && !entry->handler()->is_connection_based()) {
auth_identity_[target].source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
auth_identity_[target].invalid = false;
auth_identity_[target].username = entry->username();
@@ -1339,8 +1348,9 @@ int HttpNetworkTransaction::HandleAuthChallenge() {
return ERR_UNEXPECTED_PROXY_AUTH;
// The auth we tried just failed, hence it can't be valid. Remove it from
- // the cache so it won't be used again.
- if (HaveAuth(target))
+ // the cache so it won't be used again, unless it's a null identity.
+ if (HaveAuth(target) &&
+ auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE)
InvalidateRejectedAuthFromCache(target);
auth_identity_[target].invalid = true;
@@ -1362,9 +1372,22 @@ int HttpNetworkTransaction::HandleAuthChallenge() {
return OK;
}
- // Pick a new auth identity to try, by looking to the URL and auth cache.
- // If an identity to try is found, it is saved to auth_identity_[target].
- bool has_identity_to_try = SelectNextAuthIdentityToTry(target);
+ bool has_identity_to_try;
+ if (auth_handler_[target]->NeedsIdentity()) {
+ // Pick a new auth identity to try, by looking to the URL and auth cache.
+ // If an identity to try is found, it is saved to auth_identity_[target].
+ has_identity_to_try = SelectNextAuthIdentityToTry(target);
+ } else {
+ // Proceed with a null identity.
+ //
+ // TODO(wtc): Add a safeguard against infinite transaction restarts, if
+ // the server keeps returning "NTLM".
+ auth_identity_[target].source = HttpAuth::IDENT_SRC_NONE;
+ auth_identity_[target].invalid = false;
+ auth_identity_[target].username.clear();
+ auth_identity_[target].password.clear();
+ has_identity_to_try = true;
+ }
DCHECK(has_identity_to_try == !auth_identity_[target].invalid);
if (has_identity_to_try) {
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 87f2636..4c0002d 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -1412,6 +1412,131 @@ 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) {
+ 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 TlRMTVNTUAADAAAAGAAYAHAAAAAYABgAiA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAYABgAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAHcAdABjAGgAYQBuAGcALQBjAG"
+ "8AcgBwAMertjYHfqUhAAAAAAAAAAAAAAAAAAAAAEP3kddZKtMDMssm"
+ "KYA6SCllVGUeyoQppQ==\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"
+ "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;
+ mock_sockets[0] = &data1;
+ mock_sockets[1] = &data2;
+ mock_sockets[2] = 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);
+
+ // 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);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback2.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.
diff --git a/net/http/md4.cc b/net/http/md4.cc
new file mode 100644
index 0000000..b457f2d
--- /dev/null
+++ b/net/http/md4.cc
@@ -0,0 +1,184 @@
+// This is mozilla/security/manager/ssl/src/md4.c, CVS rev. 1.1, with trivial
+// changes to port it to our source tree.
+//
+// WARNING: MD4 is cryptographically weak. Do not use MD4 except in NTLM
+// authentication.
+
+/* vim:set ts=2 sw=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * "clean room" MD4 implementation (see RFC 1320)
+ */
+
+#include "net/http/md4.h"
+
+#include <string.h>
+
+typedef uint32 Uint32;
+typedef uint8 Uint8;
+
+/* the "conditional" function */
+#define F(x,y,z) (((x) & (y)) | (~(x) & (z)))
+
+/* the "majority" function */
+#define G(x,y,z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
+
+/* the "parity" function */
+#define H(x,y,z) ((x) ^ (y) ^ (z))
+
+/* rotate n-bits to the left */
+#define ROTL(x,n) (((x) << (n)) | ((x) >> (0x20 - n)))
+
+/* round 1: [abcd k s]: a = (a + F(b,c,d) + X[k]) <<< s */
+#define RD1(a,b,c,d,k,s) a += F(b,c,d) + X[k]; a = ROTL(a,s)
+
+/* round 2: [abcd k s]: a = (a + G(b,c,d) + X[k] + MAGIC) <<< s */
+#define RD2(a,b,c,d,k,s) a += G(b,c,d) + X[k] + 0x5A827999; a = ROTL(a,s)
+
+/* round 3: [abcd k s]: a = (a + H(b,c,d) + X[k] + MAGIC) <<< s */
+#define RD3(a,b,c,d,k,s) a += H(b,c,d) + X[k] + 0x6ED9EBA1; a = ROTL(a,s)
+
+/* converts from word array to byte array, len is number of bytes */
+static void w2b(Uint8 *out, const Uint32 *in, Uint32 len)
+{
+ Uint8 *bp; const Uint32 *wp, *wpend;
+
+ bp = out;
+ wp = in;
+ wpend = wp + (len >> 2);
+
+ for (; wp != wpend; ++wp, bp += 4)
+ {
+ bp[0] = (Uint8) ((*wp ) & 0xFF);
+ bp[1] = (Uint8) ((*wp >> 8) & 0xFF);
+ bp[2] = (Uint8) ((*wp >> 16) & 0xFF);
+ bp[3] = (Uint8) ((*wp >> 24) & 0xFF);
+ }
+}
+
+/* converts from byte array to word array, len is number of bytes */
+static void b2w(Uint32 *out, const Uint8 *in, Uint32 len)
+{
+ Uint32 *wp; const Uint8 *bp, *bpend;
+
+ wp = out;
+ bp = in;
+ bpend = in + len;
+
+ for (; bp != bpend; bp += 4, ++wp)
+ {
+ *wp = (Uint32) (bp[0] ) |
+ (Uint32) (bp[1] << 8) |
+ (Uint32) (bp[2] << 16) |
+ (Uint32) (bp[3] << 24);
+ }
+}
+
+/* update state: data is 64 bytes in length */
+static void md4step(Uint32 state[4], const Uint8 *data)
+{
+ Uint32 A, B, C, D, X[16];
+
+ b2w(X, data, 64);
+
+ A = state[0];
+ B = state[1];
+ C = state[2];
+ D = state[3];
+
+ RD1(A,B,C,D, 0,3); RD1(D,A,B,C, 1,7); RD1(C,D,A,B, 2,11); RD1(B,C,D,A, 3,19);
+ RD1(A,B,C,D, 4,3); RD1(D,A,B,C, 5,7); RD1(C,D,A,B, 6,11); RD1(B,C,D,A, 7,19);
+ RD1(A,B,C,D, 8,3); RD1(D,A,B,C, 9,7); RD1(C,D,A,B,10,11); RD1(B,C,D,A,11,19);
+ RD1(A,B,C,D,12,3); RD1(D,A,B,C,13,7); RD1(C,D,A,B,14,11); RD1(B,C,D,A,15,19);
+
+ RD2(A,B,C,D, 0,3); RD2(D,A,B,C, 4,5); RD2(C,D,A,B, 8, 9); RD2(B,C,D,A,12,13);
+ RD2(A,B,C,D, 1,3); RD2(D,A,B,C, 5,5); RD2(C,D,A,B, 9, 9); RD2(B,C,D,A,13,13);
+ RD2(A,B,C,D, 2,3); RD2(D,A,B,C, 6,5); RD2(C,D,A,B,10, 9); RD2(B,C,D,A,14,13);
+ RD2(A,B,C,D, 3,3); RD2(D,A,B,C, 7,5); RD2(C,D,A,B,11, 9); RD2(B,C,D,A,15,13);
+
+ RD3(A,B,C,D, 0,3); RD3(D,A,B,C, 8,9); RD3(C,D,A,B, 4,11); RD3(B,C,D,A,12,15);
+ RD3(A,B,C,D, 2,3); RD3(D,A,B,C,10,9); RD3(C,D,A,B, 6,11); RD3(B,C,D,A,14,15);
+ RD3(A,B,C,D, 1,3); RD3(D,A,B,C, 9,9); RD3(C,D,A,B, 5,11); RD3(B,C,D,A,13,15);
+ RD3(A,B,C,D, 3,3); RD3(D,A,B,C,11,9); RD3(C,D,A,B, 7,11); RD3(B,C,D,A,15,15);
+
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+}
+
+namespace net {
+namespace weak_crypto {
+
+void MD4Sum(const Uint8 *input, Uint32 inputLen, Uint8 *result)
+{
+ Uint8 final[128];
+ Uint32 i, n, m, state[4];
+
+ /* magic initial states */
+ state[0] = 0x67452301;
+ state[1] = 0xEFCDAB89;
+ state[2] = 0x98BADCFE;
+ state[3] = 0x10325476;
+
+ /* compute number of complete 64-byte segments contained in input */
+ m = inputLen >> 6;
+
+ /* digest first m segments */
+ for (i=0; i<m; ++i)
+ md4step(state, (input + (i << 6)));
+
+ /* build final buffer */
+ n = inputLen % 64;
+ memcpy(final, input + (m << 6), n);
+ final[n] = 0x80;
+ memset(final + n + 1, 0, 120 - (n + 1));
+
+ inputLen = inputLen << 3;
+ w2b(final + (n >= 56 ? 120 : 56), &inputLen, 4);
+
+ md4step(state, final);
+ if (n >= 56)
+ md4step(state, final + 64);
+
+ /* copy state to result */
+ w2b(result, state, 16);
+}
+
+} // namespace net::weak_crypto
+} // namespace net
diff --git a/net/http/md4.h b/net/http/md4.h
new file mode 100644
index 0000000..419ec39
--- /dev/null
+++ b/net/http/md4.h
@@ -0,0 +1,74 @@
+// This is mozilla/security/manager/ssl/src/md4.h, CVS rev. 1.1, with trivial
+// changes to port it to our source tree.
+//
+// WARNING: MD4 is cryptographically weak. Do not use MD4 except in NTLM
+// authentication.
+
+/* vim:set ts=2 sw=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_HTTP_MD4_H_
+#define NET_HTTP_MD4_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+namespace weak_crypto {
+
+/**
+ * MD4Sum - computes the MD4 sum over the input buffer per RFC 1320
+ *
+ * @param input
+ * buffer containing input data
+ * @param inputLen
+ * length of input buffer (number of bytes)
+ * @param result
+ * 16-byte buffer that will contain the MD4 sum upon return
+ *
+ * NOTE: MD4 is superceded by MD5. do not use MD4 unless required by the
+ * protocol you are implementing (e.g., NTLM requires MD4).
+ *
+ * NOTE: this interface is designed for relatively small buffers. A streaming
+ * interface would make more sense if that were a requirement. Currently, this
+ * is good enough for the applications we care about.
+ */
+void MD4Sum(const uint8 *input, uint32 inputLen, uint8 *result);
+
+} // namespace net::weak_crypto
+} // namespace net
+
+#endif // NET_HTTP_MD4_H_
diff --git a/net/net.xcodeproj/project.pbxproj b/net/net.xcodeproj/project.pbxproj
index 34567c3..63b29a2 100644
--- a/net/net.xcodeproj/project.pbxproj
+++ b/net/net.xcodeproj/project.pbxproj
@@ -55,12 +55,15 @@
04E7BD550EC4ECF60078FE58 /* http_auth_cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = 04E7BD540EC4ECF60078FE58 /* http_auth_cache.cc */; };
07B79D4A0F4221D7001EA432 /* ssl_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 07B79D480F4221D7001EA432 /* ssl_test_util.cc */; };
07FE37F10F424D9F00049AB8 /* tcp_pinger_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 07FE37F00F424D9F00049AB8 /* tcp_pinger_unittest.cc */; };
+ 26FAB6DBAA92A16139BE6DE2 /* des.cc in Sources */ = {isa = PBXBuildFile; fileRef = 462AEAFB64390556F2043711 /* des.cc */; };
3853286C2A6C0BE6E1113FA2 /* client_socket.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2C481A2E21097E841607B968 /* client_socket.cc */; };
4D4C5BE20EF1B89E002CA805 /* directory_lister_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED325A0E5A181C00A747DB /* directory_lister_unittest.cc */; };
4D4C5C060EF1B8C5002CA805 /* filter_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D4C5C050EF1B8C5002CA805 /* filter_unittest.cc */; };
4D4C5C070EF1B915002CA805 /* http_cache_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED33550E5A194700A747DB /* http_cache_unittest.cc */; };
4D4C5C080EF1B92E002CA805 /* http_util_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED33580E5A194700A747DB /* http_util_unittest.cc */; };
4DB04D3F0EB24EDF00A5633C /* dns_resolution_observer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32590E5A181C00A747DB /* dns_resolution_observer.cc */; };
+ 50680FE082000F4041394501 /* des_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = D7C9381275DBF60830B32E4A /* des_unittest.cc */; };
+ 5070A94A9AF3DE5EEEA4C873 /* md4.cc in Sources */ = {isa = PBXBuildFile; fileRef = 60EDCF2D3A3B43CD7352F2D2 /* md4.cc */; };
533102E70E5E3EBF00FF8E32 /* net_util_posix.cc in Sources */ = {isa = PBXBuildFile; fileRef = 533102E60E5E3EBF00FF8E32 /* net_util_posix.cc */; };
7B2630680E82F2A1001CE27F /* libevent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B2630650E82F282001CE27F /* libevent.a */; };
7B4DF64A0E5B98DF004D7619 /* client_socket_pool_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED326A0E5A181C00A747DB /* client_socket_pool_unittest.cc */; };
@@ -194,6 +197,7 @@
E4CE9C2E0E8C02ED00D5378C /* http_transaction_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED334A0E5A194700A747DB /* http_transaction_unittest.cc */; };
E4CE9C380E8C035C00D5378C /* http_network_transaction_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED33590E5A194700A747DB /* http_network_transaction_unittest.cc */; };
E51639B07E0FC884891C5135 /* http_response_info.cc in Sources */ = {isa = PBXBuildFile; fileRef = AD46F9C0118A7D723526A361 /* http_response_info.cc */; };
+ F1A9432ECB645F4D639C8AEC /* http_auth_handler_ntlm.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3E401287FEC64E61E672641D /* http_auth_handler_ntlm.cc */; };
FEF52DBABC719A22288B2DBE /* proxy_server.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCAE08104262ECEE01726162 /* proxy_server.cc */; };
/* End PBXBuildFile section */
@@ -490,13 +494,18 @@
04E7BD560EC4ED020078FE58 /* http_auth_cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_auth_cache.h; sourceTree = "<group>"; };
07B79D480F4221D7001EA432 /* ssl_test_util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ssl_test_util.cc; sourceTree = "<group>"; };
07FE37F00F424D9F00049AB8 /* tcp_pinger_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_pinger_unittest.cc; sourceTree = "<group>"; };
+ 0C0327D1FC10279700E8B365 /* des.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = des.h; sourceTree = "<group>"; };
0E81748E2B2E8B814DBB78EC /* ftp_auth_cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ftp_auth_cache.cc; path = ftp/ftp_auth_cache.cc; sourceTree = SOURCE_ROOT; };
+ 0E9AEE36460E669EA0329739 /* http_auth_handler_ntlm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_auth_handler_ntlm.h; sourceTree = "<group>"; };
15C6370BF6FE62308A559648 /* ftp_auth_cache_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ftp_auth_cache_unittest.cc; path = ftp/ftp_auth_cache_unittest.cc; sourceTree = SOURCE_ROOT; };
2C481A2E21097E841607B968 /* client_socket.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = client_socket.cc; sourceTree = "<group>"; };
3C5876D639CF4CE40AF8F45B /* proxy_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proxy_server.h; path = proxy/proxy_server.h; sourceTree = SOURCE_ROOT; };
+ 3E401287FEC64E61E672641D /* http_auth_handler_ntlm.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http_auth_handler_ntlm.cc; sourceTree = "<group>"; };
+ 462AEAFB64390556F2043711 /* des.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = des.cc; sourceTree = "<group>"; };
4D4C5C050EF1B8C5002CA805 /* filter_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filter_unittest.cc; sourceTree = "<group>"; };
533102E60E5E3EBF00FF8E32 /* net_util_posix.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = net_util_posix.cc; sourceTree = "<group>"; };
58586B4C2F020D851B930BF2 /* cert_verify_result.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cert_verify_result.h; sourceTree = "<group>"; };
+ 60EDCF2D3A3B43CD7352F2D2 /* md4.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = md4.cc; sourceTree = "<group>"; };
7B2630600E82F282001CE27F /* libevent.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libevent.xcodeproj; path = third_party/libevent/libevent.xcodeproj; sourceTree = "<group>"; };
7B82FF450E763620008F45CF /* host_resolver_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = host_resolver_unittest.cc; sourceTree = "<group>"; };
7B8501F10E5A372500730B43 /* googleurl.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = googleurl.xcodeproj; path = build/googleurl.xcodeproj; sourceTree = "<group>"; };
@@ -747,11 +756,13 @@
D4726BC70CCE10F4FF2A5E12 /* connection_type_histograms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = connection_type_histograms.h; sourceTree = "<group>"; };
D67E592A64772BE82718FD4C /* io_buffer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = io_buffer.cc; sourceTree = "<group>"; };
D7745B2B51F48114C05EB14A /* addr.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = addr.cc; sourceTree = "<group>"; };
+ D7C9381275DBF60830B32E4A /* des_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = des_unittest.cc; sourceTree = "<group>"; };
DD0B3349460AB0703FCE0C7A /* cert_verifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cert_verifier.h; sourceTree = "<group>"; };
DFC96EF80EF9BC5D003C335B /* eviction.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eviction.cc; sourceTree = "<group>"; };
DFC96EF90EF9BC5D003C335B /* eviction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eviction.h; sourceTree = "<group>"; };
DFEE18250E882E3600666107 /* stats_histogram.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stats_histogram.cc; sourceTree = "<group>"; };
DFEE18260E882E3600666107 /* stats_histogram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stats_histogram.h; sourceTree = "<group>"; };
+ E1787F4565A50E801295BEBC /* md4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md4.h; sourceTree = "<group>"; };
E47E933E0E8924DC00CA613E /* tcp_client_socket_libevent.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tcp_client_socket_libevent.cc; sourceTree = "<group>"; };
E49DD2E80E892F8C003C7A87 /* sdch_manager.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sdch_manager.cc; sourceTree = "<group>"; };
E49DD2E90E892F8C003C7A87 /* sdch_manager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sdch_manager.h; sourceTree = "<group>"; };
@@ -1150,12 +1161,17 @@
7BED33390E5A193400A747DB /* http */ = {
isa = PBXGroup;
children = (
+ 462AEAFB64390556F2043711 /* des.cc */,
+ 0C0327D1FC10279700E8B365 /* des.h */,
+ D7C9381275DBF60830B32E4A /* des_unittest.cc */,
7BED33600E5A194700A747DB /* http_atom_list.h */,
0435A4650E8DD69C00E4DF08 /* http_auth.cc */,
0435A4670E8DD6B300E4DF08 /* http_auth.h */,
04E7BD540EC4ECF60078FE58 /* http_auth_cache.cc */,
04E7BD560EC4ED020078FE58 /* http_auth_cache.h */,
042A4D470EC4F4500083281F /* http_auth_cache_unittest.cc */,
+ 3E401287FEC64E61E672641D /* http_auth_handler_ntlm.cc */,
+ 0E9AEE36460E669EA0329739 /* http_auth_handler_ntlm.h */,
04C626D90E8DE3BA0067E92A /* http_auth_unittest.cc */,
0435A4790E8DD6F300E4DF08 /* http_auth_handler.cc */,
0435A47B0E8DD6FA00E4DF08 /* http_auth_handler.h */,
@@ -1195,6 +1211,8 @@
7BED33490E5A194700A747DB /* http_vary_data.h */,
7BED33470E5A194700A747DB /* http_vary_data_unittest.cc */,
7BA361EB0E8C38C60023C8B9 /* http_version.h */,
+ 60EDCF2D3A3B43CD7352F2D2 /* md4.cc */,
+ E1787F4565A50E801295BEBC /* md4.h */,
);
path = http;
sourceTree = "<group>";
@@ -1601,6 +1619,7 @@
7B8B5A430E5CD1FD002F9A97 /* cookie_monster.cc in Sources */,
7B85040C0E5B2DD800730B43 /* cookie_policy.cc in Sources */,
7B85040D0E5B2DD800730B43 /* data_url.cc in Sources */,
+ 26FAB6DBAA92A16139BE6DE2 /* des.cc in Sources */,
042A4AB90ED4F0540001DBED /* directory_lister.cc in Sources */,
4DB04D3F0EB24EDF00A5633C /* dns_resolution_observer.cc in Sources */,
CBD597DCEF8626BFC880D741 /* effective_tld_names.cc in Sources */,
@@ -1622,6 +1641,7 @@
0435A47A0E8DD6F300E4DF08 /* http_auth_handler.cc in Sources */,
0435A4800E8DD73600E4DF08 /* http_auth_handler_basic.cc in Sources */,
0435A48F0E8DD74B00E4DF08 /* http_auth_handler_digest.cc in Sources */,
+ F1A9432ECB645F4D639C8AEC /* http_auth_handler_ntlm.cc in Sources */,
0482692A0E5B624D00A30786 /* http_cache.cc in Sources */,
7B85042B0E5B2E2A00730B43 /* http_chunked_decoder.cc in Sources */,
E49DD3290E893336003C7A87 /* http_network_layer.cc in Sources */,
@@ -1633,6 +1653,7 @@
A2420E59B0E92F1C5DF8FE67 /* io_buffer.cc in Sources */,
A50055FF0EBF8018007B0A90 /* listen_socket.cc in Sources */,
7B8504320E5B2E4900730B43 /* mapped_file_posix.cc in Sources */,
+ 5070A94A9AF3DE5EEEA4C873 /* md4.cc in Sources */,
7B8504330E5B2E4900730B43 /* mem_backend_impl.cc in Sources */,
7B8504340E5B2E4900730B43 /* mem_entry_impl.cc in Sources */,
7B8504350E5B2E4900730B43 /* mem_rankings.cc in Sources */,
@@ -1690,6 +1711,7 @@
7BD8F70C0E65DCD800034DE9 /* block_files_unittest.cc in Sources */,
7BA015210E5A1B9800044150 /* bzip2_filter_unittest.cc in Sources */,
7B4DF64A0E5B98DF004D7619 /* client_socket_pool_unittest.cc in Sources */,
+ 50680FE082000F4041394501 /* des_unittest.cc in Sources */,
07FE37F10F424D9F00049AB8 /* tcp_pinger_unittest.cc in Sources */,
7B8B5B530E5CEAC7002F9A97 /* cookie_monster_unittest.cc in Sources */,
7BA3614E0E8C347E0023C8B9 /* cookie_policy_unittest.cc in Sources */,
diff --git a/net/net_lib.scons b/net/net_lib.scons
index 9d0acbb..6a06759 100644
--- a/net/net_lib.scons
+++ b/net/net_lib.scons
@@ -163,6 +163,8 @@ input_files = ChromeFileList([
'url_request/url_request_view_cache_job.h',
]),
MSVSFilter('http', [
+ 'http/des.cc',
+ 'http/des.h',
'http/http_atom_list.h',
'http/http_auth.cc',
'http/http_auth.h',
@@ -174,6 +176,8 @@ input_files = ChromeFileList([
'http/http_auth_handler_basic.h',
'http/http_auth_handler_digest.cc',
'http/http_auth_handler_digest.h',
+ 'http/http_auth_handler_ntlm.cc',
+ 'http/http_auth_handler_ntlm.h',
'http/http_cache.cc',
'http/http_cache.h',
'http/http_chunked_decoder.cc',
@@ -194,6 +198,8 @@ input_files = ChromeFileList([
'http/http_util.h',
'http/http_vary_data.cc',
'http/http_vary_data.h',
+ 'http/md4.cc',
+ 'http/md4.h',
]),
MSVSFilter('disk_cache', [
'disk_cache/addr.cc',
diff --git a/net/net_unittests.scons b/net/net_unittests.scons
index 6ac239a..ce469f9 100644
--- a/net/net_unittests.scons
+++ b/net/net_unittests.scons
@@ -58,6 +58,7 @@ input_files = ChromeFileList([
'disk_cache/storage_block_unittest.cc',
]),
MSVSFilter('http', [
+ 'http/des_unittest.cc',
'http/http_auth_cache_unittest.cc',
'http/http_auth_handler_basic_unittest.cc',
'http/http_auth_handler_digest_unittest.cc',