github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/auth_ntlm.go (about)

     1  /*
     2   * Copyright (c) 2015, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package upstreamproxy
    21  
    22  import (
    23  	"encoding/base64"
    24  	"fmt"
    25  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/upstreamproxy/go-ntlm/ntlm"
    26  	"net/http"
    27  	"strings"
    28  )
    29  
    30  type NTLMHttpAuthState int
    31  
    32  const (
    33  	NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED NTLMHttpAuthState = iota
    34  	NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED
    35  	NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED
    36  )
    37  
    38  type NTLMHttpAuthenticator struct {
    39  	state    NTLMHttpAuthState
    40  	username string
    41  	password string
    42  }
    43  
    44  func newNTLMAuthenticator(username, password string) *NTLMHttpAuthenticator {
    45  	return &NTLMHttpAuthenticator{
    46  		state:    NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
    47  		username: username,
    48  		password: password,
    49  	}
    50  }
    51  
    52  func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response) error {
    53  	if a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED {
    54  		return proxyError(fmt.Errorf("authorization is not accepted by the proxy server"))
    55  	}
    56  	challenges, err := parseAuthChallenge(resp)
    57  	if err != nil {
    58  		// Already wrapped in proxyError
    59  		return err
    60  	}
    61  
    62  	challenge, ok := challenges["NTLM"]
    63  	if challenge == "" {
    64  		a.state = NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED
    65  	} else {
    66  		a.state = NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED
    67  	}
    68  	if !ok {
    69  		return proxyError(fmt.Errorf("bad proxy response, no NTLM challenge for NTLMHttpAuthenticator"))
    70  	}
    71  
    72  	var ntlmMsg []byte
    73  
    74  	session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
    75  	if err != nil {
    76  		return proxyError(err)
    77  	}
    78  	if a.state == NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
    79  		// Generate TYPE 1 message
    80  		negotiate, err := session.GenerateNegotiateMessage()
    81  		if err != nil {
    82  			return proxyError(err)
    83  		}
    84  		ntlmMsg = negotiate.Bytes()
    85  		a.state = NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED
    86  		req.Header.Set("Proxy-Authorization", "NTLM "+base64.StdEncoding.EncodeToString(ntlmMsg))
    87  		return nil
    88  	} else if a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED {
    89  		// Parse username for domain in form DOMAIN\username
    90  		var NTDomain, NTUser string
    91  		parts := strings.SplitN(a.username, "\\", 2)
    92  		if len(parts) == 2 {
    93  			NTDomain = parts[0]
    94  			NTUser = parts[1]
    95  		} else {
    96  			NTDomain = ""
    97  			NTUser = a.username
    98  		}
    99  		challengeBytes, err := base64.StdEncoding.DecodeString(challenge)
   100  		if err != nil {
   101  			return proxyError(fmt.Errorf("NTLM challenge base 64 decoding: %v", err))
   102  		}
   103  		session.SetUserInfo(NTUser, a.password, NTDomain)
   104  		ntlmChallenge, err := ntlm.ParseChallengeMessage(challengeBytes)
   105  		if err != nil {
   106  			return proxyError(err)
   107  		}
   108  		session.ProcessChallengeMessage(ntlmChallenge)
   109  		authenticate, err := session.GenerateAuthenticateMessage()
   110  		if err != nil {
   111  			return proxyError(err)
   112  		}
   113  		ntlmMsg = authenticate.Bytes()
   114  		a.state = NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED
   115  		req.Header.Set("Proxy-Authorization", "NTLM "+base64.StdEncoding.EncodeToString(ntlmMsg))
   116  		return nil
   117  	}
   118  
   119  	return proxyError(fmt.Errorf("authorization is not accepted by the proxy server"))
   120  }
   121  
   122  func (a *NTLMHttpAuthenticator) IsConnectionBased() bool {
   123  	return true
   124  }
   125  
   126  func (a *NTLMHttpAuthenticator) IsComplete() bool {
   127  	return a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED
   128  }
   129  
   130  func (a *NTLMHttpAuthenticator) Reset() {
   131  	a.state = NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED
   132  }
   133  
   134  func (a *NTLMHttpAuthenticator) PreAuthenticate(req *http.Request) error {
   135  	return nil
   136  }