initial commit

This commit is contained in:
Marcel Gebhardt
2018-09-18 08:39:46 +02:00
committed by Marcel Gebhardt
commit 428a29c33a
4 changed files with 178 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -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.

29
README.md Normal file
View File

@@ -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: ...
},
}
```

8
debug.go Normal file
View File

@@ -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
}

120
ntlm.go Normal file
View File

@@ -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
}
}