commit 428a29c33a35f1569992dd67dd1ee072bdc6a71c Author: Marcel Gebhardt Date: Tue Sep 18 08:39:46 2018 +0200 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..26eb772 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Marcel Gebhardt + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a775848 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# go-ntlm-proxy-auth + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![GoDoc](https://godoc.org/github.com/Codehardt/go-ntlm-proxy-auth?status.svg)](https://godoc.org/github.com/Codehardt/go-ntlm-proxy-auth) + +With this package, you can connect to http/https servers protected by an NTLM proxy in Golang. + +## Example + +```golang +// create a dialer +dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, +} + +// wrap dial context with NTLM +ntlmDialContext := ntlm.WrapDialContext(dialer.DialContext, "proxyAddr", "user", "password", "domain") + +// create a http(s) client +client := &http.Client{ + Transport: &http.Transport{ + Proxy: nil, // !!! IMPORTANT, do not set proxy here !!! + Dial: dialer.Dial, + DialContext: ntlmDialContext, + // TLSClientConfig: ... + }, +} +``` \ No newline at end of file diff --git a/debug.go b/debug.go new file mode 100644 index 0000000..1776e14 --- /dev/null +++ b/debug.go @@ -0,0 +1,8 @@ +package ntlm + +var debugf = func(format string, a ...interface{}) {} // discard debug + +// SetDebugf sets a debugf function for debug output +func SetDebugf(f func(format string, a ...interface{})) { + debugf = f +} diff --git a/ntlm.go b/ntlm.go new file mode 100644 index 0000000..318cbff --- /dev/null +++ b/ntlm.go @@ -0,0 +1,120 @@ +package ntlm + +import ( + "bufio" + "context" + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "strings" + + ntlmssp "github.com/Azure/go-ntlmssp" +) + +// DialContext is the DialContext function that should be wrapped with a +// NTLM Authentication. +// +// Example for DialContext: +// +// dialContext := (&net.Dialer{KeepAlive: 30*time.Second, Timeout: 30*time.Second}).DialContext +type DialContext func(ctx context.Context, network, addr string) (net.Conn, error) + +// WrapDialContext wraps a DialContext with an NTLM Authentication to a proxy. +func WrapDialContext(dialContext DialContext, proxyAddress, proxyUsername, proxyPassword, proxyDomain string) DialContext { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + conn, err := dialContext(ctx, network, proxyAddress) + if err != nil { + debugf("ntlm> Could not call dial context with proxy: %s", err) + return conn, err + } + // NTLM Step 1: Send Negotiate Message + negotiateMessage, err := ntlmssp.NewNegotiateMessage(proxyDomain, "") + if err != nil { + debugf("ntlm> Could not negotiate domain '%s': %s", proxyDomain, err) + return conn, err + } + debugf("ntlm> NTLM negotiate message: '%s'", base64.StdEncoding.EncodeToString(negotiateMessage)) + header := make(http.Header) + header.Set("Proxy-Authorization", fmt.Sprintf("NTLM %s", base64.StdEncoding.EncodeToString(negotiateMessage))) + connect := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: header, + } + if err := connect.Write(conn); err != nil { + debugf("ntlm> Could not write negotiate message to proxy: %s", err) + return conn, err + } + debugf("ntlm> Successfully sent negotiate message to proxy") + // NTLM Step 2: Receive Challenge Message + br := bufio.NewReader(conn) + resp, err := http.ReadResponse(br, connect) + if err != nil { + debugf("ntlm> Could not read response from proxy: %s", err) + return conn, err + } + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + debugf("ntlm> Could not read response body from proxy: %s", err) + return conn, err + } + resp.Body.Close() + if resp.StatusCode != http.StatusProxyAuthRequired { + debugf("ntlm> Expected %d as return status, got: %d", http.StatusProxyAuthRequired, resp.StatusCode) + return conn, errors.New(http.StatusText(resp.StatusCode)) + } + challenge := strings.Split(resp.Header.Get("Proxy-Authenticate"), " ") + if len(challenge) < 2 { + debugf("ntlm> The proxy did not return an NTLM challenge, got: '%s'", resp.Header.Get("Proxy-Authenticate")) + return conn, errors.New("no NTLM challenge received") + } + debugf("ntlm> NTLM challenge: '%s'", challenge[1]) + challengeMessage, err := base64.StdEncoding.DecodeString(challenge[1]) + if err != nil { + debugf("ntlm> Could not base64 decode the NTLM challenge: %s", err) + return conn, err + } + // NTLM Step 3: Send Authorization Message + debugf("ntlm> Processing NTLM challenge with username '%s' and password with length %d", proxyUsername, len(proxyPassword)) + authenticateMessage, err := ntlmssp.ProcessChallenge(challengeMessage, proxyUsername, proxyPassword) + if err != nil { + debugf("ntlm> Could not process the NTLM challenge: %s", err) + return conn, err + } + debugf("ntlm> NTLM authorization: '%s'", base64.StdEncoding.EncodeToString(authenticateMessage)) + header.Set("Proxy-Authorization", fmt.Sprintf("NTLM %s", base64.StdEncoding.EncodeToString(authenticateMessage))) + connect = &http.Request{ + Method: "CONNECT", + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: header, + } + if err := connect.Write(conn); err != nil { + debugf("ntlm> Could not write authorization to proxy: %s", err) + return conn, err + } + resp, err = http.ReadResponse(br, connect) + if err != nil { + debugf("ntlm> Could not read response from proxy: %s", err) + return conn, err + } + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + debugf("ntlm> Could not read response body from proxy: %s", err) + return conn, err + } + resp.Body.Close() + if resp.StatusCode != http.StatusOK { + debugf("ntlm> Expected %d as return status, got: %d", http.StatusOK, resp.StatusCode) + return conn, errors.New(http.StatusText(resp.StatusCode)) + } + // Succussfully authorized with NTLM + debugf("ntlm> Successfully injected NTLM to connection") + return conn, nil + } +}