package ntlm
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rc4"
"errors"
"strings"
"github.com/hirochachacha/go-smb2/internal/utf16le"
)
type Server struct {
targetName string
accounts map [string ]string
nmsg []byte
cmsg []byte
session *Session
}
func NewServer (targetName string ) *Server {
return &Server {
targetName : targetName ,
accounts : make (map [string ]string ),
}
}
func (s *Server ) AddAccount (user , password string ) {
s .accounts [user ] = password
}
func (s *Server ) Challenge (nmsg []byte ) (cmsg []byte , err error ) {
s .nmsg = nmsg
if len (nmsg ) < 32 {
return nil , errors .New ("message length is too short" )
}
if !bytes .Equal (nmsg [:8 ], signature ) {
return nil , errors .New ("invalid signature" )
}
if le .Uint32 (nmsg [8 :12 ]) != NtLmNegotiate {
return nil , errors .New ("invalid message type" )
}
flags := le .Uint32 (nmsg [12 :16 ]) & defaultFlags
off := 48
if flags &NTLMSSP_NEGOTIATE_VERSION != 0 {
off += 8
}
targetName := utf16le .EncodeStringToBytes (s .targetName )
cmsg = make ([]byte , off +len (targetName )+4 )
copy (cmsg [:8 ], signature )
le .PutUint32 (cmsg [8 :12 ], NtLmChallenge )
le .PutUint32 (cmsg [20 :24 ], flags )
if targetName != nil && flags &NTLMSSP_REQUEST_TARGET != 0 {
len := copy (cmsg [off :], targetName )
le .PutUint16 (cmsg [12 :14 ], uint16 (len ))
le .PutUint16 (cmsg [14 :16 ], uint16 (len ))
le .PutUint32 (cmsg [16 :20 ], uint32 (off ))
off += len
}
if flags &NTLMSSP_NEGOTIATE_TARGET_INFO != 0 {
len := copy (cmsg [off :], []byte {0x00 , 0x00 , 0x00 , 0x00 })
le .PutUint16 (cmsg [40 :42 ], uint16 (len ))
le .PutUint16 (cmsg [42 :44 ], uint16 (len ))
le .PutUint32 (cmsg [44 :48 ], uint32 (off ))
off += len
}
_, err = rand .Read (cmsg [24 :32 ])
if err != nil {
return nil , err
}
if flags &NTLMSSP_NEGOTIATE_VERSION != 0 {
copy (cmsg [48 :56 ], version )
}
s .cmsg = cmsg
return cmsg , nil
}
func (s *Server ) Authenticate (amsg []byte ) (err error ) {
if len (amsg ) < 64 {
return errors .New ("message length is too short" )
}
if !bytes .Equal (amsg [:8 ], signature ) {
return errors .New ("invalid signature" )
}
if le .Uint32 (amsg [8 :12 ]) != NtLmAuthenticate {
return errors .New ("invalid message type" )
}
flags := le .Uint32 (amsg [60 :64 ])
ntChallengeResponseLen := le .Uint16 (amsg [20 :22 ])
ntChallengeResponseMaxLen := le .Uint16 (amsg [22 :24 ])
if ntChallengeResponseMaxLen < ntChallengeResponseLen {
return errors .New ("invalid LM challenge format" )
}
ntChallengeResponseBufferOffset := le .Uint32 (amsg [24 :28 ])
if len (amsg ) < int (ntChallengeResponseBufferOffset +uint32 (ntChallengeResponseLen )) {
return errors .New ("invalid LM challenge format" )
}
ntChallengeResponse := amsg [ntChallengeResponseBufferOffset : ntChallengeResponseBufferOffset +uint32 (ntChallengeResponseLen )]
domainNameLen := le .Uint16 (amsg [28 :30 ])
domainNameMaxLen := le .Uint16 (amsg [30 :32 ])
if domainNameMaxLen < domainNameLen {
return errors .New ("invalid domain name format" )
}
domainNameBufferOffset := le .Uint32 (amsg [32 :36 ])
if len (amsg ) < int (domainNameBufferOffset +uint32 (domainNameLen )) {
return errors .New ("invalid domain name format" )
}
domainName := amsg [domainNameBufferOffset : domainNameBufferOffset +uint32 (domainNameLen )]
userNameLen := le .Uint16 (amsg [36 :38 ])
userNameMaxLen := le .Uint16 (amsg [38 :40 ])
if userNameMaxLen < userNameLen {
return errors .New ("invalid user name format" )
}
userNameBufferOffset := le .Uint32 (amsg [40 :44 ])
if len (amsg ) < int (userNameBufferOffset +uint32 (userNameLen )) {
return errors .New ("invalid user name format" )
}
userName := amsg [userNameBufferOffset : userNameBufferOffset +uint32 (userNameLen )]
encryptedRandomSessionKeyLen := le .Uint16 (amsg [52 :54 ])
encryptedRandomSessionKeyMaxLen := le .Uint16 (amsg [54 :56 ])
if encryptedRandomSessionKeyMaxLen < encryptedRandomSessionKeyLen {
return errors .New ("invalid user name format" )
}
encryptedRandomSessionKeyBufferOffset := le .Uint32 (amsg [56 :60 ])
if len (amsg ) < int (encryptedRandomSessionKeyBufferOffset +uint32 (encryptedRandomSessionKeyLen )) {
return errors .New ("invalid user name format" )
}
encryptedRandomSessionKey := amsg [encryptedRandomSessionKeyBufferOffset : encryptedRandomSessionKeyBufferOffset +uint32 (encryptedRandomSessionKeyLen )]
if len (userName ) != 0 || len (ntChallengeResponse ) != 0 {
user := utf16le .DecodeToString (userName )
expectedNtChallengeResponse := make ([]byte , len (ntChallengeResponse ))
ntlmv2ClientChallenge := ntChallengeResponse [16 :]
USER := utf16le .EncodeStringToBytes (strings .ToUpper (user ))
password := utf16le .EncodeStringToBytes (s .accounts [user ])
h := hmac .New (md5 .New , ntowfv2 (USER , password , domainName ))
serverChallenge := s .cmsg [24 :32 ]
timeStamp := ntlmv2ClientChallenge [8 :16 ]
clientChallenge := ntlmv2ClientChallenge [16 :24 ]
targetInfo := ntlmv2ClientChallenge [28 :]
encodeNtlmv2Response (expectedNtChallengeResponse , h , serverChallenge , clientChallenge , timeStamp , bytesEncoder (targetInfo ))
if !bytes .Equal (ntChallengeResponse , expectedNtChallengeResponse ) {
return errors .New ("login failure" )
}
session := new (Session )
session .isClientSide = false
session .user = user
session .negotiateFlags = flags
h .Reset ()
h .Write (ntChallengeResponse [:16 ])
sessionBaseKey := h .Sum (nil )
keyExchangeKey := sessionBaseKey
if flags &NTLMSSP_NEGOTIATE_KEY_EXCH != 0 {
session .exportedSessionKey = make ([]byte , 16 )
cipher , err := rc4 .NewCipher (keyExchangeKey )
if err != nil {
return err
}
cipher .XORKeyStream (session .exportedSessionKey , encryptedRandomSessionKey )
} else {
session .exportedSessionKey = keyExchangeKey
}
if infoMap , ok := parseAvPairs (targetInfo ); ok {
if avFlags , ok := infoMap [MsvAvFlags ]; ok && le .Uint32 (avFlags )&0x02 != 0 {
MIC := make ([]byte , 16 )
if flags &NTLMSSP_NEGOTIATE_VERSION != 0 {
copy (MIC , amsg [72 :88 ])
copy (amsg [72 :88 ], zero [:])
} else {
copy (MIC , amsg [64 :80 ])
copy (amsg [64 :80 ], zero [:])
}
h = hmac .New (md5 .New , session .exportedSessionKey )
h .Write (s .nmsg )
h .Write (s .cmsg )
h .Write (amsg )
if !bytes .Equal (MIC , h .Sum (nil )) {
return errors .New ("login failure" )
}
}
}
{
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 err
}
session .serverHandle , err = rc4 .NewCipher (sealKey (flags , session .exportedSessionKey , false ))
if err != nil {
return err
}
}
s .session = session
return nil
}
return errors .New ("credential is empty" )
}
func (s *Server ) Session () *Session {
return s .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 .