View Javadoc
1   package org.metricshub.ipmi.core.coding.commands.session;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * IPMI Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 Verax Systems, MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder;
26  import org.metricshub.ipmi.core.coding.commands.IpmiVersion;
27  import org.metricshub.ipmi.core.coding.payload.IpmiPayload;
28  import org.metricshub.ipmi.core.coding.payload.PlainMessage;
29  import org.metricshub.ipmi.core.coding.payload.lan.IPMIException;
30  import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction;
31  import org.metricshub.ipmi.core.coding.protocol.AuthenticationType;
32  import org.metricshub.ipmi.core.coding.protocol.IpmiMessage;
33  import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
34  import org.metricshub.ipmi.core.coding.protocol.PayloadType;
35  import org.metricshub.ipmi.core.coding.protocol.encoder.Protocolv20Encoder;
36  import org.metricshub.ipmi.core.coding.security.CipherSuite;
37  import org.metricshub.ipmi.core.coding.security.ConfidentialityNone;
38  import org.metricshub.ipmi.core.coding.security.SecurityConstants;
39  import org.metricshub.ipmi.core.common.TypeConverter;
40  
41  import java.security.InvalidKeyException;
42  import java.security.NoSuchAlgorithmException;
43  
44  /**
45   * A wrapper for RMCP+ RAKP3 message and it's response - RAKP4 message.
46   */
47  public class Rakp3 extends IpmiCommandCoder<Rakp3ResponseData> {
48  
49  	/**
50  	 * Status of the previous message.
51  	 */
52  	private byte statusCode;
53  
54  	/**
55  	 * The Managed System's Session ID for this session. Must be as returned by
56  	 * the Managed System in the Open Session Response message.
57  	 */
58  	private int managedSystemSessionId;
59  
60  	private Rakp1 rakp1;
61  
62  	private Rakp1ResponseData rakp1ResponseData;
63  
64  	public void setStatusCode(byte statusCode) {
65  		this.statusCode = statusCode;
66  	}
67  
68  	public byte getStatusCode() {
69  		return statusCode;
70  	}
71  
72  	public void setManagedSystemSessionId(int managedSystemSessionId) {
73  		this.managedSystemSessionId = managedSystemSessionId;
74  	}
75  
76  	public int getManagedSystemSessionId() {
77  		return managedSystemSessionId;
78  	}
79  
80  	/**
81  	 * Initiates class for decoding. Sets IPMI version to
82  	 * {@link IpmiVersion#V20} since RAKP1 is a RMCP+ command. Sets
83  	 * Authentication Type to RMCP+.
84  	 *
85  	 * @param cipherSuite
86  	 *        - {@link CipherSuite} containing authentication,
87  	 *        confidentiality and integrity algorithms for this session.
88  	 *        Only authentication algorithm is used at this point of
89  	 *        creating a session.
90  	 * @param rakp1
91  	 *        - RAKP Message 1 sent earlier in the authentification process
92  	 * @param rakp1ResponseData
93  	 *        - RAKP Message 2 received earlier in the authentification
94  	 *        process
95  	 */
96  	public Rakp3(CipherSuite cipherSuite, Rakp1 rakp1, Rakp1ResponseData rakp1ResponseData) {
97  		setSessionParameters(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
98  		this.rakp1 = rakp1;
99  		this.rakp1ResponseData = rakp1ResponseData;
100 		setCipherSuite(new CipherSuite((byte) 0, cipherSuite.getAuthenticationAlgorithm().getCode(), (byte) 0, (byte) 0));
101 	}
102 
103 	/**
104 	 * Initiates class for encoding and decoding. Sets IPMI version to
105 	 * {@link IpmiVersion#V20} since RAKP1 is a RMCP+ command. Sets
106 	 * Authentication Type to RMCP+.
107 	 *
108 	 * @param statusCode
109 	 *        - Status of the previous message.
110 	 * @param managedSystemSessionId
111 	 *        - The Managed System�s Session ID for this session. Must be as
112 	 *        returned by the Managed System in the Open Session Response
113 	 *        message.
114 	 * @param cipherSuite
115 	 *        - {@link CipherSuite} containing authentication,
116 	 *        confidentiality and integrity algorithms for this session.
117 	 *        Only authentication algorithm is used at this point of
118 	 *        creating a session.
119 	 * @param rakp1
120 	 *        - RAKP Message 1 sent earlier in the authentification process
121 	 * @param rakp1ResponseData
122 	 *        - RAKP Message 2 received earlier in the authentification
123 	 *        process
124 	 */
125 	public Rakp3(byte statusCode, int managedSystemSessionId, CipherSuite cipherSuite, Rakp1 rakp1,
126 			Rakp1ResponseData rakp1ResponseData) {
127 		setSessionParameters(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
128 		setStatusCode(statusCode);
129 		setManagedSystemSessionId(managedSystemSessionId);
130 		this.rakp1 = rakp1;
131 		this.rakp1ResponseData = rakp1ResponseData;
132 		setCipherSuite(new CipherSuite((byte) 0, cipherSuite.getAuthenticationAlgorithm().getCode(), (byte) 0, (byte) 0));
133 	}
134 
135 	@Override
136 	public IpmiMessage encodePayload(int messageSequenceNumber, int sessionSequenceNumber, int sessionId)
137 			throws NoSuchAlgorithmException,
138 			InvalidKeyException {
139 
140 		if (sessionId != 0) {
141 			throw new IllegalArgumentException("Session ID must be 0");
142 		}
143 
144 		Ipmiv20Message message = new Ipmiv20Message(new ConfidentialityNone());
145 
146 		message.setPayloadType(PayloadType.Rakp3);
147 		message.setSessionID(0);
148 		message.setSessionSequenceNumber(0);
149 		message.setAuthenticationType(getAuthenticationType());
150 		message.setPayloadAuthenticated(getCipherSuite().getIntegrityAlgorithm().getCode() != SecurityConstants.IA_NONE);
151 		message.setPayloadEncrypted(false);
152 
153 		message.setPayload(preparePayload(messageSequenceNumber));
154 
155 		message.setAuthCode(
156 				getCipherSuite().getIntegrityAlgorithm()
157 						.generateAuthCode(message.getIntegrityAlgorithmBase(new Protocolv20Encoder())));
158 
159 		return message;
160 	}
161 
162 	@Override
163 	protected IpmiPayload preparePayload(int sequenceNumber) throws NoSuchAlgorithmException, InvalidKeyException {
164 		byte[] payload = new byte[8];
165 
166 		payload[0] = TypeConverter.intToByte(sequenceNumber); // message
167 																													// tag
168 
169 		payload[1] = getStatusCode(); // last message status code
170 
171 		payload[2] = 0; // reserved
172 		payload[3] = 0; // reserved
173 
174 		byte[] manSesId = TypeConverter.intToLittleEndianByteArray(getManagedSystemSessionId());
175 
176 		System.arraycopy(manSesId, 0, payload, 4, 4); // managed system session
177 																									// id
178 
179 		byte[] exchangeAuthCode = getCipherSuite().getAuthenticationAlgorithm()
180 				.getKeyExchangeAuthenticationCode(
181 						prepareKeyExchangeAuthenticationCodeBase(rakp1, rakp1ResponseData),
182 						rakp1.getPasswordBytes());
183 
184 		byte[] result = null;
185 
186 		if (exchangeAuthCode != null) {
187 			result = new byte[8 + exchangeAuthCode.length];
188 			System.arraycopy(exchangeAuthCode, 0, result, 8, exchangeAuthCode.length);
189 		} else {
190 			result = new byte[8];
191 		}
192 
193 		System.arraycopy(payload, 0, result, 0, 8);
194 
195 		return new PlainMessage(result);
196 	}
197 
198 	/**
199 	 * @return byte array holding prepared base for calculating
200 	 *         KeyExchangeAuthenticationCode for RAKP Message 3
201 	 */
202 	private byte[] prepareKeyExchangeAuthenticationCodeBase(Rakp1 rakp1Message, Rakp1ResponseData responseData) {
203 		byte[] username = rakp1Message.getUsernameBytes();
204 		byte[] keac = new byte[22 + username.length];
205 
206 		System.arraycopy(responseData.getManagedSystemRandomNumber(), 0, keac, 0, 16);
207 
208 		System
209 				.arraycopy(TypeConverter.intToLittleEndianByteArray(responseData.getRemoteConsoleSessionId()), 0, keac, 16, 4);
210 
211 		keac[20] = TypeConverter.intToByte(encodePrivilegeLevel(rakp1Message.getRequestedMaximumPrivilegeLevel()) | 0x10);
212 
213 		keac[21] = TypeConverter.intToByte(username.length);
214 		System.arraycopy(username, 0, keac, 22, username.length);
215 
216 		return keac;
217 	}
218 
219 	@Override
220 	public byte getCommandCode() {
221 		return 0;
222 	}
223 
224 	@Override
225 	public NetworkFunction getNetworkFunction() {
226 		return null;
227 	}
228 
229 	@Override
230 	public Rakp3ResponseData getResponseData(IpmiMessage message)
231 			throws IPMIException,
232 			InvalidKeyException,
233 			NoSuchAlgorithmException {
234 
235 		if (!isCommandResponse(message)) {
236 			throw new IllegalArgumentException("This is not RAKP 4 message!");
237 		}
238 
239 		byte[] payload = validateSessionSetupResponse(message);
240 
241 		Rakp3ResponseData data = new Rakp3ResponseData();
242 
243 		data.setMessageTag(payload[0]);
244 
245 		data.setStatusCode(payload[1]);
246 
247 		int integrityCheckLength = getCipherSuite().getAuthenticationAlgorithm().getIntegrityCheckBaseLength();
248 
249 		if (payload.length < 8 + integrityCheckLength) {
250 			throw new IllegalArgumentException("Invalid payload length");
251 		}
252 
253 		byte[] buffer = new byte[4];
254 
255 		System.arraycopy(payload, 4, buffer, 0, 4);
256 
257 		data.setConsoleSessionId(TypeConverter.littleEndianByteArrayToInt(buffer));
258 
259 		byte[] integrityCheck = new byte[integrityCheckLength];
260 		System.arraycopy(payload, 8, integrityCheck, 0, integrityCheckLength);
261 
262 		if (!getCipherSuite().getAuthenticationAlgorithm()
263 				.doIntegrityCheck(
264 						prepareIntegrityCheckBase(rakp1, rakp1ResponseData),
265 						integrityCheck,
266 						rakp1.calculateSik(rakp1ResponseData))) {
267 			throw new AuthenticationException("Integrity check failed");
268 		}
269 
270 		return data;
271 	}
272 
273 	/**
274 	 * @return byte array holding prepared base for calculating Integrity Check
275 	 */
276 	private byte[] prepareIntegrityCheckBase(Rakp1 rakp1Message, Rakp1ResponseData responseData) {
277 		byte[] icb = new byte[36];
278 
279 		System.arraycopy(rakp1Message.getConsoleRandomNumber(), 0, icb, 0, 16);
280 
281 		System.arraycopy(TypeConverter.intToLittleEndianByteArray(rakp1Message.getManagedSystemSessionId()), 0, icb, 16, 4);
282 
283 		System.arraycopy(responseData.getManagedSystemGuid(), 0, icb, 20, 16);
284 
285 		return icb;
286 	}
287 
288 	@Override
289 	public boolean isCommandResponse(IpmiMessage message) {
290 		return message instanceof Ipmiv20Message && ((Ipmiv20Message) message).getPayloadType() == PayloadType.Rakp4;
291 	}
292 
293 }