View Javadoc
1   package org.sentrysoftware.ipmi.core.coding.commands.session;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * IPMI Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 Verax Systems, Sentry Software
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.sentrysoftware.ipmi.core.coding.commands.IpmiCommandCoder;
26  import org.sentrysoftware.ipmi.core.coding.commands.IpmiVersion;
27  import org.sentrysoftware.ipmi.core.coding.commands.PrivilegeLevel;
28  import org.sentrysoftware.ipmi.core.coding.commands.ResponseData;
29  import org.sentrysoftware.ipmi.core.coding.payload.CompletionCode;
30  import org.sentrysoftware.ipmi.core.coding.payload.IpmiPayload;
31  import org.sentrysoftware.ipmi.core.coding.payload.PlainMessage;
32  import org.sentrysoftware.ipmi.core.coding.payload.lan.IPMIException;
33  import org.sentrysoftware.ipmi.core.coding.payload.lan.NetworkFunction;
34  import org.sentrysoftware.ipmi.core.coding.protocol.AuthenticationType;
35  import org.sentrysoftware.ipmi.core.coding.protocol.IpmiMessage;
36  import org.sentrysoftware.ipmi.core.coding.protocol.Ipmiv20Message;
37  import org.sentrysoftware.ipmi.core.coding.protocol.PayloadType;
38  import org.sentrysoftware.ipmi.core.coding.security.CipherSuite;
39  import org.sentrysoftware.ipmi.core.coding.security.ConfidentialityNone;
40  import org.sentrysoftware.ipmi.core.common.Randomizer;
41  import org.sentrysoftware.ipmi.core.common.TypeConverter;
42  
43  import java.security.InvalidKeyException;
44  import java.security.NoSuchAlgorithmException;
45  
46  /**
47   * <p>
48   * A wrapper for RMCP+ RAKP1 message and it's response - RAKP2 message. The same
49   * instance of this class that was used to prepare RAKP Message 1 should be also
50   * used to decode matching RAKP Message 2 since the generated random number is
51   * used in encryption process.
52   * </p>
53   * <p>
54   * Capable of calculating SIK (Session Integrity Key) when RAKP Message 2 is
55   * given ({@link #calculateSik(Rakp1ResponseData)}).
56   * </p>
57   */
58  public class Rakp1 extends IpmiCommandCoder {
59  
60      /**
61       * The Managed System's Session ID for this session. Must be as returned by
62       * the Managed System in the Open Session Response message.
63       */
64      private int managedSystemSessionId;
65  
66      /**
67       * The random number generated by console.
68       */
69      private byte[] consoleRandomNumber;
70  
71      private PrivilegeLevel requestedMaximumPrivilegeLevel;
72  
73      /**
74       * ASCII character Name that the user at the Remote Console wishes to assume
75       * for this session. It's length cannot exceed 16.
76       */
77      private String username;
78  
79      /**
80       * Password matching username.
81       */
82      private String password;
83  
84      /**
85       * Kg key associated with the target BMC. Should be null if Get Channel
86       * Authentication Capabilities Response indicated that Kg is disabled which
87       * means that 'one-key' logins are being used (
88       * {@link GetChannelAuthenticationCapabilitiesResponseData#isKgEnabled()} ==
89       * false)
90       */
91      private byte[] bmcKey;
92  
93      public void setManagedSystemSessionId(int managedSystemSessionId) {
94          this.managedSystemSessionId = managedSystemSessionId;
95      }
96  
97      public int getManagedSystemSessionId() {
98          return managedSystemSessionId;
99      }
100 
101     public void setRequestedMaximumPrivilegeLevel(
102             PrivilegeLevel requestedMaximumPrivilegeLevel) {
103         this.requestedMaximumPrivilegeLevel = requestedMaximumPrivilegeLevel;
104     }
105 
106     public PrivilegeLevel getRequestedMaximumPrivilegeLevel() {
107         return requestedMaximumPrivilegeLevel;
108     }
109 
110     public void setUsername(String username) {
111         if (username.length() > 16) {
112             throw new IllegalArgumentException(
113                     "Username is too long. It's length cannot exceed 16");
114         }
115         this.username = username;
116     }
117 
118     public String getUsername() {
119         return username;
120     }
121 
122     private void setPassword(String password) {
123         this.password = password;
124     }
125 
126     public String getPassword() {
127         return password;
128     }
129 
130     private void setConsoleRandomNumber(byte[] randomNumber) {
131         this.consoleRandomNumber = randomNumber;
132     }
133 
134     public byte[] getConsoleRandomNumber() {
135         return consoleRandomNumber;
136     }
137 
138     private void setBmcKey(byte[] bmcKey) {
139         this.bmcKey = bmcKey;
140     }
141 
142     public byte[] getBmcKey() {
143         return bmcKey;
144     }
145 
146     /**
147      * Initiates class for encoding and decoding. Sets IPMI version to
148      * {@link IpmiVersion#V20} since RAKP1 is a RMCP+ command. Sets
149      * Authentication Type to RMCP+.
150      *
151      * @param managedSystemSessionId
152      *            - The Managed System's Session ID for this session. Must be as
153      *            returned by the Managed System in the Open Session Response
154      *            message.
155      * @param privilegeLevel
156      *            - Requested Maximum {@link PrivilegeLevel}
157      * @param username
158      *            - ASCII character Name that the user at the Remote Console
159      *            wishes to assume for this session. It's length cannot exceed
160      *            16.
161      * @param password
162      *            - password matching username
163      * @param bmcKey
164      *            - BMC specific key. Should be null if Get Channel
165      *            Authentication Capabilities Response indicated that Kg is
166      *            disabled which means that 'one-key' logins are being used (
167      *            {@link GetChannelAuthenticationCapabilitiesResponseData#isKgEnabled()}
168      *            == false)
169      * @param cipherSuite
170      *            - {@link CipherSuite} containing authentication,
171      *            confidentiality and integrity algorithms for this session.
172      */
173     public Rakp1(int managedSystemSessionId, PrivilegeLevel privilegeLevel,
174             String username, String password, byte[] bmcKey,
175             CipherSuite cipherSuite) {
176         super(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
177         setManagedSystemSessionId(managedSystemSessionId);
178         setRequestedMaximumPrivilegeLevel(privilegeLevel);
179         setUsername(username);
180         setPassword(password);
181         this.setBmcKey(bmcKey);
182 
183         // prepare random number
184         byte[] random = new byte[16];
185 
186         for (int i = 0; i < 4; ++i) {
187             byte[] rand = TypeConverter.intToLittleEndianByteArray(Randomizer
188                     .getInt());
189 
190             System.arraycopy(rand, 0, random, 4 * i, 4);
191         }
192 
193         setConsoleRandomNumber(random);
194     }
195 
196     @Override
197     public IpmiMessage encodePayload(int messageSequenceNumber, int sessionSequenceNumber, int sessionId) {
198         if (sessionId != 0) {
199             throw new IllegalArgumentException("Session ID must be 0");
200         }
201         Ipmiv20Message message = new Ipmiv20Message(new ConfidentialityNone());
202 
203         message.setPayloadType(PayloadType.Rakp1);
204         message.setSessionID(0);
205         message.setSessionSequenceNumber(0);
206         message.setAuthenticationType(getAuthenticationType());
207         message.setPayloadAuthenticated(false);
208         message.setPayloadEncrypted(false);
209 
210         message.setPayload(preparePayload(messageSequenceNumber));
211 
212         return message;
213     }
214 
215     @Override
216     protected IpmiPayload preparePayload(int sequenceNumber) {
217         byte[] payload = null;
218 
219         if (getUsername() == null) {
220             setUsername("");
221         }
222 
223         payload = new byte[28 + getUsername().length()];
224 
225         // message tag
226         payload[0] = TypeConverter.intToByte(sequenceNumber);
227 
228         payload[1] = 0; // reserved
229         payload[2] = 0; // reserved
230         payload[3] = 0; // reserved
231 
232         byte[] sId = TypeConverter
233                 .intToLittleEndianByteArray(getManagedSystemSessionId());
234 
235         System.arraycopy(sId, 0, payload, 4, 4); // managed system session ID
236 
237         System.arraycopy(consoleRandomNumber, 0, payload, 8, 16); // generated
238                                                                     // random
239         // number
240 
241         // requested privilege level; set name-only lookup
242         payload[24] = TypeConverter
243                 .intToByte(encodePrivilegeLevel(requestedMaximumPrivilegeLevel) | 0x10);
244 
245         payload[25] = 0; // reserved
246         payload[26] = 0; // reserved
247 
248         payload[27] = TypeConverter.intToByte(getUsername().length()); // username
249                                                                         // length
250 
251         if (getUsername().length() > 0) {
252             System.arraycopy(getUsername().getBytes(), 0, payload, 28,
253                     getUsername().length()); // username
254         }
255 
256         return new PlainMessage(payload);
257     }
258 
259     @Override
260     public byte getCommandCode() {
261         return 0;
262     }
263 
264     @Override
265     public NetworkFunction getNetworkFunction() {
266         return null;
267     }
268 
269     /**
270      * @throws IllegalArgumentException
271      *             when message is not a response for class-specific command,
272      *             response has invalid length or authentication check fails.
273      * @throws NoSuchAlgorithmException
274      *             - when authentication, confidentiality or integrity algorithm
275      *             fails.
276      * @throws InvalidKeyException
277      *             - when creating of the algorithm key fails
278      */
279     @Override
280     public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException {
281         if (!isCommandResponse(message)) {
282             throw new IllegalArgumentException("This is not RAKP 2 message!");
283         }
284 
285         byte[] payload = message.getPayload().getPayloadData();
286 
287         Rakp1ResponseData data = new Rakp1ResponseData();
288 
289         data.setMessageTag(payload[0]);
290 
291         data.setStatusCode(payload[1]);
292 
293         if (payload[1] != 0) {
294             throw new IPMIException(CompletionCode.parseInt(TypeConverter
295                     .byteToInt(payload[1])));
296         }
297 
298         if (payload.length < 40) {
299             throw new IllegalArgumentException("Invalid payload length");
300         }
301 
302         byte[] buffer = new byte[4];
303 
304         System.arraycopy(payload, 4, buffer, 0, 4);
305 
306         data.setRemoteConsoleSessionId(TypeConverter
307                 .littleEndianByteArrayToInt(buffer));
308 
309         byte[] managedSystemGuid = new byte[16];
310 
311         System.arraycopy(payload, 24, managedSystemGuid, 0, 16);
312 
313         data.setManagedSystemGuid(managedSystemGuid);
314 
315         byte[] managedSystemRandomNumber = new byte[16];
316 
317         System.arraycopy(payload, 8, managedSystemRandomNumber, 0, 16);
318 
319         data.setManagedSystemRandomNumber(managedSystemRandomNumber);
320 
321         byte[] key = null;
322 
323         int length = getCipherSuite().getAuthenticationAlgorithm()
324                 .getKeyLength();
325 
326         if (length > 0) {
327             key = new byte[length];
328             System.arraycopy(payload, 40, key, 0, length);
329         }
330 
331         if (!getCipherSuite().getAuthenticationAlgorithm()
332                 .checkKeyExchangeAuthenticationCode(
333                         prepareKeyExchangeAuthenticationCodeBase(data), key,
334                         getPassword())) {
335             throw new IllegalArgumentException("Authentication check failed");
336         }
337 
338         return data;
339     }
340 
341     /**
342      * @return byte array holding prepared base for calculating
343      *         KeyExchangeAuthenticationCode for RAKP Message 2
344      */
345     private byte[] prepareKeyExchangeAuthenticationCodeBase(
346             Rakp1ResponseData responseData) {
347         int length = 58;
348         if (getUsername() != null) {
349             length += getUsername().length();
350         }
351         byte[] keac = new byte[length];
352 
353         byte[] rSID = TypeConverter.intToLittleEndianByteArray(responseData
354                 .getRemoteConsoleSessionId());
355 
356         System.arraycopy(rSID, 0, keac, 0, 4);
357 
358         byte[] mSID = TypeConverter
359                 .intToLittleEndianByteArray(getManagedSystemSessionId());
360 
361         System.arraycopy(mSID, 0, keac, 4, 4);
362 
363         System.arraycopy(getConsoleRandomNumber(), 0, keac, 8, 16);
364 
365         System.arraycopy(responseData.getManagedSystemRandomNumber(), 0, keac,
366                 24, 16);
367 
368         System.arraycopy(responseData.getManagedSystemGuid(), 0, keac, 40, 16);
369 
370         keac[56] = TypeConverter
371                 .intToByte(encodePrivilegeLevel(requestedMaximumPrivilegeLevel) | 0x10);
372 
373         if (getUsername() != null) {
374             keac[57] = TypeConverter.intToByte(getUsername().length());
375             if (getUsername().length() > 0) {
376                 System.arraycopy(getUsername().getBytes(), 0, keac, 58,
377                         getUsername().length());
378             }
379         } else {
380             keac[57] = 0;
381         }
382 
383         return keac;
384     }
385 
386     /**
387      * Calculates SIK (Session Integrity Key) based on RAKP Messages 1 and 2
388      *
389      * @param responseData
390      *            RAKP Message 2 data
391      * @return Session Integrity Key
392      * @throws NoSuchAlgorithmException
393      *             - when authentication, confidentiality or integrity algorithm
394      *             fails.
395      * @throws InvalidKeyException
396      *             - when creating of the algorithm key fails
397      */
398     public byte[] calculateSik(Rakp1ResponseData responseData)
399             throws InvalidKeyException, NoSuchAlgorithmException {
400         byte[] key = null;
401         if (getBmcKey() == null || getBmcKey().length <= 0) {
402             key = getPassword().getBytes();
403         } else {
404             key = getBmcKey();
405         }
406 
407         return getCipherSuite().getAuthenticationAlgorithm()
408                 .getKeyExchangeAuthenticationCode(prepareSikBase(responseData),
409                         new String(key));
410     }
411 
412     /**
413      * @return byte array holding prepared base for calculating Session
414      *         Integrity Key
415      */
416     private byte[] prepareSikBase(Rakp1ResponseData responseData) {
417         int length = 34;
418         if (getUsername() != null) {
419             length += getUsername().length();
420         }
421 
422         byte[] sikBase = new byte[length];
423 
424         System.arraycopy(getConsoleRandomNumber(), 0, sikBase, 0, 16);
425 
426         System.arraycopy(responseData.getManagedSystemRandomNumber(), 0,
427                 sikBase, 16, 16);
428 
429         sikBase[32] = TypeConverter
430                 .intToByte(encodePrivilegeLevel(requestedMaximumPrivilegeLevel) | 0x10);
431 
432         if (getUsername() != null) {
433             sikBase[33] = TypeConverter.intToByte(getUsername().length());
434             if (getUsername().length() > 0) {
435                 System.arraycopy(getUsername().getBytes(), 0, sikBase, 34,
436                         getUsername().length());
437             }
438         } else {
439             sikBase[33] = 0;
440         }
441 
442         return sikBase;
443     }
444 
445     @Override
446     public boolean isCommandResponse(IpmiMessage message) {
447         return message instanceof Ipmiv20Message && ((Ipmiv20Message) message).getPayloadType() == PayloadType.Rakp2;
448     }
449 }