Skip to content

Commit 428a29c

Browse files
Marcel GebhardtMarcel Gebhardt
authored andcommitted
initial commit
0 parents  commit 428a29c

File tree

4 files changed

+178
-0
lines changed

4 files changed

+178
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Marcel Gebhardt
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# go-ntlm-proxy-auth
2+
3+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4+
[![GoDoc](https://godoc.org/github.com/Codehardt/go-ntlm-proxy-auth?status.svg)](https://godoc.org/github.com/Codehardt/go-ntlm-proxy-auth)
5+
6+
With this package, you can connect to http/https servers protected by an NTLM proxy in Golang.
7+
8+
## Example
9+
10+
```golang
11+
// create a dialer
12+
dialer := &net.Dialer{
13+
Timeout: 30 * time.Second,
14+
KeepAlive: 30 * time.Second,
15+
}
16+
17+
// wrap dial context with NTLM
18+
ntlmDialContext := ntlm.WrapDialContext(dialer.DialContext, "proxyAddr", "user", "password", "domain")
19+
20+
// create a http(s) client
21+
client := &http.Client{
22+
Transport: &http.Transport{
23+
Proxy: nil, // !!! IMPORTANT, do not set proxy here !!!
24+
Dial: dialer.Dial,
25+
DialContext: ntlmDialContext,
26+
// TLSClientConfig: ...
27+
},
28+
}
29+
```

debug.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ntlm
2+
3+
var debugf = func(format string, a ...interface{}) {} // discard debug
4+
5+
// SetDebugf sets a debugf function for debug output
6+
func SetDebugf(f func(format string, a ...interface{})) {
7+
debugf = f
8+
}

ntlm.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package ntlm
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"encoding/base64"
7+
"errors"
8+
"fmt"
9+
"io/ioutil"
10+
"net"
11+
"net/http"
12+
"net/url"
13+
"strings"
14+
15+
ntlmssp "github.com/Azure/go-ntlmssp"
16+
)
17+
18+
// DialContext is the DialContext function that should be wrapped with a
19+
// NTLM Authentication.
20+
//
21+
// Example for DialContext:
22+
//
23+
// dialContext := (&net.Dialer{KeepAlive: 30*time.Second, Timeout: 30*time.Second}).DialContext
24+
type DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
25+
26+
// WrapDialContext wraps a DialContext with an NTLM Authentication to a proxy.
27+
func WrapDialContext(dialContext DialContext, proxyAddress, proxyUsername, proxyPassword, proxyDomain string) DialContext {
28+
return func(ctx context.Context, network, addr string) (net.Conn, error) {
29+
conn, err := dialContext(ctx, network, proxyAddress)
30+
if err != nil {
31+
debugf("ntlm> Could not call dial context with proxy: %s", err)
32+
return conn, err
33+
}
34+
// NTLM Step 1: Send Negotiate Message
35+
negotiateMessage, err := ntlmssp.NewNegotiateMessage(proxyDomain, "")
36+
if err != nil {
37+
debugf("ntlm> Could not negotiate domain '%s': %s", proxyDomain, err)
38+
return conn, err
39+
}
40+
debugf("ntlm> NTLM negotiate message: '%s'", base64.StdEncoding.EncodeToString(negotiateMessage))
41+
header := make(http.Header)
42+
header.Set("Proxy-Authorization", fmt.Sprintf("NTLM %s", base64.StdEncoding.EncodeToString(negotiateMessage)))
43+
connect := &http.Request{
44+
Method: "CONNECT",
45+
URL: &url.URL{Opaque: addr},
46+
Host: addr,
47+
Header: header,
48+
}
49+
if err := connect.Write(conn); err != nil {
50+
debugf("ntlm> Could not write negotiate message to proxy: %s", err)
51+
return conn, err
52+
}
53+
debugf("ntlm> Successfully sent negotiate message to proxy")
54+
// NTLM Step 2: Receive Challenge Message
55+
br := bufio.NewReader(conn)
56+
resp, err := http.ReadResponse(br, connect)
57+
if err != nil {
58+
debugf("ntlm> Could not read response from proxy: %s", err)
59+
return conn, err
60+
}
61+
_, err = ioutil.ReadAll(resp.Body)
62+
if err != nil {
63+
debugf("ntlm> Could not read response body from proxy: %s", err)
64+
return conn, err
65+
}
66+
resp.Body.Close()
67+
if resp.StatusCode != http.StatusProxyAuthRequired {
68+
debugf("ntlm> Expected %d as return status, got: %d", http.StatusProxyAuthRequired, resp.StatusCode)
69+
return conn, errors.New(http.StatusText(resp.StatusCode))
70+
}
71+
challenge := strings.Split(resp.Header.Get("Proxy-Authenticate"), " ")
72+
if len(challenge) < 2 {
73+
debugf("ntlm> The proxy did not return an NTLM challenge, got: '%s'", resp.Header.Get("Proxy-Authenticate"))
74+
return conn, errors.New("no NTLM challenge received")
75+
}
76+
debugf("ntlm> NTLM challenge: '%s'", challenge[1])
77+
challengeMessage, err := base64.StdEncoding.DecodeString(challenge[1])
78+
if err != nil {
79+
debugf("ntlm> Could not base64 decode the NTLM challenge: %s", err)
80+
return conn, err
81+
}
82+
// NTLM Step 3: Send Authorization Message
83+
debugf("ntlm> Processing NTLM challenge with username '%s' and password with length %d", proxyUsername, len(proxyPassword))
84+
authenticateMessage, err := ntlmssp.ProcessChallenge(challengeMessage, proxyUsername, proxyPassword)
85+
if err != nil {
86+
debugf("ntlm> Could not process the NTLM challenge: %s", err)
87+
return conn, err
88+
}
89+
debugf("ntlm> NTLM authorization: '%s'", base64.StdEncoding.EncodeToString(authenticateMessage))
90+
header.Set("Proxy-Authorization", fmt.Sprintf("NTLM %s", base64.StdEncoding.EncodeToString(authenticateMessage)))
91+
connect = &http.Request{
92+
Method: "CONNECT",
93+
URL: &url.URL{Opaque: addr},
94+
Host: addr,
95+
Header: header,
96+
}
97+
if err := connect.Write(conn); err != nil {
98+
debugf("ntlm> Could not write authorization to proxy: %s", err)
99+
return conn, err
100+
}
101+
resp, err = http.ReadResponse(br, connect)
102+
if err != nil {
103+
debugf("ntlm> Could not read response from proxy: %s", err)
104+
return conn, err
105+
}
106+
_, err = ioutil.ReadAll(resp.Body)
107+
if err != nil {
108+
debugf("ntlm> Could not read response body from proxy: %s", err)
109+
return conn, err
110+
}
111+
resp.Body.Close()
112+
if resp.StatusCode != http.StatusOK {
113+
debugf("ntlm> Expected %d as return status, got: %d", http.StatusOK, resp.StatusCode)
114+
return conn, errors.New(http.StatusText(resp.StatusCode))
115+
}
116+
// Succussfully authorized with NTLM
117+
debugf("ntlm> Successfully injected NTLM to connection")
118+
return conn, nil
119+
}
120+
}

0 commit comments

Comments
 (0)