View Javadoc
1   // copy of code from apache-httpclient 4.5.13 package org.apache.http.impl.auth
2   // changes:
3   // - package name, this header, imports
4   // - gather NTLM signing key and attach to context
5   
6   /*
7    * ====================================================================
8    * Licensed to the Apache Software Foundation (ASF) under one
9    * or more contributor license agreements.  See the NOTICE file
10   * distributed with this work for additional information
11   * regarding copyright ownership.  The ASF licenses this file
12   * to you under the Apache License, Version 2.0 (the
13   * "License");
14  /*-
15   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
16   * WinRM Java Client
17   * ჻჻჻჻჻჻
18   * Copyright 2023 - 2024 Metricshub
19   * ჻჻჻჻჻჻
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
32   */
33  package org.metricshub.winrm.service.client.auth.ntlm;
34  
35  import org.apache.http.Header;
36  import org.apache.http.HttpRequest;
37  import org.apache.http.auth.AUTH;
38  import org.apache.http.auth.AuthenticationException;
39  import org.apache.http.auth.Credentials;
40  import org.apache.http.auth.InvalidCredentialsException;
41  import org.apache.http.auth.MalformedChallengeException;
42  import org.apache.http.auth.NTCredentials;
43  import org.apache.http.impl.auth.AuthSchemeBase;
44  import org.apache.http.message.BufferedHeader;
45  import org.apache.http.protocol.HttpContext;
46  import org.apache.http.util.CharArrayBuffer;
47  import org.metricshub.winrm.Utils;
48  
49  /**
50   * NTLM is a proprietary authentication scheme developed by Microsoft
51   * and optimized for Windows platforms.
52   *
53   * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMScheme
54   * release 0.12.3 @link https://github.com/cloudsoft/winrm4j
55   * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13
56   */
57  public class NTLMScheme extends AuthSchemeBase {
58  
59  	private enum State {
60  		UNINITIATED,
61  		CHALLENGE_RECEIVED,
62  		MSG_TYPE1_GENERATED,
63  		MSG_TYPE2_RECEVIED,
64  		MSG_TYPE3_GENERATED,
65  		FAILED
66  	}
67  
68  	private final NTLMEngine engine;
69  
70  	private State state;
71  	private String challenge;
72  
73  	public NTLMScheme(final NTLMEngine engine) {
74  		super();
75  		Utils.checkNonNull(engine, "engine");
76  		this.engine = engine;
77  		state = State.UNINITIATED;
78  		challenge = null;
79  	}
80  
81  	@Override
82  	public String getSchemeName() {
83  		return "ntlm";
84  	}
85  
86  	@Override
87  	public String getParameter(final String name) {
88  		// String parameters not supported
89  		return null;
90  	}
91  
92  	@Override
93  	public String getRealm() {
94  		// NTLM does not support the concept of an authentication realm
95  		return null;
96  	}
97  
98  	@Override
99  	public boolean isConnectionBased() {
100 		return true;
101 	}
102 
103 	@Override
104 	protected void parseChallenge(final CharArrayBuffer buffer, final int beginIndex, final int endIndex)
105 		throws MalformedChallengeException {
106 		challenge = buffer.substringTrimmed(beginIndex, endIndex);
107 		if (challenge.isEmpty()) {
108 			if (state == State.UNINITIATED) {
109 				state = State.CHALLENGE_RECEIVED;
110 			} else {
111 				state = State.FAILED;
112 			}
113 		} else {
114 			if (state.compareTo(State.MSG_TYPE1_GENERATED) < 0) {
115 				state = State.FAILED;
116 				throw new MalformedChallengeException("Out of sequence NTLM response message");
117 			} else if (state == State.MSG_TYPE1_GENERATED) {
118 				state = State.MSG_TYPE2_RECEVIED;
119 			}
120 		}
121 	}
122 
123 	@Override
124 	public Header authenticate(final Credentials credentials, final HttpRequest request) throws AuthenticationException {
125 		NTCredentials ntcredentials = null;
126 		try {
127 			ntcredentials = (NTCredentials) credentials;
128 		} catch (final ClassCastException e) {
129 			throw new InvalidCredentialsException(
130 				"Credentials cannot be used for NTLM authentication: " + credentials.getClass().getName()
131 			);
132 		}
133 		String response = null;
134 		if (state == State.FAILED) {
135 			throw new AuthenticationException("NTLM authentication failed");
136 		} else if (state == State.CHALLENGE_RECEIVED) {
137 			response = this.engine.generateType1Msg(ntcredentials.getDomain(), ntcredentials.getWorkstation());
138 			state = State.MSG_TYPE1_GENERATED;
139 
140 			if (credentials instanceof NTCredentialsWithEncryption) {
141 				((NTCredentialsWithEncryption) credentials).resetEncryption(request);
142 			}
143 		} else if (state == State.MSG_TYPE2_RECEVIED) {
144 			final Type3Message responseO = engine.generateType3MsgObject(
145 				ntcredentials.getUserName(),
146 				ntcredentials.getPassword(),
147 				ntcredentials.getDomain(),
148 				ntcredentials.getWorkstation(),
149 				challenge
150 			);
151 
152 			response = responseO.getResponse();
153 			state = State.MSG_TYPE3_GENERATED;
154 			if (credentials instanceof NTCredentialsWithEncryption) {
155 				((NTCredentialsWithEncryption) credentials).initEncryption(responseO, request);
156 			}
157 		} else {
158 			throw new AuthenticationException("Unexpected state: " + state);
159 		}
160 		final CharArrayBuffer buffer = new CharArrayBuffer(32);
161 		if (isProxy()) {
162 			buffer.append(AUTH.PROXY_AUTH_RESP);
163 		} else {
164 			buffer.append(AUTH.WWW_AUTH_RESP);
165 		}
166 		buffer.append(": NTLM ");
167 		buffer.append(response);
168 		return new BufferedHeader(buffer);
169 	}
170 
171 	@Override
172 	public boolean isComplete() {
173 		return state == State.MSG_TYPE3_GENERATED || state == State.FAILED;
174 	}
175 
176 	@Override
177 	public Header authenticate(final Credentials credentials, final HttpRequest request, final HttpContext context)
178 		throws AuthenticationException {
179 		return authenticate(credentials, request);
180 	}
181 }