From e6430e577f105ad8813c92e75c54660c4985026e Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Mon, 18 Sep 2017 14:34:04 +0200 Subject: BASELINE: Update Chromium to 61.0.3163.99 Change-Id: I8452f34574d88ca2b27af9bd56fc9ff3f16b1367 Reviewed-by: Alexandru Croitor --- chromium/net/ntlm/OWNERS | 6 + chromium/net/ntlm/des.cc | 88 ++++++ chromium/net/ntlm/des.h | 31 ++ chromium/net/ntlm/des_unittest.cc | 46 +++ chromium/net/ntlm/md4.cc | 185 ++++++++++++ chromium/net/ntlm/md4.h | 74 +++++ chromium/net/ntlm/ntlm.cc | 125 ++++++++ chromium/net/ntlm/ntlm.h | 131 ++++++++ chromium/net/ntlm/ntlm_buffer_reader.cc | 179 +++++++++++ chromium/net/ntlm/ntlm_buffer_reader.h | 191 ++++++++++++ chromium/net/ntlm/ntlm_buffer_reader_unittest.cc | 367 +++++++++++++++++++++++ chromium/net/ntlm/ntlm_buffer_writer.cc | 152 ++++++++++ chromium/net/ntlm/ntlm_buffer_writer.h | 177 +++++++++++ chromium/net/ntlm/ntlm_buffer_writer_unittest.cc | 235 +++++++++++++++ chromium/net/ntlm/ntlm_constants.h | 91 ++++++ chromium/net/ntlm/ntlm_test_data.h | 81 +++++ chromium/net/ntlm/ntlm_unittest.cc | 120 ++++++++ 17 files changed, 2279 insertions(+) create mode 100644 chromium/net/ntlm/OWNERS create mode 100644 chromium/net/ntlm/des.cc create mode 100644 chromium/net/ntlm/des.h create mode 100644 chromium/net/ntlm/des_unittest.cc create mode 100644 chromium/net/ntlm/md4.cc create mode 100644 chromium/net/ntlm/md4.h create mode 100644 chromium/net/ntlm/ntlm.cc create mode 100644 chromium/net/ntlm/ntlm.h create mode 100644 chromium/net/ntlm/ntlm_buffer_reader.cc create mode 100644 chromium/net/ntlm/ntlm_buffer_reader.h create mode 100644 chromium/net/ntlm/ntlm_buffer_reader_unittest.cc create mode 100644 chromium/net/ntlm/ntlm_buffer_writer.cc create mode 100644 chromium/net/ntlm/ntlm_buffer_writer.h create mode 100644 chromium/net/ntlm/ntlm_buffer_writer_unittest.cc create mode 100644 chromium/net/ntlm/ntlm_constants.h create mode 100644 chromium/net/ntlm/ntlm_test_data.h create mode 100644 chromium/net/ntlm/ntlm_unittest.cc (limited to 'chromium/net/ntlm') diff --git a/chromium/net/ntlm/OWNERS b/chromium/net/ntlm/OWNERS new file mode 100644 index 00000000000..156ce37c4df --- /dev/null +++ b/chromium/net/ntlm/OWNERS @@ -0,0 +1,6 @@ +asanka@chromium.org +rsleevi@chromium.org +zentaro@chromium.org + +# COMPONENT: Internals>Network>Auth +# TEAM: net-dev@chromium.org diff --git a/chromium/net/ntlm/des.cc b/chromium/net/ntlm/des.cc new file mode 100644 index 00000000000..1967f66c8dc --- /dev/null +++ b/chromium/net/ntlm/des.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2011 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/des.h" + +#include "base/logging.h" +#include "crypto/openssl_util.h" +#include "third_party/boringssl/src/include/openssl/des.h" + +// The iOS version of DESEncrypt is our own code. +// DESSetKeyParity and DESMakeKey are based on +// mozilla/security/manager/ssl/src/nsNTLMAuthModule.cpp, CVS rev. 1.14. +/* clang-format off */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is IBM Corporation. + * Portions created by IBM Corporation are Copyright (C) 2003 + * IBM Corporation. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Set odd parity bit (in least significant bit position). +static uint8_t DESSetKeyParity(uint8_t x) { + if ((((x >> 7) ^ (x >> 6) ^ (x >> 5) ^ + (x >> 4) ^ (x >> 3) ^ (x >> 2) ^ + (x >> 1)) & 0x01) == 0) { + x |= 0x01; + } else { + x &= 0xfe; + } + return x; +} + +namespace net { + +void DESMakeKey(const uint8_t* raw, uint8_t* key) { + key[0] = DESSetKeyParity(raw[0]); + key[1] = DESSetKeyParity((raw[0] << 7) | (raw[1] >> 1)); + key[2] = DESSetKeyParity((raw[1] << 6) | (raw[2] >> 2)); + key[3] = DESSetKeyParity((raw[2] << 5) | (raw[3] >> 3)); + key[4] = DESSetKeyParity((raw[3] << 4) | (raw[4] >> 4)); + key[5] = DESSetKeyParity((raw[4] << 3) | (raw[5] >> 5)); + key[6] = DESSetKeyParity((raw[5] << 2) | (raw[6] >> 6)); + key[7] = DESSetKeyParity((raw[6] << 1)); +} + +void DESEncrypt(const uint8_t* key, const uint8_t* src, uint8_t* hash) { + crypto::EnsureOpenSSLInit(); + + DES_key_schedule ks; + DES_set_key( + reinterpret_cast(key), &ks); + + DES_ecb_encrypt(reinterpret_cast(src), + reinterpret_cast(hash), &ks, DES_ENCRYPT); +} + +} // namespace net diff --git a/chromium/net/ntlm/des.h b/chromium/net/ntlm/des.h new file mode 100644 index 00000000000..8540d62803b --- /dev/null +++ b/chromium/net/ntlm/des.h @@ -0,0 +1,31 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_HTTP_DES_H_ +#define NET_HTTP_DES_H_ + +#include + +#include "net/base/net_export.h" + +namespace net { + +//----------------------------------------------------------------------------- +// DES support code for NTLM authentication. +// +// TODO(wtc): Turn this into a C++ API and move it to the base module. + +// Build a 64-bit DES key from a 56-bit raw key. +NET_EXPORT_PRIVATE void DESMakeKey(const uint8_t* raw, uint8_t* key); + +// Run the DES encryption algorithm in ECB mode on one block (8 bytes) of +// data. |key| is a DES key (8 bytes), |src| is the input plaintext (8 +// bytes), and |hash| is an 8-byte buffer receiving the output ciphertext. +NET_EXPORT_PRIVATE void DESEncrypt(const uint8_t* key, + const uint8_t* src, + uint8_t* hash); + +} // namespace net + +#endif // NET_HTTP_DES_H_ diff --git a/chromium/net/ntlm/des_unittest.cc b/chromium/net/ntlm/des_unittest.cc new file mode 100644 index 00000000000..d1f9ae5afe7 --- /dev/null +++ b/chromium/net/ntlm/des_unittest.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2012 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 + +#include "net/ntlm/des.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +// This test vector comes from the NSS FIPS power-up self-test. +TEST(DESTest, KnownAnswerTest1) { + // DES known key (56-bits). + static const uint8_t des_known_key[] = "ANSI DES"; + + // DES known plaintext (64-bits). + static const uint8_t des_ecb_known_plaintext[] = "Netscape"; + + // DES known ciphertext (64-bits). + static const uint8_t des_ecb_known_ciphertext[] = {0x26, 0x14, 0xe9, 0xc3, + 0x28, 0x80, 0x50, 0xb0}; + + uint8_t ciphertext[8]; + memset(ciphertext, 0xaf, sizeof(ciphertext)); + + DESEncrypt(des_known_key, des_ecb_known_plaintext, ciphertext); + EXPECT_EQ(0, memcmp(ciphertext, des_ecb_known_ciphertext, 8)); +} + +// This test vector comes from NIST Special Publication 800-17, Modes of +// Operation Validation System (MOVS): Requirements and Procedures, Appendix +// A, page 124. +TEST(DESTest, KnownAnswerTest2) { + static const uint8_t key[] = {0x10, 0x31, 0x6e, 0x02, 0x8c, 0x8f, 0x3b, 0x4a}; + static const uint8_t plaintext[] = {0, 0, 0, 0, 0, 0, 0, 0}; + static const uint8_t known_ciphertext[] = {0x82, 0xdc, 0xba, 0xfb, + 0xde, 0xab, 0x66, 0x02}; + uint8_t ciphertext[8]; + memset(ciphertext, 0xaf, sizeof(ciphertext)); + + DESEncrypt(key, plaintext, ciphertext); + EXPECT_EQ(0, memcmp(ciphertext, known_ciphertext, 8)); +} + +} // namespace net diff --git a/chromium/net/ntlm/md4.cc b/chromium/net/ntlm/md4.cc new file mode 100644 index 00000000000..cd18e9fd88e --- /dev/null +++ b/chromium/net/ntlm/md4.cc @@ -0,0 +1,185 @@ +// This is mozilla/security/manager/ssl/src/md4.c, CVS rev. 1.1, with trivial +// changes to port it to our source tree. +// +// WARNING: MD4 is cryptographically weak. Do not use MD4 except in NTLM +// authentication. + +/* clang-format off */ +/* vim:set ts=2 sw=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is IBM Corporation. + * Portions created by IBM Corporation are Copyright (C) 2003 + * IBM Corporation. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * "clean room" MD4 implementation (see RFC 1320) + */ + +#include "net/ntlm/md4.h" + +#include + +typedef uint32_t Uint32; +typedef uint8_t Uint8; + +/* the "conditional" function */ +#define F(x,y,z) (((x) & (y)) | (~(x) & (z))) + +/* the "majority" function */ +#define G(x,y,z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) + +/* the "parity" function */ +#define H(x,y,z) ((x) ^ (y) ^ (z)) + +/* rotate n-bits to the left */ +#define ROTL(x,n) (((x) << (n)) | ((x) >> (0x20 - n))) + +/* round 1: [abcd k s]: a = (a + F(b,c,d) + X[k]) <<< s */ +#define RD1(a,b,c,d,k,s) a += F(b,c,d) + X[k]; a = ROTL(a,s) + +/* round 2: [abcd k s]: a = (a + G(b,c,d) + X[k] + MAGIC) <<< s */ +#define RD2(a,b,c,d,k,s) a += G(b,c,d) + X[k] + 0x5A827999; a = ROTL(a,s) + +/* round 3: [abcd k s]: a = (a + H(b,c,d) + X[k] + MAGIC) <<< s */ +#define RD3(a,b,c,d,k,s) a += H(b,c,d) + X[k] + 0x6ED9EBA1; a = ROTL(a,s) + +/* converts from word array to byte array, len is number of bytes */ +static void w2b(Uint8 *out, const Uint32 *in, Uint32 len) +{ + Uint8 *bp; const Uint32 *wp, *wpend; + + bp = out; + wp = in; + wpend = wp + (len >> 2); + + for (; wp != wpend; ++wp, bp += 4) + { + bp[0] = (Uint8) ((*wp ) & 0xFF); + bp[1] = (Uint8) ((*wp >> 8) & 0xFF); + bp[2] = (Uint8) ((*wp >> 16) & 0xFF); + bp[3] = (Uint8) ((*wp >> 24) & 0xFF); + } +} + +/* converts from byte array to word array, len is number of bytes */ +static void b2w(Uint32 *out, const Uint8 *in, Uint32 len) +{ + Uint32 *wp; const Uint8 *bp, *bpend; + + wp = out; + bp = in; + bpend = in + len; + + for (; bp != bpend; bp += 4, ++wp) + { + *wp = (Uint32) (bp[0] ) | + (Uint32) (bp[1] << 8) | + (Uint32) (bp[2] << 16) | + (Uint32) (bp[3] << 24); + } +} + +/* update state: data is 64 bytes in length */ +static void md4step(Uint32 state[4], const Uint8 *data) +{ + Uint32 A, B, C, D, X[16]; + + b2w(X, data, 64); + + A = state[0]; + B = state[1]; + C = state[2]; + D = state[3]; + + RD1(A,B,C,D, 0,3); RD1(D,A,B,C, 1,7); RD1(C,D,A,B, 2,11); RD1(B,C,D,A, 3,19); + RD1(A,B,C,D, 4,3); RD1(D,A,B,C, 5,7); RD1(C,D,A,B, 6,11); RD1(B,C,D,A, 7,19); + RD1(A,B,C,D, 8,3); RD1(D,A,B,C, 9,7); RD1(C,D,A,B,10,11); RD1(B,C,D,A,11,19); + RD1(A,B,C,D,12,3); RD1(D,A,B,C,13,7); RD1(C,D,A,B,14,11); RD1(B,C,D,A,15,19); + + RD2(A,B,C,D, 0,3); RD2(D,A,B,C, 4,5); RD2(C,D,A,B, 8, 9); RD2(B,C,D,A,12,13); + RD2(A,B,C,D, 1,3); RD2(D,A,B,C, 5,5); RD2(C,D,A,B, 9, 9); RD2(B,C,D,A,13,13); + RD2(A,B,C,D, 2,3); RD2(D,A,B,C, 6,5); RD2(C,D,A,B,10, 9); RD2(B,C,D,A,14,13); + RD2(A,B,C,D, 3,3); RD2(D,A,B,C, 7,5); RD2(C,D,A,B,11, 9); RD2(B,C,D,A,15,13); + + RD3(A,B,C,D, 0,3); RD3(D,A,B,C, 8,9); RD3(C,D,A,B, 4,11); RD3(B,C,D,A,12,15); + RD3(A,B,C,D, 2,3); RD3(D,A,B,C,10,9); RD3(C,D,A,B, 6,11); RD3(B,C,D,A,14,15); + RD3(A,B,C,D, 1,3); RD3(D,A,B,C, 9,9); RD3(C,D,A,B, 5,11); RD3(B,C,D,A,13,15); + RD3(A,B,C,D, 3,3); RD3(D,A,B,C,11,9); RD3(C,D,A,B, 7,11); RD3(B,C,D,A,15,15); + + state[0] += A; + state[1] += B; + state[2] += C; + state[3] += D; +} + +namespace net { +namespace weak_crypto { + +void MD4Sum(const Uint8 *input, Uint32 inputLen, Uint8 *result) +{ + Uint8 final[128]; + Uint32 i, n, m, state[4]; + + /* magic initial states */ + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; + + /* compute number of complete 64-byte segments contained in input */ + m = inputLen >> 6; + + /* digest first m segments */ + for (i=0; i= 56 ? 120 : 56), &inputLen, 4); + + md4step(state, final); + if (n >= 56) + md4step(state, final + 64); + + /* copy state to result */ + w2b(result, state, 16); +} + +} // namespace weak_crypto +} // namespace net diff --git a/chromium/net/ntlm/md4.h b/chromium/net/ntlm/md4.h new file mode 100644 index 00000000000..4d37a643533 --- /dev/null +++ b/chromium/net/ntlm/md4.h @@ -0,0 +1,74 @@ +// This is mozilla/security/manager/ssl/src/md4.h, CVS rev. 1.1, with trivial +// changes to port it to our source tree. +// +// WARNING: MD4 is cryptographically weak. Do not use MD4 except in NTLM +// authentication. + +/* vim:set ts=2 sw=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is IBM Corporation. + * Portions created by IBM Corporation are Copyright (C) 2003 + * IBM Corporation. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef NET_HTTP_MD4_H_ +#define NET_HTTP_MD4_H_ + +#include + +namespace net { +namespace weak_crypto { + +/** + * MD4Sum - computes the MD4 sum over the input buffer per RFC 1320 + * + * @param input + * buffer containing input data + * @param inputLen + * length of input buffer (number of bytes) + * @param result + * 16-byte buffer that will contain the MD4 sum upon return + * + * NOTE: MD4 is superceded by MD5. do not use MD4 unless required by the + * protocol you are implementing (e.g., NTLM requires MD4). + * + * NOTE: this interface is designed for relatively small buffers. A streaming + * interface would make more sense if that were a requirement. Currently, this + * is good enough for the applications we care about. + */ +void MD4Sum(const uint8_t* input, uint32_t inputLen, uint8_t* result); + +} // namespace weak_crypto +} // namespace net + +#endif // NET_HTTP_MD4_H_ diff --git a/chromium/net/ntlm/ntlm.cc b/chromium/net/ntlm/ntlm.cc new file mode 100644 index 00000000000..14cc4bcdf64 --- /dev/null +++ b/chromium/net/ntlm/ntlm.cc @@ -0,0 +1,125 @@ +// 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.h" + +#include + +#include "base/logging.h" +#include "base/md5.h" +#include "net/ntlm/des.h" +#include "net/ntlm/md4.h" +#include "net/ntlm/ntlm_buffer_writer.h" + +namespace net { +namespace ntlm { + +void GenerateNtlmHashV1(const base::string16& password, uint8_t* hash) { + size_t length = password.length() * 2; + NtlmBufferWriter writer(length); + + // The writer will handle the big endian case if necessary. + bool result = writer.WriteUtf16String(password); + DCHECK(result); + + weak_crypto::MD4Sum( + reinterpret_cast(writer.GetBuffer().data()), length, + hash); +} + +void GenerateResponseDesl(const uint8_t* hash, + const uint8_t* challenge, + uint8_t* response) { + // See DESL(K, D) function in [MS-NLMP] Section 6 + uint8_t key1[8]; + uint8_t key2[8]; + uint8_t key3[8]; + + // The last 2 bytes of the hash are zero padded (5 zeros) as the + // input to generate key3. + uint8_t padded_hash[7]; + padded_hash[0] = hash[14]; + padded_hash[1] = hash[15]; + memset(padded_hash + 2, 0, 5); + + DESMakeKey(hash, key1); + DESMakeKey(hash + 7, key2); + DESMakeKey(padded_hash, key3); + + DESEncrypt(key1, challenge, response); + DESEncrypt(key2, challenge, response + 8); + DESEncrypt(key3, challenge, response + 16); +} + +void GenerateNtlmResponseV1(const base::string16& password, + const uint8_t* challenge, + uint8_t* ntlm_response) { + uint8_t ntlm_hash[kNtlmHashLen]; + GenerateNtlmHashV1(password, ntlm_hash); + GenerateResponseDesl(ntlm_hash, challenge, ntlm_response); +} + +void GenerateResponsesV1(const base::string16& password, + const uint8_t* server_challenge, + uint8_t* lm_response, + uint8_t* ntlm_response) { + GenerateNtlmResponseV1(password, server_challenge, ntlm_response); + + // In NTLM v1 (with LMv1 disabled), the lm_response and ntlm_response are the + // same. So just copy the ntlm_response into the lm_response. + memcpy(lm_response, ntlm_response, kResponseLenV1); +} + +void GenerateLMResponseV1WithSessionSecurity(const uint8_t* client_challenge, + uint8_t* lm_response) { + // In NTLM v1 with Session Security (aka NTLM2) the lm_response is 8 bytes of + // client challenge and 16 bytes of zeros. (See 3.3.1) + memcpy(lm_response, client_challenge, kChallengeLen); + memset(lm_response + kChallengeLen, 0, kResponseLenV1 - kChallengeLen); +} + +void GenerateSessionHashV1WithSessionSecurity(const uint8_t* server_challenge, + const uint8_t* client_challenge, + base::MD5Digest* session_hash) { + base::MD5Context ctx; + base::MD5Init(&ctx); + base::MD5Update( + &ctx, base::StringPiece(reinterpret_cast(server_challenge), + kChallengeLen)); + base::MD5Update( + &ctx, base::StringPiece(reinterpret_cast(client_challenge), + kChallengeLen)); + + base::MD5Final(session_hash, &ctx); +} + +void GenerateNtlmResponseV1WithSessionSecurity(const base::string16& password, + const uint8_t* server_challenge, + const uint8_t* client_challenge, + uint8_t* ntlm_response) { + // Generate the NTLMv1 Hash. + uint8_t ntlm_hash[kNtlmHashLen]; + GenerateNtlmHashV1(password, ntlm_hash); + + // Generate the NTLMv1 Session Hash. + base::MD5Digest session_hash; + GenerateSessionHashV1WithSessionSecurity(server_challenge, client_challenge, + &session_hash); + + // Only the first 8 bytes of |session_hash.a| are actually used. + GenerateResponseDesl(ntlm_hash, session_hash.a, ntlm_response); +} + +void GenerateResponsesV1WithSessionSecurity(const base::string16& password, + const uint8_t* server_challenge, + const uint8_t* client_challenge, + uint8_t* lm_response, + uint8_t* ntlm_response) { + GenerateLMResponseV1WithSessionSecurity(client_challenge, lm_response); + GenerateNtlmResponseV1WithSessionSecurity(password, server_challenge, + client_challenge, ntlm_response); +} + +} // namespace ntlm +} // namespace net diff --git a/chromium/net/ntlm/ntlm.h b/chromium/net/ntlm/ntlm.h new file mode 100644 index 00000000000..1d98611e66d --- /dev/null +++ b/chromium/net/ntlm/ntlm.h @@ -0,0 +1,131 @@ +// 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. + +// Based on [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol +// 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 + +#ifndef NET_BASE_NTLM_H_ +#define NET_BASE_NTLM_H_ + +#include +#include + +#include + +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/ntlm/ntlm_constants.h" + +namespace base { +struct MD5Digest; +} + +namespace net { +namespace ntlm { + +// Generates the NTLMv1 Hash and writes the |kNtlmHashLen| byte result to +// |hash|. Defined by NTOWFv1() in [MS-NLMP] Section 3.3.1. +NET_EXPORT_PRIVATE void GenerateNtlmHashV1(const base::string16& password, + uint8_t* hash); + +// Generates the |kResponseLenV1| byte NTLMv1 response field according to the +// DESL(K, V) function in [MS-NLMP] Section 6. +// +// |hash| must contain |kNtlmHashLen| bytes. +// |challenge| must contain |kChallengeLen| bytes. +// |response| must contain |kResponseLenV1| bytes. +NET_EXPORT_PRIVATE void GenerateResponseDesl(const uint8_t* hash, + const uint8_t* challenge, + uint8_t* response); + +// Generates the NTLM Response field for NTLMv1 without extended session +// security. Defined by ComputeResponse() in [MS-NLMP] Section 3.3.1 for the +// case where NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is not set. +// +// |server_challenge| must contain |kChallengeLen| bytes. +// |ntlm_response| must contain |kResponseLenV1| bytes. +NET_EXPORT_PRIVATE void GenerateNtlmResponseV1(const base::string16& password, + const uint8_t* server_challenge, + uint8_t* ntlm_response); + +// Generates both the LM Response and NTLM Response fields for NTLMv1 based +// on the users password and the servers challenge. Both the LM and NTLM +// Response are the result of |GenerateNtlmResponseV1|. +// +// NOTE: This should not be used. The default flags always include session +// security. Session security can however be disabled in NTLMv1 by omitting +// NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY from the flag set used to +// initialize |NtlmClient|. +// +// The default flags include this flag and the client will not be +// downgraded by the server. +// +// |server_challenge| must contain |kChallengeLen| bytes. +// |lm_response| must contain |kResponseLenV1| bytes. +// |ntlm_response| must contain |kResponseLenV1| bytes. +NET_EXPORT_PRIVATE void GenerateResponsesV1(const base::string16& password, + const uint8_t* server_challenge, + uint8_t* lm_response, + uint8_t* ntlm_response); + +// The LM Response in V1 with extended session security is 8 bytes of the +// |client_challenge| then 16 bytes of zero. This is the value +// LmChallengeResponse in ComputeResponse() when +// NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is set. See [MS-NLMP] Section +// 3.3.1. +// +// |lm_response| must contain |kResponseLenV1| bytes. +NET_EXPORT_PRIVATE void GenerateLMResponseV1WithSessionSecurity( + const uint8_t* client_challenge, + uint8_t* lm_response); + +// The |session_hash| is MD5(CONCAT(server_challenge, client_challenge)). +// It is used instead of just |server_challenge| in NTLMv1 when +// NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is set. See [MS-NLMP] Section +// 3.3.1. +// +// |server_challenge| must contain |kChallengeLen| bytes. +// |client_challenge| must contain |kChallengeLen| bytes. +NET_EXPORT_PRIVATE void GenerateSessionHashV1WithSessionSecurity( + const uint8_t* server_challenge, + const uint8_t* client_challenge, + base::MD5Digest* session_hash); + +// Generates the NTLM Response for NTLMv1 with session security. +// Defined by ComputeResponse() in [MS-NLMP] Section 3.3.1 for the +// case where NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is set. +// +// |server_challenge| must contain |kChallengeLen| bytes. +// |client_challenge| must contain |kChallengeLen| bytes. +// |ntlm_response| must contain |kResponseLenV1| bytes. +NET_EXPORT_PRIVATE void GenerateNtlmResponseV1WithSessionSecurity( + const base::string16& password, + const uint8_t* server_challenge, + const uint8_t* client_challenge, + uint8_t* ntlm_response); + +// Generates the responses for V1 with extended session security. +// This is also known as NTLM2 (which is not the same as NTLMv2). +// |lm_response| is the result of |GenerateLMResponseV1WithSessionSecurity| and +// |ntlm_response| is the result of |GenerateNtlmResponseV1WithSessionSecurity|. +// See [MS-NLMP] Section 3.3.1. +// +// |server_challenge| must contain |kChallengeLen| bytes. +// |client_challenge| must contain |kChallengeLen| bytes. +// |ntlm_response| must contain |kResponseLenV1| bytes. +NET_EXPORT_PRIVATE void GenerateResponsesV1WithSessionSecurity( + const base::string16& password, + const uint8_t* server_challenge, + const uint8_t* client_challenge, + uint8_t* lm_response, + uint8_t* ntlm_response); + +} // namespace ntlm +} // namespace net + +#endif // NET_BASE_NTLM_H_ diff --git a/chromium/net/ntlm/ntlm_buffer_reader.cc b/chromium/net/ntlm/ntlm_buffer_reader.cc new file mode 100644 index 00000000000..95ae2e6ee43 --- /dev/null +++ b/chromium/net/ntlm/ntlm_buffer_reader.cc @@ -0,0 +1,179 @@ +// 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_buffer_reader.h" + +#include + +#include "base/logging.h" + +namespace net { +namespace ntlm { + +NtlmBufferReader::NtlmBufferReader(base::StringPiece buffer) + : buffer_(buffer), cursor_(0) { + DCHECK(buffer.data()); +} + +NtlmBufferReader::NtlmBufferReader(const uint8_t* ptr, size_t len) + : NtlmBufferReader( + base::StringPiece(reinterpret_cast(ptr), len)) {} + +NtlmBufferReader::~NtlmBufferReader() {} + +bool NtlmBufferReader::CanRead(size_t len) const { + return CanReadFrom(GetCursor(), len); +} + +bool NtlmBufferReader::CanReadFrom(size_t offset, size_t len) const { + if (len == 0) + return true; + + return (len <= GetLength() && offset <= GetLength() - len); +} + +bool NtlmBufferReader::ReadUInt16(uint16_t* value) { + return ReadUInt(value); +} + +bool NtlmBufferReader::ReadUInt32(uint32_t* value) { + return ReadUInt(value); +} + +bool NtlmBufferReader::ReadUInt64(uint64_t* value) { + return ReadUInt(value); +} + +bool NtlmBufferReader::ReadFlags(NegotiateFlags* flags) { + uint32_t raw; + if (!ReadUInt32(&raw)) + return false; + + *flags = static_cast(raw); + return true; +} + +bool NtlmBufferReader::ReadBytes(uint8_t* buffer, size_t len) { + if (!CanRead(len)) + return false; + + memcpy(reinterpret_cast(buffer), + reinterpret_cast(GetBufferAtCursor()), len); + + AdvanceCursor(len); + return true; +} + +bool NtlmBufferReader::ReadBytesFrom(const SecurityBuffer& sec_buf, + uint8_t* buffer) { + if (!CanReadFrom(sec_buf)) + return false; + + memcpy(reinterpret_cast(buffer), + reinterpret_cast(GetBufferPtr() + sec_buf.offset), + sec_buf.length); + + return true; +} + +bool NtlmBufferReader::ReadSecurityBuffer(SecurityBuffer* sec_buf) { + return ReadUInt16(&sec_buf->length) && SkipBytes(sizeof(uint16_t)) && + ReadUInt32(&sec_buf->offset); +} + +bool NtlmBufferReader::ReadMessageType(MessageType* message_type) { + uint32_t raw_message_type; + if (!ReadUInt32(&raw_message_type)) + return false; + + *message_type = static_cast(raw_message_type); + + if (*message_type != MessageType::kNegotiate && + *message_type != MessageType::kChallenge && + *message_type != MessageType::kAuthenticate) + return false; + + return true; +} + +bool NtlmBufferReader::SkipSecurityBuffer() { + return SkipBytes(kSecurityBufferLen); +} + +bool NtlmBufferReader::SkipSecurityBufferWithValidation() { + SecurityBuffer sec_buf; + return ReadSecurityBuffer(&sec_buf) && CanReadFrom(sec_buf); +} + +bool NtlmBufferReader::SkipBytes(size_t count) { + if (!CanRead(count)) + return false; + + AdvanceCursor(count); + return true; +} + +bool NtlmBufferReader::MatchSignature() { + if (!CanRead(kSignatureLen)) + return false; + + if (memcmp(kSignature, GetBufferAtCursor(), kSignatureLen) != 0) + return false; + + AdvanceCursor(kSignatureLen); + return true; +} + +bool NtlmBufferReader::MatchMessageType(MessageType message_type) { + MessageType actual_message_type; + return ReadMessageType(&actual_message_type) && + (actual_message_type == message_type); +} + +bool NtlmBufferReader::MatchMessageHeader(MessageType message_type) { + return MatchSignature() && MatchMessageType(message_type); +} + +bool NtlmBufferReader::MatchZeros(size_t count) { + if (!CanRead(count)) + return false; + + for (size_t i = 0; i < count; i++) { + if (GetBufferAtCursor()[i] != 0) + return false; + } + + AdvanceCursor(count); + return true; +} + +bool NtlmBufferReader::MatchEmptySecurityBuffer() { + SecurityBuffer sec_buf; + return ReadSecurityBuffer(&sec_buf) && (sec_buf.offset <= GetLength()) && + (sec_buf.length == 0); +} + +template +bool NtlmBufferReader::ReadUInt(T* value) { + size_t int_size = sizeof(T); + if (!CanRead(int_size)) + return false; + + *value = 0; + for (size_t i = 0; i < int_size; i++) { + *value += static_cast(GetByteAtCursor()) << (i * 8); + AdvanceCursor(1); + } + + return true; +} + +void NtlmBufferReader::SetCursor(size_t cursor) { + DCHECK_LE(cursor, GetLength()); + + cursor_ = cursor; +} + +} // namespace ntlm +} // namespace net diff --git a/chromium/net/ntlm/ntlm_buffer_reader.h b/chromium/net/ntlm/ntlm_buffer_reader.h new file mode 100644 index 00000000000..d31429f4b71 --- /dev/null +++ b/chromium/net/ntlm/ntlm_buffer_reader.h @@ -0,0 +1,191 @@ +// 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. + +#ifndef NET_BASE_NTLM_BUFFER_READER_H_ +#define NET_BASE_NTLM_BUFFER_READER_H_ + +#include +#include + +#include + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/ntlm/ntlm_constants.h" + +namespace net { +namespace ntlm { + +// Supports various bounds-checked low level buffer operations required by an +// NTLM implementation. +// +// The class supports the sequential read of a provided buffer. All reads +// perform bounds checking to ensure enough space is remaining in the buffer. +// +// Read* methods read from the buffer at the current cursor position and +// perform any necessary type conversion and provide the data in out params. +// After a successful read the cursor position is advanced past the read +// field. +// +// Failed Read*s or Match*s leave the cursor in an undefined position and the +// buffer MUST be discarded with no further operations performed. +// +// Read*Payload methods first reads a security buffer (see +// |ReadSecurityBuffer|), then reads the requested payload from the offset +// and length stated in the security buffer. +// +// If the length and offset in the security buffer would cause a read outside +// the message buffer the payload will not be read and the function will +// return false. +// +// Based on [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol +// 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 +class NET_EXPORT_PRIVATE NtlmBufferReader { + public: + explicit NtlmBufferReader(base::StringPiece buffer); + + // This class does not take ownership of |ptr|, so the caller must ensure + // that the buffer outlives the |NtlmBufferReader|. + NtlmBufferReader(const uint8_t* ptr, size_t len); + ~NtlmBufferReader(); + + size_t GetLength() const { return buffer_.length(); } + size_t GetCursor() const { return cursor_; } + bool IsEndOfBuffer() const { return cursor_ >= GetLength(); } + + // Returns true if there are |len| more bytes between the current cursor + // position and the end of the buffer. + bool CanRead(size_t len) const; + + // Returns true if there are |len| more bytes between |offset| and the end + // of the buffer. The cursor position is not used or modified. + bool CanReadFrom(size_t offset, size_t len) const; + + // Returns true if it would be possible to read the payload described by the + // security buffer. + bool CanReadFrom(SecurityBuffer sec_buf) const { + return CanReadFrom(sec_buf.offset, sec_buf.length); + } + + // Reads a 16 bit value (little endian) as a uint16_t. If there are not 16 + // more bits available, it returns false. + bool ReadUInt16(uint16_t* value) WARN_UNUSED_RESULT; + + // Reads a 32 bit value (little endian) as a uint32_t. If there are not 32 + // more bits available, it returns false. + bool ReadUInt32(uint32_t* value) WARN_UNUSED_RESULT; + + // Reads a 64 bit value (little endian) as a uint64_t. If there are not 64 + // more bits available, it returns false. + bool ReadUInt64(uint64_t* value) WARN_UNUSED_RESULT; + + // Calls |ReadUInt32| and returns it cast as |NegotiateFlags|. No + // validation of the value takes place. + bool ReadFlags(NegotiateFlags* flags) WARN_UNUSED_RESULT; + + // Reads |len| bytes and copies them into |buffer|. + bool ReadBytes(uint8_t* buffer, size_t len) WARN_UNUSED_RESULT; + + // Reads |sec_buf.length| bytes from offset |sec_buf.offset| and copies them + // into |buffer|. If the security buffer specifies a payload outside the + // buffer, then the call fails. Unlike the other Read* methods, this does + // not move the cursor. + bool ReadBytesFrom(const SecurityBuffer& sec_buf, + uint8_t* buffer) WARN_UNUSED_RESULT; + + // A security buffer is an 8 byte structure that defines the offset and + // length of a payload (string, struct or byte array) that appears after the + // fixed part of the message. + // + // The structure is (little endian fields): + // uint16 - |length| Length of payload + // uint16 - Allocation (this is always ignored and not returned) + // uint32 - |offset| Offset from start of message + bool ReadSecurityBuffer(SecurityBuffer* sec_buf) WARN_UNUSED_RESULT; + + // There are 3 message types Negotiate (sent by client), Challenge (sent by + // server), and Authenticate (sent by client). + // + // This reads the message type from the header and will return false if the + // value is invalid. + bool ReadMessageType(MessageType* message_type) 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. + bool SkipSecurityBuffer() WARN_UNUSED_RESULT; + + // Skips over the security buffer without returning the values, but fails if + // the values would cause a read outside the buffer if the payload was + // actually read. + bool SkipSecurityBufferWithValidation() WARN_UNUSED_RESULT; + + // Skips over |count| bytes in the buffer. Returns false if there are not + // |count| bytes left in the buffer. + bool SkipBytes(size_t count) WARN_UNUSED_RESULT; + + // Reads and returns true if the next 8 bytes matches the signature in an + // NTLM message "NTLMSSP\0". The cursor advances if the the signature + // is matched. + bool MatchSignature() WARN_UNUSED_RESULT; + + // Performs |ReadMessageType| and returns true if the value is + // |message_type|. If the read fails or the message type does not match, + // the buffer is invalid and MUST be discarded. + bool MatchMessageType(MessageType message_type) WARN_UNUSED_RESULT; + + // Performs |MatchSignature| then |MatchMessageType|. + bool MatchMessageHeader(MessageType message_type) WARN_UNUSED_RESULT; + + // Performs |ReadBytes(count)| and returns true if the contents is all + // zero. + bool MatchZeros(size_t count) WARN_UNUSED_RESULT; + + // Reads the security buffer and returns true if the length is 0 and + // the offset is within the message. On failure, the buffer is invalid + // and MUST be discarded. + bool MatchEmptySecurityBuffer() WARN_UNUSED_RESULT; + + private: + // Reads |sizeof(T)| bytes of an integer type from a little-endian buffer. + template + bool ReadUInt(T* value); + + // Sets the cursor position. The caller should use |GetLength|, |CanRead|, + // or |CanReadFrom| to verify the bounds before calling this method. + void SetCursor(size_t cursor); + + // Advances the cursor by |count| bytes. The caller should use |GetLength|, + // |CanRead|, or |CanReadFrom| to verify the bounds before calling this + // method. + void AdvanceCursor(size_t count) { SetCursor(GetCursor() + count); } + + // Returns a constant pointer to the start of the buffer. + const uint8_t* GetBufferPtr() const { + return reinterpret_cast(buffer_.data()); + } + + // Returns a pointer to the underlying buffer at the current cursor + // position. + const uint8_t* GetBufferAtCursor() const { return GetBufferPtr() + cursor_; } + + // Returns the byte at the current cursor position. + uint8_t GetByteAtCursor() const { + DCHECK(!IsEndOfBuffer()); + return *(GetBufferAtCursor()); + } + + const base::StringPiece buffer_; + size_t cursor_; + + DISALLOW_COPY_AND_ASSIGN(NtlmBufferReader); +}; + +} // namespace ntlm +} // namespace net + +#endif // NET_BASE_NTLM_BUFFER_READER_H_ diff --git a/chromium/net/ntlm/ntlm_buffer_reader_unittest.cc b/chromium/net/ntlm/ntlm_buffer_reader_unittest.cc new file mode 100644 index 00000000000..592c5d31862 --- /dev/null +++ b/chromium/net/ntlm/ntlm_buffer_reader_unittest.cc @@ -0,0 +1,367 @@ +// 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_buffer_reader.h" + +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace ntlm { + +TEST(NtlmBufferReaderTest, Initialization) { + const uint8_t buf[1] = {0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_EQ(arraysize(buf), reader.GetLength()); + ASSERT_EQ(0u, reader.GetCursor()); + ASSERT_FALSE(reader.IsEndOfBuffer()); + ASSERT_TRUE(reader.CanRead(1)); + ASSERT_FALSE(reader.CanRead(2)); + ASSERT_TRUE(reader.CanReadFrom(0, 1)); + ASSERT_TRUE(reader.CanReadFrom(SecurityBuffer(0, 1))); + ASSERT_FALSE(reader.CanReadFrom(1, 1)); + ASSERT_FALSE(reader.CanReadFrom(SecurityBuffer(1, 1))); + ASSERT_FALSE(reader.CanReadFrom(0, 2)); + ASSERT_FALSE(reader.CanReadFrom(SecurityBuffer(0, 2))); + + // With length=0 the offset can be out of bounds. + ASSERT_TRUE(reader.CanReadFrom(99, 0)); + ASSERT_TRUE(reader.CanReadFrom(SecurityBuffer(99, 0))); +} + +TEST(NtlmBufferReaderTest, Read16) { + const uint8_t buf[2] = {0x22, 0x11}; + const uint16_t expected = 0x1122; + + NtlmBufferReader reader(buf, arraysize(buf)); + + uint16_t actual; + ASSERT_TRUE(reader.ReadUInt16(&actual)); + ASSERT_EQ(expected, actual); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.ReadUInt16(&actual)); +} + +TEST(NtlmBufferReaderTest, Read32) { + const uint8_t buf[4] = {0x44, 0x33, 0x22, 0x11}; + const uint32_t expected = 0x11223344; + + NtlmBufferReader reader(buf, arraysize(buf)); + + uint32_t actual; + ASSERT_TRUE(reader.ReadUInt32(&actual)); + ASSERT_EQ(expected, actual); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.ReadUInt32(&actual)); +} + +TEST(NtlmBufferReaderTest, Read64) { + const uint8_t buf[8] = {0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + const uint64_t expected = 0x1122334455667788; + + NtlmBufferReader reader(buf, arraysize(buf)); + + uint64_t actual; + ASSERT_TRUE(reader.ReadUInt64(&actual)); + ASSERT_EQ(expected, actual); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.ReadUInt64(&actual)); +} + +TEST(NtlmBufferReaderTest, ReadBytes) { + const uint8_t expected[8] = {0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + uint8_t actual[8]; + + NtlmBufferReader reader(expected, arraysize(expected)); + + ASSERT_TRUE(reader.ReadBytes(actual, arraysize(actual))); + ASSERT_EQ(0, memcmp(actual, expected, arraysize(actual))); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.ReadBytes(actual, 1)); +} + +TEST(NtlmBufferReaderTest, ReadSecurityBuffer) { + const uint8_t buf[8] = {0x22, 0x11, 0xFF, 0xEE, 0x88, 0x77, 0x66, 0x55}; + const uint16_t length = 0x1122; + const uint32_t offset = 0x55667788; + + NtlmBufferReader reader(buf, arraysize(buf)); + + SecurityBuffer sec_buf; + ASSERT_TRUE(reader.ReadSecurityBuffer(&sec_buf)); + ASSERT_EQ(length, sec_buf.length); + ASSERT_EQ(offset, sec_buf.offset); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.ReadSecurityBuffer(&sec_buf)); +} + +TEST(NtlmBufferReaderTest, ReadSecurityBufferPastEob) { + const uint8_t buf[7] = {0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + SecurityBuffer sec_buf; + ASSERT_FALSE(reader.ReadSecurityBuffer(&sec_buf)); +} + +TEST(NtlmBufferReaderTest, SkipSecurityBuffer) { + const uint8_t buf[kSecurityBufferLen] = {0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_TRUE(reader.SkipSecurityBuffer()); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.SkipSecurityBuffer()); +} + +TEST(NtlmBufferReaderTest, SkipSecurityBufferPastEob) { + // The buffer is one byte shorter than security buffer. + const uint8_t buf[kSecurityBufferLen - 1] = {0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_FALSE(reader.SkipSecurityBuffer()); +} + +TEST(NtlmBufferReaderTest, SkipSecurityBufferWithValidationEmpty) { + const uint8_t buf[kSecurityBufferLen] = {0, 0, 0, 0, 0, 0, 0, 0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.SkipSecurityBufferWithValidation()); +} + +TEST(NtlmBufferReaderTest, SkipSecurityBufferWithValidationValid) { + // A valid security buffer that points to the 1 payload byte. + const uint8_t buf[kSecurityBufferLen + 1] = { + 0x01, 0, 0x01, 0, kSecurityBufferLen, 0, 0, 0, 0xFF}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + ASSERT_EQ(kSecurityBufferLen, reader.GetCursor()); + ASSERT_FALSE(reader.SkipSecurityBufferWithValidation()); +} + +TEST(NtlmBufferReaderTest, + SkipSecurityBufferWithValidationPayloadLengthPastEob) { + // Security buffer with length that points past the end of buffer. + const uint8_t buf[kSecurityBufferLen + 1] = { + 0x02, 0, 0x02, 0, kSecurityBufferLen, 0, 0, 0, 0xFF}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_FALSE(reader.SkipSecurityBufferWithValidation()); +} + +TEST(NtlmBufferReaderTest, + SkipSecurityBufferWithValidationPayloadOffsetPastEob) { + // Security buffer with offset that points past the end of buffer. + const uint8_t buf[kSecurityBufferLen + 1] = { + 0x02, 0, 0x02, 0, kSecurityBufferLen + 1, 0, 0, 0, 0xFF}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_FALSE(reader.SkipSecurityBufferWithValidation()); +} + +TEST(NtlmBufferReaderTest, + SkipSecurityBufferWithValidationZeroLengthPayloadOffsetPastEob) { + // Security buffer with offset that points past the end of buffer but + // length is 0. + const uint8_t buf[kSecurityBufferLen] = {0, 0, 0, 0, kSecurityBufferLen + 1, + 0, 0, 0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + ASSERT_TRUE(reader.SkipSecurityBufferWithValidation()); + ASSERT_EQ(kSecurityBufferLen, reader.GetCursor()); +} + +TEST(NtlmBufferReaderTest, SkipBytes) { + const uint8_t buf[8] = {0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.SkipBytes(arraysize(buf))); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.SkipBytes(arraysize(buf))); +} + +TEST(NtlmBufferReaderTest, SkipBytesPastEob) { + const uint8_t buf[8] = {0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_FALSE(reader.SkipBytes(arraysize(buf) + 1)); +} + +TEST(NtlmBufferReaderTest, MatchSignatureTooShort) { + const uint8_t buf[7] = {0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.CanRead(7)); + ASSERT_FALSE(reader.MatchSignature()); +} + +TEST(NtlmBufferReaderTest, MatchSignatureNoMatch) { + // The last byte should be a 0. + const uint8_t buf[8] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0xff}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.CanRead(8)); + ASSERT_FALSE(reader.MatchSignature()); +} + +TEST(NtlmBufferReaderTest, MatchSignatureOk) { + const uint8_t buf[8] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.MatchSignature()); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, ReadInvalidMessageType) { + // Only 0x01, 0x02, and 0x03 are valid message types. + const uint8_t buf[4] = {0x04, 0, 0, 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + MessageType message_type; + ASSERT_FALSE(reader.ReadMessageType(&message_type)); +} + +TEST(NtlmBufferReaderTest, ReadMessageTypeNegotiate) { + const uint8_t buf[4] = {static_cast(MessageType::kNegotiate), 0, 0, + 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + MessageType message_type; + ASSERT_TRUE(reader.ReadMessageType(&message_type)); + ASSERT_EQ(MessageType::kNegotiate, message_type); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, ReadMessageTypeChallenge) { + const uint8_t buf[4] = {static_cast(MessageType::kChallenge), 0, 0, + 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + MessageType message_type; + ASSERT_TRUE(reader.ReadMessageType(&message_type)); + ASSERT_EQ(MessageType::kChallenge, message_type); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, ReadMessageTypeAuthenticate) { + const uint8_t buf[4] = {static_cast(MessageType::kAuthenticate), 0, + 0, 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + MessageType message_type; + ASSERT_TRUE(reader.ReadMessageType(&message_type)); + ASSERT_EQ(MessageType::kAuthenticate, message_type); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, MatchMessageTypeAuthenticate) { + const uint8_t buf[4] = {static_cast(MessageType::kAuthenticate), 0, + 0, 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.MatchMessageType(MessageType::kAuthenticate)); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, MatchMessageTypeInvalid) { + // Only 0x01, 0x02, and 0x03 are valid message types. + const uint8_t buf[4] = {0x04, 0, 0, 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_FALSE(reader.MatchMessageType(MessageType::kAuthenticate)); +} + +TEST(NtlmBufferReaderTest, MatchMessageTypeMismatch) { + const uint8_t buf[4] = {static_cast(MessageType::kChallenge), 0, 0, + 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_FALSE(reader.MatchMessageType(MessageType::kAuthenticate)); +} + +TEST(NtlmBufferReaderTest, MatchAuthenticateHeader) { + const uint8_t buf[12] = { + 'N', 'T', 'L', + 'M', 'S', 'S', + 'P', 0, static_cast(MessageType::kAuthenticate), + 0, 0, 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate)); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, MatchAuthenticateHeaderMisMatch) { + const uint8_t buf[12] = { + 'N', 'T', 'L', + 'M', 'S', 'S', + 'P', 0, static_cast(MessageType::kChallenge), + 0, 0, 0}; + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_FALSE(reader.MatchMessageType(MessageType::kAuthenticate)); +} + +TEST(NtlmBufferReaderTest, MatchZeros) { + const uint8_t buf[6] = {0, 0, 0, 0, 0, 0}; + + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_TRUE(reader.MatchZeros(arraysize(buf))); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.MatchZeros(1)); +} + +TEST(NtlmBufferReaderTest, MatchZerosFail) { + const uint8_t buf[6] = {0, 0, 0, 0, 0, 0xFF}; + + NtlmBufferReader reader(buf, arraysize(buf)); + + ASSERT_FALSE(reader.MatchZeros(arraysize(buf))); +} + +TEST(NtlmBufferReaderTest, MatchEmptySecurityBuffer) { + const uint8_t buf[kSecurityBufferLen] = {0, 0, 0, 0, 0, 0, 0, 0}; + + NtlmBufferReader reader(buf, kSecurityBufferLen); + + ASSERT_TRUE(reader.MatchEmptySecurityBuffer()); + ASSERT_TRUE(reader.IsEndOfBuffer()); + ASSERT_FALSE(reader.MatchEmptySecurityBuffer()); +} + +TEST(NtlmBufferReaderTest, MatchEmptySecurityBufferLengthZeroOffsetEnd) { + const uint8_t buf[kSecurityBufferLen] = {0, 0, 0, 0, 0x08, 0, 0, 0}; + + NtlmBufferReader reader(buf, kSecurityBufferLen); + + ASSERT_TRUE(reader.MatchEmptySecurityBuffer()); + ASSERT_TRUE(reader.IsEndOfBuffer()); +} + +TEST(NtlmBufferReaderTest, MatchEmptySecurityBufferLengthZeroPastEob) { + const uint8_t buf[kSecurityBufferLen] = {0, 0, 0, 0, 0x09, 0, 0, 0}; + + NtlmBufferReader reader(buf, kSecurityBufferLen); + + ASSERT_FALSE(reader.MatchEmptySecurityBuffer()); +} + +TEST(NtlmBufferReaderTest, MatchEmptySecurityBufferLengthNonZeroLength) { + const uint8_t buf[kSecurityBufferLen + 1] = {0x01, 0, 0, 0, 0x08, + 0, 0, 0, 0xff}; + + NtlmBufferReader reader(buf, kSecurityBufferLen); + + ASSERT_FALSE(reader.MatchEmptySecurityBuffer()); +} + +} // namespace ntlm +} // namespace net diff --git a/chromium/net/ntlm/ntlm_buffer_writer.cc b/chromium/net/ntlm/ntlm_buffer_writer.cc new file mode 100644 index 00000000000..0b5e3aa5c81 --- /dev/null +++ b/chromium/net/ntlm/ntlm_buffer_writer.cc @@ -0,0 +1,152 @@ +// 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_buffer_writer.h" + +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" + +namespace net { +namespace ntlm { + +NtlmBufferWriter::NtlmBufferWriter(size_t buffer_len) + : buffer_len_(buffer_len), cursor_(0) { + buffer_.reset(new uint8_t[buffer_len]()); +} + +NtlmBufferWriter::~NtlmBufferWriter() {} + +bool NtlmBufferWriter::CanWrite(size_t len) const { + if (!GetBufferPtr()) + return false; + + DCHECK_LE(GetCursor(), GetLength()); + + if (len == 0) + return true; + + return (len <= GetLength()) && (GetCursor() <= GetLength() - len); +} + +bool NtlmBufferWriter::WriteUInt16(uint16_t value) { + return WriteUInt(value); +} + +bool NtlmBufferWriter::WriteUInt32(uint32_t value) { + return WriteUInt(value); +} + +bool NtlmBufferWriter::WriteUInt64(uint64_t value) { + return WriteUInt(value); +} + +bool NtlmBufferWriter::WriteFlags(NegotiateFlags flags) { + return WriteUInt32(static_cast(flags)); +} + +bool NtlmBufferWriter::WriteBytes(const uint8_t* buffer, size_t len) { + if (!CanWrite(len)) + return false; + + memcpy(reinterpret_cast(GetBufferPtrAtCursor()), + reinterpret_cast(buffer), len); + + AdvanceCursor(len); + return true; +} + +bool NtlmBufferWriter::WriteBytes(base::StringPiece bytes) { + return WriteBytes(reinterpret_cast(bytes.data()), + bytes.length()); +} + +bool NtlmBufferWriter::WriteZeros(size_t count) { + if (!CanWrite(count)) + return false; + + memset(GetBufferPtrAtCursor(), 0, count); + AdvanceCursor(count); + return true; +} + +bool NtlmBufferWriter::WriteSecurityBuffer(SecurityBuffer sec_buf) { + return WriteUInt16(sec_buf.length) && WriteUInt16(sec_buf.length) && + WriteUInt32(sec_buf.offset); +} + +bool NtlmBufferWriter::WriteUtf8String(const std::string& str) { + return WriteBytes(reinterpret_cast(str.c_str()), + str.length()); +} + +bool NtlmBufferWriter::WriteUtf16AsUtf8String(const base::string16& str) { + std::string utf8 = base::UTF16ToUTF8(str); + return WriteUtf8String(utf8); +} + +bool NtlmBufferWriter::WriteUtf8AsUtf16String(const std::string& str) { + base::string16 unicode = base::UTF8ToUTF16(str); + return WriteUtf16String(unicode); +} + +bool NtlmBufferWriter::WriteUtf16String(const base::string16& str) { + size_t num_bytes = str.length() * 2; + if (!CanWrite(num_bytes)) + return false; + +#if defined(ARCH_CPU_BIG_ENDIAN) + uint8_t* ptr = reinterpret_cast(GetBufferPtrAtCursor()); + + for (int i = 0; i < num_bytes; i += 2) { + ptr[i] = str[i / 2] & 0xff; + ptr[i + 1] = str[i / 2] >> 8; + } +#else + memcpy(reinterpret_cast(GetBufferPtrAtCursor()), str.c_str(), + num_bytes); + +#endif + + AdvanceCursor(num_bytes); + return true; +} + +bool NtlmBufferWriter::WriteSignature() { + return WriteBytes(kSignature, kSignatureLen); +} + +bool NtlmBufferWriter::WriteMessageType(MessageType message_type) { + return WriteUInt32(static_cast(message_type)); +} + +bool NtlmBufferWriter::WriteMessageHeader(MessageType message_type) { + return WriteSignature() && WriteMessageType(message_type); +} + +template +bool NtlmBufferWriter::WriteUInt(T value) { + size_t int_size = sizeof(T); + if (!CanWrite(int_size)) + return false; + + for (size_t i = 0; i < int_size; i++) { + GetBufferPtrAtCursor()[i] = static_cast(value & 0xff); + value >>= 8; + } + + AdvanceCursor(int_size); + return true; +} + +void NtlmBufferWriter::SetCursor(size_t cursor) { + DCHECK(GetBufferPtr() && cursor <= GetLength()); + + cursor_ = cursor; +} + +} // namespace ntlm +} // namespace net diff --git a/chromium/net/ntlm/ntlm_buffer_writer.h b/chromium/net/ntlm/ntlm_buffer_writer.h new file mode 100644 index 00000000000..067885dda05 --- /dev/null +++ b/chromium/net/ntlm/ntlm_buffer_writer.h @@ -0,0 +1,177 @@ +// 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. + +#ifndef NET_BASE_NTLM_BUFFER_WRITER_H_ +#define NET_BASE_NTLM_BUFFER_WRITER_H_ + +#include +#include + +#include +#include + +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/ntlm/ntlm_constants.h" + +namespace net { +namespace ntlm { + +// Supports various bounds checked low level buffer operations required by an +// NTLM implementation. +// +// The class supports sequential write to an internally managed buffer. All +// writes perform bounds checking to ensure enough space is remaining in the +// buffer. +// +// The internal buffer is allocated in the constructor with size |buffer_len| +// and owned by the class. +// +// Write* methods write the buffer at the current cursor position and perform +// any necessary type conversion and provide the data in out params. After a +// successful write the cursor position is advanced past the written field. +// +// Failed writes leave the internal cursor at the same position as before the +// call. +// +// Based on [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol +// 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 +class NET_EXPORT_PRIVATE NtlmBufferWriter { + public: + explicit NtlmBufferWriter(size_t buffer_len); + ~NtlmBufferWriter(); + + size_t GetLength() const { return buffer_len_; } + size_t GetCursor() const { return cursor_; } + bool IsEndOfBuffer() const { return cursor_ >= GetLength(); } + + // Gets a base::StringPiece view over the entire buffer. + base::StringPiece GetBuffer() const { + return base::StringPiece(reinterpret_cast(buffer_.get()), + buffer_len_); + } + + // Returns true if there are |len| more bytes between the current cursor + // position and the end of the buffer. + bool CanWrite(size_t len) const; + + // Writes a 16 bit unsigned value (little endian). If there are not 16 + // more bits available in the buffer, it returns false. + bool WriteUInt16(uint16_t value) WARN_UNUSED_RESULT; + + // Writes a 32 bit unsigned value (little endian). If there are not 32 + // more bits available in the buffer, it returns false. + bool WriteUInt32(uint32_t value) WARN_UNUSED_RESULT; + + // Writes a 64 bit unsigned value (little endian). If there are not 64 + // more bits available in the buffer, it returns false. + bool WriteUInt64(uint64_t value) WARN_UNUSED_RESULT; + + // Writes flags as a 32 bit unsigned value (little endian). + bool WriteFlags(NegotiateFlags flags) WARN_UNUSED_RESULT; + + // Writes |len| bytes from |buffer|. If there are not |len| more bytes in + // the buffer, it returns false. + bool WriteBytes(const uint8_t* buffer, size_t len) WARN_UNUSED_RESULT; + + // Writes the bytes from the |base::StringPiece|. If there are not enough + // bytes in the buffer, it returns false. + 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. + bool WriteZeros(size_t count) WARN_UNUSED_RESULT; + + // A security buffer is an 8 byte structure that defines the offset and + // length of a payload (string, struct, or byte array) that appears after + // the fixed part of the message. + // + // The structure in the NTLM message is (little endian fields): + // uint16 - |length| Length of payload + // uint16 - Allocation (ignored and always set to |length|) + // uint32 - |offset| Offset from start of message + bool WriteSecurityBuffer(SecurityBuffer sec_buf) 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 + // this code path. The 8 bit bytes of the hostname are written to the buffer. + // The encoding is not relevant. + bool WriteUtf8String(const std::string& str) WARN_UNUSED_RESULT; + + // Converts the 16 bit characters to UTF8 and writes the resulting 8 bit + // characters. + // + // If Unicode was not negotiated, the username and domain get converted to + // UTF8 in the message. Since they are just treated as additional bytes + // input to hash the encoding doesn't matter. In practice, only a very old or + // non-Windows server might trigger this code path since we always attempt + // to negotiate Unicode and servers are supposed to honor that request. + bool WriteUtf16AsUtf8String(const base::string16& str) WARN_UNUSED_RESULT; + + // Treats |str| as UTF8, converts to UTF-16 and writes it with little-endian + // byte order to the buffer. + // + // Two specific strings go through this code path. + // + // One case is the hostname. When the the Unicode flag has been set during + // negotiation, the hostname needs to appear in the message with 16-bit + // characters. + // + // The other case is the Service Principal Name (SPN). With Extended + // Protection for Authentication (EPA) enabled, it appears in the target info + // inside an AV Pair, where strings always have 16-bit characters. + bool WriteUtf8AsUtf16String(const std::string& str) WARN_UNUSED_RESULT; + + // Writes UTF-16 LE characters to the buffer. For these strings, such as + // username and the domain the actual encoding isn't important; they are just + // treated as additional bytes of input to the hash. + bool WriteUtf16String(const base::string16& str) WARN_UNUSED_RESULT; + + // Writes the 8 byte NTLM signature "NTLMSSP\0" into the buffer. + bool WriteSignature() WARN_UNUSED_RESULT; + + // There are 3 message types Negotiate (sent by client), Challenge (sent by + // server), and Authenticate (sent by client). + // + // This writes |message_type| as a uint32_t into the buffer. + bool WriteMessageType(MessageType message_type) WARN_UNUSED_RESULT; + + // Performs |WriteSignature| then |WriteMessageType|. + bool WriteMessageHeader(MessageType message_type) WARN_UNUSED_RESULT; + + private: + // Writes |sizeof(T)| bytes little-endian of an integer type to the buffer. + template + bool WriteUInt(T value); + + // Sets the cursor position. The caller should use |GetLength| or + // |CanWrite| to verify the bounds before calling this method. + void SetCursor(size_t cursor); + + // Advances the cursor by |count|. The caller should use |GetLength| or + // |CanWrite| to verify the bounds before calling this method. + void AdvanceCursor(size_t count) { SetCursor(GetCursor() + count); } + + // Returns a pointer to the start of the buffer. + uint8_t* GetBufferPtr() const { return buffer_.get(); } + + // Returns pointer into the buffer at the current cursor location. + uint8_t* GetBufferPtrAtCursor() const { return buffer_.get() + GetCursor(); } + + std::unique_ptr buffer_; + size_t buffer_len_; + size_t cursor_; + + DISALLOW_COPY_AND_ASSIGN(NtlmBufferWriter); +}; + +} // namespace ntlm +} // namespace net + +#endif // NET_BASE_NTLM_BUFFER_WRITER_H_ diff --git a/chromium/net/ntlm/ntlm_buffer_writer_unittest.cc b/chromium/net/ntlm/ntlm_buffer_writer_unittest.cc new file mode 100644 index 00000000000..3616b9fc180 --- /dev/null +++ b/chromium/net/ntlm/ntlm_buffer_writer_unittest.cc @@ -0,0 +1,235 @@ +// 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_buffer_writer.h" + +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace ntlm { + +namespace { + +// Helper method to hide all the ugly casting. +const uint8_t* GetBufferPtr(const NtlmBufferWriter& writer) { + return reinterpret_cast(writer.GetBuffer().data()); +} + +// Helper method to get a byte at a specific index in the buffer. +uint8_t GetByteFromBuffer(const NtlmBufferWriter& writer, size_t index) { + base::StringPiece piece(writer.GetBuffer()); + EXPECT_TRUE(index < piece.length()); + return static_cast(piece.data()[index]); +} + +} // namespace + +TEST(NtlmBufferWriterTest, Initialization) { + NtlmBufferWriter writer(1); + + ASSERT_EQ(1u, writer.GetLength()); + ASSERT_EQ(1u, writer.GetBuffer().length()); + ASSERT_EQ(0u, writer.GetCursor()); + ASSERT_FALSE(writer.IsEndOfBuffer()); + ASSERT_TRUE(writer.CanWrite(1)); + ASSERT_FALSE(writer.CanWrite(2)); +} + +TEST(NtlmBufferWriterTest, Write16) { + uint8_t expected[2] = {0x22, 0x11}; + const uint16_t value = 0x1122; + + NtlmBufferWriter writer(sizeof(uint16_t)); + + ASSERT_TRUE(writer.WriteUInt16(value)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_EQ(arraysize(expected), writer.GetLength()); + ASSERT_FALSE(writer.WriteUInt16(value)); + + ASSERT_EQ(0, + memcmp(expected, writer.GetBuffer().data(), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, Write16PastEob) { + NtlmBufferWriter writer(sizeof(uint16_t) - 1); + + ASSERT_FALSE(writer.WriteUInt16(0)); + ASSERT_EQ(0u, writer.GetCursor()); +} + +TEST(NtlmBufferWriterTest, Write32) { + uint8_t expected[4] = {0x44, 0x33, 0x22, 0x11}; + const uint32_t value = 0x11223344; + + NtlmBufferWriter writer(sizeof(uint32_t)); + + ASSERT_TRUE(writer.WriteUInt32(value)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteUInt32(value)); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, Write32PastEob) { + NtlmBufferWriter writer(sizeof(uint32_t) - 1); + + ASSERT_FALSE(writer.WriteUInt32(0)); + ASSERT_EQ(0u, writer.GetCursor()); +} + +TEST(NtlmBufferWriterTest, Write64) { + uint8_t expected[8] = {0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + const uint64_t value = 0x1122334455667788; + + NtlmBufferWriter writer(sizeof(uint64_t)); + + ASSERT_TRUE(writer.WriteUInt64(value)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteUInt64(value)); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, Write64PastEob) { + NtlmBufferWriter writer(sizeof(uint64_t) - 1); + + ASSERT_FALSE(writer.WriteUInt64(0)); + ASSERT_EQ(0u, writer.GetCursor()); +} + +TEST(NtlmBufferWriterTest, WriteBytes) { + uint8_t expected[8] = {0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11}; + + NtlmBufferWriter writer(arraysize(expected)); + + ASSERT_TRUE(writer.WriteBytes(expected, arraysize(expected))); + ASSERT_EQ(0, memcmp(GetBufferPtr(writer), expected, arraysize(expected))); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteBytes(expected, 1)); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, WriteBytesPastEob) { + uint8_t buffer[8]; + + NtlmBufferWriter writer(arraysize(buffer) - 1); + + ASSERT_FALSE(writer.WriteBytes(buffer, arraysize(buffer))); +} + +TEST(NtlmBufferWriterTest, WriteSecurityBuffer) { + uint8_t expected[8] = {0x22, 0x11, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55}; + uint16_t length = 0x1122; + uint32_t offset = 0x55667788; + + NtlmBufferWriter writer(kSecurityBufferLen); + + ASSERT_TRUE(writer.WriteSecurityBuffer(SecurityBuffer(offset, length))); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteSecurityBuffer(SecurityBuffer(offset, length))); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, WriteSecurityBufferPastEob) { + SecurityBuffer sec_buf; + NtlmBufferWriter writer(kSecurityBufferLen - 1); + + ASSERT_FALSE(writer.WriteSecurityBuffer(sec_buf)); +} + +TEST(NtlmBufferWriterTest, WriteNarrowString) { + uint8_t expected[8] = {'1', '2', '3', '4', '5', '6', '7', '8'}; + std::string value("12345678"); + + NtlmBufferWriter writer(value.size()); + + ASSERT_TRUE(writer.WriteUtf8String(value)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteUtf8String(value)); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, WriteAsciiStringPastEob) { + std::string str("12345678"); + NtlmBufferWriter writer(str.length() - 1); + + ASSERT_FALSE(writer.WriteUtf8String(str)); +} + +TEST(NtlmBufferWriterTest, WriteUtf16String) { + uint8_t expected[16] = {'1', 0, '2', 0, '3', 0, '4', 0, + '5', 0, '6', 0, '7', 0, '8', 0}; + base::string16 value = base::ASCIIToUTF16("12345678"); + + NtlmBufferWriter writer(value.size() * 2); + + ASSERT_TRUE(writer.WriteUtf16String(value)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteUtf16String(value)); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, WriteUtf16StringPastEob) { + base::string16 str = base::ASCIIToUTF16("12345678"); + NtlmBufferWriter writer((str.length() * 2) - 1); + + ASSERT_FALSE(writer.WriteUtf16String(str)); +} + +TEST(NtlmBufferWriterTest, WriteUtf8AsUtf16String) { + uint8_t expected[16] = {'1', 0, '2', 0, '3', 0, '4', 0, + '5', 0, '6', 0, '7', 0, '8', 0}; + std::string input = "12345678"; + + NtlmBufferWriter writer(input.size() * 2); + + ASSERT_TRUE(writer.WriteUtf8AsUtf16String(input)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_FALSE(writer.WriteUtf8AsUtf16String(input)); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, WriteSignature) { + uint8_t expected[8] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}; + NtlmBufferWriter writer(kSignatureLen); + + ASSERT_TRUE(writer.WriteSignature()); + ASSERT_TRUE(writer.IsEndOfBuffer()); + + ASSERT_EQ(0, memcmp(expected, GetBufferPtr(writer), arraysize(expected))); +} + +TEST(NtlmBufferWriterTest, WriteSignaturePastEob) { + NtlmBufferWriter writer(1); + + ASSERT_FALSE(writer.WriteSignature()); +} + +TEST(NtlmBufferWriterTest, WriteMessageType) { + NtlmBufferWriter writer(4); + + ASSERT_TRUE(writer.WriteMessageType(MessageType::kNegotiate)); + ASSERT_TRUE(writer.IsEndOfBuffer()); + ASSERT_EQ(static_cast(MessageType::kNegotiate), + GetByteFromBuffer(writer, 0)); + ASSERT_EQ(0, GetByteFromBuffer(writer, 1)); + ASSERT_EQ(0, GetByteFromBuffer(writer, 2)); + ASSERT_EQ(0, GetByteFromBuffer(writer, 3)); +} + +TEST(NtlmBufferWriterTest, WriteMessageTypePastEob) { + NtlmBufferWriter writer(sizeof(uint32_t) - 1); + + ASSERT_FALSE(writer.WriteMessageType(MessageType::kNegotiate)); +} + +} // namespace ntlm +} // namespace net diff --git a/chromium/net/ntlm/ntlm_constants.h b/chromium/net/ntlm/ntlm_constants.h new file mode 100644 index 00000000000..8dc6bb3cde8 --- /dev/null +++ b/chromium/net/ntlm/ntlm_constants.h @@ -0,0 +1,91 @@ +// 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. + +#ifndef NET_BASE_NTLM_CONSTANTS_H_ +#define NET_BASE_NTLM_CONSTANTS_H_ + +#include +#include +#include + +#include "base/macros.h" +#include "net/base/net_export.h" + +namespace net { +namespace ntlm { + +// A security buffer is a structure within an NTLM message that indicates +// the offset from the beginning of the message and the length of a payload +// that occurs later in the message. Within the raw message there is also +// an additional field, however the field is always written with the same +// value as length, and readers must always ignore it. +struct SecurityBuffer { + SecurityBuffer(uint32_t offset, uint16_t length) + : offset(offset), length(length) {} + SecurityBuffer() : SecurityBuffer(0, 0) {} + + uint32_t offset; + uint16_t length; +}; + +enum class NtlmVersion { + kNtlmV1 = 0x01, + kNtlmV2 = 0x02, +}; + +// There are 3 types of messages in NTLM. The message type is a field in +// every NTLM message header. +enum class MessageType : uint32_t { + kNegotiate = 0x01, + kChallenge = 0x02, + kAuthenticate = 0x03, +}; + +// Defined in [MS-NLMP] Section 2.2.2.5 +// Only the used subset is defined. +enum class NegotiateFlags : uint32_t { + kNone = 0, + kUnicode = 0x01, + kOem = 0x02, + kRequestTarget = 0x04, + kNtlm = 0x200, + kAlwaysSign = 0x8000, + kExtendedSessionSecurity = 0x80000, +}; + +constexpr inline NegotiateFlags operator|(NegotiateFlags lhs, + NegotiateFlags rhs) { + using TFlagsInt = std::underlying_type::type; + + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +constexpr inline NegotiateFlags operator&(NegotiateFlags lhs, + NegotiateFlags rhs) { + using TFlagsInt = std::underlying_type::type; + + return static_cast(static_cast(lhs) & + static_cast(rhs)); +} + +static constexpr uint8_t kSignature[] = "NTLMSSP"; +static constexpr size_t kSignatureLen = arraysize(kSignature); +static constexpr size_t kSecurityBufferLen = + (2 * sizeof(uint16_t)) + sizeof(uint32_t); +static constexpr size_t kNegotiateMessageLen = 32; +static constexpr size_t kChallengeHeaderLen = 32; +static constexpr size_t kResponseLenV1 = 24; +static constexpr size_t kChallengeLen = 8; +static constexpr size_t kNtlmHashLen = 16; + +static constexpr NegotiateFlags kNegotiateMessageFlags = + NegotiateFlags::kUnicode | NegotiateFlags::kOem | + NegotiateFlags::kRequestTarget | NegotiateFlags::kNtlm | + NegotiateFlags::kAlwaysSign | NegotiateFlags::kExtendedSessionSecurity; + +} // namespace ntlm +} // namespace net + +#endif // NET_BASE_NTLM_CONSTANTS_H_ \ No newline at end of file diff --git a/chromium/net/ntlm/ntlm_test_data.h b/chromium/net/ntlm/ntlm_test_data.h new file mode 100644 index 00000000000..2a9913b7a4c --- /dev/null +++ b/chromium/net/ntlm/ntlm_test_data.h @@ -0,0 +1,81 @@ +// 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. + +// This file contains common input and result values use to verify the NTLM +// implementation. They are defined in [MS-NLMP] Section 4.2 [1]. +// +// [1] https://msdn.microsoft.com/en-us/library/cc236621.aspx + +#ifndef NET_BASE_NTLM_TEST_DATA_H_ +#define NET_BASE_NTLM_TEST_DATA_H_ + +#include "net/ntlm/ntlm_constants.h" + +namespace net { +namespace ntlm { +namespace test { + +// Common input values defined in [MS-NLMP] Section 4.2.1. +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 base::char16 kUser[] = {'U', 's', 'e', 'r', '\0'}; +constexpr base::char16 kHostname[] = {'C', 'O', 'M', 'P', 'U', + 'T', 'E', 'R', '\0'}; + +// ASCII Versions of the above strings. +constexpr char kNtlmDomainAscii[] = "Domain"; +constexpr char kUserAscii[] = "User"; +constexpr char kHostnameAscii[] = "COMPUTER"; + +// Challenge vectors defined in [MS-NLMP] Section 4.2.1. +constexpr uint8_t kServerChallenge[kChallengeLen] = {0x01, 0x23, 0x45, 0x67, + 0x89, 0xab, 0xcd, 0xef}; +constexpr uint8_t kClientChallenge[kChallengeLen] = {0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa}; + +// 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) +// [9-11] - |MessageType::kChallenge| (Message Type = 0x00000002) +// [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. +constexpr uint8_t kMinChallengeMessage[kChallengeHeaderLen] = { + 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0', 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x07, 0x82, + 0x08, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; + +// Test result value for NTOWFv1() defined in [MS-NLMP] Section 4.2.2.1.2. +constexpr uint8_t kExpectedNtlmHashV1[kNtlmHashLen] = { + 0xa4, 0xf4, 0x9c, 0x40, 0x65, 0x10, 0xbd, 0xca, + 0xb6, 0x82, 0x4e, 0xe7, 0xc3, 0x0f, 0xd8, 0x52}; + +// 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, + 0x4f, 0x16, 0x33, 0x1c, 0x44, 0xbd, 0xbe, 0xd9, 0x27, 0x84, 0x1f, 0x94}; + +// Test result value defined in [MS-NLMP] Section 4.2.3.2.2. +constexpr uint8_t kExpectedNtlmResponseWithV1SS[kResponseLenV1] = { + 0x75, 0x37, 0xf8, 0x03, 0xae, 0x36, 0x71, 0x28, 0xca, 0x45, 0x82, 0x04, + 0xbd, 0xe7, 0xca, 0xf8, 0x1e, 0x97, 0xed, 0x26, 0x83, 0x26, 0x72, 0x32}; + +// Test result value defined in [MS-NLMP] Section 4.2.3.2.1. +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}; + +} // namespace test +} // namespace ntlm +} // namespace net + +#endif // NET_BASE_NTLM_TEST_DATA_H_ \ No newline at end of file diff --git a/chromium/net/ntlm/ntlm_unittest.cc b/chromium/net/ntlm/ntlm_unittest.cc new file mode 100644 index 00000000000..6c40d16af6d --- /dev/null +++ b/chromium/net/ntlm/ntlm_unittest.cc @@ -0,0 +1,120 @@ +// 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. + +// Tests on exact results from cryptographic operations are based on test data +// provided in [MS-NLMP] Version 28.0 [1] Section 4.2. +// +// Additional sanity checks on the low level hashing operations test for +// properties of the outputs, such as whether the hashes change, whether they +// should be zeroed out, or whether they should be the same or different. +// +// [1] https://msdn.microsoft.com/en-us/library/cc236621.aspx + +#include "net/ntlm/ntlm.h" + +#include "base/strings/utf_string_conversions.h" +#include "net/ntlm/ntlm_test_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace ntlm { + +TEST(NtlmTest, GenerateNtlmHashV1PasswordSpecTests) { + uint8_t hash[kNtlmHashLen]; + GenerateNtlmHashV1(test::kPassword, hash); + ASSERT_EQ(0, memcmp(hash, test::kExpectedNtlmHashV1, kNtlmHashLen)); +} + +TEST(NtlmTest, GenerateNtlmHashV1PasswordChangesHash) { + base::string16 password1 = base::UTF8ToUTF16("pwd01"); + base::string16 password2 = base::UTF8ToUTF16("pwd02"); + uint8_t hash1[kNtlmHashLen]; + uint8_t hash2[kNtlmHashLen]; + + GenerateNtlmHashV1(password1, hash1); + GenerateNtlmHashV1(password2, hash2); + + // Verify that the hash is different with a different password. + ASSERT_NE(0, memcmp(hash1, hash2, kNtlmHashLen)); +} + +TEST(NtlmTest, GenerateResponsesV1SpecTests) { + uint8_t lm_response[kResponseLenV1]; + uint8_t ntlm_response[kResponseLenV1]; + GenerateResponsesV1(test::kPassword, test::kServerChallenge, lm_response, + ntlm_response); + + ASSERT_EQ( + 0, memcmp(test::kExpectedNtlmResponseV1, ntlm_response, kResponseLenV1)); + + // This implementation never sends an LMv1 response (spec equivalent of the + // client variable NoLMResponseNTLMv1 being false) so the LM response is + // equal to the NTLM response when + // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is not negotiated. See + // [MS-NLMP] Section 3.3.1. + ASSERT_EQ(0, + memcmp(test::kExpectedNtlmResponseV1, lm_response, kResponseLenV1)); +} + +TEST(NtlmTest, GenerateResponsesV1WithSessionSecuritySpecTests) { + uint8_t lm_response[kResponseLenV1]; + uint8_t ntlm_response[kResponseLenV1]; + GenerateResponsesV1WithSessionSecurity( + test::kPassword, test::kServerChallenge, test::kClientChallenge, + lm_response, ntlm_response); + + ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, lm_response, + kResponseLenV1)); + ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, ntlm_response, + kResponseLenV1)); +} + +TEST(NtlmTest, GenerateResponsesV1WithSessionSecurityClientChallengeUsed) { + uint8_t lm_response1[kResponseLenV1]; + uint8_t lm_response2[kResponseLenV1]; + uint8_t ntlm_response1[kResponseLenV1]; + uint8_t ntlm_response2[kResponseLenV1]; + uint8_t client_challenge1[kChallengeLen]; + uint8_t client_challenge2[kChallengeLen]; + + memset(client_challenge1, 0x01, kChallengeLen); + memset(client_challenge2, 0x02, kChallengeLen); + + GenerateResponsesV1WithSessionSecurity( + test::kPassword, test::kServerChallenge, client_challenge1, lm_response1, + ntlm_response1); + GenerateResponsesV1WithSessionSecurity( + test::kPassword, test::kServerChallenge, client_challenge2, lm_response2, + ntlm_response2); + + // The point of session security is that the client can introduce some + // randomness, so verify different client_challenge gives a different result. + ASSERT_NE(0, memcmp(lm_response1, lm_response2, kResponseLenV1)); + ASSERT_NE(0, memcmp(ntlm_response1, ntlm_response2, kResponseLenV1)); + + // With session security the lm and ntlm hash should be different. + ASSERT_NE(0, memcmp(lm_response1, ntlm_response1, kResponseLenV1)); + ASSERT_NE(0, memcmp(lm_response2, ntlm_response2, kResponseLenV1)); +} + +TEST(NtlmTest, GenerateResponsesV1WithSessionSecurityVerifySSUsed) { + uint8_t lm_response1[kResponseLenV1]; + uint8_t lm_response2[kResponseLenV1]; + uint8_t ntlm_response1[kResponseLenV1]; + uint8_t ntlm_response2[kResponseLenV1]; + + GenerateResponsesV1WithSessionSecurity( + test::kPassword, test::kServerChallenge, test::kClientChallenge, + lm_response1, ntlm_response1); + GenerateResponsesV1(test::kPassword, test::kServerChallenge, lm_response2, + ntlm_response2); + + // Verify that the responses with session security are not the + // same as without it. + ASSERT_NE(0, memcmp(lm_response1, lm_response2, kResponseLenV1)); + ASSERT_NE(0, memcmp(ntlm_response1, ntlm_response2, kResponseLenV1)); +} + +} // namespace ntlm +} // namespace net \ No newline at end of file -- cgit v1.2.1