Repository: denisenkom/go-mssqldb
Pull request #570 (2020-04-27 11:07:12)

Fix MR "Add support for NTLMv2 (#551)".
Domain has to be used from connection string, the user domain was used from the remote server.
The user domain sensitive to the case which is defined in the connection string, the fix can break some user apps, which has set domain wrong case before.

I use https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/5e550938-91d4-459f-b67d-75d70009e3f3

codecov[bot]: # [Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570?src=pr&el=h1) Report
> Merging [#570](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570?src=pr&el=desc) into [master](https://codecov.io/gh/denisenkom/go-mssqldb/commit/bbfc9a55622eeb8e47808e0f7270efcf5ae3dce2&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570/graphs/tree.svg?width=650&height=150&src=pr&token=MoMB2u5avp)](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #570   +/-   ##
=======================================
  Coverage   68.72%   68.72%           
=======================================
  Files          23       23           
  Lines        5190     5190           
=======================================
  Hits         3567     3567           
  Misses       1410     1410           
  Partials      213      213           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570?src=pr&el=footer). Last update [bbfc9a5...77512d7](https://codecov.io/gh/denisenkom/go-mssqldb/pull/570?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

kardianos: LGTM, waiting for user feedback on issue. Will merge if no feedback.

Patch:
diff --git a/README.md b/README.md
index 46e4eea..94d87fe 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ Other supported formats are listed below.
 
 ### Common parameters:
 
-* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used.
+* `user id` - enter the SQL Server Authentication user id or the Windows Authentication user id in the DOMAIN\User format. On Windows, if user id is empty or missing Single-Sign-On is used. The user domain sensitive to the case which is defined in the connection string.
 * `password`
 * `database`
 * `connection timeout` - in seconds (default is 0 for no timeout), set to 0 for no timeout. Recommended to set to 0 and use context to manage query and connection timeouts.
diff --git a/ntlm.go b/ntlm.go
index 3ba48ff..ea9148a 100644
--- a/ntlm.go
+++ b/ntlm.go
@@ -217,11 +217,11 @@ func hmacMD5(passwordHash, data []byte) []byte {
 	return hmacEntity.Sum(nil)
 }
 
-func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) {
+func getNTLMv2AndLMv2ResponsePayloads(userDomain, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) {
 	// NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response
 
 	ntlmHash := ntlmHashNoPadding(password)
-	usernameAndTargetBytes := utf16le(strings.ToUpper(username) + target)
+	usernameAndTargetBytes := utf16le(strings.ToUpper(username) + userDomain)
 	ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes)
 	targetInfoLength := len(targetInfoFields)
 	blob := make([]byte, 32+targetInfoLength)
@@ -234,7 +234,7 @@ func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challen
 	binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000)
 	challengeLength := len(challenge)
 	blobLength := len(blob)
-	challengeAndBlob := make([]byte, challengeLength + blobLength)
+	challengeAndBlob := make([]byte, challengeLength+blobLength)
 	copy(challengeAndBlob[:challengeLength], challenge[:])
 	copy(challengeAndBlob[challengeLength:], blob)
 	hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob)
@@ -251,18 +251,18 @@ func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challen
 	return
 }
 
-func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password string) (lm, nt []byte, err error) {
+func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password, userDom string) (lm, nt []byte, err error) {
 	nonce := clientChallenge()
 
 	// Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
 	// Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html
 	if (flags & _NEGOTIATE_TARGET_INFO) != 0 {
-		targetInfoFields, target, err := getNTLMv2TargetInfoFields(message)
+		targetInfoFields, err := getNTLMv2TargetInfoFields(message)
 		if err != nil {
 			return lm, nt, err
 		}
 
-		nt, lm = getNTLMv2AndLMv2ResponsePayloads(target, username, password, challenge, nonce, targetInfoFields, time.Now())
+		nt, lm = getNTLMv2AndLMv2ResponsePayloads(userDom, username, password, challenge, nonce, targetInfoFields, time.Now())
 
 		return lm, nt, nil
 	}
@@ -276,36 +276,31 @@ func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8
 	return lm, nt, nil
 }
 
