View Javadoc
1   package org.metricshub.winrm.service.client.auth.ntlm;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 - 2024 Metricshub
8    * ჻჻჻჻჻჻
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21   */
22  
23  import org.apache.commons.codec.binary.Base64;
24  import org.apache.http.impl.auth.NTLMEngineException;
25  
26  /**
27   * NTLM message generation, base class
28   *
29   * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl
30   * release 0.12.3 @link https://github.com/cloudsoft/winrm4j
31   * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13
32   */
33  class NTLMMessage {
34  
35  	/** The signature string as bytes in the default encoding */
36  	private static final byte[] SIGNATURE;
37  
38  	static {
39  		final byte[] bytesWithoutNull = "NTLMSSP".getBytes(NTLMEngineUtils.DEFAULT_CHARSET);
40  		final byte[] target = new byte[bytesWithoutNull.length + 1];
41  		System.arraycopy(bytesWithoutNull, 0, target, 0, bytesWithoutNull.length);
42  		target[bytesWithoutNull.length] = (byte) 0x00;
43  
44  		SIGNATURE = target;
45  	}
46  
47  	/** The current response */
48  	protected byte[] messageContents = null;
49  
50  	/** The current output position */
51  	protected int currentOutputPosition = 0;
52  
53  	/** Constructor to use when message contents are not yet known */
54  	NTLMMessage() {}
55  
56  	/** Constructor to use when message bytes are known */
57  	NTLMMessage(final byte[] message, final int expectedType) throws NTLMEngineException {
58  		messageContents = message;
59  		// Look for NTLM message
60  		if (messageContents.length < SIGNATURE.length) {
61  			throw new NTLMEngineException("NTLM message decoding error - packet too short");
62  		}
63  		int i = 0;
64  		while (i < SIGNATURE.length) {
65  			if (messageContents[i] != SIGNATURE[i]) {
66  				throw new NTLMEngineException("NTLM message expected - instead got unrecognized bytes");
67  			}
68  			i++;
69  		}
70  
71  		// Check to be sure there's a type 2 message indicator next
72  		final int type = readULong(SIGNATURE.length);
73  		if (type != expectedType) {
74  			throw new NTLMEngineException(
75  				String.format("NTLM type %d message expected - instead got type %d", expectedType, type)
76  			);
77  		}
78  
79  		currentOutputPosition = messageContents.length;
80  	}
81  
82  	/** Read a ulong from a position within the message buffer */
83  	int readULong(final int position) {
84  		return readULong(messageContents, position);
85  	}
86  
87  	static int readULong(final byte[] src, final int index) {
88  		if (src.length < index + 4) {
89  			return 0;
90  		}
91  		return (
92  			(src[index] & 0xff) |
93  			((src[index + 1] & 0xff) << 8) |
94  			((src[index + 2] & 0xff) << 16) |
95  			((src[index + 3] & 0xff) << 24)
96  		);
97  	}
98  
99  	/**
100 	 * Prepares the object to create a response of the given length.
101 	 *
102 	 * @param maxlength
103 	 *			the maximum length of the response to prepare,
104 	 *			including the type and the signature (which this method
105 	 *			adds).
106 	 */
107 	void prepareResponse(final int maxlength, final int messageType) {
108 		messageContents = new byte[maxlength];
109 		currentOutputPosition = 0;
110 		addBytes(SIGNATURE);
111 		addULong(messageType);
112 	}
113 
114 	/**
115 	 * Adds the given byte to the response.
116 	 *
117 	 * @param b
118 	 *			the byte to add.
119 	 */
120 	private void addByte(final byte b) {
121 		messageContents[currentOutputPosition] = b;
122 		currentOutputPosition++;
123 	}
124 
125 	/**
126 	 * Adds the given bytes to the response.
127 	 *
128 	 * @param bytes
129 	 *			the bytes to add.
130 	 */
131 	void addBytes(final byte[] bytes) {
132 		if (bytes == null) {
133 			return;
134 		}
135 		for (final byte b : bytes) {
136 			messageContents[currentOutputPosition] = b;
137 			currentOutputPosition++;
138 		}
139 	}
140 
141 	/** Adds a USHORT to the response */
142 	void addUShort(final int value) {
143 		addByte((byte) (value & 0xff));
144 		addByte((byte) ((value >> 8) & 0xff));
145 	}
146 
147 	/** Adds a ULong to the response */
148 	void addULong(final int value) {
149 		addByte((byte) (value & 0xff));
150 		addByte((byte) ((value >> 8) & 0xff));
151 		addByte((byte) ((value >> 16) & 0xff));
152 		addByte((byte) ((value >> 24) & 0xff));
153 	}
154 
155 	/**
156 	 * Returns the response that has been generated after shrinking the
157 	 * array if required and base64 encodes the response.
158 	 *
159 	 * @return The response as above.
160 	 */
161 	String getResponse() {
162 		return new String(Base64.encodeBase64(getBytes()), NTLMEngineUtils.DEFAULT_CHARSET);
163 	}
164 
165 	private byte[] getBytes() {
166 		if (messageContents == null) {
167 			buildMessage();
168 		}
169 
170 		if (messageContents.length > currentOutputPosition) {
171 			final byte[] tmp = new byte[currentOutputPosition];
172 			System.arraycopy(messageContents, 0, tmp, 0, currentOutputPosition);
173 			messageContents = tmp;
174 		}
175 		return messageContents;
176 	}
177 
178 	protected void buildMessage() {
179 		throw new RuntimeException("Message builder not implemented for " + getClass().getName());
180 	}
181 }