diff options
author | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-28 01:29:24 +0000 |
---|---|---|
committer | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-28 01:29:24 +0000 |
commit | 3f918787e1073e17439e55ed34f23ffdc31f891f (patch) | |
tree | 3e591f7dc3c54e8859815486057725366532ca22 /net | |
parent | 0a5f0a187c73e47417511ea2ed988c5b3876f563 (diff) | |
download | chromium_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.vcproj | 24 | ||||
-rw-r--r-- | net/build/net_unittests.vcproj | 8 | ||||
-rw-r--r-- | net/http/des.cc | 220 | ||||
-rw-r--r-- | net/http/des.h | 27 | ||||
-rw-r--r-- | net/http/des_unittest.cc | 50 | ||||
-rw-r--r-- | net/http/http_auth.cc | 33 | ||||
-rw-r--r-- | net/http/http_auth.h | 11 | ||||
-rw-r--r-- | net/http/http_auth_cache_unittest.cc | 19 | ||||
-rw-r--r-- | net/http/http_auth_handler.cc | 10 | ||||
-rw-r--r-- | net/http/http_auth_handler.h | 38 | ||||
-rw-r--r-- | net/http/http_auth_handler_basic.cc | 1 | ||||
-rw-r--r-- | net/http/http_auth_handler_digest.cc | 1 | ||||
-rw-r--r-- | net/http/http_auth_handler_ntlm.cc | 787 | ||||
-rw-r--r-- | net/http/http_auth_handler_ntlm.h | 60 | ||||
-rw-r--r-- | net/http/http_auth_unittest.cc | 86 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 41 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 125 | ||||
-rw-r--r-- | net/http/md4.cc | 184 | ||||
-rw-r--r-- | net/http/md4.h | 74 | ||||
-rw-r--r-- | net/net.xcodeproj/project.pbxproj | 22 | ||||
-rw-r--r-- | net/net_lib.scons | 6 | ||||
-rw-r--r-- | net/net_unittests.scons | 1 |
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', |