diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h index 0ab8d953c7808..fe12132b60eda 100644 --- a/net/http/http_auth_handler_ntlm.h +++ b/net/http/http_auth_handler_ntlm.h @@ -72,10 +72,6 @@ class NET_EXPORT_PRIVATE HttpAuthHandlerNTLM : public HttpAuthHandler { }; #if defined(NTLM_PORTABLE) - // A function that returns the time as the number of 100 nanosecond ticks - // since Jan 1, 1601 (UTC). - typedef uint64_t (*GetMSTimeProc)(); - // A function that generates n random bytes in the output buffer. typedef void (*GenerateRandomProc)(uint8_t* output, size_t n); @@ -87,22 +83,18 @@ class NET_EXPORT_PRIVATE HttpAuthHandlerNTLM : public HttpAuthHandler { // GetHostName functions. class ScopedProcSetter { public: - ScopedProcSetter(GetMSTimeProc ms_time_proc, - GenerateRandomProc random_proc, + ScopedProcSetter(GenerateRandomProc random_proc, HostNameProc host_name_proc) { - old_ms_time_proc_ = SetGetMSTimeProc(ms_time_proc); old_random_proc_ = SetGenerateRandomProc(random_proc); old_host_name_proc_ = SetHostNameProc(host_name_proc); } ~ScopedProcSetter() { - SetGetMSTimeProc(old_ms_time_proc_); SetGenerateRandomProc(old_random_proc_); SetHostNameProc(old_host_name_proc_); } private: - GetMSTimeProc old_ms_time_proc_; GenerateRandomProc old_random_proc_; HostNameProc old_host_name_proc_; }; @@ -140,9 +132,8 @@ class NET_EXPORT_PRIVATE HttpAuthHandlerNTLM : public HttpAuthHandler { ~HttpAuthHandlerNTLM() override; #if defined(NTLM_PORTABLE) - // For unit tests to override the GetMSTime, GenerateRandom and GetHostName - // functions. Returns the old function. - static GetMSTimeProc SetGetMSTimeProc(GetMSTimeProc proc); + // For unit tests to override the GenerateRandom and GetHostName functions. + // Returns the old function. static GenerateRandomProc SetGenerateRandomProc(GenerateRandomProc proc); static HostNameProc SetHostNameProc(HostNameProc proc); @@ -165,7 +156,6 @@ class NET_EXPORT_PRIVATE HttpAuthHandlerNTLM : public HttpAuthHandler { #endif #if defined(NTLM_PORTABLE) - static GetMSTimeProc get_ms_time_proc_; static GenerateRandomProc generate_random_proc_; static HostNameProc get_host_name_proc_; #endif diff --git a/net/http/http_auth_handler_ntlm_portable.cc b/net/http/http_auth_handler_ntlm_portable.cc index fe943575d25f8..97627f6327c81 100644 --- a/net/http/http_auth_handler_ntlm_portable.cc +++ b/net/http/http_auth_handler_ntlm_portable.cc @@ -5,7 +5,6 @@ #include "net/http/http_auth_handler_ntlm.h" #include "base/rand_util.h" -#include "base/time/time.h" #include "net/base/net_errors.h" #include "net/base/network_interfaces.h" @@ -13,20 +12,12 @@ namespace net { namespace { -uint64_t GetMSTime() { - return base::Time::Now().since_origin().InMicroseconds() * 10; -} - void GenerateRandom(uint8_t* output, size_t n) { base::RandBytes(output, n); } } // namespace -// static -HttpAuthHandlerNTLM::GetMSTimeProc HttpAuthHandlerNTLM::get_ms_time_proc_ = - GetMSTime; - // static HttpAuthHandlerNTLM::GenerateRandomProc HttpAuthHandlerNTLM::generate_random_proc_ = GenerateRandom; @@ -35,8 +26,7 @@ HttpAuthHandlerNTLM::GenerateRandomProc HttpAuthHandlerNTLM::HostNameProc HttpAuthHandlerNTLM::get_host_name_proc_ = GetHostName; -HttpAuthHandlerNTLM::HttpAuthHandlerNTLM() - : ntlm_client_(ntlm::NtlmFeatures(false)) {} +HttpAuthHandlerNTLM::HttpAuthHandlerNTLM() : ntlm_client_() {} bool HttpAuthHandlerNTLM::NeedsIdentity() { // This gets called for each round-trip. Only require identity on @@ -57,14 +47,6 @@ int HttpAuthHandlerNTLM::InitializeBeforeFirstChallenge() { HttpAuthHandlerNTLM::~HttpAuthHandlerNTLM() {} -// static -HttpAuthHandlerNTLM::GetMSTimeProc HttpAuthHandlerNTLM::SetGetMSTimeProc( - GetMSTimeProc proc) { - GetMSTimeProc old_proc = get_ms_time_proc_; - get_ms_time_proc_ = proc; - return old_proc; -} - // static HttpAuthHandlerNTLM::GenerateRandomProc HttpAuthHandlerNTLM::SetGenerateRandomProc(GenerateRandomProc proc) { @@ -98,12 +80,10 @@ ntlm::Buffer HttpAuthHandlerNTLM::GetNextToken(const ntlm::Buffer& in_token) { return ntlm::Buffer(); uint8_t client_challenge[8]; generate_random_proc_(client_challenge, 8); - uint64_t client_time = get_ms_time_proc_(); return ntlm_client_.GenerateAuthenticateMessage( domain_, credentials_.username(), credentials_.password(), hostname, - channel_bindings_, CreateSPN(origin_), client_time, client_challenge, - in_token); + client_challenge, in_token); } int HttpAuthHandlerNTLM::Factory::CreateAuthHandler( diff --git a/net/http/http_auth_handler_ntlm_portable_unittest.cc b/net/http/http_auth_handler_ntlm_portable_unittest.cc index 66fbbf7e338d1..2f3ffcb64684b 100644 --- a/net/http/http_auth_handler_ntlm_portable_unittest.cc +++ b/net/http/http_auth_handler_ntlm_portable_unittest.cc @@ -142,11 +142,6 @@ class HttpAuthHandlerNtlmPortableTest : public PlatformTest { memset(output, 0xaa, n); } - static uint64_t MockGetMSTime() { - // Tue, 23 May 2017 20:13:07 +0000 - return 131400439870000000; - } - static std::string MockGetHostName() { return ntlm::test::kHostnameAscii; } private: @@ -210,27 +205,373 @@ TEST_F(HttpAuthHandlerNtlmPortableTest, CantChangeSchemeMidway) { HandleAnotherChallenge("Negotiate SSdtIG5vdCBhIHJlYWwgdG9rZW4h")); } -TEST_F(HttpAuthHandlerNtlmPortableTest, NtlmV1AuthenticationSuccess) { - HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGetMSTime, MockRandom, +TEST_F(HttpAuthHandlerNtlmPortableTest, MinimalStructurallyValidType2) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader( + ntlm::test::kMinChallengeMessage, ntlm::kChallengeHeaderLen))); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type2MessageTooShort) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + uint8_t raw[31]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 31); + + // Fail because the minimum size valid message is 32 bytes. + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(ERR_UNEXPECTED, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type2MessageWrongSignature) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + uint8_t raw[32]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 32); + // Modify the default valid message to overwrite the last byte of the + // signature. + raw[7] = 0xff; + + // Fail because the first 8 bytes don't match "NTLMSSP\0" + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(ERR_UNEXPECTED, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type2WrongMessageType) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + uint8_t raw[32]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 32); + // Modify the message type so it is not 0x00000002 + raw[8] = 0x03; + + // Fail because the message type should be MessageType::kChallenge + // (0x00000002) + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(ERR_UNEXPECTED, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type2MessageWithNoTargetName) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset + // SHOULD be where the payload would be if it was present. This is the + // expected response from a compliant server when no target name is sent. + // In reality the offset should always be ignored if the length is zero. + // Also implementations often just write zeros. + uint8_t raw[32]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 32); + // Modify the default valid message to overwrite the offset to zero. + raw[16] = 0x00; + + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type2MessageWithTargetName) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + // One extra byte is provided for target name. + uint8_t raw[33]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 32); + // Modify the default valid message to indicate 1 byte is present in the + // target name payload. + raw[12] = 0x01; + raw[14] = 0x01; + // Put something in the target name. + raw[32] = 'Z'; + + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, NoTargetNameOverflowFromOffset) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + uint8_t raw[32]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 32); + // Modify the default valid message to claim that the target name field is 1 + // byte long overrunning the end of the message message. + raw[12] = 0x01; + raw[14] = 0x01; + + // The above malformed message could cause an implementation to read outside + // the message buffer because the offset is past the end of the message. + // Verify it gets rejected. + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(ERR_UNEXPECTED, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, NoTargetNameOverflowFromLength) { + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + // Message has 1 extra byte of space after the header for the target name. + // One extra byte is provided for target name. + uint8_t raw[33]; + memcpy(raw, ntlm::test::kMinChallengeMessage, 32); + // Modify the default valid message to indicate 2 bytes are present in the + // target name payload (however there is only space for 1). + raw[12] = 0x02; + raw[14] = 0x02; + // Put something in the target name. + raw[32] = 'Z'; + + // The above malformed message could cause an implementation to read outside + // the message buffer because the length is longer than available space. + // Verify it gets rejected. + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(raw, arraysize(raw)))); + ASSERT_EQ(ERR_UNEXPECTED, GetGenerateAuthTokenResult()); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type3RespectsUnicode) { + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockRandom, + MockGetHostName); + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + // Generate the type 2 message from the server. + ntlm::NtlmBufferWriter writer(ntlm::kChallengeHeaderLen); + ASSERT_TRUE(writer.WriteMessageHeader(ntlm::MessageType::kChallenge)); + // No target name. It is never used. + ASSERT_TRUE(writer.WriteSecurityBuffer( + ntlm::SecurityBuffer(ntlm::kChallengeHeaderLen, 0))); + // Set the unicode flag. + ASSERT_TRUE(writer.WriteFlags(ntlm::NegotiateFlags::kUnicode)); + + std::string token; + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(writer.GetBuffer()))); + ASSERT_EQ(OK, GenerateAuthToken(&token)); + + // Validate the type 3 message + std::string decoded; + ASSERT_TRUE(DecodeChallenge(token, &decoded)); + ntlm::NtlmBufferReader reader(decoded); + ASSERT_TRUE(reader.MatchMessageHeader(ntlm::MessageType::kAuthenticate)); + + // Skip the LM and NTLM Hash fields. This test isn't testing that. + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + base::string16 domain; + base::string16 username; + base::string16 hostname; + ReadString16Payload(&reader, &domain); + ASSERT_EQ(ntlm::test::kNtlmDomain, domain); + ReadString16Payload(&reader, &username); + ASSERT_EQ(ntlm::test::kUser, username); + ReadString16Payload(&reader, &hostname); + ASSERT_EQ(ntlm::test::kHostname, hostname); + + // The session key is not used for the NTLM scheme in HTTP. Since + // NTLMSSP_NEGOTIATE_KEY_EXCH was not sent this is empty. + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + + // Verify the unicode flag is set. + ntlm::NegotiateFlags flags; + ASSERT_TRUE(reader.ReadFlags(&flags)); + ASSERT_EQ(ntlm::NegotiateFlags::kUnicode, + flags & ntlm::NegotiateFlags::kUnicode); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type3WithoutUnicode) { + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockRandom, + MockGetHostName); + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + // Generate the type 2 message from the server. + ntlm::NtlmBufferWriter writer(ntlm::kChallengeHeaderLen); + ASSERT_TRUE(writer.WriteMessageHeader(ntlm::MessageType::kChallenge)); + // No target name. It is never used. + ASSERT_TRUE(writer.WriteSecurityBuffer( + ntlm::SecurityBuffer(ntlm::kChallengeHeaderLen, 0))); + // Set the OEM flag. + ASSERT_TRUE(writer.WriteFlags(ntlm::NegotiateFlags::kOem)); + + std::string token; + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(writer.GetBuffer()))); + ASSERT_EQ(OK, GenerateAuthToken(&token)); + + // Validate the type 3 message + std::string decoded; + ASSERT_TRUE(DecodeChallenge(token, &decoded)); + ntlm::NtlmBufferReader reader(decoded); + ASSERT_TRUE(reader.MatchMessageHeader(ntlm::MessageType::kAuthenticate)); + + // Skip the 2 hash fields. This test isn't testing that. + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + std::string domain; + std::string username; + std::string hostname; + ASSERT_TRUE(ReadStringPayload(&reader, &domain)); + ASSERT_EQ(ntlm::test::kNtlmDomainAscii, domain); + ASSERT_TRUE(ReadStringPayload(&reader, &username)); + ASSERT_EQ(ntlm::test::kUserAscii, username); + ASSERT_TRUE(ReadStringPayload(&reader, &hostname)); + ASSERT_EQ(ntlm::test::kHostnameAscii, hostname); + + // The session key is not used for the NTLM scheme in HTTP. Since + // NTLMSSP_NEGOTIATE_KEY_EXCH was not sent this is empty. + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + + // Verify the unicode flag is not set and OEM flag is. + ntlm::NegotiateFlags flags; + ASSERT_TRUE(reader.ReadFlags(&flags)); + ASSERT_EQ(ntlm::NegotiateFlags::kNone, + flags & ntlm::NegotiateFlags::kUnicode); + ASSERT_EQ(ntlm::NegotiateFlags::kOem, flags & ntlm::NegotiateFlags::kOem); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type3UnicodeNoSessionSecurity) { + // Verify that the client won't be downgraded if the server clears + // the session security flag. + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockRandom, + MockGetHostName); + ASSERT_EQ(OK, CreateHandler()); + ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + + // Generate the type 2 message from the server. + ntlm::NtlmBufferWriter writer(ntlm::kChallengeHeaderLen); + ASSERT_TRUE(writer.WriteMessageHeader(ntlm::MessageType::kChallenge)); + // No target name. It is never used. + ASSERT_TRUE(writer.WriteSecurityBuffer( + ntlm::SecurityBuffer(ntlm::kChallengeHeaderLen, 0))); + // Set the unicode but not the session security flag. + ASSERT_TRUE(writer.WriteFlags(ntlm::NegotiateFlags::kUnicode)); + + ASSERT_TRUE( + writer.WriteBytes(ntlm::test::kServerChallenge, ntlm::kChallengeLen)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + + std::string token; + ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, + HandleAnotherChallenge(CreateNtlmAuthHeader(writer.GetBuffer()))); + ASSERT_EQ(OK, GenerateAuthToken(&token)); + + // Validate the type 3 message + std::string decoded; + ASSERT_TRUE(DecodeChallenge(token, &decoded)); + ntlm::NtlmBufferReader reader(decoded); + ASSERT_TRUE(reader.MatchMessageHeader(ntlm::MessageType::kAuthenticate)); + + // Read the LM and NTLM Response Payloads. + uint8_t actual_lm_response[ntlm::kResponseLenV1]; + uint8_t actual_ntlm_response[ntlm::kResponseLenV1]; + ASSERT_TRUE( + ReadBytesPayload(&reader, actual_lm_response, ntlm::kResponseLenV1)); + ASSERT_TRUE( + ReadBytesPayload(&reader, actual_ntlm_response, ntlm::kResponseLenV1)); + + // Verify that the client still generated a response that uses + // session security. + ASSERT_EQ(0, memcmp(ntlm::test::kExpectedLmResponseWithV1SS, + actual_lm_response, ntlm::kResponseLenV1)); + ASSERT_EQ(0, memcmp(ntlm::test::kExpectedNtlmResponseWithV1SS, + actual_ntlm_response, ntlm::kResponseLenV1)); + + base::string16 domain; + base::string16 username; + base::string16 hostname; + ReadString16Payload(&reader, &domain); + ASSERT_EQ(ntlm::test::kNtlmDomain, domain); + ReadString16Payload(&reader, &username); + ASSERT_EQ(ntlm::test::kUser, username); + ReadString16Payload(&reader, &hostname); + ASSERT_EQ(ntlm::test::kHostname, hostname); + + // The session key is not used for the NTLM scheme in HTTP. Since + // NTLMSSP_NEGOTIATE_KEY_EXCH was not sent this is empty. + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + + // Verify the unicode flag is set. + ntlm::NegotiateFlags flags; + ASSERT_TRUE(reader.ReadFlags(&flags)); + ASSERT_EQ(ntlm::NegotiateFlags::kUnicode, + flags & ntlm::NegotiateFlags::kUnicode); +} + +TEST_F(HttpAuthHandlerNtlmPortableTest, Type3UnicodeWithSessionSecurity) { + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockRandom, MockGetHostName); ASSERT_EQ(OK, CreateHandler()); ASSERT_EQ(OK, GetGenerateAuthTokenResult()); + // Generate the type 2 message from the server. + ntlm::NtlmBufferWriter writer(ntlm::kChallengeHeaderLen); + ASSERT_TRUE(writer.WriteMessageHeader(ntlm::MessageType::kChallenge)); + // No target name. It is never used. + ASSERT_TRUE(writer.WriteSecurityBuffer( + ntlm::SecurityBuffer(ntlm::kChallengeHeaderLen, 0))); + // Set the unicode and session security flag. + ASSERT_TRUE( + writer.WriteFlags((ntlm::NegotiateFlags::kUnicode | + ntlm::NegotiateFlags::kExtendedSessionSecurity))); + + ASSERT_TRUE( + writer.WriteBytes(ntlm::test::kServerChallenge, ntlm::kChallengeLen)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + std::string token; ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, - HandleAnotherChallenge( - CreateNtlmAuthHeader(ntlm::test::kChallengeMsgV1, - arraysize(ntlm::test::kChallengeMsgV1)))); + HandleAnotherChallenge(CreateNtlmAuthHeader(writer.GetBuffer()))); ASSERT_EQ(OK, GenerateAuthToken(&token)); - // Validate the authenticate message + // Validate the type 3 message std::string decoded; ASSERT_TRUE(DecodeChallenge(token, &decoded)); - ASSERT_EQ(arraysize(ntlm::test::kExpectedAuthenticateMsgSpecResponseV1), - decoded.size()); - ASSERT_EQ(0, memcmp(decoded.data(), - ntlm::test::kExpectedAuthenticateMsgSpecResponseV1, - decoded.size())); + ntlm::NtlmBufferReader reader(decoded); + ASSERT_TRUE(reader.MatchMessageHeader(ntlm::MessageType::kAuthenticate)); + + // Read the LM and NTLM Response Payloads. + uint8_t actual_lm_response[ntlm::kResponseLenV1]; + uint8_t actual_ntlm_response[ntlm::kResponseLenV1]; + ASSERT_TRUE( + ReadBytesPayload(&reader, actual_lm_response, ntlm::kResponseLenV1)); + ASSERT_TRUE( + ReadBytesPayload(&reader, actual_ntlm_response, ntlm::kResponseLenV1)); + + ASSERT_EQ(0, memcmp(ntlm::test::kExpectedLmResponseWithV1SS, + actual_lm_response, ntlm::kResponseLenV1)); + ASSERT_EQ(0, memcmp(ntlm::test::kExpectedNtlmResponseWithV1SS, + actual_ntlm_response, ntlm::kResponseLenV1)); + + base::string16 domain; + base::string16 username; + base::string16 hostname; + ReadString16Payload(&reader, &domain); + ASSERT_EQ(ntlm::test::kNtlmDomain, domain); + ReadString16Payload(&reader, &username); + ASSERT_EQ(ntlm::test::kUser, username); + ReadString16Payload(&reader, &hostname); + ASSERT_EQ(ntlm::test::kHostname, hostname); + + // The session key is not used for the NTLM scheme in HTTP. Since + // NTLMSSP_NEGOTIATE_KEY_EXCH was not sent this is empty. + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + + // Verify the unicode flag is set. + ntlm::NegotiateFlags flags; + ASSERT_TRUE(reader.ReadFlags(&flags)); + ASSERT_EQ(ntlm::NegotiateFlags::kUnicode, + flags & ntlm::NegotiateFlags::kUnicode); } } // namespace net diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 15f08309e020d..e5d7fcdd1810d 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -648,11 +648,6 @@ void FillLargeHeadersString(std::string* str, int size) { } #if defined(NTLM_PORTABLE) -uint64_t MockGetMSTime() { - // Tue, 23 May 2017 20:13:07 +0000 - return 131400439870000000; -} - // Alternative functions that eliminate randomness and dependency on the local // host name so that the generated NTLM messages are reproducible. void MockGenerateRandom(uint8_t* output, size_t n) { @@ -5953,8 +5948,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuthV1) { // to other auth schemes. request.load_flags = LOAD_DO_NOT_USE_EMBEDDED_IDENTITY; - HttpAuthHandlerNTLM::ScopedProcSetter proc_setter( - MockGetMSTime, MockGenerateRandom, MockGetHostName); + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom, + MockGetHostName); std::unique_ptr session(CreateSession(&session_deps_)); // Generate the NTLM messages based on known test data. @@ -5972,9 +5967,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuthV1) { &challenge_msg); base::Base64Encode( base::StringPiece( - reinterpret_cast( - ntlm::test::kExpectedAuthenticateMsgSpecResponseV1), - arraysize(ntlm::test::kExpectedAuthenticateMsgSpecResponseV1)), + reinterpret_cast(ntlm::test::kExpectedAuthenticateMsgV1), + arraysize(ntlm::test::kExpectedAuthenticateMsgV1)), &authenticate_msg); MockWrite data_writes1[] = { @@ -6103,8 +6097,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuthV1WrongThenRightPassword) { request.method = "GET"; request.url = GURL("https://172.22.68.17/kids/login.aspx"); - HttpAuthHandlerNTLM::ScopedProcSetter proc_setter( - MockGetMSTime, MockGenerateRandom, MockGetHostName); + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom, + MockGetHostName); std::unique_ptr session(CreateSession(&session_deps_)); // Generate the NTLM messages based on known test data. @@ -6122,9 +6116,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuthV1WrongThenRightPassword) { &challenge_msg); base::Base64Encode( base::StringPiece( - reinterpret_cast( - ntlm::test::kExpectedAuthenticateMsgSpecResponseV1), - arraysize(ntlm::test::kExpectedAuthenticateMsgSpecResponseV1)), + reinterpret_cast(ntlm::test::kExpectedAuthenticateMsgV1), + arraysize(ntlm::test::kExpectedAuthenticateMsgV1)), &authenticate_msg); // The authenticate message when |kWrongPassword| is sent. @@ -6137,7 +6130,6 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuthV1WrongThenRightPassword) { // 24 bytes (32 encoded chars) of the NTLM Response. ASSERT_EQ(authenticate_msg.length(), wrong_password_authenticate_msg.length()); - ASSERT_EQ(authenticate_msg.length(), 200u); ASSERT_EQ(base::StringPiece(authenticate_msg.data(), 117), base::StringPiece(wrong_password_authenticate_msg.data(), 117)); ASSERT_EQ( @@ -6330,8 +6322,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuthV1WrongThenRightPassword) { // Server requests NTLM authentication, which is not supported over HTTP/2. // Subsequent request with authorization header should be sent over HTTP/1.1. TEST_F(HttpNetworkTransactionTest, NTLMOverHttp2) { - HttpAuthHandlerNTLM::ScopedProcSetter proc_setter( - MockGetMSTime, MockGenerateRandom, MockGetHostName); + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom, + MockGetHostName); const char* kUrl = "https://172.22.68.17/kids/login.aspx"; @@ -6368,9 +6360,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMOverHttp2) { &challenge_msg); base::Base64Encode( base::StringPiece( - reinterpret_cast( - ntlm::test::kExpectedAuthenticateMsgSpecResponseV1), - arraysize(ntlm::test::kExpectedAuthenticateMsgSpecResponseV1)), + reinterpret_cast(ntlm::test::kExpectedAuthenticateMsgV1), + arraysize(ntlm::test::kExpectedAuthenticateMsgV1)), &authenticate_msg); // Retry with authorization header. diff --git a/net/ntlm/ntlm.cc b/net/ntlm/ntlm.cc index a93d9c44fbcf6..14cc4bcdf64bf 100644 --- a/net/ntlm/ntlm.cc +++ b/net/ntlm/ntlm.cc @@ -8,136 +8,13 @@ #include "base/logging.h" #include "base/md5.h" -#include "base/strings/utf_string_conversions.h" -#include "net/base/net_string_util.h" #include "net/ntlm/des.h" #include "net/ntlm/md4.h" #include "net/ntlm/ntlm_buffer_writer.h" -#include "third_party/boringssl/src/include/openssl/hmac.h" namespace net { namespace ntlm { -namespace { - -// Takes the parsed target info in |av_pairs| and performs the following -// actions. -// -// 1) If a |TargetInfoAvId::kTimestamp| AvPair exists, |server_timestamp| -// is set to the payload. -// 2) If |is_mic_enabled| is true, the existing |TargetInfoAvId::kFlags| AvPair -// will have the |TargetInfoAvFlags::kMicPresent| bit set. If an existing -// flags AvPair does not already exist, a new one is added with the value of -// |TargetInfoAvFlags::kMicPresent|. -// 3) If |is_epa_enabled| is true, two new AvPair entries will be added to -// |av_pairs|. The first will be of type |TargetInfoAvId::kChannelBindings| -// and contains MD5(|channel_bindings|) as the payload. The second will be -// of type |TargetInfoAvId::kTargetName| and contains |spn| as a little -// endian UTF16 string. -// 4) Sets |target_info_len| to the size of |av_pairs| when serialized into -// a payload. -void UpdateTargetInfoAvPairs(bool is_mic_enabled, - bool is_epa_enabled, - const std::string& channel_bindings, - const std::string& spn, - std::vector* av_pairs, - uint64_t* server_timestamp, - size_t* target_info_len) { - // Do a pass to update flags and calculate current length and - // pull out the server timestamp if it is there. - *server_timestamp = UINT64_MAX; - *target_info_len = 0; - - bool need_flags_added = is_mic_enabled; - for (AvPair& pair : *av_pairs) { - *target_info_len += pair.avlen + kAvPairHeaderLen; - switch (pair.avid) { - case TargetInfoAvId::kFlags: - // The parsing phase already set the payload to the |flags| field. - if (is_mic_enabled) { - pair.flags = pair.flags | TargetInfoAvFlags::kMicPresent; - } - - need_flags_added = false; - break; - case TargetInfoAvId::kTimestamp: - // The parsing phase already set the payload to the |timestamp| field. - *server_timestamp = pair.timestamp; - break; - case TargetInfoAvId::kEol: - case TargetInfoAvId::kChannelBindings: - case TargetInfoAvId::kTargetName: - // The terminator, |kEol|, should already have been removed from the - // end of the list and would have been rejected if it has been inside - // the list. Additionally |kChannelBindings| and |kTargetName| pairs - // would have been rejected during the initial parsing. See - // |NtlmBufferReader::ReadTargetInfo|. - NOTREACHED(); - break; - default: - // Ignore entries we don't care about. - break; - } - } - - if (need_flags_added) { - DCHECK(is_mic_enabled); - AvPair flags_pair(TargetInfoAvId::kFlags, sizeof(uint32_t)); - flags_pair.flags = TargetInfoAvFlags::kMicPresent; - - av_pairs->push_back(flags_pair); - *target_info_len += kAvPairHeaderLen + flags_pair.avlen; - } - - if (is_epa_enabled) { - if (channel_bindings.empty()) { - // When no channel bindings are supplied, the channel binding hash is - // set to all zeros. - av_pairs->emplace_back(TargetInfoAvId::kChannelBindings, - Buffer(kChannelBindingsHashLen, 0)); - } else { - // Hash the channel bindings. - base::MD5Digest channel_bindings_hash; - GenerateChannelBindingHashV2(channel_bindings, &channel_bindings_hash); - av_pairs->emplace_back( - TargetInfoAvId::kChannelBindings, - Buffer(channel_bindings_hash.a, kChannelBindingsHashLen)); - } - - // Convert the SPN to little endian unicode. - base::string16 spn16 = base::UTF8ToUTF16(spn); - NtlmBufferWriter spn_writer(spn16.length() * 2); - bool spn_writer_result = - spn_writer.WriteUtf16String(spn16) && spn_writer.IsEndOfBuffer(); - DCHECK(spn_writer_result); - - av_pairs->emplace_back(TargetInfoAvId::kTargetName, spn_writer.Pass()); - - // Add the length of the two new AV Pairs to the total length. - *target_info_len += - (2 * kAvPairHeaderLen) + kChannelBindingsHashLen + (spn16.length() * 2); - } - - // Add extra space for the terminator at the end. - *target_info_len += kAvPairHeaderLen; -} - -Buffer WriteUpdatedTargetInfo(const std::vector& av_pairs, - size_t updated_target_info_len) { - bool result = true; - NtlmBufferWriter writer(updated_target_info_len); - for (const AvPair& pair : av_pairs) { - result = writer.WriteAvPair(pair); - DCHECK(result); - } - - result = writer.WriteAvPairTerminator() && writer.IsEndOfBuffer(); - DCHECK(result); - return writer.Pass(); -} - -} // namespace - void GenerateNtlmHashV1(const base::string16& password, uint8_t* hash) { size_t length = password.length() * 2; NtlmBufferWriter writer(length); @@ -244,119 +121,5 @@ void GenerateResponsesV1WithSessionSecurity(const base::string16& password, client_challenge, ntlm_response); } -void GenerateNtlmHashV2(const base::string16& domain, - const base::string16& username, - const base::string16& password, - uint8_t* v2_hash) { - // NOTE: According to [MS-NLMP] Section 3.3.2 only the username and not the - // domain is uppercased. - base::string16 upper_username; - bool result = ToUpper(username, &upper_username); - DCHECK(result); - - uint8_t v1_hash[kNtlmHashLen]; - GenerateNtlmHashV1(password, v1_hash); - NtlmBufferWriter input_writer((upper_username.length() + domain.length()) * - 2); - bool writer_result = input_writer.WriteUtf16String(upper_username) && - input_writer.WriteUtf16String(domain) && - input_writer.IsEndOfBuffer(); - DCHECK(writer_result); - - bssl::ScopedHMAC_CTX ctx; - HMAC_Init_ex(ctx.get(), v1_hash, sizeof(v1_hash), EVP_md5(), NULL); - DCHECK_EQ(sizeof(v1_hash), HMAC_size(ctx.get())); - HMAC_Update(ctx.get(), input_writer.GetBuffer().data(), - input_writer.GetLength()); - HMAC_Final(ctx.get(), v2_hash, nullptr); -} - -Buffer GenerateProofInputV2(uint64_t timestamp, - const uint8_t* client_challenge) { - NtlmBufferWriter writer(kProofInputLenV2); - bool result = writer.WriteUInt16(kProofInputVersionV2) && - writer.WriteZeros(6) && writer.WriteUInt64(timestamp) && - writer.WriteBytes(client_challenge, kChallengeLen) && - writer.WriteZeros(4) && writer.IsEndOfBuffer(); - - DCHECK(result); - return writer.Pass(); -} - -void GenerateNtlmProofV2(const uint8_t* v2_hash, - const uint8_t* server_challenge, - const Buffer& v2_input, - const Buffer& target_info, - uint8_t* v2_proof) { - DCHECK_EQ(kProofInputLenV2, v2_input.size()); - - bssl::ScopedHMAC_CTX ctx; - HMAC_Init_ex(ctx.get(), v2_hash, kNtlmHashLen, EVP_md5(), NULL); - DCHECK_EQ(kNtlmProofLenV2, HMAC_size(ctx.get())); - HMAC_Update(ctx.get(), server_challenge, kChallengeLen); - HMAC_Update(ctx.get(), v2_input.data(), v2_input.size()); - HMAC_Update(ctx.get(), target_info.data(), target_info.size()); - const uint32_t zero = 0; - HMAC_Update(ctx.get(), reinterpret_cast(&zero), - sizeof(uint32_t)); - HMAC_Final(ctx.get(), v2_proof, nullptr); -} - -void GenerateSessionBaseKeyV2(const uint8_t* v2_hash, - const uint8_t* v2_proof, - uint8_t* session_key) { - bssl::ScopedHMAC_CTX ctx; - HMAC_Init_ex(ctx.get(), v2_hash, kNtlmHashLen, EVP_md5(), NULL); - DCHECK_EQ(kSessionKeyLenV2, HMAC_size(ctx.get())); - HMAC_Update(ctx.get(), v2_proof, kNtlmProofLenV2); - HMAC_Final(ctx.get(), session_key, nullptr); -} - -void GenerateChannelBindingHashV2(const std::string& channel_bindings, - base::MD5Digest* channel_bindings_hash) { - NtlmBufferWriter writer(kEpaUnhashedStructHeaderLen); - bool result = writer.WriteZeros(16) && - writer.WriteUInt32(channel_bindings.length()) && - writer.IsEndOfBuffer(); - DCHECK(result); - - base::MD5Context ctx; - base::MD5Init(&ctx); - base::MD5Update(&ctx, base::StringPiece(reinterpret_cast( - writer.GetBuffer().data()), - writer.GetBuffer().size())); - base::MD5Update(&ctx, channel_bindings); - base::MD5Final(channel_bindings_hash, &ctx); -} - -void GenerateMicV2(const uint8_t* session_key, - const Buffer& negotiate_msg, - const Buffer& challenge_msg, - const Buffer& authenticate_msg, - uint8_t* mic) { - bssl::ScopedHMAC_CTX ctx; - HMAC_Init_ex(ctx.get(), session_key, kNtlmHashLen, EVP_md5(), NULL); - DCHECK_EQ(kMicLenV2, HMAC_size(ctx.get())); - HMAC_Update(ctx.get(), negotiate_msg.data(), negotiate_msg.size()); - HMAC_Update(ctx.get(), challenge_msg.data(), challenge_msg.size()); - HMAC_Update(ctx.get(), authenticate_msg.data(), authenticate_msg.size()); - HMAC_Final(ctx.get(), mic, nullptr); -} - -NET_EXPORT_PRIVATE Buffer -GenerateUpdatedTargetInfo(bool is_mic_enabled, - bool is_epa_enabled, - const std::string& channel_bindings, - const std::string& spn, - const std::vector& av_pairs, - uint64_t* server_timestamp) { - size_t updated_target_info_len = 0; - std::vector updated_av_pairs(av_pairs); - UpdateTargetInfoAvPairs(is_mic_enabled, is_epa_enabled, channel_bindings, spn, - &updated_av_pairs, server_timestamp, - &updated_target_info_len); - return WriteUpdatedTargetInfo(updated_av_pairs, updated_target_info_len); -} - } // namespace ntlm } // namespace net diff --git a/net/ntlm/ntlm.h b/net/ntlm/ntlm.h index e1d7dbcd1e528..1d98611e66dfd 100644 --- a/net/ntlm/ntlm.h +++ b/net/ntlm/ntlm.h @@ -15,7 +15,6 @@ #include #include -#include #include "base/strings/string16.h" #include "base/strings/string_piece.h" @@ -126,130 +125,6 @@ NET_EXPORT_PRIVATE void GenerateResponsesV1WithSessionSecurity( uint8_t* lm_response, uint8_t* ntlm_response); -// Generates the NTLMv2 Hash and writes the |kNtlmHashLen| byte result to -// |v2_hash|. -// |v2_hash| must contain |kNtlmHashLen| bytes. -NET_EXPORT_PRIVATE void GenerateNtlmHashV2(const base::string16& domain, - const base::string16& username, - const base::string16& password, - uint8_t* v2_hash); - -// In this implementation the Proof Input is the first 28 bytes of what -// [MS-NLMP] section 3.3.2 calls "temp". "temp" is part of the input to -// generate the NTLMv2 proof. "temp" is composed of a fixed 28 byte prefix -// (the Proof Input), then the variable length updated target info that is -// sent in the authenticate message, then followed by 4 zero bytes. See -// [MS-NLMP] Section 2.2.2.7. -// -// |timestamp| contains a 64 bit Windows timestamp defined as the number of -// 100 nanosecond ticks since midnight Jan 01, 1601 (UTC). -// -// The format of the returned |proof_input| is; -// -// [0-1] - 0x0101 (Version) -// [2-7] - 0x000000000000 (Reserved - all zero) -// [8-15] - |timestamp| (Timestamp) -// [16-23] - |client_challenge| (Client challenge) -// [24-27] - 0x00000000 (Reserved - all zero) -// -// |client_challenge| must contain |kChallengeLen| bytes. -NET_EXPORT_PRIVATE Buffer GenerateProofInputV2(uint64_t timestamp, - const uint8_t* client_challenge); - -// The NTLMv2 Proof is part of the NTLMv2 Response. See NTProofStr in [MS-NLMP] -// Section 3.3.2. -// -// The NTLMv2 Proof is defined as; -// v2_proof = HMAC_MD5( -// v2_hash, -// CONCAT(server_challenge, v2_input, target_info, 0x00000000)) -// -// |v2_hash| must contain |kNtlmHashLen| bytes. -// |server_challenge| must contain |kChallengeLen| bytes. -// |v2_input| must contain |kProofInputLenV2| bytes. -// |target_info| contains the target info field that will be sent in the -// authenticate message. -// |v2_proof| must contain |kNtlmProofLenV2| bytes. -NET_EXPORT_PRIVATE void GenerateNtlmProofV2(const uint8_t* v2_hash, - const uint8_t* server_challenge, - const Buffer& v2_input, - const Buffer& target_info, - uint8_t* v2_proof); - -// The session base key is used to generate the Message Integrity Check (MIC). -// See [MS-NLMP] Section 3.3.2. -// -// It is defined as; -// session_key = HMAC_MD5(v2_hash, v2_proof) -// -// |v2_hash| must contain |kNtlmHashLen| bytes. -// |v2_proof| must contain |kNtlmProofLenV2| bytes. -// |session_key| must contain |kSessionKeyLenV2| bytes. -NET_EXPORT_PRIVATE void GenerateSessionBaseKeyV2(const uint8_t* v2_hash, - const uint8_t* v2_proof, - uint8_t* session_key); - -// The channel bindings hash is an MD5 hash of a data structure containing -// a hash of the server's certificate. -// -// The |channel_bindings| string is supplied out of band (usually from a web -// browser) and is a (21+sizeof(hash)) byte ASCII string, where 'hash' is -// usually a SHA-256 of the servers certificate, but may be another hash -// algorithm. The format as defined by RFC 5929 Section 4 is shown below; -// -// [0-20] - "tls-server-end-point:" (Literal string) -// [21-(20+sizeof(hash)] - HASH(server_certificate) (Certificate hash) -// -// The |channel_bindings| string is then combined into a data structure called -// gss_channel_bindings_struct (on Windows SEC_CHANNEL_BINDINGS) and MD5 hashed -// according to the rules in RFC 4121 Section 4.1.1.2. When simplified this -// results in the input to the hash (aka "ClientChannelBindingsUnhashed") -// being defined as follows; -// -// [0-15] - 16 zero bytes (Collapsed fields) -// [16-19] - |strlen(channel_bindings)| (Length=0x00000035) -// [20-72] - |channel_bindings| (Channel bindings) -// -// See also RFC 5056 and [MS-NLMP] Section 3.1.5.1.2. -// -// The channel bindings hash is then defined as; -// channel_bindings_hash = MD5(ClientChannelBindingsUnhashed) -NET_EXPORT_PRIVATE void GenerateChannelBindingHashV2( - const std::string& channel_bindings, - base::MD5Digest* channel_bindings_hash); - -// The Message Integrity Check (MIC) is a hash calculated over all three -// messages in the NTLM protocol. The MIC field in the authenticate message -// is set to all zeros when calculating the hash. See [MS-NLMP] Section -// 3.1.5.1.2. -// -// In this implementation NTLMSSP_NEGOTIATE_KEY_EXCH never negotiated and -// the MIC for this case is defined as below. If NTLMSSP_NEGOTIATE_KEY_EXCH -// was negotiated, an alternate key is used. See [MS-NLMP] SEction 3.1.5.1.2 -// for additional details. -// -// mic = HMAC_MD5( -// session_base_key, -// CONCAT(negotiate_msg, challenge_msg, authenticate_msg)) -// -// |session_key| must contain |kSessionKeyLenV2| bytes. -// |mic| must contain |kMicLenV2| bytes. -NET_EXPORT_PRIVATE void GenerateMicV2(const uint8_t* session_key, - const Buffer& negotiate_msg, - const Buffer& challenge_msg, - const Buffer& authenticate_msg, - uint8_t* mic); - -// Updates the target info sent by the server, and generates the clients -// response target info. -NET_EXPORT_PRIVATE Buffer -GenerateUpdatedTargetInfo(bool is_mic_enabled, - bool is_epa_enabled, - const std::string& channel_bindings, - const std::string& spn, - const std::vector& av_pairs, - uint64_t* server_timestamp); - } // namespace ntlm } // namespace net diff --git a/net/ntlm/ntlm_buffer_reader.cc b/net/ntlm/ntlm_buffer_reader.cc index b099cddec31e4..ebebc5014bacc 100644 --- a/net/ntlm/ntlm_buffer_reader.cc +++ b/net/ntlm/ntlm_buffer_reader.cc @@ -85,127 +85,6 @@ bool NtlmBufferReader::ReadSecurityBuffer(SecurityBuffer* sec_buf) { ReadUInt32(&sec_buf->offset); } -bool NtlmBufferReader::ReadAvPairHeader(ntlm::TargetInfoAvId* avid, - uint16_t* avlen) { - if (!CanRead(ntlm::kAvPairHeaderLen)) - return false; - - uint16_t raw_avid; - bool result = ReadUInt16(&raw_avid) && ReadUInt16(avlen); - DCHECK(result); - - // Don't try and validate the avid because the code only cares about a few - // specific ones and it is likely a future version might extend this field. - // The implementation can ignore and skip over AV Pairs it doesn't - // understand. - *avid = static_cast(raw_avid); - - return true; -} - -bool NtlmBufferReader::ReadTargetInfo(size_t target_info_len, - std::vector* av_pairs) { - DCHECK(av_pairs->empty()); - - // There has to be at least one terminating header. - if (!CanRead(target_info_len) || target_info_len < ntlm::kAvPairHeaderLen) { - return false; - } - - size_t target_info_end = GetCursor() + target_info_len; - bool saw_eol = false; - - while ((GetCursor() < target_info_end)) { - ntlm::AvPair pair; - if (!ReadAvPairHeader(&pair.avid, &pair.avlen)) - break; - - // Make sure the length wouldn't read outside the buffer. - if (!CanRead(pair.avlen)) - return false; - - // Take a copy of the payload in the AVPair. - pair.buffer.assign(GetBufferAtCursor(), pair.avlen); - if (pair.avid == ntlm::TargetInfoAvId::kEol) { - // Terminator must have zero length. - if (pair.avlen != 0) - return false; - - // Break out of the loop once a valid terminator is found. After the - // loop it will be validated that the whole target info was consumed. - saw_eol = true; - break; - } - - switch (pair.avid) { - case ntlm::TargetInfoAvId::kFlags: - // For flags also populate the flags field so it doesn't - // have to be modified through the raw buffer later. - if (pair.avlen != sizeof(uint32_t) || - !ReadUInt32(reinterpret_cast(&pair.flags))) - return false; - break; - case ntlm::TargetInfoAvId::kTimestamp: - // Populate timestamp so it doesn't need to be read through the - // raw buffer later. - if (pair.avlen != sizeof(uint64_t) || !ReadUInt64(&pair.timestamp)) - return false; - break; - case ntlm::TargetInfoAvId::kChannelBindings: - case ntlm::TargetInfoAvId::kTargetName: - // The server should never send these, and with EPA enabled the client - // will add these to the authenticate message. To avoid issues with - // duplicates or only one being read, just don't allow them. - return false; - default: - // For all other types, just jump over the payload to the next pair. - // If there aren't enough bytes left, then fail. - if (!SkipBytes(pair.avlen)) - return false; - break; - } - - av_pairs->push_back(std::move(pair)); - } - - // Fail if the buffer wasn't properly formed. The entire payload should have - // been consumed and a terminator found. - if ((GetCursor() != target_info_end) || !saw_eol) - return false; - - return true; -} - -bool NtlmBufferReader::ReadTargetInfoPayload( - std::vector* av_pairs) { - DCHECK(av_pairs->empty()); - - ntlm::SecurityBuffer sec_buf; - - // First read the security buffer. - if (!ReadSecurityBuffer(&sec_buf)) - return false; - - // If the security buffer has zero length, an empty av_pairs will be the - // result. - if (sec_buf.length == 0) - return true; - - // If there is a non-zero length payload there has to be at least one - // terminating header. - if (!CanReadFrom(sec_buf.offset, sec_buf.length) || - sec_buf.length < ntlm::kAvPairHeaderLen) - return false; - - size_t old_cursor = GetCursor(); - SetCursor(sec_buf.offset); - if (!ReadTargetInfo(sec_buf.length, av_pairs)) - return false; - - SetCursor(old_cursor); - return true; -} - bool NtlmBufferReader::ReadMessageType(MessageType* message_type) { uint32_t raw_message_type; if (!ReadUInt32(&raw_message_type)) diff --git a/net/ntlm/ntlm_buffer_reader.h b/net/ntlm/ntlm_buffer_reader.h index 41e86cf93ad43..dd72b9b65ad14 100644 --- a/net/ntlm/ntlm_buffer_reader.h +++ b/net/ntlm/ntlm_buffer_reader.h @@ -9,7 +9,6 @@ #include #include -#include #include "base/strings/string_piece.h" #include "net/base/net_export.h" @@ -109,19 +108,6 @@ class NET_EXPORT_PRIVATE NtlmBufferReader { // uint32 - |offset| Offset from start of message bool ReadSecurityBuffer(SecurityBuffer* sec_buf) WARN_UNUSED_RESULT; - // Reads an AvPair header. AvPairs appear sequentially, terminated by a - // special EOL AvPair, in the target info payload of the Challenge message. - // See [MS-NLMP] Section 2.2.2.1. - // - // An AvPair contains an inline payload, and has the structure below ( - // little endian fields): - // uint16 - AvID: Identifies the type of the payload. - // uint16 - AvLen: The length of the following payload. - // (variable) - Payload: Variable length payload. The content and - // format are determined by the AvId. - bool ReadAvPairHeader(ntlm::TargetInfoAvId* avid, - uint16_t* avlen) WARN_UNUSED_RESULT; - // There are 3 message types Negotiate (sent by client), Challenge (sent by // server), and Authenticate (sent by client). // @@ -129,24 +115,6 @@ class NET_EXPORT_PRIVATE NtlmBufferReader { // value is invalid. bool ReadMessageType(MessageType* message_type) WARN_UNUSED_RESULT; - // Reads |target_info_len| bytes and parses them as a sequence of Av Pairs. - // |av_pairs| should be empty on entry to this function. If |ReadTargetInfo| - // returns false, the content of |av_pairs| is in an undefined state and - // should be discarded. - bool ReadTargetInfo(size_t target_info_len, - std::vector* av_pairs) WARN_UNUSED_RESULT; - - // Reads a security buffer, then parses the security buffer payload as a - // target info. The target info is returned as a sequence of AvPairs, with - // the terminating AvPair omitted. A zero length payload is valid and will - // result in an empty list in |av_pairs|. Any non-zero length payload must - // have a terminating AvPair. - // |av_pairs| should be empty on entry to this function. If |ReadTargetInfo| - // returns false, the content of |av_pairs| is in an undefined state and - // should be discarded. - bool ReadTargetInfoPayload(std::vector* av_pairs) - WARN_UNUSED_RESULT; - // Skips over a security buffer field without reading the fields. This is // the equivalent of advancing the cursor 8 bytes. Returns false if there // are less than 8 bytes left in the buffer. diff --git a/net/ntlm/ntlm_buffer_reader_unittest.cc b/net/ntlm/ntlm_buffer_reader_unittest.cc index dbd1149a898b6..592c5d3186286 100644 --- a/net/ntlm/ntlm_buffer_reader_unittest.cc +++ b/net/ntlm/ntlm_buffer_reader_unittest.cc @@ -32,27 +32,6 @@ TEST(NtlmBufferReaderTest, Initialization) { ASSERT_TRUE(reader.CanReadFrom(SecurityBuffer(99, 0))); } -TEST(NtlmBufferReaderTest, EmptyBuffer) { - Buffer b; - NtlmBufferReader reader(b); - - ASSERT_EQ(0u, reader.GetCursor()); - ASSERT_EQ(0u, reader.GetLength()); - ASSERT_TRUE(reader.CanRead(0)); - ASSERT_FALSE(reader.CanRead(1)); - ASSERT_TRUE(reader.IsEndOfBuffer()); -} - -TEST(NtlmBufferReaderTest, NullBuffer) { - NtlmBufferReader reader(nullptr, 0); - - ASSERT_EQ(0u, reader.GetCursor()); - ASSERT_EQ(0u, reader.GetLength()); - ASSERT_TRUE(reader.CanRead(0)); - ASSERT_FALSE(reader.CanRead(1)); - ASSERT_TRUE(reader.IsEndOfBuffer()); -} - TEST(NtlmBufferReaderTest, Read16) { const uint8_t buf[2] = {0x22, 0x11}; const uint16_t expected = 0x1122; @@ -271,236 +250,6 @@ TEST(NtlmBufferReaderTest, ReadMessageTypeChallenge) { ASSERT_TRUE(reader.IsEndOfBuffer()); } -TEST(NtlmBufferReaderTest, ReadTargetInfoEolOnly) { - // Buffer contains only an EOL terminator. - const uint8_t buf[4] = {0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); - ASSERT_TRUE(reader.IsEndOfBuffer()); - ASSERT_TRUE(av_pairs.empty()); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoTimestampAndEolOnly) { - // Buffer contains a timestamp av pair and an EOL terminator. - const uint8_t buf[16] = {0x07, 0, 0x08, 0, 0x11, 0x22, 0x33, 0x44, - 0x55, 0x66, 0x77, 0x88, 0, 0, 0, 0}; - const uint64_t expected_timestamp = 0x8877665544332211; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); - ASSERT_TRUE(reader.IsEndOfBuffer()); - ASSERT_EQ(1u, av_pairs.size()); - - // Verify the timestamp av pair. - ASSERT_EQ(ntlm::TargetInfoAvId::kTimestamp, av_pairs[0].avid); - ASSERT_EQ(sizeof(uint64_t), av_pairs[0].avlen); - ASSERT_EQ(sizeof(uint64_t), av_pairs[0].buffer.size()); - ASSERT_EQ(expected_timestamp, av_pairs[0].timestamp); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoFlagsAndEolOnly) { - // Buffer contains a flags av pair with the MIC bit and an EOL terminator. - const uint8_t buf[12] = {0x06, 0, 0x04, 0, 0x02, 0, 0, 0, 0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); - ASSERT_TRUE(reader.IsEndOfBuffer()); - ASSERT_EQ(1u, av_pairs.size()); - - // Verify the flags av pair. - ASSERT_EQ(ntlm::TargetInfoAvId::kFlags, av_pairs[0].avid); - ASSERT_EQ(sizeof(ntlm::TargetInfoAvFlags), av_pairs[0].avlen); - ASSERT_EQ(ntlm::TargetInfoAvFlags::kMicPresent, av_pairs[0].flags); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoTooSmall) { - // Target info must least contain enough space for a terminator pair. - const uint8_t buf[3] = {0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoInvalidTimestampSize) { - // Timestamps must be 64 bits/8 bytes. A timestamp av pair with a - // different length is invalid. - const uint8_t buf[15] = {0x07, 0, 0x07, 0, 0x11, 0x22, 0x33, 0x44, - 0x55, 0x66, 0x77, 0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoInvalidTimestampPastEob) { - // The timestamp avlen is correct but would read past the end of the buffer. - const uint8_t buf[11] = {0x07, 0, 0x08, 0, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoOtherField) { - // A domain name AvPair containing the string L'ABCD' followed by - // a terminating AvPair. - const uint8_t buf[16] = {0x02, 0, 0x08, 0, 'A', 0, 'B', 0, - 'C', 0, 'D', 0, 0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); - ASSERT_TRUE(reader.IsEndOfBuffer()); - ASSERT_EQ(1u, av_pairs.size()); - - // Verify the domain name AvPair. - ASSERT_EQ(ntlm::TargetInfoAvId::kDomainName, av_pairs[0].avid); - ASSERT_EQ(8, av_pairs[0].avlen); - ASSERT_EQ(0, memcmp(buf + 4, av_pairs[0].buffer.data(), 8)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoNoTerminator) { - // A domain name AvPair containing the string L'ABCD' but there is no - // terminating AvPair. - const uint8_t buf[12] = {0x02, 0, 0x08, 0, 'A', 0, 'B', 0, 'C', 0, 'D', 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoTerminatorAtLocationOtherThanEnd) { - // Target info contains [flags, terminator, domain, terminator]. This - // should fail because the terminator should only appear at the end. - const uint8_t buf[] = {0x06, 0, 0x04, 0, 0x02, 0, 0, 0, 0, 0, - 0, 0, 0x02, 0, 0x08, 0, 'A', 0, 'B', 0, - 'C', 0, 'D', 0, 0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoTerminatorNonZeroLength) { - // A flags Av Pair followed by a terminator pair with a non-zero length. - const uint8_t buf[] = {0x06, 0, 0x04, 0, 0x02, 0, 0, 0, 0, 0, 0x01, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoTerminatorNonZeroLength2) { - // A flags Av Pair followed by a terminator pair with a non-zero length, - // but otherwise in bounds payload. Terminator pairs must have zero - // length, so this is not valid. - const uint8_t buf[] = {0x06, 0, 0x04, 0, 0x02, 0, 0, 0, 0, - 0, 0x01, 0, 0xff, 0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfo(arraysize(buf), &av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoEmptyPayload) { - // Security buffer with no payload. - const uint8_t buf[] = {0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfoPayload(&av_pairs)); - ASSERT_TRUE(reader.IsEndOfBuffer()); - ASSERT_TRUE(av_pairs.empty()); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoEolOnlyPayload) { - // Security buffer with an EOL payload - const uint8_t buf[] = {0x04, 0x00, 0x04, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfoPayload(&av_pairs)); - ASSERT_FALSE(reader.IsEndOfBuffer()); - - // Should only have advanced over the security buffer. - ASSERT_EQ(kSecurityBufferLen, reader.GetCursor()); - ASSERT_TRUE(av_pairs.empty()); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoTooShortPayload) { - // Security buffer with a payload too small to contain any pairs. - const uint8_t buf[] = {0x03, 0x00, 0x03, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_FALSE(reader.ReadTargetInfoPayload(&av_pairs)); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoFlagsPayload) { - // Security buffer followed by a 12 byte payload containing a flags AvPair - // with the MIC bit, followed by a terminator pair. - const uint8_t buf[] = {0x0c, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, - 0x00, 0x06, 0, 0x04, 0, 0x02, 0, - 0, 0, 0, 0, 0, 0}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfoPayload(&av_pairs)); - ASSERT_FALSE(reader.IsEndOfBuffer()); - - // Should only have advanced over the security buffer. - ASSERT_EQ(kSecurityBufferLen, reader.GetCursor()); - - // Contains a single flags AVPair containing the MIC bit. - ASSERT_EQ(1u, av_pairs.size()); - ASSERT_EQ(TargetInfoAvFlags::kMicPresent, av_pairs[0].flags); -} - -TEST(NtlmBufferReaderTest, ReadTargetInfoFlagsPayloadWithPaddingBetween) { - // Security buffer followed by a 12 byte payload containing a flags AvPair - // with the MIC bit, followed by a terminator pair. 5 bytes of 0xff padding - // are between the SecurityBuffer and the payload to test when the payload - // is not contiguous. - const uint8_t buf[] = {0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0x06, 0, 0x04, 0, - 0x02, 0, 0, 0, 0, 0, 0, 0}; - NtlmBufferReader reader(buf, arraysize(buf)); - - std::vector av_pairs; - ASSERT_TRUE(reader.ReadTargetInfoPayload(&av_pairs)); - ASSERT_FALSE(reader.IsEndOfBuffer()); - - // Should only have advanced over the security buffer. - ASSERT_EQ(kSecurityBufferLen, reader.GetCursor()); - - // Contains a single flags AVPair containing the MIC bit. - ASSERT_EQ(1u, av_pairs.size()); - ASSERT_EQ(TargetInfoAvFlags::kMicPresent, av_pairs[0].flags); -} - TEST(NtlmBufferReaderTest, ReadMessageTypeAuthenticate) { const uint8_t buf[4] = {static_cast(MessageType::kAuthenticate), 0, 0, 0}; @@ -614,29 +363,5 @@ TEST(NtlmBufferReaderTest, MatchEmptySecurityBufferLengthNonZeroLength) { ASSERT_FALSE(reader.MatchEmptySecurityBuffer()); } -TEST(NtlmBufferReaderTest, ReadAvPairHeader) { - const uint8_t buf[4] = {0x06, 0x00, 0x11, 0x22}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - ntlm::TargetInfoAvId actual_avid; - uint16_t actual_avlen; - ASSERT_TRUE(reader.ReadAvPairHeader(&actual_avid, &actual_avlen)); - ASSERT_EQ(ntlm::TargetInfoAvId::kFlags, actual_avid); - ASSERT_EQ(0x2211, actual_avlen); - ASSERT_TRUE(reader.IsEndOfBuffer()); - ASSERT_FALSE(reader.ReadAvPairHeader(&actual_avid, &actual_avlen)); -} - -TEST(NtlmBufferReaderTest, ReadAvPairHeaderPastEob) { - const uint8_t buf[3] = {0x06, 0x00, 0x11}; - - NtlmBufferReader reader(buf, arraysize(buf)); - - ntlm::TargetInfoAvId avid; - uint16_t avlen; - ASSERT_FALSE(reader.ReadAvPairHeader(&avid, &avlen)); -} - } // namespace ntlm } // namespace net diff --git a/net/ntlm/ntlm_buffer_writer.cc b/net/ntlm/ntlm_buffer_writer.cc index a10be0f50b70f..746b453dd50a7 100644 --- a/net/ntlm/ntlm_buffer_writer.cc +++ b/net/ntlm/ntlm_buffer_writer.cc @@ -59,8 +59,9 @@ bool NtlmBufferWriter::WriteBytes(const uint8_t* buffer, size_t len) { return true; } -bool NtlmBufferWriter::WriteBytes(const Buffer& bytes) { - return WriteBytes(bytes.data(), bytes.length()); +bool NtlmBufferWriter::WriteBytes(base::StringPiece bytes) { + return WriteBytes(reinterpret_cast(bytes.data()), + bytes.length()); } bool NtlmBufferWriter::WriteZeros(size_t count) { @@ -77,34 +78,6 @@ bool NtlmBufferWriter::WriteSecurityBuffer(SecurityBuffer sec_buf) { WriteUInt32(sec_buf.offset); } -bool NtlmBufferWriter::WriteAvPairHeader(ntlm::TargetInfoAvId avid, - uint16_t avlen) { - if (!CanWrite(ntlm::kAvPairHeaderLen)) - return false; - - bool result = WriteUInt16(static_cast(avid)) && WriteUInt16(avlen); - - DCHECK(result); - return result; -} - -bool NtlmBufferWriter::WriteAvPairTerminator() { - return WriteAvPairHeader(ntlm::TargetInfoAvId::kEol, 0); -} - -bool NtlmBufferWriter::WriteAvPair(const AvPair& pair) { - if (!WriteAvPairHeader(pair)) - return false; - - if (pair.avid == TargetInfoAvId::kFlags) { - if (pair.avlen != sizeof(uint32_t)) - return false; - return WriteUInt32(static_cast(pair.flags)); - } else { - return WriteBytes(pair.buffer); - } -} - bool NtlmBufferWriter::WriteUtf8String(const std::string& str) { return WriteBytes(reinterpret_cast(str.c_str()), str.length()); diff --git a/net/ntlm/ntlm_buffer_writer.h b/net/ntlm/ntlm_buffer_writer.h index eb8b7483e67c2..ecda8cd8d8ed7 100644 --- a/net/ntlm/ntlm_buffer_writer.h +++ b/net/ntlm/ntlm_buffer_writer.h @@ -75,9 +75,9 @@ class NET_EXPORT_PRIVATE NtlmBufferWriter { // the buffer, it returns false. bool WriteBytes(const uint8_t* buffer, size_t len) WARN_UNUSED_RESULT; - // Writes the bytes from the |Buffer|. If there are not enough + // Writes the bytes from the |base::StringPiece|. If there are not enough // bytes in the buffer, it returns false. - bool WriteBytes(const Buffer& buffer) WARN_UNUSED_RESULT; + bool WriteBytes(base::StringPiece bytes) WARN_UNUSED_RESULT; // Writes |count| bytes of zeros to the buffer. If there are not |count| // more bytes in available in the buffer, it returns false. @@ -93,30 +93,6 @@ class NET_EXPORT_PRIVATE NtlmBufferWriter { // uint32 - |offset| Offset from start of message bool WriteSecurityBuffer(SecurityBuffer sec_buf) WARN_UNUSED_RESULT; - // Writes an AvPair header. See [MS-NLMP] Section 2.2.2.1. - // - // The header has the following structure: - // uint16 - |avid| The identifier of the following payload. - // uint16 - |avlen| The length of the following payload. - bool WriteAvPairHeader(ntlm::TargetInfoAvId avid, - uint16_t avlen) WARN_UNUSED_RESULT; - - // Writes an AvPair header for an |AvPair|. See [MS-NLMP] Section 2.2.2.1. - bool WriteAvPairHeader(const AvPair& pair) WARN_UNUSED_RESULT { - return WriteAvPairHeader(pair.avid, pair.avlen); - } - - // Writes a special AvPair header with both fields equal to 0. This zero - // length AvPair signals the end of the AvPair list. - bool WriteAvPairTerminator() WARN_UNUSED_RESULT; - - // Writes an |AvPair| header and its payload to the buffer. If the |avid| - // is of type |TargetInfoAvId::kFlags| the |flags| field of |pair| will be - // used as the payload and the |buffer| field is ignored. In all other cases - // |buffer| is used as the payload. See also - // |NtlmBufferReader::ReadTargetInfo|. - bool WriteAvPair(const AvPair& pair) WARN_UNUSED_RESULT; - // Writes a string of 8 bit characters to the buffer. // // When Unicode was not negotiated only the hostname string will go through diff --git a/net/ntlm/ntlm_buffer_writer_unittest.cc b/net/ntlm/ntlm_buffer_writer_unittest.cc index ab451cd9a02b6..c05a1728a1222 100644 --- a/net/ntlm/ntlm_buffer_writer_unittest.cc +++ b/net/ntlm/ntlm_buffer_writer_unittest.cc @@ -230,22 +230,5 @@ TEST(NtlmBufferWriterTest, WriteMessageTypePastEob) { ASSERT_FALSE(writer.WriteMessageType(MessageType::kNegotiate)); } -TEST(NtlmBufferWriterTest, WriteAvPairHeader) { - const uint8_t expected[4] = {0x06, 0x00, 0x11, 0x22}; - NtlmBufferWriter writer(4); - - ASSERT_TRUE(writer.WriteAvPairHeader(ntlm::TargetInfoAvId::kFlags, 0x2211)); - ASSERT_TRUE(writer.IsEndOfBuffer()); - - ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); -} - -TEST(NtlmBufferWriterTest, WriteAvPairHeaderPastEob) { - NtlmBufferWriter writer(ntlm::kAvPairHeaderLen - 1); - - ASSERT_FALSE(writer.WriteAvPairHeader(ntlm::TargetInfoAvId::kFlags, 0x2211)); - ASSERT_EQ(0u, writer.GetCursor()); -} - } // namespace ntlm } // namespace net diff --git a/net/ntlm/ntlm_client.cc b/net/ntlm/ntlm_client.cc index a034fd37fbd52..a2b8f54bcb366 100644 --- a/net/ntlm/ntlm_client.cc +++ b/net/ntlm/ntlm_client.cc @@ -7,7 +7,6 @@ #include #include "base/logging.h" -#include "base/md5.h" #include "base/strings/utf_string_conversions.h" #include "net/ntlm/ntlm.h" #include "net/ntlm/ntlm_buffer_reader.h" @@ -31,34 +30,12 @@ bool ParseChallengeMessage(const Buffer& challenge_message, challenge_reader.ReadBytes(server_challenge, kChallengeLen); } -// Parses the challenge message and extracts the information necessary to -// make an NTLMv2 response. -// |server_challenge| must contain at least 8 bytes. -bool ParseChallengeMessageV2(const Buffer& challenge_message, - NegotiateFlags* challenge_flags, - uint8_t* server_challenge, - std::vector* av_pairs) { - NtlmBufferReader challenge_reader(challenge_message); - - return challenge_reader.MatchMessageHeader(MessageType::kChallenge) && - challenge_reader.SkipSecurityBufferWithValidation() && - challenge_reader.ReadFlags(challenge_flags) && - challenge_reader.ReadBytes(server_challenge, kChallengeLen) && - challenge_reader.SkipBytes(8) && - // challenge_reader.ReadTargetInfoPayload(av_pairs); - (((*challenge_flags & NegotiateFlags::kTargetInfo) == - NegotiateFlags::kTargetInfo) - ? challenge_reader.ReadTargetInfoPayload(av_pairs) - : true); -} - bool WriteAuthenticateMessage(NtlmBufferWriter* authenticate_writer, SecurityBuffer lm_payload, SecurityBuffer ntlm_payload, SecurityBuffer domain_payload, SecurityBuffer username_payload, SecurityBuffer hostname_payload, - SecurityBuffer session_key_payload, NegotiateFlags authenticate_flags) { return authenticate_writer->WriteMessageHeader(MessageType::kAuthenticate) && authenticate_writer->WriteSecurityBuffer(lm_payload) && @@ -66,35 +43,18 @@ bool WriteAuthenticateMessage(NtlmBufferWriter* authenticate_writer, authenticate_writer->WriteSecurityBuffer(domain_payload) && authenticate_writer->WriteSecurityBuffer(username_payload) && authenticate_writer->WriteSecurityBuffer(hostname_payload) && - authenticate_writer->WriteSecurityBuffer(session_key_payload) && + authenticate_writer->WriteSecurityBuffer( + SecurityBuffer(kAuthenticateHeaderLenV1, 0)) && authenticate_writer->WriteFlags(authenticate_flags); } -// Writes the NTLMv1 LM Response and NTLM Response. -// |lm_response| must contain |kResponseLenV1| bytes. -// |ntlm_response| must contain |kResponseLenV1| bytes. bool WriteResponsePayloads(NtlmBufferWriter* authenticate_writer, const uint8_t* lm_response, - const uint8_t* ntlm_response) { - return authenticate_writer->WriteBytes(lm_response, kResponseLenV1) && - authenticate_writer->WriteBytes(ntlm_response, kResponseLenV1); -} - -// Writes the |lm_response| and writes the NTLMv2 response by concatenating -// |v2_proof|, |v2_proof_input|, |updated_target_info| and 4 zero bytes. -// -// |lm_response| must contain |kResponseLenV1| bytes. -// |v2_proof| must contain |kNtlmProofLenV2| bytes. -bool WriteResponsePayloadsV2(NtlmBufferWriter* authenticate_writer, - const uint8_t* lm_response, - const uint8_t* v2_proof, - const Buffer& v2_proof_input, - const Buffer& updated_target_info) { - return authenticate_writer->WriteBytes(lm_response, kResponseLenV1) && - authenticate_writer->WriteBytes(v2_proof, kNtlmProofLenV2) && - authenticate_writer->WriteBytes(v2_proof_input) && - authenticate_writer->WriteBytes(updated_target_info) && - authenticate_writer->WriteUInt32(0); + size_t lm_response_len, + const uint8_t* ntlm_response, + size_t ntlm_response_len) { + return authenticate_writer->WriteBytes(lm_response, lm_response_len) && + authenticate_writer->WriteBytes(ntlm_response, ntlm_response_len); } bool WriteStringPayloads(NtlmBufferWriter* authenticate_writer, @@ -136,11 +96,10 @@ size_t GetStringPayloadLength(const std::string& str, bool is_unicode) { } // namespace -NtlmClient::NtlmClient(NtlmFeatures features) - : features_(features), negotiate_flags_(kNegotiateMessageFlags) { +NtlmClient::NtlmClient() : negotiate_flags_(kNegotiateMessageFlags) { // Just generate the negotiate message once and hold on to it. It never - // changes and in NTLMv2 it's used as an input to the Message Integrity - // Check (MIC) in the Authenticate message. + // changes and in a NTLMv2 it's used as an input + // to the Message Integrity Check in the Authenticate message. GenerateNegotiateMessage(); } @@ -169,9 +128,6 @@ Buffer NtlmClient::GenerateAuthenticateMessage( const base::string16& username, const base::string16& password, const std::string& hostname, - const std::string& channel_bindings, - const std::string& spn, - uint64_t client_time, const uint8_t* client_challenge, const Buffer& server_challenge_message) const { // Limit the size of strings that are accepted. As an absolute limit any @@ -193,55 +149,26 @@ Buffer NtlmClient::GenerateAuthenticateMessage( NegotiateFlags challenge_flags; uint8_t server_challenge[kChallengeLen]; - uint8_t lm_response[kResponseLenV1]; - uint8_t ntlm_response[kResponseLenV1]; - // Response fields only for NTLMv2 - Buffer updated_target_info; - Buffer v2_proof_input; - uint8_t v2_proof[kNtlmProofLenV2]; - uint8_t v2_session_key[kSessionKeyLenV2]; - - if (IsNtlmV2()) { - std::vector av_pairs; - if (!ParseChallengeMessageV2(server_challenge_message, &challenge_flags, - server_challenge, &av_pairs)) { - return Buffer(); - } - - uint64_t timestamp; - updated_target_info = - GenerateUpdatedTargetInfo(IsMicEnabled(), IsEpaEnabled(), - channel_bindings, spn, av_pairs, ×tamp); - - memset(lm_response, 0, kResponseLenV1); - if (timestamp == UINT64_MAX) { - // If the server didn't send a time, then use the clients time. - timestamp = client_time; - } - - uint8_t v2_hash[kNtlmHashLen]; - GenerateNtlmHashV2(domain, username, password, v2_hash); - v2_proof_input = GenerateProofInputV2(timestamp, client_challenge); - GenerateNtlmProofV2(v2_hash, server_challenge, v2_proof_input, - updated_target_info, v2_proof); - GenerateSessionBaseKeyV2(v2_hash, v2_proof, v2_session_key); - } else { - if (!ParseChallengeMessage(server_challenge_message, &challenge_flags, - server_challenge)) { - return Buffer(); - } - - // Calculate the responses for the authenticate message. - GenerateResponsesV1WithSessionSecurity(password, server_challenge, - client_challenge, lm_response, - ntlm_response); + // Read the flags and the server's random challenge from the challenge + // message. + if (!ParseChallengeMessage(server_challenge_message, &challenge_flags, + server_challenge)) { + return Buffer(); } + // Calculate the responses for the authenticate message. + uint8_t lm_response[kResponseLenV1]; + uint8_t ntlm_response[kResponseLenV1]; + // Always use extended session security even if the server tries to downgrade. NegotiateFlags authenticate_flags = (challenge_flags & negotiate_flags_) | NegotiateFlags::kExtendedSessionSecurity; + // Generate the LM and NTLM responses. + GenerateResponsesV1WithSessionSecurity( + password, server_challenge, client_challenge, lm_response, ntlm_response); + // Calculate all the payload lengths and offsets. bool is_unicode = (authenticate_flags & NegotiateFlags::kUnicode) == NegotiateFlags::kUnicode; @@ -251,52 +178,21 @@ Buffer NtlmClient::GenerateAuthenticateMessage( SecurityBuffer domain_info; SecurityBuffer username_info; SecurityBuffer hostname_info; - SecurityBuffer session_key_info; size_t authenticate_message_len; - - CalculatePayloadLayout(is_unicode, domain, username, hostname, - updated_target_info.size(), &lm_info, &ntlm_info, - &domain_info, &username_info, &hostname_info, - &session_key_info, &authenticate_message_len); + CalculatePayloadLayout(is_unicode, domain, username, hostname, &lm_info, + &ntlm_info, &domain_info, &username_info, + &hostname_info, &authenticate_message_len); NtlmBufferWriter authenticate_writer(authenticate_message_len); bool writer_result = WriteAuthenticateMessage( &authenticate_writer, lm_info, ntlm_info, domain_info, username_info, - hostname_info, session_key_info, authenticate_flags); + hostname_info, authenticate_flags); DCHECK(writer_result); + DCHECK_EQ(authenticate_writer.GetCursor(), GetAuthenticateHeaderLength()); - if (IsNtlmV2()) { - // Write the optional (for V1) Version and MIC fields. Note that they - // could also safely be sent in V1. However, the server should never try to - // read them, because neither the version negotiate flag nor the - // |TargetInfoAvFlags::kMicPresent| in the target info are set. - // - // Version is never supported so it is filled with zeros. MIC is a hash - // calculated over all 3 messages while the MIC is set to zeros then - // backfilled at the end if the MIC feature is enabled. - writer_result = authenticate_writer.WriteZeros(kVersionFieldLen) && - authenticate_writer.WriteZeros(kMicLenV2); - - DCHECK(writer_result); - } - - // Verify the location in the payload buffer. - DCHECK(authenticate_writer.GetCursor() == GetAuthenticateHeaderLength()); - DCHECK(GetAuthenticateHeaderLength() == lm_info.offset); - - if (IsNtlmV2()) { - // Write the response payloads for V2. - writer_result = - WriteResponsePayloadsV2(&authenticate_writer, lm_response, v2_proof, - v2_proof_input, updated_target_info); - } else { - // Write the response payloads. - DCHECK_EQ(kResponseLenV1, lm_info.length); - DCHECK_EQ(kResponseLenV1, ntlm_info.length); - writer_result = - WriteResponsePayloads(&authenticate_writer, lm_response, ntlm_response); - } - + writer_result = + WriteResponsePayloads(&authenticate_writer, lm_response, lm_info.length, + ntlm_response, ntlm_info.length); DCHECK(writer_result); DCHECK_EQ(authenticate_writer.GetCursor(), domain_info.offset); @@ -306,20 +202,7 @@ Buffer NtlmClient::GenerateAuthenticateMessage( DCHECK(authenticate_writer.IsEndOfBuffer()); DCHECK_EQ(authenticate_message_len, authenticate_writer.GetLength()); - Buffer auth_msg = authenticate_writer.Pass(); - - // Backfill the MIC if enabled. - if (IsMicEnabled()) { - // The MIC has to be generated over all 3 completed messages with the MIC - // set to zeros. - DCHECK_LT(kMicOffsetV2 + kMicLenV2, authenticate_message_len); - - uint8_t* mic_ptr = reinterpret_cast(&auth_msg[kMicOffsetV2]); - GenerateMicV2(v2_session_key, negotiate_message_, server_challenge_message, - auth_msg, mic_ptr); - } - - return auth_msg; + return authenticate_writer.Pass(); } void NtlmClient::CalculatePayloadLayout( @@ -327,26 +210,20 @@ void NtlmClient::CalculatePayloadLayout( const base::string16& domain, const base::string16& username, const std::string& hostname, - size_t updated_target_info_len, SecurityBuffer* lm_info, SecurityBuffer* ntlm_info, SecurityBuffer* domain_info, SecurityBuffer* username_info, SecurityBuffer* hostname_info, - SecurityBuffer* session_key_info, size_t* authenticate_message_len) const { size_t upto = GetAuthenticateHeaderLength(); - session_key_info->offset = upto; - session_key_info->length = 0; - upto += session_key_info->length; - lm_info->offset = upto; lm_info->length = kResponseLenV1; upto += lm_info->length; ntlm_info->offset = upto; - ntlm_info->length = GetNtlmResponseLength(updated_target_info_len); + ntlm_info->length = GetNtlmResponseLength(); upto += ntlm_info->length; domain_info->offset = upto; @@ -365,18 +242,10 @@ void NtlmClient::CalculatePayloadLayout( } size_t NtlmClient::GetAuthenticateHeaderLength() const { - if (IsNtlmV2()) { - return kAuthenticateHeaderLenV2; - } - return kAuthenticateHeaderLenV1; } -size_t NtlmClient::GetNtlmResponseLength(size_t updated_target_info_len) const { - if (IsNtlmV2()) { - return kNtlmResponseHeaderLenV2 + updated_target_info_len + 4; - } - +size_t NtlmClient::GetNtlmResponseLength() const { return kResponseLenV1; } diff --git a/net/ntlm/ntlm_client.h b/net/ntlm/ntlm_client.h index 5ef1bd4f8a6be..94dd8c95714de 100644 --- a/net/ntlm/ntlm_client.h +++ b/net/ntlm/ntlm_client.h @@ -3,13 +3,10 @@ // found in the LICENSE file. // Based on [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol -// Specification version 28.0 [1], an unofficial NTLM reference [2], and a -// blog post describing Extended Protection for Authentication [3]. +// Specification version 28.0 [1]. Additional NTLM reference [2]. // // [1] https://msdn.microsoft.com/en-us/library/cc236621.aspx // [2] http://davenport.sourceforge.net/ntlm.html -// [3] -// https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/ #ifndef NET_BASE_NTLM_CLIENT_H_ #define NET_BASE_NTLM_CLIENT_H_ @@ -28,59 +25,24 @@ namespace net { namespace ntlm { -// Provides an implementation of an NTLMv1 or NTLMv2 Client with support -// for MIC and EPA [1]. This implementation does not support the key exchange, -// signing or sealing feature as the NTLMSSP_NEGOTIATE_KEY_EXCH flag is never -// negotiated. +// Provides an implementation of an NTLMv1 Client. // -// [1] - -// https://support.microsoft.com/en-us/help/968389/extended-protection-for-authentication +// The implementation supports NTLMv1 with extended session security (NTLM2). class NET_EXPORT_PRIVATE NtlmClient { public: - // Pass feature flags to enable/disable NTLMv2 and additional NTLMv2 - // features such as Extended Protection for Authentication (EPA) and Message - // Integrity Check (MIC). - explicit NtlmClient(NtlmFeatures features); - + NtlmClient(); ~NtlmClient(); - bool IsNtlmV2() const { return features_.enable_NTLMv2; } - - bool IsMicEnabled() const { return IsNtlmV2() && features_.enable_MIC; } - - bool IsEpaEnabled() const { return IsNtlmV2() && features_.enable_EPA; } - // Returns a |Buffer| containing the Negotiate message. Buffer GetNegotiateMessage() const; // Returns a |Buffer| containing the Authenticate message. If the method // fails an empty |Buffer| is returned. // - // |username| is treated case insensitively by NTLM however the mechanism - // to uppercase is not clearly defined. In this implementation the default - // locale is used. Additionally for names longer than 20 characters, the - // fully qualified name in the new '@' format must be used. - // eg. very_long_name@domain.com. Names shorter than 20 characters can - // optionally omit the '@domain.com' part. // |hostname| can be a short NetBIOS name or an FQDN, however the server will // only inspect this field if the default domain policy is to restrict NTLM. // In this case the hostname will be compared to a whitelist stored in this // group policy [1]. - // |channel_bindings| is a string supplied out of band (usually from a web - // browser) and is a (21+sizeof(hash)) byte ASCII string, where 'hash' is - // usually a SHA-256 of the servers certificate, but may be another hash - // algorithm. The format as defined by RFC 5929 Section 4 is shown below; - // - // [0-20] - "tls-server-end-point:" (Literal string) - // [21-(20+sizeof(hash)] - HASH(server_certificate) (Certificate hash) - // - // |spn| is a string supplied out of band (usually from a web browser) and - // is a Service Principal Name [2]. For NTLM over HTTP the value of this - // string will usually be "HTTP/". - // |client_time| 64 bit Windows timestamp defined as the number of - // 100 nanosecond ticks since midnight Jan 01, 1601 (UTC). If the server does - // not send a timestamp, the client timestamp is used in the Proof Input - // instead. // |client_challenge| must contain 8 bytes of random data. // |server_challenge_message| is the full content of the challenge message // sent by the server. @@ -91,64 +53,34 @@ class NET_EXPORT_PRIVATE NtlmClient { const base::string16& username, const base::string16& password, const std::string& hostname, - const std::string& channel_bindings, - const std::string& spn, - uint64_t client_time, const uint8_t* client_challenge, const Buffer& server_challenge_message) const; - // Simplified method for NTLMv1 which does not require |channel_bindings|, - // |spn|, or |client_time|. See |GenerateAuthenticateMessage| for more - // details. - Buffer GenerateAuthenticateMessageV1( - const base::string16& domain, - const base::string16& username, - const base::string16& password, - const std::string& hostname, - const uint8_t* client_challenge, - const Buffer& server_challenge_message) const { - DCHECK(!IsNtlmV2()); - - return GenerateAuthenticateMessage( - domain, username, password, hostname, std::string(), std::string(), 0, - client_challenge, server_challenge_message); - } - private: - // Returns the length of the Authenticate message based on the length of the - // variable length parts of the message and whether Unicode support was - // negotiated. - size_t CalculateAuthenticateMessageLength( - bool is_unicode, - const base::string16& domain, - const base::string16& username, - const std::string& hostname, - size_t updated_target_info_len) const; - + // Calculates the lengths and offset for all the payloads in the message. void CalculatePayloadLayout(bool is_unicode, const base::string16& domain, const base::string16& username, const std::string& hostname, - size_t updated_target_info_len, SecurityBuffer* lm_info, SecurityBuffer* ntlm_info, SecurityBuffer* domain_info, SecurityBuffer* username_info, SecurityBuffer* hostname_info, - SecurityBuffer* session_key_info, size_t* authenticate_message_len) const; // Returns the length of the header part of the Authenticate message. + // NOTE: When NTLMv2 support is added this is no longer a fixed value. size_t GetAuthenticateHeaderLength() const; // Returns the length of the NTLM response. - size_t GetNtlmResponseLength(size_t updated_target_info_len) const; + // NOTE: When NTLMv2 support is added this is no longer a fixed value. + size_t GetNtlmResponseLength() const; // Generates the negotiate message (which is always the same) into // |negotiate_message_|. void GenerateNegotiateMessage(); - NtlmFeatures features_; NegotiateFlags negotiate_flags_; Buffer negotiate_message_; diff --git a/net/ntlm/ntlm_client_fuzzer.cc b/net/ntlm/ntlm_client_fuzzer.cc index 048dee71b50e9..e937f695b827e 100644 --- a/net/ntlm/ntlm_client_fuzzer.cc +++ b/net/ntlm/ntlm_client_fuzzer.cc @@ -18,7 +18,7 @@ base::string16 ConsumeRandomLengthString16( } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - net::ntlm::NtlmClient client(net::ntlm::NtlmFeatures(false)); + net::ntlm::NtlmClient client; // Generate the input strings and challenge message. The strings will have a // maximum length 1 character longer than the maximum that |NtlmClient| will @@ -34,7 +34,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { fdp.ConsumeRandomLengthString(net::ntlm::kMaxFqdnLen + 1); std::string challenge_msg_bytes = fdp.ConsumeRemainingBytes(); - client.GenerateAuthenticateMessageV1( + client.GenerateAuthenticateMessage( domain, username, password, hostname, net::ntlm::test::kClientChallenge, net::ntlm::Buffer( reinterpret_cast(challenge_msg_bytes.data()), diff --git a/net/ntlm/ntlm_client_unittest.cc b/net/ntlm/ntlm_client_unittest.cc index 07ddfa77300b5..7d785d45ccb20 100644 --- a/net/ntlm/ntlm_client_unittest.cc +++ b/net/ntlm/ntlm_client_unittest.cc @@ -21,7 +21,6 @@ namespace { Buffer GenerateAuthMsg(const NtlmClient& client, const Buffer& challenge_msg) { return client.GenerateAuthenticateMessage( test::kNtlmDomain, test::kUser, test::kPassword, test::kHostnameAscii, - test::kChannelBindings, test::kNtlmSpn, test::kClientTimestamp, test::kClientChallenge, challenge_msg); } @@ -87,16 +86,8 @@ bool ReadString16Payload(NtlmBufferReader* reader, base::string16* str) { } // namespace -TEST(NtlmClientTest, SimpleConstructionV1) { - NtlmClient client(NtlmFeatures(false)); - - ASSERT_FALSE(client.IsNtlmV2()); - ASSERT_FALSE(client.IsEpaEnabled()); - ASSERT_FALSE(client.IsMicEnabled()); -} - TEST(NtlmClientTest, VerifyNegotiateMessageV1) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; Buffer result = client.GetNegotiateMessage(); @@ -106,7 +97,7 @@ TEST(NtlmClientTest, VerifyNegotiateMessageV1) { } TEST(NtlmClientTest, MinimalStructurallyValidChallenge) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; NtlmBufferWriter writer(kMinChallengeHeaderLen); ASSERT_TRUE( @@ -116,7 +107,7 @@ TEST(NtlmClientTest, MinimalStructurallyValidChallenge) { } TEST(NtlmClientTest, MinimalStructurallyValidChallengeZeroOffset) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset // SHOULD be where the payload would be if it was present. This is the @@ -136,7 +127,7 @@ TEST(NtlmClientTest, MinimalStructurallyValidChallengeZeroOffset) { } TEST(NtlmClientTest, ChallengeMsgTooShort) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // Fail because the minimum size valid message is 32 bytes. NtlmBufferWriter writer(kMinChallengeHeaderLen - 1); @@ -146,7 +137,7 @@ TEST(NtlmClientTest, ChallengeMsgTooShort) { } TEST(NtlmClientTest, ChallengeMsgNoSig) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // Fail because the first 8 bytes don't match "NTLMSSP\0" uint8_t raw[kMinChallengeHeaderLen]; @@ -161,7 +152,7 @@ TEST(NtlmClientTest, ChallengeMsgNoSig) { } TEST(NtlmClientTest, ChallengeMsgWrongMessageType) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // Fail because the message type should be MessageType::kChallenge // (0x00000002) @@ -178,7 +169,7 @@ TEST(NtlmClientTest, ChallengeMsgWrongMessageType) { } TEST(NtlmClientTest, ChallengeWithNoTargetName) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset // SHOULD be where the payload would be if it was present. This is the @@ -198,7 +189,7 @@ TEST(NtlmClientTest, ChallengeWithNoTargetName) { } TEST(NtlmClientTest, Type2MessageWithTargetName) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // One extra byte is provided for target name. uint8_t raw[kMinChallengeHeaderLen + 1]; @@ -217,11 +208,12 @@ TEST(NtlmClientTest, Type2MessageWithTargetName) { NtlmBufferWriter writer(kChallengeHeaderLen + 1); ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); + ASSERT_TRUE(GetAuthMsgResult(client, writer)); } TEST(NtlmClientTest, NoTargetNameOverflowFromOffset) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; uint8_t raw[kMinChallengeHeaderLen]; memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); @@ -244,7 +236,7 @@ TEST(NtlmClientTest, NoTargetNameOverflowFromOffset) { } TEST(NtlmClientTest, NoTargetNameOverflowFromLength) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; // Message has 1 extra byte of space after the header for the target name. // One extra byte is provided for target name. @@ -272,20 +264,19 @@ TEST(NtlmClientTest, NoTargetNameOverflowFromLength) { } TEST(NtlmClientTest, Type3UnicodeWithSessionSecuritySpecTest) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; Buffer result = GenerateAuthMsg(client, test::kChallengeMsgV1, arraysize(test::kChallengeMsgV1)); ASSERT_FALSE(result.empty()); - ASSERT_EQ(arraysize(test::kExpectedAuthenticateMsgSpecResponseV1), - result.size()); - ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgSpecResponseV1, - result.data(), result.size())); + ASSERT_EQ(arraysize(test::kExpectedAuthenticateMsgV1), result.size()); + ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgV1, result.data(), + result.size())); } TEST(NtlmClientTest, Type3WithoutUnicode) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; Buffer result = GenerateAuthMsg(client, test::kMinChallengeMessageNoUnicode, kMinChallengeHeaderLen); @@ -328,7 +319,7 @@ TEST(NtlmClientTest, Type3WithoutUnicode) { } TEST(NtlmClientTest, ClientDoesNotDowngradeSessionSecurity) { - NtlmClient client(NtlmFeatures(false)); + NtlmClient client; Buffer result = GenerateAuthMsg(client, test::kMinChallengeMessageNoSS, kMinChallengeHeaderLen); @@ -374,58 +365,5 @@ TEST(NtlmClientTest, ClientDoesNotDowngradeSessionSecurity) { flags & NegotiateFlags::kExtendedSessionSecurity); } -// ------------------------------------------------ -// NTLM V2 specific tests. -// ------------------------------------------------ - -TEST(NtlmClientTest, SimpleConstructionV2) { - NtlmClient client(NtlmFeatures(true)); - - ASSERT_TRUE(client.IsNtlmV2()); - ASSERT_TRUE(client.IsEpaEnabled()); - ASSERT_TRUE(client.IsMicEnabled()); -} - -TEST(NtlmClientTest, VerifyNegotiateMessageV2) { - NtlmClient client(NtlmFeatures(true)); - - Buffer result = client.GetNegotiateMessage(); - ASSERT_FALSE(result.empty()); - ASSERT_EQ(arraysize(test::kExpectedNegotiateMsg), result.size()); - ASSERT_EQ(0, - memcmp(test::kExpectedNegotiateMsg, result.data(), result.size())); -} - -TEST(NtlmClientTest, VerifyAuthenticateMessageV2) { - // Generate the auth message from the client based on the test challenge - // message. - NtlmClient client(NtlmFeatures(true)); - Buffer result = GenerateAuthMsg(client, test::kChallengeMsgFromSpecV2, - arraysize(test::kChallengeMsgFromSpecV2)); - ASSERT_FALSE(result.empty()); - ASSERT_EQ(arraysize(test::kExpectedAuthenticateMsgSpecResponseV2), - result.size()); - ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgSpecResponseV2, - result.data(), result.size())); -} - -TEST(NtlmClientTest, - VerifyAuthenticateMessageInResponseToChallengeWithoutTargetInfoV2) { - // Test how the V2 client responds when the server sends a challenge that - // does not contain target info. eg. Windows 2003 and earlier do not send - // this. See [MS-NLMP] Appendix B Item 8. These older Windows servers - // support NTLMv2 but don't send target info. Other implementations may - // also be affected. - NtlmClient client(NtlmFeatures(true)); - Buffer result = GenerateAuthMsg(client, test::kChallengeMsgV1, - arraysize(test::kChallengeMsgV1)); - ASSERT_FALSE(result.empty()); - - ASSERT_EQ(arraysize(test::kExpectedAuthenticateMsgToOldV1ChallegeV2), - result.size()); - ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgToOldV1ChallegeV2, - result.data(), result.size())); -} - } // namespace ntlm } // namespace net diff --git a/net/ntlm/ntlm_constants.h b/net/ntlm/ntlm_constants.h index d74e38bbd2652..cebba53b8f35d 100644 --- a/net/ntlm/ntlm_constants.h +++ b/net/ntlm/ntlm_constants.h @@ -32,19 +32,9 @@ struct SecurityBuffer { uint16_t length; }; -struct NtlmFeatures { - explicit NtlmFeatures(bool enable_NTLMv2) : enable_NTLMv2(enable_NTLMv2) {} - - // Whether the use NTLMv2. - bool enable_NTLMv2 = true; - - // Enables Message Integrity Check (MIC). This flag is ignored if - // enable_NTLMv2 is false. - bool enable_MIC = true; - - // Enables Extended Protection for Authentication (EPA). This flag is - // ignored if enable_NTLMv2 is false. - bool enable_EPA = true; +enum class NtlmVersion { + kNtlmV1 = 0x01, + kNtlmV2 = 0x02, }; // There are 3 types of messages in NTLM. The message type is a field in @@ -65,7 +55,6 @@ enum class NegotiateFlags : uint32_t { kNtlm = 0x200, kAlwaysSign = 0x8000, kExtendedSessionSecurity = 0x80000, - kTargetInfo = 0x800000, }; constexpr inline NegotiateFlags operator|(NegotiateFlags lhs, @@ -84,96 +73,17 @@ constexpr inline NegotiateFlags operator&(NegotiateFlags lhs, static_cast(rhs)); } -// Identifies the payload type in an AV Pair. See [MS-NLMP] 2.2.2.1 -enum class TargetInfoAvId : uint16_t { - kEol = 0x0000, - kServerName = 0x00001, - kDomainName = 0x00002, - kFlags = 0x0006, - kTimestamp = 0x0007, - kTargetName = 0x0009, - kChannelBindings = 0x000A, -}; - -// Flags used in an TargetInfoAvId::kFlags AV Pair. See [MS-NLMP] 2.2.2.1 -enum class TargetInfoAvFlags : uint32_t { - kNone = 0, - kMicPresent = 0x00000002, -}; - -using TAvFlagsInt = std::underlying_type::type; - -constexpr inline TargetInfoAvFlags operator|(TargetInfoAvFlags lhs, - TargetInfoAvFlags rhs) { - return static_cast(static_cast(lhs) | - static_cast(rhs)); -} - -constexpr inline TargetInfoAvFlags operator&(TargetInfoAvFlags lhs, - TargetInfoAvFlags rhs) { - return static_cast(static_cast(lhs) & - static_cast(rhs)); -} - -// An AV Pair is a structure that appears inside the target info field. It -// consists of an |avid| to identify the data type and an |avlen| specifying -// the size of the payload. Following that is |avlen| bytes of inline payload. -// AV Pairs are concatenated together and a special terminator with |avid| -// equal to |kEol| and |avlen| equal to zero signals that no further pairs -// follow. See [MS-NLMP] 2.2.2.1 -// -// AV Pairs from the Challenge message are read from the challenge message -// and a potentially modified version is written into the authenticate -// message. In some cases the existing AV Pair is modified, eg. flags. In -// some cases new AV Pairs are add, eg. channel bindings and spn. -// -// For simplicity of processing two special fields |flags|, and |timestamp| -// are populated during the initial parsing phase for AVIDs |kFlags| and -// |kTimestamp| respectively. This avoids subsequent code having to -// manipulate the payload value through the buffer directly. For all -// other AvPairs the value of these 2 fields is undefined and the payload -// is in the |buffer| field. For these fields the payload is copied verbatim -// and it's content is not read or validated in any way. -struct AvPair { - AvPair() {} - AvPair(TargetInfoAvId avid, uint16_t avlen) : avid(avid), avlen(avlen) {} - AvPair(TargetInfoAvId avid, Buffer buffer) - : buffer(std::move(buffer)), avid(avid) { - avlen = this->buffer.size(); - } - - Buffer buffer; - uint64_t timestamp; - TargetInfoAvFlags flags; - TargetInfoAvId avid; - uint16_t avlen; -}; - static constexpr uint8_t kSignature[] = "NTLMSSP"; static constexpr size_t kSignatureLen = arraysize(kSignature); -static constexpr uint16_t kProofInputVersionV2 = 0x0101; static constexpr size_t kSecurityBufferLen = (2 * sizeof(uint16_t)) + sizeof(uint32_t); static constexpr size_t kNegotiateMessageLen = 32; static constexpr size_t kMinChallengeHeaderLen = 32; -static constexpr size_t kChallengeHeaderLen = 48; +static constexpr size_t kChallengeHeaderLen = 32; static constexpr size_t kResponseLenV1 = 24; static constexpr size_t kChallengeLen = 8; -static constexpr size_t kVersionFieldLen = 8; static constexpr size_t kNtlmHashLen = 16; -static constexpr size_t kNtlmProofLenV2 = kNtlmHashLen; -static constexpr size_t kSessionKeyLenV2 = kNtlmHashLen; -static constexpr size_t kMicLenV2 = kNtlmHashLen; -static constexpr size_t kChannelBindingsHashLen = kNtlmHashLen; -static constexpr size_t kEpaUnhashedStructHeaderLen = 20; -static constexpr size_t kProofInputLenV2 = 28; -static constexpr size_t kAvPairHeaderLen = 2 * sizeof(uint16_t); -static constexpr size_t kNtlmResponseHeaderLenV2 = - kNtlmProofLenV2 + kProofInputLenV2; static constexpr size_t kAuthenticateHeaderLenV1 = 64; -static constexpr size_t kMicOffsetV2 = 72; -static constexpr size_t kAuthenticateHeaderLenV2 = 88; - static constexpr size_t kMaxFqdnLen = 255; static constexpr size_t kMaxUsernameLen = 104; static constexpr size_t kMaxPasswordLen = 256; diff --git a/net/ntlm/ntlm_test_data.h b/net/ntlm/ntlm_test_data.h index eed3b0d588b84..5f147490482c0 100644 --- a/net/ntlm/ntlm_test_data.h +++ b/net/ntlm/ntlm_test_data.h @@ -5,13 +5,7 @@ // This file contains common input and result values use to verify the NTLM // implementation. They are defined in [MS-NLMP] Section 4.2 [1]. // -// [MS-NLMP] has no test data for Extended Protection for Authentication (EPA). -// Test vectors related to EPA (aka Channel Binding) have been taken from -// a Microsoft blog post [2]. -// // [1] https://msdn.microsoft.com/en-us/library/cc236621.aspx -// [2] https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and- -// channel-binding-hash-aka-extended-protection-for-authentication/ #ifndef NET_BASE_NTLM_TEST_DATA_H_ #define NET_BASE_NTLM_TEST_DATA_H_ @@ -26,46 +20,17 @@ namespace test { constexpr base::char16 kPassword[] = {'P', 'a', 's', 's', 'w', 'o', 'r', 'd', '\0'}; constexpr base::char16 kNtlmDomain[] = {'D', 'o', 'm', 'a', 'i', 'n', '\0'}; -constexpr uint8_t kNtlmDomainRaw[] = {'D', 0x00, 'o', 0x00, 'm', 0x00, - 'a', 0x00, 'i', 0x00, 'n', 0x00}; constexpr base::char16 kUser[] = {'U', 's', 'e', 'r', '\0'}; constexpr base::char16 kDomainUserCombined[] = {'D', 'o', 'm', 'a', 'i', 'n', '\\', 'U', 's', 'e', 'r', '\0'}; constexpr base::char16 kHostname[] = {'C', 'O', 'M', 'P', 'U', 'T', 'E', 'R', '\0'}; -constexpr base::char16 kServer[] = {'S', 'e', 'r', 'v', 'e', 'r', '\0'}; -constexpr uint8_t kServerRaw[] = {'S', 0x00, 'e', 0x00, 'r', 0x00, - 'v', 0x00, 'e', 0x00, 'r', 0x00}; // ASCII Versions of the above strings. constexpr char kNtlmDomainAscii[] = "Domain"; constexpr char kUserAscii[] = "User"; constexpr char kHostnameAscii[] = "COMPUTER"; -// Test data obtained from [2]. -constexpr char kChannelBindings[] = { - 't', 'l', 's', '-', 's', 'e', 'r', 'v', 'e', 'r', '-', - 'e', 'n', 'd', '-', 'p', 'o', 'i', 'n', 't', ':', 0xea, - 0x05, 0xfe, 0xfe, 0xcc, 0x6b, 0x0b, 0xd5, 0x71, 0xdb, 0xbc, 0x5b, - 0xaa, 0x3e, 0xd4, 0x53, 0x86, 0xd0, 0x44, 0x68, 0x35, 0xf7, 0xb7, - 0x4c, 0x85, 0x62, 0x1b, 0x99, 0x83, 0x47, 0x5f, 0x95, '\0'}; - -constexpr char kNtlmSpn[] = {'H', 'T', 'T', 'P', '/', 'S', - 'e', 'r', 'v', 'e', 'r', '\0'}; -constexpr uint8_t kNtlmSpnRaw[] = {'H', 0x00, 'T', 0x00, 'T', 0x00, 'P', 0x00, - '/', 0x00, 'S', 0x00, 'e', 0x00, 'r', 0x00, - 'v', 0x00, 'e', 0x00, 'r', 0x00}; - -// Input value defined in [MS-NLMP] Section 4.2.1. -constexpr uint64_t kServerTimestamp = 0; - -// Arbitrary value for client timestamp. The spec does not provide test data -// or scenarios involving the client timestamp. The relevant thing is that it -// is not equal to |kServerTimestamp| so it can be determined which timestamp -// is within the message. -// Tue, 23 May 2017 20:13:07 +0000 -constexpr uint64_t kClientTimestamp = 131400439870000000; - // Challenge vectors defined in [MS-NLMP] Section 4.2.1. constexpr uint8_t kServerChallenge[kChallengeLen] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; @@ -81,30 +46,17 @@ constexpr uint8_t kChallengeMsgV1[] = { 0x06, 0x00, 0x70, 0x17, 0x00, 0x00, 0x00, 0x0f, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00}; -// Test input defined in [MS-NLMP] Section 4.2.4.3. -constexpr uint8_t kChallengeMsgFromSpecV2[] = { - 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x0c, 0x00, 0x38, 0x00, 0x00, 0x00, 0x33, 0x82, 0x8a, 0xe2, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x24, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x70, 0x17, 0x00, 0x00, 0x00, 0x0f, 0x53, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x02, 0x00, 0x0c, 0x00, - 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, - 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, - 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - // A minimal challenge message for tests. For NTLMv1 this implementation only // reads the smallest required version of the message (32 bytes). Some // servers may still send messages this small. The only relevant flags // that affect behavior are that both NTLMSSP_NEGOTIATE_UNICODE and // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are set. // -// [0-7] - "NTLMSSP\0" (Signature) +// [0-7] - "NTLMSSP\0" (Signature) // [9-11] - |MessageType::kChallenge| (Message Type = 0x00000002) -// [12-19] - |SecBuf(kNegotiateMessageLen, 0)| (Target Name - Not Used) -// [20-23] - |kNegotiateMessageFlags| (Flags = 0x00088207) -// [24-31] - |kServerChallenge| (Server Challenge) +// [12-19] - |SecBuf(kNegotiateMessageLen, 0)|(Target Name - Not Used) +// [20-23] - |NEGOTIATE_MESSAGE_FLAGS| (Flags = 0x00088207) +// [24-31] - |SERVER_CHALLENGE| (Server Challenge) // // See [MS-NLMP] Section 2.2.2.2 for more information about the Challenge // message. @@ -132,11 +84,6 @@ constexpr uint8_t kExpectedNtlmHashV1[kNtlmHashLen] = { 0xa4, 0xf4, 0x9c, 0x40, 0x65, 0x10, 0xbd, 0xca, 0xb6, 0x82, 0x4e, 0xe7, 0xc3, 0x0f, 0xd8, 0x52}; -// Test result value for NTOWFv2() defined in [MS-NLMP] Section 4.2.4.1.1. -constexpr uint8_t kExpectedNtlmHashV2[kNtlmHashLen] = { - 0x0c, 0x86, 0x8a, 0x40, 0x3b, 0xfd, 0x7a, 0x93, - 0xa3, 0x00, 0x1e, 0xf2, 0x2e, 0xf0, 0x2e, 0x3f}; - // Test result value defined in [MS-NLMP] Section 4.2.2.1. constexpr uint8_t kExpectedNtlmResponseV1[kResponseLenV1] = { 0x67, 0xc4, 0x30, 0x11, 0xf3, 0x02, 0x98, 0xa2, 0xad, 0x35, 0xec, 0xe6, @@ -152,170 +99,12 @@ constexpr uint8_t kExpectedLmResponseWithV1SS[kResponseLenV1] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -// Test result value defined in [MS-NLMP] Section 4.2.4.1.3. -// -// "temp" is defined in Section 3.3.2 and is part of the data to be hashed -// to generate the NTLMv2 Proof. It is composed of 3 parts; -// -// 1) [0-27] A fixed length part in the first 28 (|kProofInputLenV2|) bytes -// which in this implementation is generated by |GenerateProofInputV2|. -// -// 2) [28-63] A variable length part which the spec calls "ServerName" but -// defines as the AV Pairs (aka Target Information) from the Authenticate -// message. See |kExpectedTargetInfoFromSpecV2| for more information. -// -// 3) [64-68] 4 zero bytes. -// -// NOTE: The timestamp (bytes [8-15]) should not actually be 0 here. In order -// to use the test data from the spec some lower level tests do generate this -// value. The target info sent by the server does not contain a timestamp -// AvPair, and section 3.1.5.1.2 states that the client should populate the -// timestamp with the servers timestamp if it exists, otherwise with the -// client's local time. For end to end tests the alternate value -// |kExpectedTempWithClientTimestampV2| below is used for end to end tests. -// Having different test data for the server and client time allows testing -// the logic more correctly. -constexpr uint8_t kExpectedTempFromSpecV2[] = { - 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, - 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x01, 0x00, 0x0c, 0x00, - 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// This value is the same as |kExpectedTempFromSpecV2| but with the timestamp -// field at bytes [8-15] populated with |kClientTimestamp|. -constexpr uint8_t kExpectedTempWithClientTimestampV2[] = { - 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0b, 0xc8, 0xfd, - 0x00, 0xd4, 0xd2, 0x01, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, - 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x01, 0x00, 0x0c, 0x00, - 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// Test result value defined (indirectly) in [MS-NLMP] Section 4.2.4. -// -// This is part 2 (bytes [28-63]) of |kExpectedTempFromSpecV2|. Additional -// notes; -// -// a) The spec defines the AV Pairs to be in the opposite order to which they -// actually appear in the output in Section 4.2.4.1.3. -// -// b) The implicit presence of a terminating AV Pair is not mentioned. -// -// c) Section 4.2.4 does not show the byte sequences of the AV Pair Headers. -// -// NOTE: The real implementation in default settings would not have such a -// simple set of AV Pairs since a flags field to indicate the presence of a -// MIC, and a channel bindings field would also have been added. -constexpr uint8_t kExpectedTargetInfoFromSpecV2[] = { - 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, - 0x69, 0x00, 0x6e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// This target info is to test the behavior when a server timestamp is -// present. It is the same as |kExpectedTargetInfoFromSpecV2| but with -// an additional timestamp AvPair. -constexpr uint8_t kExpectedTargetInfoFromSpecPlusServerTimestampV2[] = { - 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, - 0x69, 0x00, 0x6e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x07, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// The target info after being updated by the client when the server sends -// |kExpectedTargetInfoFromSpecV2| in the challenge message with both EPA and -// MIC enabled. -// -// When MIC and EPA are enabled, 3 additional AvPairs are added. -// 1) A flags AVPair with the MIC_PRESENT bit set. -// 2) A channel bindings AVPair containing the channel bindings hash. -// 3) A target name AVPair containing the SPN of the server. -// -// AvPair 1 [0-1] |TargetInfoAvId::kDomainName| Av ID = 0x0002 -// AvPair 1 [2-3] |len(kNtlmDomainRaw)| Av Length = 0x000c -// AvPair 1 [4-15] |kNtlmDomainRaw| Av Payload = L"Domain" -// -// AvPair 2 [16-17] |TargetInfoAvId::kServerName| Av ID = 0x0001 -// AvPair 2 [18-19] |len(kServerRaw)| Av Length = 0x000c -// AvPair 2 [20-31] |kServerRaw| Av Payload = L"Server" -// -// AvPair 3 [32-33] |TargetInfoAvId::kFlags| Av ID = 0x0006 -// AvPair 3 [34-35] |sizeof(uint32_t)| Av Length = 0x0004 -// AvPair 3 [36-39] |TargetInfoAvFlags::kMicPresent| Av Payload = 0x00000002 -// -// AvPair 4 [40-41] |TargetInfoAvId::kChannelBindings| Av ID = 0x000a -// AvPair 4 [42-43] |kChannelBindingsHashLen| Av Length = 0x0010 -// AvPair 4 [44-59] |kExpectedChannelBindingHashV2| Av Payload -// -// AvPair 5 [60-61] |TargetInfoAvId::kTargetName| Av ID = 0x0009 -// AvPair 5 [62-63] |len(kNtlmSpnRaw)| Av Length = 0x0016 -// AvPair 5 [64-85] |kNtlmSpnRaw| Av Payload = -// L"HTTP/Server" -// -// AvPair 6 [86-87] |TargetInfoAvId::kEol| Av ID = 0x0000 -// AvPair 6 [88-89] Av Length = 0x0000 -constexpr uint8_t kExpectedTargetInfoSpecResponseV2[] = { - 0x02, 0x00, 0x0c, 0x00, 'D', 0x00, 'o', 0x00, 'm', 0x00, 'a', 0x00, - 'i', 0x00, 'n', 0x00, 0x01, 0x00, 0x0c, 0x00, 'S', 0x00, 'e', 0x00, - 'r', 0x00, 'v', 0x00, 'e', 0x00, 'r', 0x00, 0x06, 0x00, 0x04, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x65, 0x86, 0xE9, 0x9D, - 0x81, 0xC2, 0xFC, 0x98, 0x4E, 0x47, 0x17, 0x2F, 0xD4, 0xDD, 0x03, 0x10, - 0x09, 0x00, 0x16, 0x00, 'H', 0x00, 'T', 0x00, 'T', 0x00, 'P', 0x00, - '/', 0x00, 'S', 0x00, 'e', 0x00, 'r', 0x00, 'v', 0x00, 'e', 0x00, - 'r', 0x00, 0x00, 0x00, 0x00, 0x00}; - -// Test result value defined in [MS-NLMP] Section 4.2.4.2.2. -constexpr uint8_t kExpectedProofFromSpecV2[kNtlmProofLenV2] = { - 0x68, 0xcd, 0x0a, 0xb8, 0x51, 0xe5, 0x1c, 0x96, - 0xaa, 0xbc, 0x92, 0x7b, 0xeb, 0xef, 0x6a, 0x1c}; - -// The value of the NTLMv2 proof when |kExpectedTargetInfoSpecResponseV2| is -// the updated target info in the Authenticate message. -constexpr uint8_t kExpectedProofSpecResponseV2[kNtlmProofLenV2] = { - 0x01, 0x0c, 0x0b, 0xd1, 0x4e, 0xf7, 0xa2, 0x96, - 0x89, 0xc0, 0xc1, 0x9c, 0xea, 0xe8, 0xb7, 0xdf}; - -// The value of the NTLMv2 proof when |kExpectedTargetInfoSpecResponseV2| is -// the updated target info, and |kClientTimestamp| is correctly set in the -// Authenticate message. -constexpr uint8_t - kExpectedProofSpecResponseWithClientTimestampV2[kNtlmProofLenV2] = { - 0x8c, 0x02, 0x60, 0xdb, 0xef, 0x69, 0x06, 0x62, - 0xaf, 0x9c, 0x42, 0xd5, 0x07, 0x82, 0xd2, 0xed}; - -// Test result data obtained from [2]. -constexpr uint8_t kExpectedChannelBindingHashV2[kChannelBindingsHashLen] = { - 0x65, 0x86, 0xE9, 0x9D, 0x81, 0xC2, 0xFC, 0x98, - 0x4E, 0x47, 0x17, 0x2F, 0xD4, 0xDD, 0x03, 0x10}; - -// Test result value defined in [MS-NLMP] Section 4.2.4.1.2. -constexpr uint8_t kExpectedSessionBaseKeyFromSpecV2[kSessionKeyLenV2] = { - 0x8d, 0xe4, 0x0c, 0xca, 0xdb, 0xc1, 0x4a, 0x82, - 0xf1, 0x5c, 0xb0, 0xad, 0x0d, 0xe9, 0x5c, 0xa3}; - -// The session base key when the proof is -// |kExpectedProofSpecResponseWithClientTimestampV2|. -constexpr uint8_t - kExpectedSessionBaseKeyWithClientTimestampV2[kSessionKeyLenV2] = { - 0x62, 0x3d, 0xbd, 0x07, 0x1b, 0xe7, 0xa5, 0x30, - 0xb6, 0xa9, 0x5c, 0x2e, 0xb4, 0x98, 0x24, 0x70}; - -// The Message Integrity Check (MIC) using -// |kExpectedSessionBaseKeyWithClientTimestampV2| over the following 3 -// messages; |kExpectedNegotiateMsg|, |kChallengeMsgFromSpecV2|, and -// |kExpectedAuthenticateMsgSpecResponseV2|. -// The MIC field in |kExpectedAuthenticateMsgSpecResponseV2| is set to all -// zeros while calculating the hash. -constexpr uint8_t kExpectedMicV2[kMicLenV2] = { - 0xf7, 0x36, 0x16, 0x33, 0xf0, 0xad, 0x9b, 0xdf, - 0x4a, 0x7c, 0x42, 0x1b, 0xc6, 0xb8, 0x24, 0xa3}; - // Expected negotiate message from this implementation. // [0-7] - "NTLMSSP\0" (Signature) -// [9-11] - |MessageType::kNegotiate| (Message Type = 0x00000001) -// [12-15] - |kNegotiateMessageFlags| (Flags = 0x00088207) -// [16-23] - |SecBuf(kNegotiateMessageLen, 0)| (Domain) -// [24-32] - |SecBuf(kNegotiateMessageLen, 0)| (Workstation) +// [9-11] - |MessageType::NEGOTIATE| (Message Type = 0x00000001) +// [12-15] - |NEGOTIATE_MESSAGE_FLAGS| (Flags = 0x00088207) +// [16-23] - |SecBuf(kNegotiateMessageLen, 0)|(Domain) +// [24-32] - |SecBuf(kNegotiateMessageLen, 0)|(Workstation) // // NOTE: Message does not include Version field. Since // NTLMSSP_NEGOTIATE_VERSION is never sent, it is not required, and the server @@ -343,8 +132,8 @@ constexpr uint8_t kExpectedNegotiateMsg[kNegotiateMessageLen] = { // // [0-7] - "NTLMSSP\0" (Signature) // [9-11] - |MessageType::kAuthenticate| (Message Type = 0x00000003) -// [12-19] - |SecBuf(64, kResponseLenV1)| (LM Response) -// [20-27] - |SecBuf(88, kResponseLenV1)| (NTLM Response) +// [12-19] - |SecBuf(64, RESPONSE_V1_LEN)| (LM Response) +// [20-27] - |SecBuf(88, RESPONSE_V1_LEN)| (NTLM Response) // [28-35] - |SecBuf(112, 12)| (Target Name = L"Domain") // [36-43] - |SecBuf(124, 8)| (User = L"User") // [44-51] - |SecBuf(132, 16)| (Workstation = L"COMPUTER") @@ -368,7 +157,7 @@ constexpr uint8_t kExpectedNegotiateMsg[kNegotiateMessageLen] = { // prior) implementation uses a different payload order than the example. // 4) The version field is Windows specific and there is no provision for // non-Windows OS information. This message does not include a version field. -constexpr uint8_t kExpectedAuthenticateMsgSpecResponseV1[] = { +constexpr uint8_t kExpectedAuthenticateMsgV1[] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0', 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x40, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x70, 0x00, 0x00, 0x00, @@ -384,146 +173,6 @@ constexpr uint8_t kExpectedAuthenticateMsgSpecResponseV1[] = { 'E', 0x00, 'R', 0x00, }; -// Expected V2 Authenticate message from this implementation when sent -// |kChallengeMsgFromSpecV2| as the challenge using default features. -// -// [0-7] - "NTLMSSP\0" (Signature) -// [9-11] - |MessageType::kAuthenticate| (Message Type = 0x00000003) -// [12-19] - |SecBuf(88, kResponseLenV1)| (LM Response) -// [20-27] - |SecBuf(112, 138)| (NTLM Response) -// [28-35] - |SecBuf(250, 12)| (Target Name = L"Domain") -// [36-43] - |SecBuf(262, 8)| (User = L"User") -// [44-51] - |SecBuf(270, 16)| (Workstation = L"COMPUTER") -// [52-59] - |SecBuf(88, 0)| (Session Key (empty)) -// [60-63] - 0x00088203 (Flags) -// [64-71] - All zero (Version) -// [72-87] - |kExpectedMicV2| (MIC) -// [88-111] - All zero (LM Response Payload) -// [112-249]-------------------------------------- (NTLM Response Payload) -// [112-127]-|kExpectedProofSpecResponseWithClientTimestampV2| -// (NTLMv2 Proof) -// [128-155]-|kExpectedTempWithClientTimestampV2[0-27]| -// (Proof Input) -// [156-245]-|kExpectedTargetInfoSpecResponseV2| (Updated target info) -// [246-249]-0x00000000 (Reserved - zeros) -// ----------------------------------------------------------------------- -// [250-261]- L"Domain" (Target Name Payload) -// [262-269]- L"User" (User Payload) -// [270-285]- L"COMPUTER" (Workstation Payload) -// -// NOTE: This is not identical to the message in [MS-NLMP] Section TODO(X) for -// several reasons. -// -// 1) The flags are different because this implementation does not support -// the flags related to version, key exchange, signing and sealing. These -// flags are not relevant to implementing the NTLM scheme in HTTP. -// 2) Since key exchange is not required nor supported, the session base key -// payload is not required nor present. -// 3) The specification allows payloads to be in any order. This (and the -// prior) implementation uses a different payload order than the example. -// 4) The version field is Windows specific and there is no provision for a -// non-Windows OS information. This message does not include a version field. -// 5) The example in the spec does not use Extended Protection for -// Authentication (EPA). This message includes an extra AV Pair containing -// the hashed channel bindings. -// 6) The example in the spec does not use Message Integrity Check (MIC). -// The optional field is not present, nor is the flags AV Pair that indicates -// it's presence. -// 7) Since the server does not provide a timestamp, the client should -// provide one. -constexpr uint8_t kExpectedAuthenticateMsgSpecResponseV2[] = { - 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0', 0x03, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x18, 0x00, 0x58, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x8a, 0x00, - 0x70, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0xfa, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x08, 0x00, 0x06, 0x01, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, - 0x0e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, - 0x03, 0x82, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xf7, 0x36, 0x16, 0x33, 0xf0, 0xad, 0x9b, 0xdf, 0x4a, 0x7c, 0x42, 0x1b, - 0xc6, 0xb8, 0x24, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x8c, 0x02, 0x60, 0xdb, 0xef, 0x69, 0x06, 0x62, - 0xaf, 0x9c, 0x42, 0xd5, 0x07, 0x82, 0xd2, 0xed, 0x01, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x80, 0x0b, 0xc8, 0xfd, 0x00, 0xd4, 0xd2, 0x01, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x0c, 0x00, 'D', 0x00, 'o', 0x00, 'm', 0x00, 'a', 0x00, - 'i', 0x00, 'n', 0x00, 0x01, 0x00, 0x0c, 0x00, 'S', 0x00, 'e', 0x00, - 'r', 0x00, 'v', 0x00, 'e', 0x00, 'r', 0x00, 0x06, 0x00, 0x04, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x65, 0x86, 0xE9, 0x9D, - 0x81, 0xC2, 0xFC, 0x98, 0x4E, 0x47, 0x17, 0x2F, 0xD4, 0xDD, 0x03, 0x10, - 0x09, 0x00, 0x16, 0x00, 'H', 0x00, 'T', 0x00, 'T', 0x00, 'P', 0x00, - '/', 0x00, 'S', 0x00, 'e', 0x00, 'r', 0x00, 'v', 0x00, 'e', 0x00, - 'r', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'D', 0x00, - 'o', 0x00, 'm', 0x00, 'a', 0x00, 'i', 0x00, 'n', 0x00, 'U', 0x00, - 's', 0x00, 'e', 0x00, 'r', 0x00, 'C', 0x00, 'O', 0x00, 'M', 0x00, - 'P', 0x00, 'U', 0x00, 'T', 0x00, 'E', 0x00, 'R', 0x00, -}; - -// Expected V2 Authenticate message from this implementation when sent -// |kChallengeMsgV1| as the challenge using default features. This scenario -// can occur because some older implementations (Windows 2003 and earlier), -// do not send NTLMSSP_NEGOTIATE_TARGET_INFO, nor a Target Info payload in -// the challenge message. -// -// [0-7] - "NTLMSSP\0" (Signature) -// [9-11] - |MessageType::kAuthenticate| (Message Type = 0x00000003) -// [12-19] - |SecBuf(88, kResponseLenV1)| (LM Response) -// [20-27] - |SecBuf(112, 106)| (NTLM Response) -// [28-35] - |SecBuf(218, 12)| (Target Name = L"Domain") -// [36-43] - |SecBuf(230, 8)| (User = L"User") -// [44-51] - |SecBuf(238, 16)| (Workstation = L"COMPUTER") -// [52-59] - |SecBuf(88, 0)| (Session Key (empty)) -// [60-63] - 0x00088203 (Flags) -// [64-71] - All zero (Version) -// [72-87] - (MIC) -// [88-111] - All zero (LM Response Payload) -// [112-217]-------------------------------------- (NTLM Response Payload) -// [112-127]- (NTLMv2 Proof) -// [128-155]-|kExpectedTempWithClientTimestampV2[0-27]| -// (Proof Input) -// [156-213]-|kExpectedTargetInfoSpecResponseV2[32-89]| -// (Updated target info) -// [214-217]-0x00000000 (Reserved - zeros) -// ----------------------------------------------------------------------- -// [218-229]- L"Domain" (Target Name Payload) -// [230-237]- L"User" (User Payload) -// [238-253]- L"COMPUTER" (Workstation Payload) -// -// NOTE: This is message is almost the same as -// |kExpectedAuthenticateMsgSpecResponseV2| with the following changes. -// 1) The target info within the NTLM response is missing the first 32 -// bytes, which represent the 2 AvPairs that the server does not send in -// this case. -// 2) The NTLM Response security buffer length is reduced by 32 and therefore -// all subsequent security buffer offsets are reduced by 32. -// 3) The NTLMv2 Proof is different since the different target info changes -// the hash. -// 4) As with the NTLMv2 Proof, the MIC is different because the message is -// different. -constexpr uint8_t kExpectedAuthenticateMsgToOldV1ChallegeV2[] = { - 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0', 0x03, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x18, 0x00, 0x58, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x6a, 0x00, - 0x70, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0xda, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x08, 0x00, 0xe6, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, - 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, - 0x03, 0x82, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x31, 0x37, 0xd6, 0x9e, 0x5c, 0xc8, 0x7d, 0x7a, 0x9f, 0x7c, 0xf4, 0x1a, - 0x5a, 0x19, 0xdc, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xbe, 0xd9, 0xa9, 0x42, 0x20, 0xc2, 0x25, 0x2b, - 0x91, 0x6e, 0x9c, 0xe3, 0x9d, 0x97, 0x3a, 0x2d, 0x01, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x80, 0x0b, 0xc8, 0xfd, 0x00, 0xd4, 0xd2, 0x01, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, - 0x65, 0x86, 0xE9, 0x9D, 0x81, 0xC2, 0xFC, 0x98, 0x4E, 0x47, 0x17, 0x2F, - 0xD4, 0xDD, 0x03, 0x10, 0x09, 0x00, 0x16, 0x00, 'H', 0x00, 'T', 0x00, - 'T', 0x00, 'P', 0x00, '/', 0x00, 'S', 0x00, 'e', 0x00, 'r', 0x00, - 'v', 0x00, 'e', 0x00, 'r', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 'D', 0x00, 'o', 0x00, 'm', 0x00, 'a', 0x00, 'i', 0x00, - 'n', 0x00, 'U', 0x00, 's', 0x00, 'e', 0x00, 'r', 0x00, 'C', 0x00, - 'O', 0x00, 'M', 0x00, 'P', 0x00, 'U', 0x00, 'T', 0x00, 'E', 0x00, - 'R', 0x00, -}; - } // namespace test } // namespace ntlm } // namespace net diff --git a/net/ntlm/ntlm_unittest.cc b/net/ntlm/ntlm_unittest.cc index 01c07a7da1ccc..6a2ce032acb7a 100644 --- a/net/ntlm/ntlm_unittest.cc +++ b/net/ntlm/ntlm_unittest.cc @@ -15,7 +15,6 @@ #include -#include "base/md5.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" #include "net/ntlm/ntlm_test_data.h" @@ -24,20 +23,6 @@ namespace net { namespace ntlm { -namespace { - -AvPair MakeDomainAvPair() { - return AvPair(TargetInfoAvId::kDomainName, - Buffer(test::kNtlmDomainRaw, arraysize(test::kNtlmDomainRaw))); -} - -AvPair MakeServerAvPair() { - return AvPair(TargetInfoAvId::kServerName, - Buffer(test::kServerRaw, arraysize(test::kServerRaw))); -} - -} // namespace - TEST(NtlmTest, GenerateNtlmHashV1PasswordSpecTests) { uint8_t hash[kNtlmHashLen]; GenerateNtlmHashV1(test::kPassword, hash); @@ -134,242 +119,5 @@ TEST(NtlmTest, GenerateResponsesV1WithSessionSecurityVerifySSUsed) { ASSERT_NE(0, memcmp(ntlm_response1, ntlm_response2, kResponseLenV1)); } -// ------------------------------------------------ -// NTLM V2 specific tests. -// ------------------------------------------------ - -TEST(NtlmTest, GenerateNtlmHashV2SpecTests) { - uint8_t hash[kNtlmHashLen]; - GenerateNtlmHashV2(test::kNtlmDomain, test::kUser, test::kPassword, hash); - ASSERT_EQ(0, memcmp(hash, test::kExpectedNtlmHashV2, kNtlmHashLen)); -} - -TEST(NtlmTest, GenerateProofInputV2SpecTests) { - Buffer proof_input; - proof_input = - GenerateProofInputV2(test::kServerTimestamp, test::kClientChallenge); - ASSERT_EQ(kProofInputLenV2, proof_input.size()); - - // |GenerateProofInputV2| generates the first |kProofInputLenV2| bytes of - // what [MS-NLMP] calls "temp". - ASSERT_EQ(0, memcmp(test::kExpectedTempFromSpecV2, proof_input.data(), - proof_input.size())); -} - -TEST(NtlmTest, GenerateNtlmProofV2SpecTests) { - // Only the first |kProofInputLenV2| bytes of |test::kExpectedTempFromSpecV2| - // are read and this is equivalent to the output of |GenerateProofInputV2|. - // See |GenerateProofInputV2SpecTests| for validation. - uint8_t v2_proof[kNtlmProofLenV2]; - GenerateNtlmProofV2(test::kExpectedNtlmHashV2, test::kServerChallenge, - Buffer(test::kExpectedTempFromSpecV2, kProofInputLenV2), - Buffer(test::kExpectedTargetInfoFromSpecV2, - arraysize(test::kExpectedTargetInfoFromSpecV2)), - v2_proof); - - ASSERT_EQ(0, - memcmp(test::kExpectedProofFromSpecV2, v2_proof, kNtlmProofLenV2)); -} - -TEST(NtlmTest, GenerateSessionBaseKeyV2SpecTests) { - // Generate the session base key. - uint8_t session_base_key[kSessionKeyLenV2]; - GenerateSessionBaseKeyV2(test::kExpectedNtlmHashV2, - test::kExpectedProofFromSpecV2, session_base_key); - - // Verify the session base key. - ASSERT_EQ(0, memcmp(test::kExpectedSessionBaseKeyFromSpecV2, session_base_key, - kSessionKeyLenV2)); -} - -TEST(NtlmTest, GenerateSessionBaseKeyWithClientTimestampV2SpecTests) { - // Generate the session base key. - uint8_t session_base_key[kSessionKeyLenV2]; - GenerateSessionBaseKeyV2( - test::kExpectedNtlmHashV2, - test::kExpectedProofSpecResponseWithClientTimestampV2, session_base_key); - - // Verify the session base key. - ASSERT_EQ(0, memcmp(test::kExpectedSessionBaseKeyWithClientTimestampV2, - session_base_key, kSessionKeyLenV2)); -} - -TEST(NtlmTest, GenerateChannelBindingHashV2SpecTests) { - base::MD5Digest v2_channel_binding_hash; - GenerateChannelBindingHashV2(test::kChannelBindings, - &v2_channel_binding_hash); - - ASSERT_EQ(0, memcmp(test::kExpectedChannelBindingHashV2, - v2_channel_binding_hash.a, kChannelBindingsHashLen)); -} - -TEST(NtlmTest, GenerateMicV2Simple) { - // The MIC is defined as HMAC_MD5(session_base_key, CONCAT(a, b, c)) where - // a, b, c are the negotiate, challenge and authenticate messages - // respectively. - // - // This compares a simple set of inputs to a precalculated result. - const Buffer a{0x44, 0x44, 0x44, 0x44}; - const Buffer b{0x66, 0x66, 0x66, 0x66, 0x66, 0x66}; - const Buffer c{0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88}; - - // expected_mic = HMAC_MD5( - // key=8de40ccadbc14a82f15cb0ad0de95ca3, - // input=444444446666666666668888888888888888) - uint8_t expected_mic[kMicLenV2] = {0x71, 0xfe, 0xef, 0xd7, 0x76, 0xd4, - 0x42, 0xa8, 0x5f, 0x6e, 0x18, 0x0a, - 0x6b, 0x02, 0x47, 0x20}; - - uint8_t mic[kMicLenV2]; - GenerateMicV2(test::kExpectedSessionBaseKeyFromSpecV2, a, b, c, mic); - ASSERT_EQ(0, memcmp(expected_mic, mic, kMicLenV2)); -} - -TEST(NtlmTest, GenerateMicSpecResponseV2) { - Buffer negotiate_msg(test::kExpectedNegotiateMsg, - arraysize(test::kExpectedNegotiateMsg)); - Buffer challenge_msg(test::kChallengeMsgFromSpecV2, - arraysize(test::kChallengeMsgFromSpecV2)); - size_t authenticate_msg_len = - arraysize(test::kExpectedAuthenticateMsgSpecResponseV2); - Buffer authenticate_msg(authenticate_msg_len, '\0'); - memcpy(&authenticate_msg[0], test::kExpectedAuthenticateMsgSpecResponseV2, - authenticate_msg_len); - memset(&authenticate_msg[kMicOffsetV2], 0x00, kMicLenV2); - - uint8_t mic[kMicLenV2]; - GenerateMicV2(test::kExpectedSessionBaseKeyWithClientTimestampV2, - negotiate_msg, challenge_msg, authenticate_msg, mic); - ASSERT_EQ(0, memcmp(test::kExpectedMicV2, mic, kMicLenV2)); -} - -TEST(NtlmTest, GenerateUpdatedTargetInfo) { - // This constructs a std::vector that corresponds to the test input - // values in [MS-NLMP] Section 4.2.4. - std::vector server_av_pairs; - server_av_pairs.push_back(MakeDomainAvPair()); - server_av_pairs.push_back(MakeServerAvPair()); - - uint64_t server_timestamp = UINT64_MAX; - Buffer updated_target_info = GenerateUpdatedTargetInfo( - true, true, test::kChannelBindings, test::kNtlmSpn, server_av_pairs, - &server_timestamp); - - // With MIC and EPA enabled 3 additional AvPairs will be added. - // 1) A flags AVPair with the MIC_PRESENT bit set. - // 2) A channel bindings AVPair containing the channel bindings hash. - // 3) A target name AVPair containing the SPN of the server. - ASSERT_EQ(arraysize(test::kExpectedTargetInfoSpecResponseV2), - updated_target_info.size()); - ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoSpecResponseV2, - updated_target_info.data(), updated_target_info.size())); -} - -TEST(NtlmTest, GenerateUpdatedTargetInfoNoEpaOrMic) { - // This constructs a std::vector that corresponds to the test input - // values in [MS-NLMP] Section 4.2.4. - std::vector server_av_pairs; - server_av_pairs.push_back(MakeDomainAvPair()); - server_av_pairs.push_back(MakeServerAvPair()); - - uint64_t server_timestamp = UINT64_MAX; - - // When both EPA and MIC are false the target info does not get modified by - // the client. - Buffer updated_target_info = GenerateUpdatedTargetInfo( - false, false, test::kChannelBindings, test::kNtlmSpn, server_av_pairs, - &server_timestamp); - ASSERT_EQ(arraysize(test::kExpectedTargetInfoFromSpecV2), - updated_target_info.size()); - ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoFromSpecV2, - updated_target_info.data(), updated_target_info.size())); -} - -TEST(NtlmTest, GenerateUpdatedTargetInfoWithServerTimestamp) { - // This constructs a std::vector that corresponds to the test input - // values in [MS-NLMP] Section 4.2.4 with an additional server timestamp. - std::vector server_av_pairs; - server_av_pairs.push_back(MakeDomainAvPair()); - server_av_pairs.push_back(MakeServerAvPair()); - - // Set the timestamp to |test::kServerTimestamp| and the buffer to all zeros. - AvPair pair(TargetInfoAvId::kTimestamp, Buffer(sizeof(uint64_t), 0)); - pair.timestamp = test::kServerTimestamp; - server_av_pairs.push_back(std::move(pair)); - - uint64_t server_timestamp = UINT64_MAX; - // When both EPA and MIC are false the target info does not get modified by - // the client. - Buffer updated_target_info = GenerateUpdatedTargetInfo( - false, false, test::kChannelBindings, test::kNtlmSpn, server_av_pairs, - &server_timestamp); - // Verify that the server timestamp was read from the target info. - ASSERT_EQ(test::kServerTimestamp, server_timestamp); - ASSERT_EQ(arraysize(test::kExpectedTargetInfoFromSpecPlusServerTimestampV2), - updated_target_info.size()); - ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoFromSpecPlusServerTimestampV2, - updated_target_info.data(), updated_target_info.size())); -} - -TEST(NtlmTest, GenerateUpdatedTargetInfoWhenServerSendsNoTargetInfo) { - // In some older implementations the server supports NTLMv2 but does not - // send target info. This manifests as an empty list of AvPairs. - std::vector server_av_pairs; - - uint64_t server_timestamp = UINT64_MAX; - Buffer updated_target_info = GenerateUpdatedTargetInfo( - true, true, test::kChannelBindings, test::kNtlmSpn, server_av_pairs, - &server_timestamp); - - // With MIC and EPA enabled 3 additional AvPairs will be added. - // 1) A flags AVPair with the MIC_PRESENT bit set. - // 2) A channel bindings AVPair containing the channel bindings hash. - // 3) A target name AVPair containing the SPN of the server. - // - // Compared to the spec example in |GenerateUpdatedTargetInfo| the result - // is the same but with the first 32 bytes (which were the Domain and - // Server pairs) not present. - const size_t kMissingServerPairsLength = 32; - - ASSERT_EQ(arraysize(test::kExpectedTargetInfoSpecResponseV2) - - kMissingServerPairsLength, - updated_target_info.size()); - ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoSpecResponseV2 + - kMissingServerPairsLength, - updated_target_info.data(), updated_target_info.size())); -} - -TEST(NtlmTest, GenerateNtlmProofV2) { - uint8_t proof[kNtlmProofLenV2]; - - // NOTE: Only the first |kProofInputLenV2| bytes of |kExpectedTempFromSpecV2| - // are read. - GenerateNtlmProofV2( - test::kExpectedNtlmHashV2, test::kServerChallenge, - Buffer(test::kExpectedTempFromSpecV2, kProofInputLenV2), - Buffer(test::kExpectedTargetInfoSpecResponseV2, - arraysize(test::kExpectedTargetInfoSpecResponseV2)), - proof); - ASSERT_EQ(0, - memcmp(test::kExpectedProofSpecResponseV2, proof, kNtlmProofLenV2)); -} - -TEST(NtlmTest, GenerateNtlmProofWithClientTimestampV2) { - uint8_t proof[kNtlmProofLenV2]; - - // NOTE: Only the first |kProofInputLenV2| bytes of - // |kExpectedTempWithClientTimestampV2| are read. Since the test data for - // "temp" in the spec does not include the client timestamp, a separate - // proof test value must be validated for use in full message validation. - GenerateNtlmProofV2( - test::kExpectedNtlmHashV2, test::kServerChallenge, - Buffer(test::kExpectedTempWithClientTimestampV2, kProofInputLenV2), - Buffer(test::kExpectedTargetInfoSpecResponseV2, - arraysize(test::kExpectedTargetInfoSpecResponseV2)), - proof); - ASSERT_EQ(0, memcmp(test::kExpectedProofSpecResponseWithClientTimestampV2, - proof, kNtlmProofLenV2)); -} - } // namespace ntlm } // namespace net