@@ -3,6 +3,7 @@ package ntlm
33import (
44 "bufio"
55 "context"
6+ "crypto/tls"
67 "encoding/base64"
78 "errors"
89 "fmt"
@@ -23,93 +24,118 @@ import (
2324// dialContext := (&net.Dialer{KeepAlive: 30*time.Second, Timeout: 30*time.Second}).DialContext
2425type DialContext func (ctx context.Context , network , addr string ) (net.Conn , error )
2526
26- // WrapDialContext wraps a DialContext with an NTLM Authentication to a proxy.
27- func WrapDialContext (dialContext DialContext , proxyAddress , proxyUsername , proxyPassword , proxyDomain string ) DialContext {
27+ // NewNTLMProxyDialContext provides a DialContext function that includes transparent NTLM proxy authentication.
28+ // Unlike WrapDialContext, it describes the proxy location with a full URL, whose scheme can be HTTP or HTTPS.
29+ func NewNTLMProxyDialContext (dialer * net.Dialer , proxyURL url.URL , proxyUsername , proxyPassword , proxyDomain string , tlsConfig * tls.Config ) DialContext {
30+ if dialer == nil {
31+ dialer = & net.Dialer {}
32+ }
2833 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- header .Set ("Proxy-Connection" , "Keep-Alive" )
44- connect := & http.Request {
45- Method : "CONNECT" ,
46- URL : & url.URL {Opaque : addr },
47- Host : addr ,
48- Header : header ,
49- }
50- if err := connect .Write (conn ); err != nil {
51- debugf ("ntlm> Could not write negotiate message to proxy: %s" , err )
52- return conn , err
53- }
54- debugf ("ntlm> Successfully sent negotiate message to proxy" )
55- // NTLM Step 2: Receive Challenge Message
56- br := bufio .NewReader (conn )
57- resp , err := http .ReadResponse (br , connect )
58- if err != nil {
59- debugf ("ntlm> Could not read response from proxy: %s" , err )
60- return conn , err
61- }
62- _ , err = ioutil .ReadAll (resp .Body )
63- if err != nil {
64- debugf ("ntlm> Could not read response body from proxy: %s" , err )
65- return conn , err
66- }
67- resp .Body .Close ()
68- if resp .StatusCode != http .StatusProxyAuthRequired {
69- debugf ("ntlm> Expected %d as return status, got: %d" , http .StatusProxyAuthRequired , resp .StatusCode )
70- return conn , errors .New (http .StatusText (resp .StatusCode ))
71- }
72- challenge := strings .Split (resp .Header .Get ("Proxy-Authenticate" ), " " )
73- if len (challenge ) < 2 {
74- debugf ("ntlm> The proxy did not return an NTLM challenge, got: '%s'" , resp .Header .Get ("Proxy-Authenticate" ))
75- return conn , errors .New ("no NTLM challenge received" )
34+ dialProxy := func () (net.Conn , error ) {
35+ debugf ("ntlm> Will connect to proxy at " + proxyURL .Host )
36+ if proxyURL .Scheme == "https" {
37+ return tls .DialWithDialer (dialer , "tcp" , proxyURL .Host , tlsConfig )
38+ }
39+ return dialer .DialContext (ctx , network , proxyURL .Host )
7640 }
77- debugf ("ntlm> NTLM challenge: '%s'" , challenge [1 ])
78- challengeMessage , err := base64 .StdEncoding .DecodeString (challenge [1 ])
79- if err != nil {
80- debugf ("ntlm> Could not base64 decode the NTLM challenge: %s" , err )
81- return conn , err
82- }
83- // NTLM Step 3: Send Authorization Message
84- debugf ("ntlm> Processing NTLM challenge with username '%s' and password with length %d" , proxyUsername , len (proxyPassword ))
85- authenticateMessage , err := ntlmssp .ProcessChallenge (challengeMessage , proxyUsername , proxyPassword )
86- if err != nil {
87- debugf ("ntlm> Could not process the NTLM challenge: %s" , err )
88- return conn , err
89- }
90- debugf ("ntlm> NTLM authorization: '%s'" , base64 .StdEncoding .EncodeToString (authenticateMessage ))
91- header .Set ("Proxy-Authorization" , fmt .Sprintf ("NTLM %s" , base64 .StdEncoding .EncodeToString (authenticateMessage )))
92- connect = & http.Request {
93- Method : "CONNECT" ,
94- URL : & url.URL {Opaque : addr },
95- Host : addr ,
96- Header : header ,
97- }
98- if err := connect .Write (conn ); err != nil {
99- debugf ("ntlm> Could not write authorization to proxy: %s" , err )
100- return conn , err
101- }
102- resp , err = http .ReadResponse (br , connect )
103- if err != nil {
104- debugf ("ntlm> Could not read response from proxy: %s" , err )
105- return conn , err
106- }
107- if resp .StatusCode != http .StatusOK {
108- debugf ("ntlm> Expected %d as return status, got: %d" , http .StatusOK , resp .StatusCode )
109- return conn , errors .New (http .StatusText (resp .StatusCode ))
110- }
111- // Succussfully authorized with NTLM
112- debugf ("ntlm> Successfully injected NTLM to connection" )
113- return conn , nil
41+ return dialAndNegotiate (addr , proxyUsername , proxyPassword , proxyDomain , dialProxy )
42+ }
43+ }
44+
45+ // WrapDialContext wraps a DialContext with an NTLM Authentication to a proxy. Note that this does not support
46+ // using HTTPS to connect to the proxy; use NewNTLMProxyDialContext if that is required.
47+ func WrapDialContext (dialContext DialContext , proxyAddress , proxyUsername , proxyPassword , proxyDomain string ) DialContext {
48+ return func (ctx context.Context , network , addr string ) (net.Conn , error ) {
49+ return dialAndNegotiate (addr , proxyUsername , proxyPassword , proxyDomain , func () (net.Conn , error ) {
50+ return dialContext (ctx , network , proxyAddress )
51+ })
52+ }
53+ }
54+
55+ func dialAndNegotiate (addr , proxyUsername , proxyPassword , proxyDomain string , baseDial func () (net.Conn , error )) (net.Conn , error ) {
56+ conn , err := baseDial ()
57+ if err != nil {
58+ debugf ("ntlm> Could not call dial context with proxy: %s" , err )
59+ return conn , err
60+ }
61+ // NTLM Step 1: Send Negotiate Message
62+ negotiateMessage , err := ntlmssp .NewNegotiateMessage (proxyDomain , "" )
63+ if err != nil {
64+ debugf ("ntlm> Could not negotiate domain '%s': %s" , proxyDomain , err )
65+ return conn , err
66+ }
67+ debugf ("ntlm> NTLM negotiate message: '%s'" , base64 .StdEncoding .EncodeToString (negotiateMessage ))
68+ header := make (http.Header )
69+ header .Set ("Proxy-Authorization" , fmt .Sprintf ("NTLM %s" , base64 .StdEncoding .EncodeToString (negotiateMessage )))
70+ header .Set ("Proxy-Connection" , "Keep-Alive" )
71+ connect := & http.Request {
72+ Method : "CONNECT" ,
73+ URL : & url.URL {Opaque : addr },
74+ Host : addr ,
75+ Header : header ,
76+ }
77+ if err := connect .Write (conn ); err != nil {
78+ debugf ("ntlm> Could not write negotiate message to proxy: %s" , err )
79+ return conn , err
80+ }
81+ debugf ("ntlm> Successfully sent negotiate message to proxy" )
82+ // NTLM Step 2: Receive Challenge Message
83+ br := bufio .NewReader (conn )
84+ resp , err := http .ReadResponse (br , connect )
85+ if err != nil {
86+ debugf ("ntlm> Could not read response from proxy: %s" , err )
87+ return conn , err
88+ }
89+ _ , err = ioutil .ReadAll (resp .Body )
90+ if err != nil {
91+ debugf ("ntlm> Could not read response body from proxy: %s" , err )
92+ return conn , err
93+ }
94+ resp .Body .Close ()
95+ if resp .StatusCode != http .StatusProxyAuthRequired {
96+ debugf ("ntlm> Expected %d as return status, got: %d" , http .StatusProxyAuthRequired , resp .StatusCode )
97+ return conn , errors .New (http .StatusText (resp .StatusCode ))
98+ }
99+ challenge := strings .Split (resp .Header .Get ("Proxy-Authenticate" ), " " )
100+ if len (challenge ) < 2 {
101+ debugf ("ntlm> The proxy did not return an NTLM challenge, got: '%s'" , resp .Header .Get ("Proxy-Authenticate" ))
102+ return conn , errors .New ("no NTLM challenge received" )
103+ }
104+ debugf ("ntlm> NTLM challenge: '%s'" , challenge [1 ])
105+ challengeMessage , err := base64 .StdEncoding .DecodeString (challenge [1 ])
106+ if err != nil {
107+ debugf ("ntlm> Could not base64 decode the NTLM challenge: %s" , err )
108+ return conn , err
109+ }
110+ // NTLM Step 3: Send Authorization Message
111+ debugf ("ntlm> Processing NTLM challenge with username '%s' and password with length %d" , proxyUsername , len (proxyPassword ))
112+ authenticateMessage , err := ntlmssp .ProcessChallenge (challengeMessage , proxyUsername , proxyPassword )
113+ if err != nil {
114+ debugf ("ntlm> Could not process the NTLM challenge: %s" , err )
115+ return conn , err
116+ }
117+ debugf ("ntlm> NTLM authorization: '%s'" , base64 .StdEncoding .EncodeToString (authenticateMessage ))
118+ header .Set ("Proxy-Authorization" , fmt .Sprintf ("NTLM %s" , base64 .StdEncoding .EncodeToString (authenticateMessage )))
119+ connect = & http.Request {
120+ Method : "CONNECT" ,
121+ URL : & url.URL {Opaque : addr },
122+ Host : addr ,
123+ Header : header ,
124+ }
125+ if err := connect .Write (conn ); err != nil {
126+ debugf ("ntlm> Could not write authorization to proxy: %s" , err )
127+ return conn , err
128+ }
129+ resp , err = http .ReadResponse (br , connect )
130+ if err != nil {
131+ debugf ("ntlm> Could not read response from proxy: %s" , err )
132+ return conn , err
133+ }
134+ if resp .StatusCode != http .StatusOK {
135+ debugf ("ntlm> Expected %d as return status, got: %d" , http .StatusOK , resp .StatusCode )
136+ return conn , errors .New (http .StatusText (resp .StatusCode ))
114137 }
138+ // Succussfully authorized with NTLM
139+ debugf ("ntlm> Successfully injected NTLM to connection" )
140+ return conn , nil
115141}
0 commit comments