summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsNTLMAuthModule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/nsNTLMAuthModule.cpp')
-rw-r--r--security/manager/ssl/nsNTLMAuthModule.cpp1040
1 files changed, 1040 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNTLMAuthModule.cpp b/security/manager/ssl/nsNTLMAuthModule.cpp
new file mode 100644
index 0000000000..c943b35871
--- /dev/null
+++ b/security/manager/ssl/nsNTLMAuthModule.cpp
@@ -0,0 +1,1040 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNTLMAuthModule.h"
+
+#include <time.h>
+
+#include "ScopedNSSTypes.h"
+#include "md4.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Casting.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetCID.h"
+#include "nsUnicharUtils.h"
+#include "pk11pub.h"
+#include "prsystem.h"
+
+static mozilla::LazyLogModule sNTLMLog("NTLM");
+
+#define LOG(x) MOZ_LOG(sNTLMLog, mozilla::LogLevel::Debug, x)
+#define LOG_ENABLED() MOZ_LOG_TEST(sNTLMLog, mozilla::LogLevel::Debug)
+
+static void des_makekey(const uint8_t* raw, uint8_t* key);
+static void des_encrypt(const uint8_t* key, const uint8_t* src, uint8_t* hash);
+
+//-----------------------------------------------------------------------------
+// this file contains a cross-platform NTLM authentication implementation. it
+// is based on documentation from: http://davenport.sourceforge.net/ntlm.html
+//-----------------------------------------------------------------------------
+
+#define NTLM_NegotiateUnicode 0x00000001
+#define NTLM_NegotiateOEM 0x00000002
+#define NTLM_RequestTarget 0x00000004
+#define NTLM_Unknown1 0x00000008
+#define NTLM_NegotiateSign 0x00000010
+#define NTLM_NegotiateSeal 0x00000020
+#define NTLM_NegotiateDatagramStyle 0x00000040
+#define NTLM_NegotiateLanManagerKey 0x00000080
+#define NTLM_NegotiateNetware 0x00000100
+#define NTLM_NegotiateNTLMKey 0x00000200
+#define NTLM_Unknown2 0x00000400
+#define NTLM_Unknown3 0x00000800
+#define NTLM_NegotiateDomainSupplied 0x00001000
+#define NTLM_NegotiateWorkstationSupplied 0x00002000
+#define NTLM_NegotiateLocalCall 0x00004000
+#define NTLM_NegotiateAlwaysSign 0x00008000
+#define NTLM_TargetTypeDomain 0x00010000
+#define NTLM_TargetTypeServer 0x00020000
+#define NTLM_TargetTypeShare 0x00040000
+#define NTLM_NegotiateNTLM2Key 0x00080000
+#define NTLM_RequestInitResponse 0x00100000
+#define NTLM_RequestAcceptResponse 0x00200000
+#define NTLM_RequestNonNTSessionKey 0x00400000
+#define NTLM_NegotiateTargetInfo 0x00800000
+#define NTLM_Unknown4 0x01000000
+#define NTLM_Unknown5 0x02000000
+#define NTLM_Unknown6 0x04000000
+#define NTLM_Unknown7 0x08000000
+#define NTLM_Unknown8 0x10000000
+#define NTLM_Negotiate128 0x20000000
+#define NTLM_NegotiateKeyExchange 0x40000000
+#define NTLM_Negotiate56 0x80000000
+
+// we send these flags with our type 1 message
+#define NTLM_TYPE1_FLAGS \
+ (NTLM_NegotiateUnicode | NTLM_NegotiateOEM | NTLM_RequestTarget | \
+ NTLM_NegotiateNTLMKey | NTLM_NegotiateAlwaysSign | NTLM_NegotiateNTLM2Key)
+
+static const char NTLM_SIGNATURE[] = "NTLMSSP";
+static const char NTLM_TYPE1_MARKER[] = {0x01, 0x00, 0x00, 0x00};
+static const char NTLM_TYPE2_MARKER[] = {0x02, 0x00, 0x00, 0x00};
+static const char NTLM_TYPE3_MARKER[] = {0x03, 0x00, 0x00, 0x00};
+
+#define NTLM_TYPE1_HEADER_LEN 32
+#define NTLM_TYPE2_HEADER_LEN 48
+#define NTLM_TYPE3_HEADER_LEN 64
+
+/**
+ * We don't actually send a LM response, but we still have to send something in
+ * this spot
+ */
+#define LM_RESP_LEN 24
+
+#define NTLM_CHAL_LEN 8
+
+#define NTLM_HASH_LEN 16
+#define NTLMv2_HASH_LEN 16
+#define NTLM_RESP_LEN 24
+#define NTLMv2_RESP_LEN 16
+#define NTLMv2_BLOB1_LEN 28
+
+//-----------------------------------------------------------------------------
+
+/**
+ * Prints a description of flags to the NSPR Log, if enabled.
+ */
+static void LogFlags(uint32_t flags) {
+ if (!LOG_ENABLED()) return;
+#define TEST(_flag) \
+ if (flags & NTLM_##_flag) \
+ PR_LogPrint(" 0x%08x (" #_flag ")\n", NTLM_##_flag)
+
+ TEST(NegotiateUnicode);
+ TEST(NegotiateOEM);
+ TEST(RequestTarget);
+ TEST(Unknown1);
+ TEST(NegotiateSign);
+ TEST(NegotiateSeal);
+ TEST(NegotiateDatagramStyle);
+ TEST(NegotiateLanManagerKey);
+ TEST(NegotiateNetware);
+ TEST(NegotiateNTLMKey);
+ TEST(Unknown2);
+ TEST(Unknown3);
+ TEST(NegotiateDomainSupplied);
+ TEST(NegotiateWorkstationSupplied);
+ TEST(NegotiateLocalCall);
+ TEST(NegotiateAlwaysSign);
+ TEST(TargetTypeDomain);
+ TEST(TargetTypeServer);
+ TEST(TargetTypeShare);
+ TEST(NegotiateNTLM2Key);
+ TEST(RequestInitResponse);
+ TEST(RequestAcceptResponse);
+ TEST(RequestNonNTSessionKey);
+ TEST(NegotiateTargetInfo);
+ TEST(Unknown4);
+ TEST(Unknown5);
+ TEST(Unknown6);
+ TEST(Unknown7);
+ TEST(Unknown8);
+ TEST(Negotiate128);
+ TEST(NegotiateKeyExchange);
+ TEST(Negotiate56);
+
+#undef TEST
+}
+
+/**
+ * Prints a hexdump of buf to the NSPR Log, if enabled.
+ * @param tag Description of the data, will be printed in front of the data
+ * @param buf the data to print
+ * @param bufLen length of the data
+ */
+static void LogBuf(const char* tag, const uint8_t* buf, uint32_t bufLen) {
+ int i;
+
+ if (!LOG_ENABLED()) return;
+
+ PR_LogPrint("%s =\n", tag);
+ char line[80];
+ while (bufLen > 0) {
+ int count = bufLen;
+ if (count > 8) count = 8;
+
+ strcpy(line, " ");
+ for (i = 0; i < count; ++i) {
+ int len = strlen(line);
+ snprintf(line + len, sizeof(line) - len, "0x%02x ", int(buf[i]));
+ }
+ for (; i < 8; ++i) {
+ int len = strlen(line);
+ snprintf(line + len, sizeof(line) - len, " ");
+ }
+
+ int len = strlen(line);
+ snprintf(line + len, sizeof(line) - len, " ");
+ for (i = 0; i < count; ++i) {
+ len = strlen(line);
+ if (isprint(buf[i])) {
+ snprintf(line + len, sizeof(line) - len, "%c", buf[i]);
+ } else {
+ snprintf(line + len, sizeof(line) - len, ".");
+ }
+ }
+ PR_LogPrint("%s\n", line);
+
+ bufLen -= count;
+ buf += count;
+ }
+}
+
+/**
+ * Print base64-encoded token to the NSPR Log.
+ * @param name Description of the token, will be printed in front
+ * @param token The token to print
+ * @param tokenLen length of the data in token
+ */
+static void LogToken(const char* name, const void* token, uint32_t tokenLen) {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ nsDependentCSubstring tokenString(static_cast<const char*>(token), tokenLen);
+ nsAutoCString base64Token;
+ nsresult rv = mozilla::Base64Encode(tokenString, base64Token);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ PR_LogPrint("%s: %s\n", name, base64Token.get());
+}
+
+//-----------------------------------------------------------------------------
+
+// byte order swapping
+#define SWAP16(x) ((((x)&0xff) << 8) | (((x) >> 8) & 0xff))
+#define SWAP32(x) ((SWAP16((x)&0xffff) << 16) | (SWAP16((x) >> 16)))
+
+static void* WriteBytes(void* buf, const void* data, uint32_t dataLen) {
+ memcpy(buf, data, dataLen);
+ return (uint8_t*)buf + dataLen;
+}
+
+static void* WriteDWORD(void* buf, uint32_t dword) {
+#ifdef IS_BIG_ENDIAN
+ // NTLM uses little endian on the wire
+ dword = SWAP32(dword);
+#endif
+ return WriteBytes(buf, &dword, sizeof(dword));
+}
+
+static void* WriteSecBuf(void* buf, uint16_t length, uint32_t offset) {
+#ifdef IS_BIG_ENDIAN
+ length = SWAP16(length);
+ offset = SWAP32(offset);
+#endif
+ buf = WriteBytes(buf, &length, sizeof(length));
+ buf = WriteBytes(buf, &length, sizeof(length));
+ buf = WriteBytes(buf, &offset, sizeof(offset));
+ return buf;
+}
+
+#ifdef IS_BIG_ENDIAN
+/**
+ * WriteUnicodeLE copies a unicode string from one buffer to another. The
+ * resulting unicode string is in little-endian format. The input string is
+ * assumed to be in the native endianness of the local machine. It is safe
+ * to pass the same buffer as both input and output, which is a handy way to
+ * convert the unicode buffer to little-endian on big-endian platforms.
+ */
+static void* WriteUnicodeLE(void* buf, const char16_t* str, uint32_t strLen) {
+ // convert input string from BE to LE
+ uint8_t *cursor = (uint8_t*)buf, *input = (uint8_t*)str;
+ for (uint32_t i = 0; i < strLen; ++i, input += 2, cursor += 2) {
+ // allow for the case where |buf == str|
+ uint8_t temp = input[0];
+ cursor[0] = input[1];
+ cursor[1] = temp;
+ }
+ return buf;
+}
+#endif
+
+static uint16_t ReadUint16(const uint8_t*& buf) {
+ uint16_t x = ((uint16_t)buf[0]) | ((uint16_t)buf[1] << 8);
+ buf += sizeof(x);
+ return x;
+}
+
+static uint32_t ReadUint32(const uint8_t*& buf) {
+ uint32_t x = ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8) |
+ (((uint32_t)buf[2]) << 16) | (((uint32_t)buf[3]) << 24);
+ buf += sizeof(x);
+ return x;
+}
+
+//-----------------------------------------------------------------------------
+
+static void ZapBuf(void* buf, size_t bufLen) { memset(buf, 0, bufLen); }
+
+static void ZapString(nsString& s) { ZapBuf(s.BeginWriting(), s.Length() * 2); }
+
+/**
+ * NTLM_Hash computes the NTLM hash of the given password.
+ *
+ * @param password
+ * null-terminated unicode password.
+ * @param hash
+ * 16-byte result buffer
+ */
+static void NTLM_Hash(const nsString& password, unsigned char* hash) {
+ uint32_t len = password.Length();
+ uint8_t* passbuf;
+
+#ifdef IS_BIG_ENDIAN
+ passbuf = (uint8_t*)malloc(len * 2);
+ WriteUnicodeLE(passbuf, password.get(), len);
+#else
+ passbuf = (uint8_t*)password.get();
+#endif
+
+ md4sum(passbuf, len * 2, hash);
+
+#ifdef IS_BIG_ENDIAN
+ ZapBuf(passbuf, len * 2);
+ free(passbuf);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+/**
+ * LM_Response generates the LM response given a 16-byte password hash and the
+ * challenge from the Type-2 message.
+ *
+ * @param hash
+ * 16-byte password hash
+ * @param challenge
+ * 8-byte challenge from Type-2 message
+ * @param response
+ * 24-byte buffer to contain the LM response upon return
+ */
+static void LM_Response(const uint8_t* hash, const uint8_t* challenge,
+ uint8_t* response) {
+ uint8_t keybytes[21], k1[8], k2[8], k3[8];
+
+ memcpy(keybytes, hash, 16);
+ ZapBuf(keybytes + 16, 5);
+
+ des_makekey(keybytes, k1);
+ des_makekey(keybytes + 7, k2);
+ des_makekey(keybytes + 14, k3);
+
+ des_encrypt(k1, challenge, response);
+ des_encrypt(k2, challenge, response + 8);
+ des_encrypt(k3, challenge, response + 16);
+}
+
+//-----------------------------------------------------------------------------
+
+static nsresult GenerateType1Msg(void** outBuf, uint32_t* outLen) {
+ //
+ // verify that bufLen is sufficient
+ //
+ *outLen = NTLM_TYPE1_HEADER_LEN;
+ *outBuf = moz_xmalloc(*outLen);
+
+ //
+ // write out type 1 msg
+ //
+ void* cursor = *outBuf;
+
+ // 0 : signature
+ cursor = WriteBytes(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+
+ // 8 : marker
+ cursor = WriteBytes(cursor, NTLM_TYPE1_MARKER, sizeof(NTLM_TYPE1_MARKER));
+
+ // 12 : flags
+ cursor = WriteDWORD(cursor, NTLM_TYPE1_FLAGS);
+
+ //
+ // NOTE: it is common for the domain and workstation fields to be empty.
+ // this is true of Win2k clients, and my guess is that there is
+ // little utility to sending these strings before the charset has
+ // been negotiated. we follow suite -- anyways, it doesn't hurt
+ // to save some bytes on the wire ;-)
+ //
+
+ // 16 : supplied domain security buffer (empty)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ // 24 : supplied workstation security buffer (empty)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ return NS_OK;
+}
+
+struct Type2Msg {
+ uint32_t flags; // NTLM_Xxx bitwise combination
+ uint8_t challenge[NTLM_CHAL_LEN]; // 8 byte challenge
+ const uint8_t* target; // target string (type depends on flags)
+ uint32_t targetLen; // target length in bytes
+ const uint8_t*
+ targetInfo; // target Attribute-Value pairs (DNS domain, et al)
+ uint32_t targetInfoLen; // target AV pairs length in bytes
+};
+
+static nsresult ParseType2Msg(const void* inBuf, uint32_t inLen,
+ Type2Msg* msg) {
+ // make sure inBuf is long enough to contain a meaningful type2 msg.
+ //
+ // 0 NTLMSSP Signature
+ // 8 NTLM Message Type
+ // 12 Target Name
+ // 20 Flags
+ // 24 Challenge
+ // 32 targetInfo
+ // 48 start of optional data blocks
+ //
+ if (inLen < NTLM_TYPE2_HEADER_LEN) return NS_ERROR_UNEXPECTED;
+
+ const auto* cursor = static_cast<const uint8_t*>(inBuf);
+
+ // verify NTLMSSP signature
+ if (memcmp(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ cursor += sizeof(NTLM_SIGNATURE);
+
+ // verify Type-2 marker
+ if (memcmp(cursor, NTLM_TYPE2_MARKER, sizeof(NTLM_TYPE2_MARKER)) != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ cursor += sizeof(NTLM_TYPE2_MARKER);
+
+ // Read target name security buffer: ...
+ // ... read target length.
+ uint32_t targetLen = ReadUint16(cursor);
+ // ... skip next 16-bit "allocated space" value.
+ ReadUint16(cursor);
+ // ... read offset from inBuf.
+ uint32_t offset = ReadUint32(cursor);
+ mozilla::CheckedInt<uint32_t> targetEnd = offset;
+ targetEnd += targetLen;
+ // Check the offset / length combo is in range of the input buffer, including
+ // integer overflow checking.
+ if (MOZ_LIKELY(targetEnd.isValid() && targetEnd.value() <= inLen)) {
+ msg->targetLen = targetLen;
+ msg->target = static_cast<const uint8_t*>(inBuf) + offset;
+ } else {
+ // Do not error out, for (conservative) backward compatibility.
+ msg->targetLen = 0;
+ msg->target = nullptr;
+ }
+
+ // read flags
+ msg->flags = ReadUint32(cursor);
+
+ // read challenge
+ memcpy(msg->challenge, cursor, sizeof(msg->challenge));
+ cursor += sizeof(msg->challenge);
+
+ LOG(("NTLM type 2 message:\n"));
+ LogBuf("target", msg->target, msg->targetLen);
+ LogBuf("flags",
+ mozilla::BitwiseCast<const uint8_t*, const uint32_t*>(&msg->flags), 4);
+ LogFlags(msg->flags);
+ LogBuf("challenge", msg->challenge, sizeof(msg->challenge));
+
+ // Read (and skip) the reserved field
+ ReadUint32(cursor);
+ ReadUint32(cursor);
+ // Read target name security buffer: ...
+ // ... read target length.
+ uint32_t targetInfoLen = ReadUint16(cursor);
+ // ... skip next 16-bit "allocated space" value.
+ ReadUint16(cursor);
+ // ... read offset from inBuf.
+ offset = ReadUint32(cursor);
+ mozilla::CheckedInt<uint32_t> targetInfoEnd = offset;
+ targetInfoEnd += targetInfoLen;
+ // Check the offset / length combo is in range of the input buffer, including
+ // integer overflow checking.
+ if (MOZ_LIKELY(targetInfoEnd.isValid() && targetInfoEnd.value() <= inLen)) {
+ msg->targetInfoLen = targetInfoLen;
+ msg->targetInfo = static_cast<const uint8_t*>(inBuf) + offset;
+ } else {
+ NS_ERROR("failed to get NTLMv2 target info");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+static nsresult GenerateType3Msg(const nsString& domain,
+ const nsString& username,
+ const nsString& password, const void* inBuf,
+ uint32_t inLen, void** outBuf,
+ uint32_t* outLen) {
+ // inBuf contains Type-2 msg (the challenge) from server
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv;
+ Type2Msg msg{};
+
+ rv = ParseType2Msg(inBuf, inLen, &msg);
+ if (NS_FAILED(rv)) return rv;
+
+ bool unicode = (msg.flags & NTLM_NegotiateUnicode);
+
+ // There is no negotiation for NTLMv2, so we just do it unless we are forced
+ // by explict user configuration to use the older DES-based cryptography.
+ bool ntlmv2 = !mozilla::StaticPrefs::network_auth_force_generic_ntlm_v1();
+
+ // temporary buffers for unicode strings
+#ifdef IS_BIG_ENDIAN
+ nsAutoString ucsDomainBuf, ucsUserBuf;
+#endif
+ nsAutoCString hostBuf;
+ nsAutoString ucsHostBuf;
+ // temporary buffers for oem strings
+ nsAutoCString oemDomainBuf, oemUserBuf, oemHostBuf;
+ // pointers and lengths for the string buffers; encoding is unicode if
+ // the "negotiate unicode" flag was set in the Type-2 message.
+ const void *domainPtr, *userPtr, *hostPtr;
+ uint32_t domainLen, userLen, hostLen;
+
+ // This is for NTLM, for NTLMv2 we set the new full length once we know it
+ mozilla::CheckedInt<uint16_t> ntlmRespLen = NTLM_RESP_LEN;
+
+ //
+ // get domain name
+ //
+ if (unicode) {
+#ifdef IS_BIG_ENDIAN
+ ucsDomainBuf = domain;
+ domainPtr = ucsDomainBuf.get();
+ domainLen = ucsDomainBuf.Length() * 2;
+ WriteUnicodeLE(const_cast<void*>(domainPtr),
+ static_cast<const char16_t*>(domainPtr),
+ ucsDomainBuf.Length());
+#else
+ domainPtr = domain.get();
+ domainLen = domain.Length() * 2;
+#endif
+ } else {
+ NS_CopyUnicodeToNative(domain, oemDomainBuf);
+ domainPtr = oemDomainBuf.get();
+ domainLen = oemDomainBuf.Length();
+ }
+
+ //
+ // get user name
+ //
+ if (unicode) {
+#ifdef IS_BIG_ENDIAN
+ ucsUserBuf = username;
+ userPtr = ucsUserBuf.get();
+ userLen = ucsUserBuf.Length() * 2;
+ WriteUnicodeLE(const_cast<void*>(userPtr),
+ static_cast<const char16_t*>(userPtr), ucsUserBuf.Length());
+#else
+ userPtr = username.get();
+ userLen = username.Length() * 2;
+#endif
+ } else {
+ NS_CopyUnicodeToNative(username, oemUserBuf);
+ userPtr = oemUserBuf.get();
+ userLen = oemUserBuf.Length();
+ }
+
+ //
+ // get workstation name
+ // (do not use local machine's hostname after bug 1046421)
+ //
+ rv = mozilla::Preferences::GetCString("network.generic-ntlm-auth.workstation",
+ hostBuf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (unicode) {
+ CopyUTF8toUTF16(hostBuf, ucsHostBuf);
+ hostPtr = ucsHostBuf.get();
+ hostLen = ucsHostBuf.Length() * 2;
+#ifdef IS_BIG_ENDIAN
+ WriteUnicodeLE(const_cast<void*>(hostPtr),
+ static_cast<const char16_t*>(hostPtr), ucsHostBuf.Length());
+#endif
+ } else {
+ hostPtr = hostBuf.get();
+ hostLen = hostBuf.Length();
+ }
+
+ //
+ // now that we have generated all of the strings, we can allocate outBuf.
+ //
+ //
+ // next, we compute the NTLM or NTLM2 responses.
+ //
+ uint8_t lmResp[LM_RESP_LEN];
+ uint8_t ntlmResp[NTLM_RESP_LEN];
+ uint8_t ntlmv2Resp[NTLMv2_RESP_LEN];
+ uint8_t ntlmHash[NTLM_HASH_LEN];
+ uint8_t ntlmv2_blob1[NTLMv2_BLOB1_LEN];
+ if (ntlmv2) {
+ // NTLMv2 mode, the default
+ nsString userUpper, domainUpper;
+
+ // temporary buffers for unicode strings
+ nsAutoString ucsDomainUpperBuf;
+ nsAutoString ucsUserUpperBuf;
+ const void* domainUpperPtr;
+ const void* userUpperPtr;
+ uint32_t domainUpperLen;
+ uint32_t userUpperLen;
+
+ if (msg.targetInfoLen == 0) {
+ NS_ERROR("failed to get NTLMv2 target info, can not do NTLMv2");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ToUpperCase(username, ucsUserUpperBuf);
+ userUpperPtr = ucsUserUpperBuf.get();
+ userUpperLen = ucsUserUpperBuf.Length() * 2;
+#ifdef IS_BIG_ENDIAN
+ WriteUnicodeLE(const_cast<void*>(userUpperPtr),
+ static_cast<const char16_t*>(userUpperPtr),
+ ucsUserUpperBuf.Length());
+#endif
+ ToUpperCase(domain, ucsDomainUpperBuf);
+ domainUpperPtr = ucsDomainUpperBuf.get();
+ domainUpperLen = ucsDomainUpperBuf.Length() * 2;
+#ifdef IS_BIG_ENDIAN
+ WriteUnicodeLE(const_cast<void*>(domainUpperPtr),
+ static_cast<const char16_t*>(domainUpperPtr),
+ ucsDomainUpperBuf.Length());
+#endif
+
+ NTLM_Hash(password, ntlmHash);
+
+ mozilla::HMAC ntlmv2HashHmac;
+ rv = ntlmv2HashHmac.Begin(SEC_OID_MD5,
+ mozilla::Span(ntlmHash, NTLM_HASH_LEN));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ntlmv2HashHmac.Update(static_cast<const uint8_t*>(userUpperPtr),
+ userUpperLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ntlmv2HashHmac.Update(static_cast<const uint8_t*>(domainUpperPtr),
+ domainUpperLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> ntlmv2Hash;
+ rv = ntlmv2HashHmac.End(ntlmv2Hash);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint8_t client_random[NTLM_CHAL_LEN];
+ PK11_GenerateRandom(client_random, NTLM_CHAL_LEN);
+
+ mozilla::HMAC lmv2ResponseHmac;
+ rv = lmv2ResponseHmac.Begin(SEC_OID_MD5, mozilla::Span(ntlmv2Hash));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = lmv2ResponseHmac.Update(msg.challenge, NTLM_CHAL_LEN);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = lmv2ResponseHmac.Update(client_random, NTLM_CHAL_LEN);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> lmv2Response;
+ rv = lmv2ResponseHmac.End(lmv2Response);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (lmv2Response.Length() != NTLMv2_HASH_LEN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ memcpy(lmResp, lmv2Response.Elements(), NTLMv2_HASH_LEN);
+ memcpy(lmResp + NTLMv2_HASH_LEN, client_random, NTLM_CHAL_LEN);
+
+ memset(ntlmv2_blob1, 0, NTLMv2_BLOB1_LEN);
+
+ time_t unix_time;
+ uint64_t nt_time = time(&unix_time);
+ nt_time += 11644473600LL; // Number of seconds betwen 1601 and 1970
+ nt_time *= 1000 * 1000 * 10; // Convert seconds to 100 ns units
+
+ ntlmv2_blob1[0] = 1;
+ ntlmv2_blob1[1] = 1;
+ mozilla::LittleEndian::writeUint64(&ntlmv2_blob1[8], nt_time);
+ PK11_GenerateRandom(&ntlmv2_blob1[16], NTLM_CHAL_LEN);
+
+ mozilla::HMAC ntlmv2ResponseHmac;
+ rv = ntlmv2ResponseHmac.Begin(SEC_OID_MD5, mozilla::Span(ntlmv2Hash));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ntlmv2ResponseHmac.Update(msg.challenge, NTLM_CHAL_LEN);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ntlmv2ResponseHmac.Update(ntlmv2_blob1, NTLMv2_BLOB1_LEN);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ntlmv2ResponseHmac.Update(msg.targetInfo, msg.targetInfoLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsTArray<uint8_t> ntlmv2Response;
+ rv = ntlmv2ResponseHmac.End(ntlmv2Response);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (ntlmv2Response.Length() != NTLMv2_RESP_LEN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ memcpy(ntlmv2Resp, ntlmv2Response.Elements(), NTLMv2_RESP_LEN);
+ ntlmRespLen = NTLMv2_RESP_LEN + NTLMv2_BLOB1_LEN;
+ ntlmRespLen += msg.targetInfoLen;
+ if (!ntlmRespLen.isValid()) {
+ NS_ERROR("failed to do NTLMv2: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else if (msg.flags & NTLM_NegotiateNTLM2Key) {
+ // compute NTLM2 session response
+ nsCString sessionHashString;
+
+ PK11_GenerateRandom(lmResp, NTLM_CHAL_LEN);
+ memset(lmResp + NTLM_CHAL_LEN, 0, LM_RESP_LEN - NTLM_CHAL_LEN);
+
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = hasher->Init(nsICryptoHash::MD5);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = hasher->Update(msg.challenge, NTLM_CHAL_LEN);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = hasher->Update(lmResp, NTLM_CHAL_LEN);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = hasher->Finish(false, sessionHashString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ const auto* sessionHash = mozilla::BitwiseCast<const uint8_t*, const char*>(
+ sessionHashString.get());
+
+ LogBuf("NTLM2 effective key: ", sessionHash, 8);
+
+ NTLM_Hash(password, ntlmHash);
+ LM_Response(ntlmHash, sessionHash, ntlmResp);
+ } else {
+ NTLM_Hash(password, ntlmHash);
+ LM_Response(ntlmHash, msg.challenge, ntlmResp);
+
+ // According to http://davenport.sourceforge.net/ntlm.html#ntlmVersion2,
+ // the correct way to not send the LM hash is to send the NTLM hash twice
+ // in both the LM and NTLM response fields.
+ LM_Response(ntlmHash, msg.challenge, lmResp);
+ }
+
+ mozilla::CheckedInt<uint32_t> totalLen = NTLM_TYPE3_HEADER_LEN + LM_RESP_LEN;
+ totalLen += hostLen;
+ totalLen += domainLen;
+ totalLen += userLen;
+ totalLen += ntlmRespLen.value();
+
+ if (!totalLen.isValid()) {
+ NS_ERROR("failed preparing to allocate NTLM response: integer overflow?!?");
+ return NS_ERROR_FAILURE;
+ }
+ *outBuf = moz_xmalloc(totalLen.value());
+ *outLen = totalLen.value();
+
+ //
+ // finally, we assemble the Type-3 msg :-)
+ //
+ void* cursor = *outBuf;
+ mozilla::CheckedInt<uint32_t> offset;
+
+ // 0 : signature
+ cursor = WriteBytes(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+
+ // 8 : marker
+ cursor = WriteBytes(cursor, NTLM_TYPE3_MARKER, sizeof(NTLM_TYPE3_MARKER));
+
+ // 12 : LM response sec buf
+ offset = NTLM_TYPE3_HEADER_LEN;
+ offset += domainLen;
+ offset += userLen;
+ offset += hostLen;
+ if (!offset.isValid()) {
+ NS_ERROR("failed preparing to write NTLM response: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ cursor = WriteSecBuf(cursor, LM_RESP_LEN, offset.value());
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), lmResp, LM_RESP_LEN);
+
+ // 20 : NTLM or NTLMv2 response sec buf
+ offset += LM_RESP_LEN;
+ if (!offset.isValid()) {
+ NS_ERROR("failed preparing to write NTLM response: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ cursor = WriteSecBuf(cursor, ntlmRespLen.value(), offset.value());
+ if (ntlmv2) {
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), ntlmv2Resp,
+ NTLMv2_RESP_LEN);
+ offset += NTLMv2_RESP_LEN;
+ if (!offset.isValid()) {
+ NS_ERROR("failed preparing to write NTLM response: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), ntlmv2_blob1,
+ NTLMv2_BLOB1_LEN);
+ offset += NTLMv2_BLOB1_LEN;
+ if (!offset.isValid()) {
+ NS_ERROR("failed preparing to write NTLM response: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), msg.targetInfo,
+ msg.targetInfoLen);
+ } else {
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), ntlmResp,
+ NTLM_RESP_LEN);
+ }
+ // 28 : domain name sec buf
+ offset = NTLM_TYPE3_HEADER_LEN;
+ cursor = WriteSecBuf(cursor, domainLen, offset.value());
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), domainPtr, domainLen);
+
+ // 36 : user name sec buf
+ offset += domainLen;
+ if (!offset.isValid()) {
+ NS_ERROR("failed preparing to write NTLM response: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ cursor = WriteSecBuf(cursor, userLen, offset.value());
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), userPtr, userLen);
+
+ // 44 : workstation (host) name sec buf
+ offset += userLen;
+ if (!offset.isValid()) {
+ NS_ERROR("failed preparing to write NTLM response: integer overflow?!?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ cursor = WriteSecBuf(cursor, hostLen, offset.value());
+ memcpy(static_cast<uint8_t*>(*outBuf) + offset.value(), hostPtr, hostLen);
+
+ // 52 : session key sec buf (not used)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ // 60 : negotiated flags
+ cursor = WriteDWORD(cursor, msg.flags & NTLM_TYPE1_FLAGS);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsNTLMAuthModule, nsIAuthModule)
+
+nsNTLMAuthModule::~nsNTLMAuthModule() { ZapString(mPassword); }
+
+nsresult nsNTLMAuthModule::InitTest() {
+ // disable NTLM authentication when FIPS mode is enabled.
+ return PK11_IsFIPS() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsNTLMAuthModule::Init(const nsACString& serviceName, uint32_t serviceFlags,
+ const nsAString& domain, const nsAString& username,
+ const nsAString& password) {
+ MOZ_ASSERT((serviceFlags & ~nsIAuthModule::REQ_PROXY_AUTH) ==
+ nsIAuthModule::REQ_DEFAULT,
+ "Unexpected service flags");
+
+ mDomain = domain;
+ mUsername = username;
+ mPassword = password;
+ mNTLMNegotiateSent = false;
+
+ static bool sTelemetrySent = false;
+ if (!sTelemetrySent) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::NTLM_MODULE_USED_2,
+ serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
+ ? NTLM_MODULE_GENERIC_PROXY
+ : NTLM_MODULE_GENERIC_DIRECT);
+ sTelemetrySent = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNTLMAuthModule::GetNextToken(const void* inToken, uint32_t inTokenLen,
+ void** outToken, uint32_t* outTokenLen) {
+ nsresult rv;
+
+ // disable NTLM authentication when FIPS mode is enabled.
+ if (PK11_IsFIPS()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mNTLMNegotiateSent) {
+ // if inToken is non-null, and we have sent the NTLMSSP_NEGOTIATE (type 1),
+ // then the NTLMSSP_CHALLENGE (type 2) is expected
+ if (inToken) {
+ LogToken("in-token", inToken, inTokenLen);
+ // Now generate the NTLMSSP_AUTH (type 3)
+ rv = GenerateType3Msg(mDomain, mUsername, mPassword, inToken, inTokenLen,
+ outToken, outTokenLen);
+ } else {
+ LOG(
+ ("NTLMSSP_NEGOTIATE already sent and presumably "
+ "rejected by the server, refusing to send another"));
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ if (inToken) {
+ LOG(("NTLMSSP_NEGOTIATE not sent but NTLM reply already received?!?"));
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ rv = GenerateType1Msg(outToken, outTokenLen);
+ if (NS_SUCCEEDED(rv)) {
+ mNTLMNegotiateSent = true;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) LogToken("out-token", *outToken, *outTokenLen);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNTLMAuthModule::Unwrap(const void* inToken, uint32_t inTokenLen,
+ void** outToken, uint32_t* outTokenLen) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsNTLMAuthModule::Wrap(const void* inToken, uint32_t inTokenLen,
+ bool confidential, void** outToken,
+ uint32_t* outTokenLen) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// DES support code
+
+// set odd parity bit (in least significant bit position)
+static uint8_t des_setkeyparity(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;
+}
+
+// build 64-bit des key from 56-bit raw key
+static void des_makekey(const uint8_t* raw, uint8_t* key) {
+ key[0] = des_setkeyparity(raw[0]);
+ key[1] = des_setkeyparity((raw[0] << 7) | (raw[1] >> 1));
+ key[2] = des_setkeyparity((raw[1] << 6) | (raw[2] >> 2));
+ key[3] = des_setkeyparity((raw[2] << 5) | (raw[3] >> 3));
+ key[4] = des_setkeyparity((raw[3] << 4) | (raw[4] >> 4));
+ key[5] = des_setkeyparity((raw[4] << 3) | (raw[5] >> 5));
+ key[6] = des_setkeyparity((raw[5] << 2) | (raw[6] >> 6));
+ key[7] = des_setkeyparity((raw[6] << 1));
+}
+
+// run des encryption algorithm (using NSS)
+static void des_encrypt(const uint8_t* key, const uint8_t* src, uint8_t* hash) {
+ CK_MECHANISM_TYPE cipherMech = CKM_DES_ECB;
+ PK11SymKey* symkey = nullptr;
+ PK11Context* ctxt = nullptr;
+ SECItem keyItem;
+ mozilla::UniqueSECItem param;
+ SECStatus rv;
+ unsigned int n;
+
+ mozilla::UniquePK11SlotInfo slot(PK11_GetBestSlot(cipherMech, nullptr));
+ if (!slot) {
+ NS_ERROR("no slot");
+ goto done;
+ }
+
+ keyItem.data = const_cast<uint8_t*>(key);
+ keyItem.len = 8;
+ symkey = PK11_ImportSymKey(slot.get(), cipherMech, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &keyItem, nullptr);
+ if (!symkey) {
+ NS_ERROR("no symkey");
+ goto done;
+ }
+
+ // no initialization vector required
+ param = mozilla::UniqueSECItem(PK11_ParamFromIV(cipherMech, nullptr));
+ if (!param) {
+ NS_ERROR("no param");
+ goto done;
+ }
+
+ ctxt =
+ PK11_CreateContextBySymKey(cipherMech, CKA_ENCRYPT, symkey, param.get());
+ if (!ctxt) {
+ NS_ERROR("no context");
+ goto done;
+ }
+
+ rv = PK11_CipherOp(ctxt, hash, (int*)&n, 8, (uint8_t*)src, 8);
+ if (rv != SECSuccess) {
+ NS_ERROR("des failure");
+ goto done;
+ }
+
+ rv = PK11_DigestFinal(ctxt, hash + 8, &n, 0);
+ if (rv != SECSuccess) {
+ NS_ERROR("des failure");
+ goto done;
+ }
+
+done:
+ if (ctxt) PK11_DestroyContext(ctxt, true);
+ if (symkey) PK11_FreeSymKey(symkey);
+}