-func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, target string, err error) {
+func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) {
 	type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d"
 	type2MessageLength := len(type2Message)
 	if type2MessageLength < 20 {
-		return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, 20)
+		return nil, fmt.Errorf(type2MessageError, type2MessageLength, 20)
 	}
 
 	targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16])
 	targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20])
 	endOfOffset := int(targetNameOffset + uint32(targetNameAllocated))
 	if type2MessageLength < endOfOffset {
-		return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
-	}
-
-	target, err = ucs22str(type2Message[targetNameOffset:targetNameOffset+uint32(targetNameAllocated)])
-	if err != nil {
-		return nil, target, err
+		return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
 	}
 
 	targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44])
 	targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48])
 	endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated))
 	if type2MessageLength < endOfOffset {
-		return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
+		return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
 	}
 
 	targetInformationBytes := make([]byte, targetInformationAllocated)
 	copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)])
 
-	return targetInformationBytes, target, nil
+	return targetInformationBytes, nil
 }
 
 func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) {
@@ -389,7 +384,7 @@ func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) {
 	copy(challenge[:], bytes[24:32])
 	flags := binary.LittleEndian.Uint32(bytes[20:24])
 	if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 {
-		lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password)
+		lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password, auth.Domain)
 		if err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/ntlm_test.go b/ntlm_test.go
index a85317e..607d2c2 100644
--- a/ntlm_test.go
+++ b/ntlm_test.go
@@ -81,7 +81,7 @@ func TestNTLMV2Response(t *testing.T) {
 	target := "DOMAIN"
 	username := "user"
 	password := "SecREt01"
-	challenge := [8]byte{ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
+	challenge := [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
 	targetInformationBlock, _ := hex.DecodeString("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000")
 	nonceBytes, _ := hex.DecodeString("ffffff0011223344")
 	var nonce [8]byte
@@ -105,16 +105,11 @@ func TestNTLMV2Response(t *testing.T) {
 
 func TestGetNTLMv2TargetInfoFields(t *testing.T) {
 	type2Message, _ := hex.DecodeString("4e544c4d53535000020000000600060038000000058289026999bc21067c77f40000000000000000ac00ac003e0000000a0039380000000f4600570042000200060046005700420001000c00590037004100410041003400040022006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00030030007900370041004100410034002e006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00050024006100610058006d002e00610058006e0071006e0070006e00650074002e0063006f006d00070008007d9647e8aed6d50100000000")
-	info, target, err := getNTLMv2TargetInfoFields(type2Message)
+	info, err := getNTLMv2TargetInfoFields(type2Message)
 	if err != nil {
 		t.Errorf("got:\n%e\nexpected:\nnil", err)
 	}
 
-	expectedTarget := "FWB"
-	if target != expectedTarget {
-		t.Errorf("got:\n%s\nexpected:\n%s", target, expectedTarget)
-	}
-
 	expectedResponseLength := 172
 	responseLength := len(info)
 	if responseLength != expectedResponseLength {
@@ -124,7 +119,7 @@ func TestGetNTLMv2TargetInfoFields(t *testing.T) {
 
 func TestGetNTLMv2TargetInfoFieldsInvalidMessage(t *testing.T) {
 	type2Message, _ := hex.DecodeString("4e544c4d53535000020000000600060038000000058289026999bc21067c77f40000000000000000ac00ac003e0000000a0039380000000f4600570042000200060046005700420001000c00590037004100410041003400040022006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00030030007900370041004100410034002e006000700065002e00610058006e0071006e0070006e00650074002e0063006f006d00050024006100610058006d002e00610058006e0071006e0070006e00650074002e0063006f006d00070008007")
-	_, _, err := getNTLMv2TargetInfoFields(type2Message)
+	_, err := getNTLMv2TargetInfoFields(type2Message)
 	if err == nil {
 		t.Error("expected to get an error")
 	}