blob: 7a2cc5d9146fb4c2f2cfb2b212333a06c1dfca4a [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/ntlm/ntlm_client.h"
#include <string.h>
#include "base/containers/span.h"
#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"
#include "net/ntlm/ntlm_buffer_writer.h"
#include "net/ntlm/ntlm_constants.h"
#include "starboard/memory.h"
#include "starboard/types.h"
namespace net {
namespace ntlm {
namespace {
// Parses the challenge message and returns the |challenge_flags| and
// |server_challenge| into the supplied buffer.
bool ParseChallengeMessage(
base::span<const uint8_t> challenge_message,
NegotiateFlags* challenge_flags,
base::span<uint8_t, kChallengeLen> server_challenge) {
NtlmBufferReader challenge_reader(challenge_message);
return challenge_reader.MatchMessageHeader(MessageType::kChallenge) &&
challenge_reader.SkipSecurityBufferWithValidation() &&
challenge_reader.ReadFlags(challenge_flags) &&
challenge_reader.ReadBytes(server_challenge);
}
// Parses the challenge message and extracts the information necessary to
// make an NTLMv2 response.
bool ParseChallengeMessageV2(
base::span<const uint8_t> challenge_message,
NegotiateFlags* challenge_flags,
base::span<uint8_t, kChallengeLen> server_challenge,
std::vector<AvPair>* 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) &&
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) &&
authenticate_writer->WriteSecurityBuffer(ntlm_payload) &&
authenticate_writer->WriteSecurityBuffer(domain_payload) &&
authenticate_writer->WriteSecurityBuffer(username_payload) &&
authenticate_writer->WriteSecurityBuffer(hostname_payload) &&
authenticate_writer->WriteSecurityBuffer(session_key_payload) &&
authenticate_writer->WriteFlags(authenticate_flags);
}
// Writes the NTLMv1 LM Response and NTLM Response.
bool WriteResponsePayloads(
NtlmBufferWriter* authenticate_writer,
base::span<const uint8_t, kResponseLenV1> lm_response,
base::span<const uint8_t, kResponseLenV1> ntlm_response) {
return authenticate_writer->WriteBytes(lm_response) &&
authenticate_writer->WriteBytes(ntlm_response);
}
// Writes the |lm_response| and writes the NTLMv2 response by concatenating
// |v2_proof|, |v2_proof_input|, |updated_target_info| and 4 zero bytes.
bool WriteResponsePayloadsV2(
NtlmBufferWriter* authenticate_writer,
base::span<const uint8_t, kResponseLenV1> lm_response,
base::span<const uint8_t, kNtlmProofLenV2> v2_proof,
base::span<const uint8_t> v2_proof_input,
base::span<const uint8_t> updated_target_info) {
return authenticate_writer->WriteBytes(lm_response) &&
authenticate_writer->WriteBytes(v2_proof) &&
authenticate_writer->WriteBytes(v2_proof_input) &&
authenticate_writer->WriteBytes(updated_target_info) &&
authenticate_writer->WriteUInt32(0);
}
bool WriteStringPayloads(NtlmBufferWriter* authenticate_writer,
bool is_unicode,
const base::string16& domain,
const base::string16& username,
const std::string& hostname) {
if (is_unicode) {
return authenticate_writer->WriteUtf16String(domain) &&
authenticate_writer->WriteUtf16String(username) &&
authenticate_writer->WriteUtf8AsUtf16String(hostname);
} else {
return authenticate_writer->WriteUtf16AsUtf8String(domain) &&
authenticate_writer->WriteUtf16AsUtf8String(username) &&
authenticate_writer->WriteUtf8String(hostname);
}
}
// Returns the size in bytes of a string16 depending whether unicode
// was negotiated.
size_t GetStringPayloadLength(const base::string16& str, bool is_unicode) {
if (is_unicode)
return str.length() * 2;
// When |WriteUtf16AsUtf8String| is called with a |base::string16|, the string
// is converted to UTF8. Do the conversion to ensure that the character
// count is correct.
return base::UTF16ToUTF8(str).length();
}
// Returns the size in bytes of a std::string depending whether unicode
// was negotiated.
size_t GetStringPayloadLength(const std::string& str, bool is_unicode) {
if (!is_unicode)
return str.length();
return base::UTF8ToUTF16(str).length() * 2;
}
} // namespace
NtlmClient::NtlmClient(NtlmFeatures features)
: features_(features), 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.
GenerateNegotiateMessage();
}
NtlmClient::~NtlmClient() = default;
std::vector<uint8_t> NtlmClient::GetNegotiateMessage() const {
return negotiate_message_;
}
void NtlmClient::GenerateNegotiateMessage() {
NtlmBufferWriter writer(kNegotiateMessageLen);
bool result =
writer.WriteMessageHeader(MessageType::kNegotiate) &&
writer.WriteFlags(negotiate_flags_) &&
writer.WriteSecurityBuffer(SecurityBuffer(kNegotiateMessageLen, 0)) &&
writer.WriteSecurityBuffer(SecurityBuffer(kNegotiateMessageLen, 0)) &&
writer.IsEndOfBuffer();
DCHECK(result);
negotiate_message_ = writer.Pass();
}
std::vector<uint8_t> NtlmClient::GenerateAuthenticateMessage(
const base::string16& domain,
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,
base::span<const uint8_t, kChallengeLen> client_challenge,
base::span<const uint8_t> server_challenge_message) const {
// Limit the size of strings that are accepted. As an absolute limit any
// field represented by a |SecurityBuffer| or |AvPair| must be less than
// UINT16_MAX bytes long. The strings are restricted to the maximum sizes
// without regard to encoding. As such this isn't intended to restrict all
// invalid inputs, only to allow all possible valid inputs.
//
// |domain| and |hostname| can be no longer than 255 characters.
// |username| can be no longer than 104 characters. See [1].
// |password| can be no longer than 256 characters. See [2].
//
// [1] - https://technet.microsoft.com/en-us/library/bb726984.aspx
// [2] - https://technet.microsoft.com/en-us/library/cc512606.aspx
if (hostname.length() > kMaxFqdnLen || domain.length() > kMaxFqdnLen ||
username.length() > kMaxUsernameLen ||
password.length() > kMaxPasswordLen) {
return {};
}
NegotiateFlags challenge_flags;
uint8_t server_challenge[kChallengeLen];
uint8_t lm_response[kResponseLenV1];
uint8_t ntlm_response[kResponseLenV1];
// Response fields only for NTLMv2
std::vector<uint8_t> updated_target_info;
std::vector<uint8_t> v2_proof_input;
uint8_t v2_proof[kNtlmProofLenV2];
uint8_t v2_session_key[kSessionKeyLenV2];
if (IsNtlmV2()) {
std::vector<AvPair> av_pairs;
if (!ParseChallengeMessageV2(server_challenge_message, &challenge_flags,
server_challenge, &av_pairs)) {
return {};
}
uint64_t timestamp;
updated_target_info =
GenerateUpdatedTargetInfo(IsMicEnabled(), IsEpaEnabled(),
channel_bindings, spn, av_pairs, &timestamp);
SbMemorySet(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,
#if defined(STARBOARD)
base::span<uint8_t, kProofInputLenV2>(
v2_proof_input.data(), kProofInputLenV2),
base::span<const uint8_t>(updated_target_info.data(),
updated_target_info.size()),
v2_proof);
#else
base::make_span<kProofInputLenV2>(v2_proof_input),
updated_target_info, v2_proof);
#endif
GenerateSessionBaseKeyV2(v2_hash, v2_proof, v2_session_key);
} else {
if (!ParseChallengeMessage(server_challenge_message, &challenge_flags,
server_challenge)) {
return {};
}
// Calculate the responses for the authenticate message.
GenerateResponsesV1WithSessionSecurity(password, server_challenge,
client_challenge, lm_response,
ntlm_response);
}
// Always use extended session security even if the server tries to downgrade.
NegotiateFlags authenticate_flags = (challenge_flags & negotiate_flags_) |
NegotiateFlags::kExtendedSessionSecurity;
// Calculate all the payload lengths and offsets.
bool is_unicode = (authenticate_flags & NegotiateFlags::kUnicode) ==
NegotiateFlags::kUnicode;
SecurityBuffer lm_info;
SecurityBuffer ntlm_info;
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);
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);
DCHECK(writer_result);
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.
#if defined(STARBOARD)
writer_result = WriteResponsePayloadsV2(
&authenticate_writer,
base::span<const uint8_t, kResponseLenV1>(lm_response, kResponseLenV1),
v2_proof,
base::span<const uint8_t>(v2_proof_input.data(), v2_proof_input.size()),
base::span<const uint8_t>(updated_target_info.data(),
updated_target_info.size()));
#else
writer_result =
WriteResponsePayloadsV2(&authenticate_writer, lm_response, v2_proof,
v2_proof_input, updated_target_info);
#endif
} 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);
}
DCHECK(writer_result);
DCHECK_EQ(authenticate_writer.GetCursor(), domain_info.offset);
writer_result = WriteStringPayloads(&authenticate_writer, is_unicode, domain,
username, hostname);
DCHECK(writer_result);
DCHECK(authenticate_writer.IsEndOfBuffer());
DCHECK_EQ(authenticate_message_len, authenticate_writer.GetLength());
std::vector<uint8_t> 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);
base::span<uint8_t, kMicLenV2> mic(
const_cast<uint8_t*>(auth_msg.data()) + kMicOffsetV2, kMicLenV2);
#if defined(STARBOARD)
GenerateMicV2(v2_session_key,
base::span<const uint8_t>(negotiate_message_.data(),
negotiate_message_.size()),
server_challenge_message,
base::span<const uint8_t>(auth_msg.data(), auth_msg.size()),
mic);
#else
GenerateMicV2(v2_session_key, negotiate_message_, server_challenge_message,
auth_msg, mic);
#endif
}
return auth_msg;
}
void NtlmClient::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 {
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);
upto += ntlm_info->length;
domain_info->offset = upto;
domain_info->length = GetStringPayloadLength(domain, is_unicode);
upto += domain_info->length;
username_info->offset = upto;
username_info->length = GetStringPayloadLength(username, is_unicode);
upto += username_info->length;
hostname_info->offset = upto;
hostname_info->length = GetStringPayloadLength(hostname, is_unicode);
upto += hostname_info->length;
*authenticate_message_len = upto;
}
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;
}
return kResponseLenV1;
}
} // namespace ntlm
} // namespace net