Browse Source
[Vendor] Update go-ldap to v3.2.4 (#13163)
[Vendor] Update go-ldap to v3.2.4 (#13163)
* [Vendor] update go-ldap to v3.0.3 * update go-ldap to v3.2.4 Co-authored-by: techknowlogick <techknowlogick@gitea.io>mj-v1.14.3
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 2744 additions and 1347 deletions
-
3go.mod
-
11go.sum
-
2modules/auth/ldap/ldap.go
-
17vendor/github.com/Azure/go-ntlmssp/.travis.yml
-
21vendor/github.com/Azure/go-ntlmssp/LICENSE
-
29vendor/github.com/Azure/go-ntlmssp/README.md
-
183vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
-
37vendor/github.com/Azure/go-ntlmssp/authheader.go
-
17vendor/github.com/Azure/go-ntlmssp/avids.go
-
82vendor/github.com/Azure/go-ntlmssp/challenge_message.go
-
21vendor/github.com/Azure/go-ntlmssp/messageheader.go
-
52vendor/github.com/Azure/go-ntlmssp/negotiate_flags.go
-
64vendor/github.com/Azure/go-ntlmssp/negotiate_message.go
-
144vendor/github.com/Azure/go-ntlmssp/negotiator.go
-
51vendor/github.com/Azure/go-ntlmssp/nlmp.go
-
29vendor/github.com/Azure/go-ntlmssp/unicode.go
-
40vendor/github.com/Azure/go-ntlmssp/varfield.go
-
20vendor/github.com/Azure/go-ntlmssp/version.go
-
39vendor/github.com/go-asn1-ber/asn1-ber/.travis.yml
-
22vendor/github.com/go-asn1-ber/asn1-ber/LICENSE
-
0vendor/github.com/go-asn1-ber/asn1-ber/README.md
-
224vendor/github.com/go-asn1-ber/asn1-ber/ber.go
-
2vendor/github.com/go-asn1-ber/asn1-ber/content_int.go
-
105vendor/github.com/go-asn1-ber/asn1-ber/generalizedTime.go
-
3vendor/github.com/go-asn1-ber/asn1-ber/go.mod
-
25vendor/github.com/go-asn1-ber/asn1-ber/header.go
-
43vendor/github.com/go-asn1-ber/asn1-ber/identifier.go
-
26vendor/github.com/go-asn1-ber/asn1-ber/length.go
-
157vendor/github.com/go-asn1-ber/asn1-ber/real.go
-
2vendor/github.com/go-asn1-ber/asn1-ber/util.go
-
0vendor/github.com/go-ldap/ldap/v3/LICENSE
-
62vendor/github.com/go-ldap/ldap/v3/add.go
-
540vendor/github.com/go-ldap/ldap/v3/bind.go
-
30vendor/github.com/go-ldap/ldap/v3/client.go
-
61vendor/github.com/go-ldap/ldap/v3/compare.go
-
120vendor/github.com/go-ldap/ldap/v3/conn.go
-
39vendor/github.com/go-ldap/ldap/v3/control.go
-
10vendor/github.com/go-ldap/ldap/v3/debug.go
-
59vendor/github.com/go-ldap/ldap/v3/del.go
-
46vendor/github.com/go-ldap/ldap/v3/dn.go
-
0vendor/github.com/go-ldap/ldap/v3/doc.go
-
37vendor/github.com/go-ldap/ldap/v3/error.go
-
176vendor/github.com/go-ldap/ldap/v3/filter.go
-
9vendor/github.com/go-ldap/ldap/v3/go.mod
-
11vendor/github.com/go-ldap/ldap/v3/go.sum
-
53vendor/github.com/go-ldap/ldap/v3/ldap.go
-
80vendor/github.com/go-ldap/ldap/v3/moddn.go
-
132vendor/github.com/go-ldap/ldap/v3/modify.go
-
71vendor/github.com/go-ldap/ldap/v3/passwdmodify.go
-
66vendor/github.com/go-ldap/ldap/v3/request.go
-
176vendor/github.com/go-ldap/ldap/v3/search.go
-
15vendor/gopkg.in/asn1-ber.v1/.travis.yml
-
27vendor/gopkg.in/asn1-ber.v1/LICENSE
-
0vendor/gopkg.in/ldap.v3/.gitignore
-
32vendor/gopkg.in/ldap.v3/.travis.yml
-
12vendor/gopkg.in/ldap.v3/CONTRIBUTING.md
-
82vendor/gopkg.in/ldap.v3/Makefile
-
54vendor/gopkg.in/ldap.v3/README.md
-
135vendor/gopkg.in/ldap.v3/bind.go
-
28vendor/gopkg.in/ldap.v3/client.go
-
83vendor/gopkg.in/ldap.v3/compare.go
-
84vendor/gopkg.in/ldap.v3/del.go
-
104vendor/gopkg.in/ldap.v3/moddn.go
-
173vendor/gopkg.in/ldap.v3/modify.go
-
13vendor/modules.txt
@ -0,0 +1,17 @@ |
|||
sudo: 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 ./... |
@ -0,0 +1,21 @@ |
|||
The 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. |
@ -0,0 +1,29 @@ |
|||
# go-ntlmssp |
|||
Golang package that provides NTLM/Negotiate authentication over HTTP |
|||
|
|||
[](https://godoc.org/github.com/Azure/go-ntlmssp) [](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. |
@ -0,0 +1,183 @@ |
|||
package 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("go-ntlmssp") |
|||
|
|||
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) ([]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)") |
|||
} |
|||
|
|||
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() |
|||
} |
@ -0,0 +1,37 @@ |
|||
package ntlmssp |
|||
|
|||
import ( |
|||
"encoding/base64" |
|||
"strings" |
|||
) |
|||
|
|||
type authheader string |
|||
|
|||
func (h authheader) IsBasic() bool { |
|||
return strings.HasPrefix(string(h), "Basic ") |
|||
} |
|||
|
|||
func (h authheader) IsNegotiate() bool { |
|||
return strings.HasPrefix(string(h), "Negotiate") |
|||
} |
|||
|
|||
func (h authheader) IsNTLM() bool { |
|||
return strings.HasPrefix(string(h), "NTLM") |
|||
} |
|||
|
|||
func (h authheader) GetData() ([]byte, error) { |
|||
p := strings.Split(string(h), " ") |
|||
if len(p) < 2 { |
|||
return nil, nil |
|||
} |
|||
return base64.StdEncoding.DecodeString(string(p[1])) |
|||
} |
|||
|
|||
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 |
|||
} |
@ -0,0 +1,17 @@ |
|||
package ntlmssp |
|||
|
|||
type avID uint16 |
|||
|
|||
const ( |
|||
avIDMsvAvEOL avID = iota |
|||
avIDMsvAvNbComputerName |
|||
avIDMsvAvNbDomainName |
|||
avIDMsvAvDNSComputerName |
|||
avIDMsvAvDNSDomainName |
|||
avIDMsvAvDNSTreeName |
|||
avIDMsvAvFlags |
|||
avIDMsvAvTimestamp |
|||
avIDMsvAvSingleHost |
|||
avIDMsvAvTargetName |
|||
avIDMsvChannelBindings |
|||
) |
@ -0,0 +1,82 @@ |
|||
package 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 |
|||
} |
@ -0,0 +1,21 @@ |
|||
package 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} |
|||
} |
@ -0,0 +1,52 @@ |
|||
package 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) |
|||
} |
@ -0,0 +1,64 @@ |
|||
package 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 |
|||
} |
@ -0,0 +1,144 @@ |
|||
package ntlmssp |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/base64" |
|||
"io" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"strings" |
|||
) |
|||
|
|||
// GetDomain : parse domain name from based on slashes in the input
|
|||
func GetDomain(user string) (string, string) { |
|||
domain := "" |
|||
|
|||
if strings.Contains(user, "\\") { |
|||
ucomponents := strings.SplitN(user, "\\", 2) |
|||
domain = ucomponents[0] |
|||
user = ucomponents[1] |
|||
} |
|||
return user, domain |
|||
} |
|||
|
|||
//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.Get("Authorization")) |
|||
if !reqauth.IsBasic() { |
|||
return rt.RoundTrip(req) |
|||
} |
|||
// 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.Get("Www-Authenticate")) |
|||
if !resauth.IsNegotiate() && !resauth.IsNTLM() { |
|||
// Unauthorized, Negotiate not requested, let's try with basic auth
|
|||
req.Header.Set("Authorization", string(reqauth)) |
|||
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.Get("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 = 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.Get("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) |
|||
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 |
|||
} |
@ -0,0 +1,51 @@ |
|||
// 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) |
|||
} |
@ -0,0 +1,29 @@ |
|||
package 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() |
|||
} |
@ -0,0 +1,40 @@ |
|||
package 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 |
|||
} |
@ -0,0 +1,20 @@ |
|||
package 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, |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
language: go |
|||
|
|||
go: |
|||
- 1.2.x |
|||
- 1.6.x |
|||
- 1.9.x |
|||
- 1.10.x |
|||
- 1.11.x |
|||
- 1.12.x |
|||
- 1.14.x |
|||
- tip |
|||
|
|||
os: |
|||
- linux |
|||
|
|||
arch: |
|||
- amd64 |
|||
|
|||
dist: xenial |
|||
|
|||
env: |
|||
- GOARCH=amd64 |
|||
|
|||
jobs: |
|||
include: |
|||
- os: windows |
|||
go: 1.14.x |
|||
- os: osx |
|||
go: 1.14.x |
|||
- os: linux |
|||
go: 1.14.x |
|||
arch: arm64 |
|||
- os: linux |
|||
go: 1.14.x |
|||
env: |
|||
- GOARCH=386 |
|||
|
|||
script: |
|||
- go test -v -cover ./... || go test -v ./... |
@ -0,0 +1,22 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) |
|||
Portions copyright (c) 2015-2016 go-asn1-ber Authors |
|||
|
|||
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. |
@ -0,0 +1,105 @@ |
|||
package ber |
|||
|
|||
import ( |
|||
"bytes" |
|||
"errors" |
|||
"fmt" |
|||
"strconv" |
|||
"time" |
|||
) |
|||
|
|||
// ErrInvalidTimeFormat is returned when the generalizedTime string was not correct.
|
|||
var ErrInvalidTimeFormat = errors.New("invalid time format") |
|||
|
|||
var zeroTime = time.Time{} |
|||
|
|||
// ParseGeneralizedTime parses a string value and if it conforms to
|
|||
// GeneralizedTime[^0] format, will return a time.Time for that value.
|
|||
//
|
|||
// [^0]: https://www.itu.int/rec/T-REC-X.690-201508-I/en Section 11.7
|
|||
func ParseGeneralizedTime(v []byte) (time.Time, error) { |
|||
var format string |
|||
var fract time.Duration |
|||
|
|||
str := []byte(DecodeString(v)) |
|||
tzIndex := bytes.IndexAny(str, "Z+-") |
|||
if tzIndex < 0 { |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
|
|||
dot := bytes.IndexAny(str, ".,") |
|||
switch dot { |
|||
case -1: |
|||
switch tzIndex { |
|||
case 10: |
|||
format = `2006010215Z` |
|||
case 12: |
|||
format = `200601021504Z` |
|||
case 14: |
|||
format = `20060102150405Z` |
|||
default: |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
|
|||
case 10, 12: |
|||
if tzIndex < dot { |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
// a "," is also allowed, but would not be parsed by time.Parse():
|
|||
str[dot] = '.' |
|||
|
|||
// If <minute> is omitted, then <fraction> represents a fraction of an
|
|||
// hour; otherwise, if <second> and <leap-second> are omitted, then
|
|||
// <fraction> represents a fraction of a minute; otherwise, <fraction>
|
|||
// represents a fraction of a second.
|
|||
|
|||
// parse as float from dot to timezone
|
|||
f, err := strconv.ParseFloat(string(str[dot:tzIndex]), 64) |
|||
if err != nil { |
|||
return zeroTime, fmt.Errorf("failed to parse float: %s", err) |
|||
} |
|||
// ...and strip that part
|
|||
str = append(str[:dot], str[tzIndex:]...) |
|||
tzIndex = dot |
|||
|
|||
if dot == 10 { |
|||
fract = time.Duration(int64(f * float64(time.Hour))) |
|||
format = `2006010215Z` |
|||
} else { |
|||
fract = time.Duration(int64(f * float64(time.Minute))) |
|||
format = `200601021504Z` |
|||
} |
|||
|
|||
case 14: |
|||
if tzIndex < dot { |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
str[dot] = '.' |
|||
// no need for fractional seconds, time.Parse() handles that
|
|||
format = `20060102150405Z` |
|||
|
|||
default: |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
|
|||
l := len(str) |
|||
switch l - tzIndex { |
|||
case 1: |
|||
if str[l-1] != 'Z' { |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
case 3: |
|||
format += `0700` |
|||
str = append(str, []byte("00")...) |
|||
case 5: |
|||
format += `0700` |
|||
default: |
|||
return zeroTime, ErrInvalidTimeFormat |
|||
} |
|||
|
|||
t, err := time.Parse(format, string(str)) |