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 }