package  ntlm 
 
import  ( 
	"bytes"  
	"crypto/hmac"  
	"crypto/md5"  
	"crypto/rand"  
	"crypto/rc4"  
	"errors"  
	"hash"  
	"strings"  
	"time"  
 
	"github.com/hirochachacha/go-smb2/internal/utf16le"  
) 
 
 
type  Client  struct  { 
	User        string  
	Password    string  
	Hash        []byte  
	Domain      string   
	Workstation string   
 
	TargetSPN       string             
	channelBindings *channelBindings   
 
	nmsg    []byte  
	session *Session  
} 
 
func  (c  *Client ) Negotiate () (nmsg  []byte , err  error ) { 
	 
 
 
 
 
 
 
 
 
	off  := 32  + 8  
 
	nmsg  = make ([]byte , off ) 
 
	copy (nmsg [:8 ], signature ) 
	le .PutUint32 (nmsg [8 :12 ], NtLmNegotiate ) 
	le .PutUint32 (nmsg [12 :16 ], defaultFlags ) 
 
	copy (nmsg [32 :], version ) 
 
	c .nmsg  = nmsg  
 
	return  nmsg , nil  
} 
 
func  (c  *Client ) Authenticate (cmsg  []byte ) (amsg  []byte , err  error ) { 
	 
 
 
 
 
 
 
 
 
 
 
	if  len (cmsg ) < 48  { 
		return  nil , errors .New ("message length is too short" ) 
	} 
 
	if  !bytes .Equal (cmsg [:8 ], signature ) { 
		return  nil , errors .New ("invalid signature" ) 
	} 
 
	if  le .Uint32 (cmsg [8 :12 ]) != NtLmChallenge  { 
		return  nil , errors .New ("invalid message type" ) 
	} 
 
	flags  := le .Uint32 (c .nmsg [12 :16 ]) & le .Uint32 (cmsg [20 :24 ]) 
 
	if  flags &NTLMSSP_REQUEST_TARGET  == 0  { 
		return  nil , errors .New ("invalid negotiate flags" ) 
	} 
 
	targetNameLen  := le .Uint16 (cmsg [12 :14 ])     
	targetNameMaxLen  := le .Uint16 (cmsg [14 :16 ])  
	if  targetNameMaxLen  < targetNameLen  { 
		return  nil , errors .New ("invalid target name format" ) 
	} 
	targetNameBufferOffset  := le .Uint32 (cmsg [16 :20 ])  
	if  len (cmsg ) < int (targetNameBufferOffset +uint32 (targetNameLen )) { 
		return  nil , errors .New ("invalid target name format" ) 
	} 
	targetName  := cmsg [targetNameBufferOffset  : targetNameBufferOffset +uint32 (targetNameLen )]  
 
	if  flags &NTLMSSP_NEGOTIATE_TARGET_INFO  == 0  { 
		return  nil , errors .New ("invalid negotiate flags" ) 
	} 
 
	targetInfoLen  := le .Uint16 (cmsg [40 :42 ])     
	targetInfoMaxLen  := le .Uint16 (cmsg [42 :44 ])  
	if  targetInfoMaxLen  < targetInfoLen  { 
		return  nil , errors .New ("invalid target info format" ) 
	} 
	targetInfoBufferOffset  := le .Uint32 (cmsg [44 :48 ])  
	if  len (cmsg ) < int (targetInfoBufferOffset +uint32 (targetInfoLen )) { 
		return  nil , errors .New ("invalid target info format" ) 
	} 
	targetInfo  := cmsg [targetInfoBufferOffset  : targetInfoBufferOffset +uint32 (targetInfoLen )]  
	info  := newTargetInfoEncoder (targetInfo , utf16le .EncodeStringToBytes (c .TargetSPN )) 
	if  info  == nil  { 
		return  nil , errors .New ("invalid target info format" ) 
	} 
 
	 
 
 
 
 
 
 
 
 
 
 
 
 
 
	off  := 64  + 8  + 16  
 
	domain  := utf16le .EncodeStringToBytes (c .Domain ) 
	user  := utf16le .EncodeStringToBytes (c .User ) 
	workstation  := utf16le .EncodeStringToBytes (c .Workstation ) 
 
	if  domain  == nil  { 
		domain  = targetName  
	} 
 
	 
 
 
 
 
 
 
 
 
	amsg  = make ([]byte , off +len (domain )+len (user )+len (workstation )+ 
		24 + 
		(16 +(28 +info .size ()+4 ))+ 
		16 ) 
 
	copy (amsg [:8 ], signature ) 
	le .PutUint32 (amsg [8 :12 ], NtLmAuthenticate ) 
 
	if  domain  != nil  { 
		len  := copy (amsg [off :], domain ) 
		le .PutUint16 (amsg [28 :30 ], uint16 (len )) 
		le .PutUint16 (amsg [30 :32 ], uint16 (len )) 
		le .PutUint32 (amsg [32 :36 ], uint32 (off )) 
		off  += len  
	} 
 
	if  user  != nil  { 
		len  := copy (amsg [off :], user ) 
		le .PutUint16 (amsg [36 :38 ], uint16 (len )) 
		le .PutUint16 (amsg [38 :40 ], uint16 (len )) 
		le .PutUint32 (amsg [40 :44 ], uint32 (off )) 
		off  += len  
	} 
 
	if  workstation  != nil  { 
		len  := copy (amsg [off :], workstation ) 
		le .PutUint16 (amsg [44 :46 ], uint16 (len )) 
		le .PutUint16 (amsg [46 :48 ], uint16 (len )) 
		le .PutUint32 (amsg [48 :52 ], uint32 (off )) 
		off  += len  
	} 
 
	if  c .User  != ""  || c .Password  != ""  || c .Hash  != nil  { 
		var  err  error  
		var  h  hash .Hash  
 
		if  c .Hash  != nil  { 
			USER  := utf16le .EncodeStringToBytes (strings .ToUpper (c .User )) 
 
			h  = hmac .New (md5 .New , ntowfv2Hash (USER , c .Hash , domain )) 
		} else  { 
			USER  := utf16le .EncodeStringToBytes (strings .ToUpper (c .User )) 
			password  := utf16le .EncodeStringToBytes (c .Password ) 
 
			h  = hmac .New (md5 .New , ntowfv2 (USER , password , domain )) 
		} 
 
		 
 
 
 
		lmChallengeResponse  := amsg [off  : off +24 ] 
		{ 
			le .PutUint16 (amsg [12 :14 ], uint16 (len (lmChallengeResponse ))) 
			le .PutUint16 (amsg [14 :16 ], uint16 (len (lmChallengeResponse ))) 
			le .PutUint32 (amsg [16 :20 ], uint32 (off )) 
 
			off  += 24  
		} 
 
		 
 
 
 
		ntChallengeResponse  := amsg [off  : len (amsg )-16 ] 
		{ 
			ntlmv2ClientChallenge  := ntChallengeResponse [16 :] 
 
			 
 
 
 
 
 
 
 
 
 
			serverChallenge  := cmsg [24 :32 ] 
 
			clientChallenge  := ntlmv2ClientChallenge [16 :24 ] 
 
			_ , err  := rand .Read (clientChallenge ) 
			if  err  != nil  { 
				return  nil , err  
			} 
 
			timeStamp , ok  := info .InfoMap [MsvAvTimestamp ] 
			if  !ok  { 
				timeStamp  = ntlmv2ClientChallenge [8 :16 ] 
				le .PutUint64 (timeStamp , uint64 ((time .Now ().UnixNano ()/100 )+116444736000000000 )) 
			} 
 
			encodeNtlmv2Response (ntChallengeResponse , h , serverChallenge , clientChallenge , timeStamp , info ) 
 
			le .PutUint16 (amsg [20 :22 ], uint16 (len (ntChallengeResponse ))) 
			le .PutUint16 (amsg [22 :24 ], uint16 (len (ntChallengeResponse ))) 
			le .PutUint32 (amsg [24 :28 ], uint32 (off )) 
 
			off  = len (amsg ) - 16  
		} 
 
		session  := new (Session ) 
 
		session .isClientSide  = true  
 
		session .user  = c .User  
		session .negotiateFlags  = flags  
		session .infoMap  = info .InfoMap  
 
		h .Reset () 
		h .Write (ntChallengeResponse [:16 ]) 
		sessionBaseKey  := h .Sum (nil ) 
 
		keyExchangeKey  := sessionBaseKey   
 
		if  flags &NTLMSSP_NEGOTIATE_KEY_EXCH  != 0  { 
			session .exportedSessionKey  = make ([]byte , 16 ) 
			_ , err  := rand .Read (session .exportedSessionKey ) 
			if  err  != nil  { 
				return  nil , err  
			} 
			cipher , err  := rc4 .NewCipher (keyExchangeKey ) 
			if  err  != nil  { 
				return  nil , err  
			} 
			encryptedRandomSessionKey  := amsg [off :] 
			cipher .XORKeyStream (encryptedRandomSessionKey , session .exportedSessionKey ) 
 
			le .PutUint16 (amsg [52 :54 ], 16 )           
			le .PutUint16 (amsg [54 :56 ], 16 )           
			le .PutUint32 (amsg [56 :60 ], uint32 (off ))  
		} else  { 
			session .exportedSessionKey  = keyExchangeKey  
		} 
 
		le .PutUint32 (amsg [60 :64 ], flags ) 
 
		copy (amsg [64 :], version ) 
		h  = hmac .New (md5 .New , session .exportedSessionKey ) 
		h .Write (c .nmsg ) 
		h .Write (cmsg ) 
		h .Write (amsg ) 
		h .Sum (amsg [:72 ])  
 
		{ 
			session .clientSigningKey  = signKey (flags , session .exportedSessionKey , true ) 
			session .serverSigningKey  = signKey (flags , session .exportedSessionKey , false ) 
 
			session .clientHandle , err  = rc4 .NewCipher (sealKey (flags , session .exportedSessionKey , true )) 
			if  err  != nil  { 
				return  nil , err  
			} 
 
			session .serverHandle , err  = rc4 .NewCipher (sealKey (flags , session .exportedSessionKey , false )) 
			if  err  != nil  { 
				return  nil , err  
			} 
		} 
 
		c .session  = session  
	} 
 
	return  amsg , nil  
} 
 
func  (c  *Client ) Session () *Session  { 
	return  c .session  
} 
  
The pages are generated with Golds   v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds  is a Go 101  project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1  (reachable from the left QR code) to get the latest news of Golds .