// +build !windows package mssql import ( "crypto/des" "crypto/md5" "crypto/rand" "encoding/binary" "errors" "strings" "unicode/utf16" "golang.org/x/crypto/md4" ) const ( NEGOTIATE_MESSAGE = 1 CHALLENGE_MESSAGE = 2 AUTHENTICATE_MESSAGE = 3 ) const ( NEGOTIATE_UNICODE = 0x00000001 NEGOTIATE_OEM = 0x00000002 NEGOTIATE_TARGET = 0x00000004 NEGOTIATE_SIGN = 0x00000010 NEGOTIATE_SEAL = 0x00000020 NEGOTIATE_DATAGRAM = 0x00000040 NEGOTIATE_LMKEY = 0x00000080 NEGOTIATE_NTLM = 0x00000200 NEGOTIATE_ANONYMOUS = 0x00000800 NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000 NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000 NEGOTIATE_ALWAYS_SIGN = 0x00008000 NEGOTIATE_TARGET_TYPE_DOMAIN = 0x00010000 NEGOTIATE_TARGET_TYPE_SERVER = 0x00020000 NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000 NEGOTIATE_IDENTIFY = 0x00100000 REQUEST_NON_NT_SESSION_KEY = 0x00400000 NEGOTIATE_TARGET_INFO = 0x00800000 NEGOTIATE_VERSION = 0x02000000 NEGOTIATE_128 = 0x20000000 NEGOTIATE_KEY_EXCH = 0x40000000 NEGOTIATE_56 = 0x80000000 ) const NEGOTIATE_FLAGS = NEGOTIATE_UNICODE | NEGOTIATE_NTLM | NEGOTIATE_OEM_DOMAIN_SUPPLIED | NEGOTIATE_OEM_WORKSTATION_SUPPLIED | NEGOTIATE_ALWAYS_SIGN | NEGOTIATE_EXTENDED_SESSIONSECURITY type NTLMAuth struct { Domain string UserName string Password string Workstation string } func getAuth(user, password, service, workstation string) (auth, bool) { if !strings.ContainsRune(user, '\\') { return nil, false } domain_user := strings.SplitN(user, "\\", 2) return &NTLMAuth{ Domain: domain_user[0], UserName: domain_user[1], Password: password, Workstation: workstation, }, true } func utf16le(val string) []byte { var v []byte for _, r := range val { if utf16.IsSurrogate(r) { r1, r2 := utf16.EncodeRune(r) v = append(v, byte(r1), byte(r1>>8)) v = append(v, byte(r2), byte(r2>>8)) } else { v = append(v, byte(r), byte(r>>8)) } } return v } func (auth *NTLMAuth) InitialBytes() ([]byte, error) { domain_len := len(auth.Domain) workstation_len := len(auth.Workstation) msg := make([]byte, 40+domain_len+workstation_len) copy(msg, []byte("NTLMSSP\x00")) binary.LittleEndian.PutUint32(msg[8:], NEGOTIATE_MESSAGE) binary.LittleEndian.PutUint32(msg[12:], NEGOTIATE_FLAGS) // Domain Name Fields binary.LittleEndian.PutUint16(msg[16:], uint16(domain_len)) binary.LittleEndian.PutUint16(msg[18:], uint16(domain_len)) binary.LittleEndian.PutUint32(msg[20:], 40) // Workstation Fields binary.LittleEndian.PutUint16(msg[24:], uint16(workstation_len)) binary.LittleEndian.PutUint16(msg[26:], uint16(workstation_len)) binary.LittleEndian.PutUint32(msg[28:], uint32(40+domain_len)) // Version binary.LittleEndian.PutUint32(msg[32:], 0) binary.LittleEndian.PutUint32(msg[36:], 0) // Payload copy(msg[40:], auth.Domain) copy(msg[40+domain_len:], auth.Workstation) return msg, nil } var errorNTLM = errors.New("NTLM protocol error") func createDesKey(bytes, material []byte) { material[0] = bytes[0] material[1] = (byte)(bytes[0]<<7 | (bytes[1]&0xff)>>1) material[2] = (byte)(bytes[1]<<6 | (bytes[2]&0xff)>>2) material[3] = (byte)(bytes[2]<<5 | (bytes[3]&0xff)>>3) material[4] = (byte)(bytes[3]<<4 | (bytes[4]&0xff)>>4) material[5] = (byte)(bytes[4]<<3 | (bytes[5]&0xff)>>5) material[6] = (byte)(bytes[5]<<2 | (bytes[6]&0xff)>>6) material[7] = (byte)(bytes[6] << 1) } func oddParity(bytes []byte) { for i := 0; i < len(bytes); i++ { b := bytes[i] needsParity := (((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0 if needsParity { bytes[i] = bytes[i] | byte(0x01) } else { bytes[i] = bytes[i] & byte(0xfe) } } } func encryptDes(key []byte, cleartext []byte, ciphertext []byte) { var desKey [8]byte createDesKey(key, desKey[:]) cipher, err := des.NewCipher(desKey[:]) if err != nil { panic(err) } cipher.Encrypt(ciphertext, cleartext) } func response(challenge [8]byte, hash [21]byte) (ret [24]byte) { encryptDes(hash[:7], challenge[:], ret[:8]) encryptDes(hash[7:14], challenge[:], ret[8:16]) encryptDes(hash[14:], challenge[:], ret[16:]) return } func lmHash(password string) (hash [21]byte) { var lmpass [14]byte copy(lmpass[:14], []byte(strings.ToUpper(password))) magic := []byte("KGS!@#$%") encryptDes(lmpass[:7], magic, hash[:8]) encryptDes(lmpass[7:], magic, hash[8:]) return } func lmResponse(challenge [8]byte, password string) [24]byte { hash := lmHash(password) return response(challenge, hash) } func ntlmHash(password string) (hash [21]byte) { h := md4.New() h.Write(utf16le(password)) h.Sum(hash[:0]) return } func ntResponse(challenge [8]byte, password string) [24]byte { hash := ntlmHash(password) return response(challenge, hash) } func clientChallenge() (nonce [8]byte) { _, err := rand.Read(nonce[:]) if err != nil { panic(err) } return } func ntlmSessionResponse(clientNonce [8]byte, serverChallenge [8]byte, password string) [24]byte { var sessionHash [16]byte h := md5.New() h.Write(serverChallenge[:]) h.Write(clientNonce[:]) h.Sum(sessionHash[:0]) var hash [8]byte copy(hash[:], sessionHash[:8]) passwordHash := ntlmHash(password) return response(hash, passwordHash) } func (auth *NTLMAuth) NextBytes(bytes []byte) ([]byte, error) { if string(bytes[0:8]) != "NTLMSSP\x00" { return nil, errorNTLM } if binary.LittleEndian.Uint32(bytes[8:12]) != CHALLENGE_MESSAGE { return nil, errorNTLM } flags := binary.LittleEndian.Uint32(bytes[20:24]) var challenge [8]byte copy(challenge[:], bytes[24:32]) var lm, nt []byte if (flags & NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 { nonce := clientChallenge() var lm_bytes [24]byte copy(lm_bytes[:8], nonce[:]) lm = lm_bytes[:] nt_bytes := ntlmSessionResponse(nonce, challenge, auth.Password) nt = nt_bytes[:] } else { lm_bytes := lmResponse(challenge, auth.Password) lm = lm_bytes[:] nt_bytes := ntResponse(challenge, auth.Password) nt = nt_bytes[:] } lm_len := len(lm) nt_len := len(nt) domain16 := utf16le(auth.Domain) domain_len := len(domain16) user16 := utf16le(auth.UserName) user_len := len(user16) workstation16 := utf16le(auth.Workstation) workstation_len := len(workstation16) msg := make([]byte, 88+lm_len+nt_len+domain_len+user_len+workstation_len) copy(msg, []byte("NTLMSSP\x00")) binary.LittleEndian.PutUint32(msg[8:], AUTHENTICATE_MESSAGE) // Lm Challenge Response Fields binary.LittleEndian.PutUint16(msg[12:], uint16(lm_len)) binary.LittleEndian.PutUint16(msg[14:], uint16(lm_len)) binary.LittleEndian.PutUint32(msg[16:], 88) // Nt Challenge Response Fields binary.LittleEndian.PutUint16(msg[20:], uint16(nt_len)) binary.LittleEndian.PutUint16(msg[22:], uint16(nt_len)) binary.LittleEndian.PutUint32(msg[24:], uint32(88+lm_len)) // Domain Name Fields binary.LittleEndian.PutUint16(msg[28:], uint16(domain_len)) binary.LittleEndian.PutUint16(msg[30:], uint16(domain_len)) binary.LittleEndian.PutUint32(msg[32:], uint32(88+lm_len+nt_len)) // User Name Fields binary.LittleEndian.PutUint16(msg[36:], uint16(user_len)) binary.LittleEndian.PutUint16(msg[38:], uint16(user_len)) binary.LittleEndian.PutUint32(msg[40:], uint32(88+lm_len+nt_len+domain_len)) // Workstation Fields binary.LittleEndian.PutUint16(msg[44:], uint16(workstation_len)) binary.LittleEndian.PutUint16(msg[46:], uint16(workstation_len)) binary.LittleEndian.PutUint32(msg[48:], uint32(88+lm_len+nt_len+domain_len+user_len)) // Encrypted Random Session Key Fields binary.LittleEndian.PutUint16(msg[52:], 0) binary.LittleEndian.PutUint16(msg[54:], 0) binary.LittleEndian.PutUint32(msg[56:], uint32(88+lm_len+nt_len+domain_len+user_len+workstation_len)) // Negotiate Flags binary.LittleEndian.PutUint32(msg[60:], flags) // Version binary.LittleEndian.PutUint32(msg[64:], 0) binary.LittleEndian.PutUint32(msg[68:], 0) // MIC binary.LittleEndian.PutUint32(msg[72:], 0) binary.LittleEndian.PutUint32(msg[76:], 0) binary.LittleEndian.PutUint32(msg[88:], 0) binary.LittleEndian.PutUint32(msg[84:], 0) // Payload copy(msg[88:], lm) copy(msg[88+lm_len:], nt) copy(msg[88+lm_len+nt_len:], domain16) copy(msg[88+lm_len+nt_len+domain_len:], user16) copy(msg[88+lm_len+nt_len+domain_len+user_len:], workstation16) return msg, nil } func (auth *NTLMAuth) Free() { }