pax_global_header00006660000000000000000000000064143412066370014520gustar00rootroot0000000000000052 comment=754e69321358ada85ce213a4ec971d3e4d1bfdf7 go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/000077500000000000000000000000001434120663700207135ustar00rootroot00000000000000go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/.travis.yml000066400000000000000000000004231434120663700230230ustar00rootroot00000000000000sudo: false language: go before_script: - go get -u golang.org/x/lint/golint go: - 1.10.x - master script: - test -z "$(gofmt -s -l . | tee /dev/stderr)" - test -z "$(golint ./... | tee /dev/stderr)" - go vet ./... - go build -v ./... - go test -v ./... go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/LICENSE000066400000000000000000000020641434120663700217220ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/README.md000066400000000000000000000024061434120663700221740ustar00rootroot00000000000000# go-ntlmssp Golang package that provides NTLM/Negotiate authentication over HTTP [![GoDoc](https://godoc.org/github.com/Azure/go-ntlmssp?status.svg)](https://godoc.org/github.com/Azure/go-ntlmssp) [![Build Status](https://travis-ci.org/Azure/go-ntlmssp.svg?branch=dev)](https://travis-ci.org/Azure/go-ntlmssp) Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx Implementation hints from http://davenport.sourceforge.net/ntlm.html This package only implements authentication, no key exchange or encryption. It only supports Unicode (UTF16LE) encoding of protocol strings, no OEM encoding. This package implements NTLMv2. # Usage ``` url, user, password := "http://www.example.com/secrets", "robpike", "pw123" client := &http.Client{ Transport: ntlmssp.Negotiator{ RoundTripper:&http.Transport{}, }, } req, _ := http.NewRequest("GET", url, nil) req.SetBasicAuth(user, password) res, _ := client.Do(req) ``` ----- This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/SECURITY.md000066400000000000000000000053051434120663700225070ustar00rootroot00000000000000 ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/authenticate_message.go000066400000000000000000000126041434120663700254270ustar00rootroot00000000000000package ntlmssp import ( "bytes" "crypto/rand" "encoding/binary" "encoding/hex" "errors" "strings" "time" ) type authenicateMessage struct { LmChallengeResponse []byte NtChallengeResponse []byte TargetName string UserName string // only set if negotiateFlag_NTLMSSP_NEGOTIATE_KEY_EXCH EncryptedRandomSessionKey []byte NegotiateFlags negotiateFlags MIC []byte } type authenticateMessageFields struct { messageHeader LmChallengeResponse varField NtChallengeResponse varField TargetName varField UserName varField Workstation varField _ [8]byte NegotiateFlags negotiateFlags } func (m authenicateMessage) MarshalBinary() ([]byte, error) { if !m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE) { return nil, errors.New("Only unicode is supported") } target, user := toUnicode(m.TargetName), toUnicode(m.UserName) workstation := toUnicode("") ptr := binary.Size(&authenticateMessageFields{}) f := authenticateMessageFields{ messageHeader: newMessageHeader(3), NegotiateFlags: m.NegotiateFlags, LmChallengeResponse: newVarField(&ptr, len(m.LmChallengeResponse)), NtChallengeResponse: newVarField(&ptr, len(m.NtChallengeResponse)), TargetName: newVarField(&ptr, len(target)), UserName: newVarField(&ptr, len(user)), Workstation: newVarField(&ptr, len(workstation)), } f.NegotiateFlags.Unset(negotiateFlagNTLMSSPNEGOTIATEVERSION) b := bytes.Buffer{} if err := binary.Write(&b, binary.LittleEndian, &f); err != nil { return nil, err } if err := binary.Write(&b, binary.LittleEndian, &m.LmChallengeResponse); err != nil { return nil, err } if err := binary.Write(&b, binary.LittleEndian, &m.NtChallengeResponse); err != nil { return nil, err } if err := binary.Write(&b, binary.LittleEndian, &target); err != nil { return nil, err } if err := binary.Write(&b, binary.LittleEndian, &user); err != nil { return nil, err } if err := binary.Write(&b, binary.LittleEndian, &workstation); err != nil { return nil, err } return b.Bytes(), nil } //ProcessChallenge crafts an AUTHENTICATE message in response to the CHALLENGE message //that was received from the server func ProcessChallenge(challengeMessageData []byte, user, password string, domainNeeded bool) ([]byte, error) { if user == "" && password == "" { return nil, errors.New("Anonymous authentication not supported") } var cm challengeMessage if err := cm.UnmarshalBinary(challengeMessageData); err != nil { return nil, err } if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATELMKEY) { return nil, errors.New("Only NTLM v2 is supported, but server requested v1 (NTLMSSP_NEGOTIATE_LM_KEY)") } if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) { return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)") } if !domainNeeded { cm.TargetName = "" } am := authenicateMessage{ UserName: user, TargetName: cm.TargetName, NegotiateFlags: cm.NegotiateFlags, } timestamp := cm.TargetInfo[avIDMsvAvTimestamp] if timestamp == nil { // no time sent, take current time ft := uint64(time.Now().UnixNano()) / 100 ft += 116444736000000000 // add time between unix & windows offset timestamp = make([]byte, 8) binary.LittleEndian.PutUint64(timestamp, ft) } clientChallenge := make([]byte, 8) rand.Reader.Read(clientChallenge) ntlmV2Hash := getNtlmV2Hash(password, user, cm.TargetName) am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash, cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw) if cm.TargetInfoRaw == nil { am.LmChallengeResponse = computeLmV2Response(ntlmV2Hash, cm.ServerChallenge[:], clientChallenge) } return am.MarshalBinary() } func ProcessChallengeWithHash(challengeMessageData []byte, user, hash string) ([]byte, error) { if user == "" && hash == "" { return nil, errors.New("Anonymous authentication not supported") } var cm challengeMessage if err := cm.UnmarshalBinary(challengeMessageData); err != nil { return nil, err } if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATELMKEY) { return nil, errors.New("Only NTLM v2 is supported, but server requested v1 (NTLMSSP_NEGOTIATE_LM_KEY)") } if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) { return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)") } am := authenicateMessage{ UserName: user, TargetName: cm.TargetName, NegotiateFlags: cm.NegotiateFlags, } timestamp := cm.TargetInfo[avIDMsvAvTimestamp] if timestamp == nil { // no time sent, take current time ft := uint64(time.Now().UnixNano()) / 100 ft += 116444736000000000 // add time between unix & windows offset timestamp = make([]byte, 8) binary.LittleEndian.PutUint64(timestamp, ft) } clientChallenge := make([]byte, 8) rand.Reader.Read(clientChallenge) hashParts := strings.Split(hash, ":") if len(hashParts) > 1 { hash = hashParts[1] } hashBytes, err := hex.DecodeString(hash) if err != nil { return nil, err } ntlmV2Hash := hmacMd5(hashBytes, toUnicode(strings.ToUpper(user)+cm.TargetName)) am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash, cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw) if cm.TargetInfoRaw == nil { am.LmChallengeResponse = computeLmV2Response(ntlmV2Hash, cm.ServerChallenge[:], clientChallenge) } return am.MarshalBinary() } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/authheader.go000066400000000000000000000023411434120663700233540ustar00rootroot00000000000000package ntlmssp import ( "encoding/base64" "strings" ) type authheader []string func (h authheader) IsBasic() bool { for _, s := range h { if strings.HasPrefix(string(s), "Basic ") { return true } } return false } func (h authheader) Basic() string { for _, s := range h { if strings.HasPrefix(string(s), "Basic ") { return s } } return "" } func (h authheader) IsNegotiate() bool { for _, s := range h { if strings.HasPrefix(string(s), "Negotiate") { return true } } return false } func (h authheader) IsNTLM() bool { for _, s := range h { if strings.HasPrefix(string(s), "NTLM") { return true } } return false } func (h authheader) GetData() ([]byte, error) { for _, s := range h { if strings.HasPrefix(string(s), "NTLM") || strings.HasPrefix(string(s), "Negotiate") || strings.HasPrefix(string(s), "Basic ") { p := strings.Split(string(s), " ") if len(p) < 2 { return nil, nil } return base64.StdEncoding.DecodeString(string(p[1])) } } return nil, nil } func (h authheader) GetBasicCreds() (username, password string, err error) { d, err := h.GetData() if err != nil { return "", "", err } parts := strings.SplitN(string(d), ":", 2) return parts[0], parts[1], nil } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/avids.go000066400000000000000000000004451434120663700223530ustar00rootroot00000000000000package ntlmssp type avID uint16 const ( avIDMsvAvEOL avID = iota avIDMsvAvNbComputerName avIDMsvAvNbDomainName avIDMsvAvDNSComputerName avIDMsvAvDNSDomainName avIDMsvAvDNSTreeName avIDMsvAvFlags avIDMsvAvTimestamp avIDMsvAvSingleHost avIDMsvAvTargetName avIDMsvChannelBindings ) go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/challenge_message.go000066400000000000000000000034201434120663700246670ustar00rootroot00000000000000package ntlmssp import ( "bytes" "encoding/binary" "fmt" ) type challengeMessageFields struct { messageHeader TargetName varField NegotiateFlags negotiateFlags ServerChallenge [8]byte _ [8]byte TargetInfo varField } func (m challengeMessageFields) IsValid() bool { return m.messageHeader.IsValid() && m.MessageType == 2 } type challengeMessage struct { challengeMessageFields TargetName string TargetInfo map[avID][]byte TargetInfoRaw []byte } func (m *challengeMessage) UnmarshalBinary(data []byte) error { r := bytes.NewReader(data) err := binary.Read(r, binary.LittleEndian, &m.challengeMessageFields) if err != nil { return err } if !m.challengeMessageFields.IsValid() { return fmt.Errorf("Message is not a valid challenge message: %+v", m.challengeMessageFields.messageHeader) } if m.challengeMessageFields.TargetName.Len > 0 { m.TargetName, err = m.challengeMessageFields.TargetName.ReadStringFrom(data, m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE)) if err != nil { return err } } if m.challengeMessageFields.TargetInfo.Len > 0 { d, err := m.challengeMessageFields.TargetInfo.ReadFrom(data) m.TargetInfoRaw = d if err != nil { return err } m.TargetInfo = make(map[avID][]byte) r := bytes.NewReader(d) for { var id avID var l uint16 err = binary.Read(r, binary.LittleEndian, &id) if err != nil { return err } if id == avIDMsvAvEOL { break } err = binary.Read(r, binary.LittleEndian, &l) if err != nil { return err } value := make([]byte, l) n, err := r.Read(value) if err != nil { return err } if n != int(l) { return fmt.Errorf("Expected to read %d bytes, got only %d", l, n) } m.TargetInfo[id] = value } } return nil } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/messageheader.go000066400000000000000000000006401434120663700240370ustar00rootroot00000000000000package ntlmssp import ( "bytes" ) var signature = [8]byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0} type messageHeader struct { Signature [8]byte MessageType uint32 } func (h messageHeader) IsValid() bool { return bytes.Equal(h.Signature[:], signature[:]) && h.MessageType > 0 && h.MessageType < 4 } func newMessageHeader(messageType uint32) messageHeader { return messageHeader{signature, messageType} } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/negotiate_flags.go000066400000000000000000000026741434120663700244060ustar00rootroot00000000000000package ntlmssp type negotiateFlags uint32 const ( /*A*/ negotiateFlagNTLMSSPNEGOTIATEUNICODE negotiateFlags = 1 << 0 /*B*/ negotiateFlagNTLMNEGOTIATEOEM = 1 << 1 /*C*/ negotiateFlagNTLMSSPREQUESTTARGET = 1 << 2 /*D*/ negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4 /*E*/ negotiateFlagNTLMSSPNEGOTIATESEAL = 1 << 5 /*F*/ negotiateFlagNTLMSSPNEGOTIATEDATAGRAM = 1 << 6 /*G*/ negotiateFlagNTLMSSPNEGOTIATELMKEY = 1 << 7 /*H*/ negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9 /*J*/ negotiateFlagANONYMOUS = 1 << 11 /*K*/ negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED = 1 << 12 /*L*/ negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED = 1 << 13 /*M*/ negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15 /*N*/ negotiateFlagNTLMSSPTARGETTYPEDOMAIN = 1 << 16 /*O*/ negotiateFlagNTLMSSPTARGETTYPESERVER = 1 << 17 /*P*/ negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19 /*Q*/ negotiateFlagNTLMSSPNEGOTIATEIDENTIFY = 1 << 20 /*R*/ negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22 /*S*/ negotiateFlagNTLMSSPNEGOTIATETARGETINFO = 1 << 23 /*T*/ negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25 /*U*/ negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29 /*V*/ negotiateFlagNTLMSSPNEGOTIATEKEYEXCH = 1 << 30 /*W*/ negotiateFlagNTLMSSPNEGOTIATE56 = 1 << 31 ) func (field negotiateFlags) Has(flags negotiateFlags) bool { return field&flags == flags } func (field *negotiateFlags) Unset(flags negotiateFlags) { *field = *field ^ (*field & flags) } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/negotiate_message.go000066400000000000000000000027431434120663700247330ustar00rootroot00000000000000package ntlmssp import ( "bytes" "encoding/binary" "errors" "strings" ) const expMsgBodyLen = 40 type negotiateMessageFields struct { messageHeader NegotiateFlags negotiateFlags Domain varField Workstation varField Version } var defaultFlags = negotiateFlagNTLMSSPNEGOTIATETARGETINFO | negotiateFlagNTLMSSPNEGOTIATE56 | negotiateFlagNTLMSSPNEGOTIATE128 | negotiateFlagNTLMSSPNEGOTIATEUNICODE | negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY //NewNegotiateMessage creates a new NEGOTIATE message with the //flags that this package supports. func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) { payloadOffset := expMsgBodyLen flags := defaultFlags if domainName != "" { flags |= negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED } if workstationName != "" { flags |= negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED } msg := negotiateMessageFields{ messageHeader: newMessageHeader(1), NegotiateFlags: flags, Domain: newVarField(&payloadOffset, len(domainName)), Workstation: newVarField(&payloadOffset, len(workstationName)), Version: DefaultVersion(), } b := bytes.Buffer{} if err := binary.Write(&b, binary.LittleEndian, &msg); err != nil { return nil, err } if b.Len() != expMsgBodyLen { return nil, errors.New("incorrect body length") } payload := strings.ToUpper(domainName + workstationName) if _, err := b.WriteString(payload); err != nil { return nil, err } return b.Bytes(), nil } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/negotiator.go000066400000000000000000000077561434120663700234340ustar00rootroot00000000000000package ntlmssp import ( "bytes" "encoding/base64" "io" "io/ioutil" "net/http" "strings" ) // GetDomain : parse domain name from based on slashes in the input // Need to check for upn as well func GetDomain(user string) (string, string, bool) { domain := "" domainNeeded := false if strings.Contains(user, "\\") { ucomponents := strings.SplitN(user, "\\", 2) domain = ucomponents[0] user = ucomponents[1] domainNeeded = true } else if strings.Contains(user, "@") { domainNeeded = false } else { domainNeeded = true } return user, domain, domainNeeded } //Negotiator is a http.Roundtripper decorator that automatically //converts basic authentication to NTLM/Negotiate authentication when appropriate. type Negotiator struct{ http.RoundTripper } //RoundTrip sends the request to the server, handling any authentication //re-sends as needed. func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error) { // Use default round tripper if not provided rt := l.RoundTripper if rt == nil { rt = http.DefaultTransport } // If it is not basic auth, just round trip the request as usual reqauth := authheader(req.Header.Values("Authorization")) if !reqauth.IsBasic() { return rt.RoundTrip(req) } reqauthBasic := reqauth.Basic() // Save request body body := bytes.Buffer{} if req.Body != nil { _, err = body.ReadFrom(req.Body) if err != nil { return nil, err } req.Body.Close() req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes())) } // first try anonymous, in case the server still finds us // authenticated from previous traffic req.Header.Del("Authorization") res, err = rt.RoundTrip(req) if err != nil { return nil, err } if res.StatusCode != http.StatusUnauthorized { return res, err } resauth := authheader(res.Header.Values("Www-Authenticate")) if !resauth.IsNegotiate() && !resauth.IsNTLM() { // Unauthorized, Negotiate not requested, let's try with basic auth req.Header.Set("Authorization", string(reqauthBasic)) io.Copy(ioutil.Discard, res.Body) res.Body.Close() req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes())) res, err = rt.RoundTrip(req) if err != nil { return nil, err } if res.StatusCode != http.StatusUnauthorized { return res, err } resauth = authheader(res.Header.Values("Www-Authenticate")) } if resauth.IsNegotiate() || resauth.IsNTLM() { // 401 with request:Basic and response:Negotiate io.Copy(ioutil.Discard, res.Body) res.Body.Close() // recycle credentials u, p, err := reqauth.GetBasicCreds() if err != nil { return nil, err } // get domain from username domain := "" u, domain, domainNeeded := GetDomain(u) // send negotiate negotiateMessage, err := NewNegotiateMessage(domain, "") if err != nil { return nil, err } if resauth.IsNTLM() { req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage)) } else { req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(negotiateMessage)) } req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes())) res, err = rt.RoundTrip(req) if err != nil { return nil, err } // receive challenge? resauth = authheader(res.Header.Values("Www-Authenticate")) challengeMessage, err := resauth.GetData() if err != nil { return nil, err } if !(resauth.IsNegotiate() || resauth.IsNTLM()) || len(challengeMessage) == 0 { // Negotiation failed, let client deal with response return res, nil } io.Copy(ioutil.Discard, res.Body) res.Body.Close() // send authenticate authenticateMessage, err := ProcessChallenge(challengeMessage, u, p, domainNeeded) if err != nil { return nil, err } if resauth.IsNTLM() { req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(authenticateMessage)) } else { req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(authenticateMessage)) } req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes())) return rt.RoundTrip(req) } return res, err } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/nlmp.go000066400000000000000000000030151434120663700222070ustar00rootroot00000000000000// Package ntlmssp provides NTLM/Negotiate authentication over HTTP // // Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx, // implementation hints from http://davenport.sourceforge.net/ntlm.html . // This package only implements authentication, no key exchange or encryption. It // only supports Unicode (UTF16LE) encoding of protocol strings, no OEM encoding. // This package implements NTLMv2. package ntlmssp import ( "crypto/hmac" "crypto/md5" "golang.org/x/crypto/md4" "strings" ) func getNtlmV2Hash(password, username, target string) []byte { return hmacMd5(getNtlmHash(password), toUnicode(strings.ToUpper(username)+target)) } func getNtlmHash(password string) []byte { hash := md4.New() hash.Write(toUnicode(password)) return hash.Sum(nil) } func computeNtlmV2Response(ntlmV2Hash, serverChallenge, clientChallenge, timestamp, targetInfo []byte) []byte { temp := []byte{1, 1, 0, 0, 0, 0, 0, 0} temp = append(temp, timestamp...) temp = append(temp, clientChallenge...) temp = append(temp, 0, 0, 0, 0) temp = append(temp, targetInfo...) temp = append(temp, 0, 0, 0, 0) NTProofStr := hmacMd5(ntlmV2Hash, serverChallenge, temp) return append(NTProofStr, temp...) } func computeLmV2Response(ntlmV2Hash, serverChallenge, clientChallenge []byte) []byte { return append(hmacMd5(ntlmV2Hash, serverChallenge, clientChallenge), clientChallenge...) } func hmacMd5(key []byte, data ...[]byte) []byte { mac := hmac.New(md5.New, key) for _, d := range data { mac.Write(d) } return mac.Sum(nil) } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/nlmp_test.go000066400000000000000000000175451434120663700232630ustar00rootroot00000000000000package ntlmssp import ( "bytes" "encoding/hex" "strings" "testing" ) // test cases from http://davenport.sourceforge.net/ntlm.html var username = "user" var password = "SecREt01" var ntlmHashHex = "CD06CA7C7E10C99B1D33B7485A2ED808" // NT Hash in hex of "SecREt01" var target = "DOMAIN" var domain = "MYDOMAIN" var workstation = "MYPC" var challenge = []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef} func TestUsernameDomainWorkstation(t *testing.T) { // taking a username and workstation as input, check that the username, domain, workstation // and negotiate message bytes all match their expected values tables := []struct { u string w string xu string xd string xb []byte }{ {username, "", username, "", []byte{ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x88, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f}}, {domain + "\\" + username, "", username, domain, []byte{ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x88, 0xa0, 0x08, 0x00, 0x08, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0x59, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e}}, {domain + "\\" + username, workstation, username, domain, []byte{ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x30, 0x88, 0xa0, 0x08, 0x00, 0x08, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0x59, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e, 0x4d, 0x59, 0x50, 0x43}}, {username, workstation, username, "", []byte{ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, 0x88, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, 0x4d, 0x59, 0x50, 0x43}}, } for _, table := range tables { tuser, tdomain := GetDomain(table.u) if tuser != table.xu { t.Fatalf("username not correct, expected %v got %v", tuser, table.xu) } if tdomain != table.xd { t.Fatalf("domain not correct, expected %v got %v", tdomain, table.xd) } tb, err := NewNegotiateMessage(tdomain, table.w) if err != nil { t.Fatalf("error creating new negotiate message with domain '%v' and workstation '%v'", tdomain, table.w) } if !bytes.Equal(tb, table.xb) { t.Fatalf("negotiate message bytes not correct, expected %v got %v", tb, table.xb) } } } func TestCalculateNTLMv2Response(t *testing.T) { NTLMv2Hash := getNtlmV2Hash(password, username, target) ClientChallenge := []byte{0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44} Time := []byte{0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01} targetInfo := []byte{0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00} v := computeNtlmV2Response(NTLMv2Hash, challenge, ClientChallenge, Time, targetInfo) if expected := []byte{ 0xcb, 0xab, 0xbc, 0xa7, 0x13, 0xeb, 0x79, 0x5d, 0x04, 0xc9, 0x7a, 0xbc, 0x01, 0xee, 0x49, 0x83, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01, 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; !bytes.Equal(v, expected) { t.Fatalf("expected %x, got %x", expected, v) } } func TestCalculateNTLMv2ResponseWithHash(t *testing.T) { hashBytes, err := hex.DecodeString(ntlmHashHex) if err != nil { t.Fatalf("error decoding hex hash: %s", ntlmHashHex) } ClientChallenge := []byte{0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44} Time := []byte{0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01} targetInfo := []byte{0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00} NTLMv2Hash := hmacMd5(hashBytes, toUnicode(strings.ToUpper(username)+target)) v := computeNtlmV2Response(NTLMv2Hash, challenge, ClientChallenge, Time, targetInfo) if expected := []byte{ 0xcb, 0xab, 0xbc, 0xa7, 0x13, 0xeb, 0x79, 0x5d, 0x04, 0xc9, 0x7a, 0xbc, 0x01, 0xee, 0x49, 0x83, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xd3, 0x36, 0xb7, 0x34, 0xc3, 0x01, 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; !bytes.Equal(v, expected) { t.Fatalf("expected %x, got %x", expected, v) } } func TestCalculateLMv2Response(t *testing.T) { NTLMv2Hash := getNtlmV2Hash(password, username, target) ClientChallenge := []byte{0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44} v := computeLmV2Response(NTLMv2Hash, challenge, ClientChallenge) if expected := []byte{ 0xd6, 0xe6, 0x15, 0x2e, 0xa2, 0x5d, 0x03, 0xb7, 0xc6, 0xba, 0x66, 0x29, 0xc2, 0xd6, 0xaa, 0xf0, 0xff, 0xff, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, }; !bytes.Equal(v, expected) { t.Fatalf("expected %x, got %x", expected, v) } } func TestToUnicode(t *testing.T) { v := toUnicode(password) if expected := []byte{0x53, 0x00, 0x65, 0x00, 0x63, 0x00, 0x52, 0x00, 0x45, 0x00, 0x74, 0x00, 0x30, 0x00, 0x31, 0x00}; !bytes.Equal(v, expected) { t.Fatalf("expected %v, got %v", expected, v) } } func TestNTLMhash(t *testing.T) { v := getNtlmHash(password) if expected := []byte{0xcd, 0x06, 0xca, 0x7c, 0x7e, 0x10, 0xc9, 0x9b, 0x1d, 0x33, 0xb7, 0x48, 0x5a, 0x2e, 0xd8, 0x08}; !bytes.Equal(v, expected) { t.Fatalf("expected %v, got %v", expected, v) } } func TestNTLMv2Hash(t *testing.T) { v := getNtlmV2Hash(password, username, target) if expected := []byte{0x04, 0xb8, 0xe0, 0xba, 0x74, 0x28, 0x9c, 0xc5, 0x40, 0x82, 0x6b, 0xab, 0x1d, 0xee, 0x63, 0xae}; !bytes.Equal(v, expected) { t.Fatalf("expected %v, got %v", expected, v) } } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/unicode.go000066400000000000000000000011561434120663700226730ustar00rootroot00000000000000package ntlmssp import ( "bytes" "encoding/binary" "errors" "unicode/utf16" ) // helper func's for dealing with Windows Unicode (UTF16LE) func fromUnicode(d []byte) (string, error) { if len(d)%2 > 0 { return "", errors.New("Unicode (UTF 16 LE) specified, but uneven data length") } s := make([]uint16, len(d)/2) err := binary.Read(bytes.NewReader(d), binary.LittleEndian, &s) if err != nil { return "", err } return string(utf16.Decode(s)), nil } func toUnicode(s string) []byte { uints := utf16.Encode([]rune(s)) b := bytes.Buffer{} binary.Write(&b, binary.LittleEndian, &uints) return b.Bytes() } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/varfield.go000066400000000000000000000016201434120663700230350ustar00rootroot00000000000000package ntlmssp import ( "errors" ) type varField struct { Len uint16 MaxLen uint16 BufferOffset uint32 } func (f varField) ReadFrom(buffer []byte) ([]byte, error) { if len(buffer) < int(f.BufferOffset+uint32(f.Len)) { return nil, errors.New("Error reading data, varField extends beyond buffer") } return buffer[f.BufferOffset : f.BufferOffset+uint32(f.Len)], nil } func (f varField) ReadStringFrom(buffer []byte, unicode bool) (string, error) { d, err := f.ReadFrom(buffer) if err != nil { return "", err } if unicode { // UTF-16LE encoding scheme return fromUnicode(d) } // OEM encoding, close enough to ASCII, since no code page is specified return string(d), err } func newVarField(ptr *int, fieldsize int) varField { f := varField{ Len: uint16(fieldsize), MaxLen: uint16(fieldsize), BufferOffset: uint32(*ptr), } *ptr += fieldsize return f } go-ntlmssp-754e69321358ada85ce213a4ec971d3e4d1bfdf7/version.go000066400000000000000000000007721434120663700227350ustar00rootroot00000000000000package ntlmssp // Version is a struct representing https://msdn.microsoft.com/en-us/library/cc236654.aspx type Version struct { ProductMajorVersion uint8 ProductMinorVersion uint8 ProductBuild uint16 _ [3]byte NTLMRevisionCurrent uint8 } // DefaultVersion returns a Version with "sensible" defaults (Windows 7) func DefaultVersion() Version { return Version{ ProductMajorVersion: 6, ProductMinorVersion: 1, ProductBuild: 7601, NTLMRevisionCurrent: 15, } }