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.commands.ResponseData;
28  import org.metricshub.ipmi.core.coding.payload.CompletionCode;
29  import org.metricshub.ipmi.core.coding.payload.IpmiPayload;
30  import org.metricshub.ipmi.core.coding.payload.PlainMessage;
31  import org.metricshub.ipmi.core.coding.payload.lan.IPMIException;
32  import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction;
33  import org.metricshub.ipmi.core.coding.protocol.AuthenticationType;
34  import org.metricshub.ipmi.core.coding.protocol.IpmiMessage;
35  import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
36  import org.metricshub.ipmi.core.coding.protocol.PayloadType;
37  import org.metricshub.ipmi.core.coding.protocol.encoder.Protocolv20Encoder;
38  import org.metricshub.ipmi.core.coding.security.CipherSuite;
39  import org.metricshub.ipmi.core.coding.security.ConfidentialityNone;
40  import org.metricshub.ipmi.core.coding.security.SecurityConstants;
41  import org.metricshub.ipmi.core.common.TypeConverter;
42  
43  import java.security.InvalidKeyException;
44  import java.security.NoSuchAlgorithmException;
45  
46  /**
47   * A wrapper for RMCP+ RAKP3 message and it's response - RAKP4 message.
48   */
49  public class Rakp3 extends IpmiCommandCoder {
50  
51      /**
52       * Status of the previous message.
53       */
54      private byte statusCode;
55  
56      /**
57       * The Managed System's Session ID for this session. Must be as returned by
58       * the Managed System in the Open Session Response message.
59       */
60      private int managedSystemSessionId;
61  
62      private Rakp1 rakp1;
63  
64      private Rakp1ResponseData rakp1ResponseData;
65  
66      public void setStatusCode(byte statusCode) {
67          this.statusCode = statusCode;
68      }
69  
70      public byte getStatusCode() {
71          return statusCode;
72      }
73  
74      public void setManagedSystemSessionId(int managedSystemSessionId) {
75          this.managedSystemSessionId = managedSystemSessionId;
76      }
77  
78      public int getManagedSystemSessionId() {
79          return managedSystemSessionId;
80      }
81  
82      /**
83       * Initiates class for decoding. Sets IPMI version to
84       * {@link IpmiVersion#V20} since RAKP1 is a RMCP+ command. Sets
85       * Authentication Type to RMCP+.
86       *
87       * @param cipherSuite
88       *            - {@link CipherSuite} containing authentication,
89       *            confidentiality and integrity algorithms for this session.
90       *            Only authentication algorithm is used at this point of
91       *            creating a session.
92       * @param rakp1
93       *            - RAKP Message 1 sent earlier in the authentification process
94       * @param rakp1ResponseData
95       *            - RAKP Message 2 received earlier in the authentification
96       *            process
97       *
98       */
99      public Rakp3(CipherSuite cipherSuite, Rakp1 rakp1,
100             Rakp1ResponseData rakp1ResponseData) {
101         super(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
102         this.rakp1 = rakp1;
103         this.rakp1ResponseData = rakp1ResponseData;
104         setCipherSuite(new CipherSuite((byte) 0, cipherSuite
105                 .getAuthenticationAlgorithm().getCode(), (byte) 0, (byte) 0));
106     }
107 
108     /**
109      * Initiates class for encoding and decoding. Sets IPMI version to
110      * {@link IpmiVersion#V20} since RAKP1 is a RMCP+ command. Sets
111      * Authentication Type to RMCP+.
112      *
113      * @param statusCode
114      *            - Status of the previous message.
115      * @param managedSystemSessionId
116      *            - The Managed System�s Session ID for this session. Must be as
117      *            returned by the Managed System in the Open Session Response
118      *            message.
119      * @param cipherSuite
120      *            - {@link CipherSuite} containing authentication,
121      *            confidentiality and integrity algorithms for this session.
122      *            Only authentication algorithm is used at this point of
123      *            creating a session.
124      * @param rakp1
125      *            - RAKP Message 1 sent earlier in the authentification process
126      * @param rakp1ResponseData
127      *            - RAKP Message 2 received earlier in the authentification
128      *            process
129      */
130     public Rakp3(byte statusCode, int managedSystemSessionId,
131             CipherSuite cipherSuite, Rakp1 rakp1,
132             Rakp1ResponseData rakp1ResponseData) {
133         super(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
134         setStatusCode(statusCode);
135         setManagedSystemSessionId(managedSystemSessionId);
136         this.rakp1 = rakp1;
137         this.rakp1ResponseData = rakp1ResponseData;
138         setCipherSuite(new CipherSuite((byte) 0, cipherSuite
139                 .getAuthenticationAlgorithm().getCode(), (byte) 0, (byte) 0));
140     }
141 
142     @Override
143     public IpmiMessage encodePayload(int messageSequenceNumber, int sessionSequenceNumber, int sessionId)
144             throws NoSuchAlgorithmException, InvalidKeyException {
145 
146         if (sessionId != 0) {
147             throw new IllegalArgumentException("Session ID must be 0");
148         }
149 
150         Ipmiv20Message message = new Ipmiv20Message(new ConfidentialityNone());
151 
152         message.setPayloadType(PayloadType.Rakp3);
153         message.setSessionID(0);
154         message.setSessionSequenceNumber(0);
155         message.setAuthenticationType(getAuthenticationType());
156         message.setPayloadAuthenticated(getCipherSuite()
157                 .getIntegrityAlgorithm().getCode() != SecurityConstants.IA_NONE);
158         message.setPayloadEncrypted(false);
159 
160         message.setPayload(preparePayload(messageSequenceNumber));
161 
162         message.setAuthCode(getCipherSuite()
163                 .getIntegrityAlgorithm()
164                 .generateAuthCode(
165                         message.getIntegrityAlgorithmBase(new Protocolv20Encoder())));
166 
167         return message;
168     }
169 
170     @Override
171     protected IpmiPayload preparePayload(int sequenceNumber)
172             throws NoSuchAlgorithmException, InvalidKeyException {
173         byte[] payload = new byte[8];
174 
175         payload[0] = TypeConverter.intToByte(sequenceNumber); // message
176                                                                     // tag
177 
178         payload[1] = getStatusCode(); // last message status code
179 
180         payload[2] = 0; // reserved
181         payload[3] = 0; // reserved
182 
183         byte[] manSesId = TypeConverter
184                 .intToLittleEndianByteArray(getManagedSystemSessionId());
185 
186         System.arraycopy(manSesId, 0, payload, 4, 4); // managed system session
187                                                         // id
188 
189         byte[] exchangeAuthCode = getCipherSuite().getAuthenticationAlgorithm()
190                 .getKeyExchangeAuthenticationCode(
191                         prepareKeyExchangeAuthenticationCodeBase(rakp1,
192                                 rakp1ResponseData), rakp1.getPassword());
193 
194         byte[] result = null;
195 
196         if (exchangeAuthCode != null) {
197             result = new byte[8 + exchangeAuthCode.length];
198             System.arraycopy(exchangeAuthCode, 0, result, 8,
199                     exchangeAuthCode.length);
200         } else {
201             result = new byte[8];
202         }
203 
204         System.arraycopy(payload, 0, result, 0, 8);
205 
206         return new PlainMessage(result);
207     }
208 
209     /**
210      * @return byte array holding prepared base for calculating
211      *         KeyExchangeAuthenticationCode for RAKP Message 3
212      */
213     private byte[] prepareKeyExchangeAuthenticationCodeBase(Rakp1 rakp1,
214             Rakp1ResponseData responseData) {
215         int length = 22;
216         if (rakp1.getUsername() != null) {
217             length += rakp1.getUsername().length();
218         }
219         byte[] keac = new byte[length];
220 
221         System.arraycopy(responseData.getManagedSystemRandomNumber(), 0, keac,
222                 0, 16);
223 
224         System.arraycopy(TypeConverter.intToLittleEndianByteArray(responseData
225                 .getRemoteConsoleSessionId()), 0, keac, 16, 4);
226 
227         keac[20] = TypeConverter.intToByte(encodePrivilegeLevel(rakp1
228                 .getRequestedMaximumPrivilegeLevel()) | 0x10);
229 
230         if (rakp1.getUsername() != null) {
231             keac[21] = TypeConverter.intToByte(rakp1.getUsername().length());
232             if (rakp1.getUsername().length() > 0) {
233                 System.arraycopy(rakp1.getUsername().getBytes(), 0, keac, 22,
234                         rakp1.getUsername().length());
235             }
236         } else {
237             keac[21] = 0;
238         }
239 
240         return keac;
241     }
242 
243     @Override
244     public byte getCommandCode() {
245         return 0;
246     }
247 
248     @Override
249     public NetworkFunction getNetworkFunction() {
250         return null;
251     }
252 
253     @Override
254     public ResponseData getResponseData(IpmiMessage message) throws IPMIException, InvalidKeyException, NoSuchAlgorithmException {
255 
256         if (!isCommandResponse(message)) {
257             throw new IllegalArgumentException("This is not RAKP 4 message!");
258         }
259 
260         byte[] payload = message.getPayload().getPayloadData();
261 
262         Rakp3ResponseData data = new Rakp3ResponseData();
263 
264         data.setMessageTag(payload[0]);
265 
266         data.setStatusCode(payload[1]);
267 
268         if (payload[1] != 0) {
269             throw new IPMIException(CompletionCode.parseInt(TypeConverter
270                     .byteToInt(payload[1])));
271         }
272 
273         if (payload.length < 8) {
274             throw new IllegalArgumentException("Invalid payload length");
275         }
276 
277         byte[] buffer = new byte[4];
278 
279         System.arraycopy(payload, 4, buffer, 0, 4);
280 
281         data.setConsoleSessionId(TypeConverter
282                 .littleEndianByteArrayToInt(buffer));
283 
284         byte[] integrityCheck = null;
285 
286         if (payload.length > 8) {
287             integrityCheck = new byte[getCipherSuite()
288                     .getAuthenticationAlgorithm().getIntegrityCheckBaseLength()];
289             System.arraycopy(payload, 8, integrityCheck, 0, getCipherSuite()
290                     .getAuthenticationAlgorithm().getIntegrityCheckBaseLength());
291         }
292 
293         if (!getCipherSuite().getAuthenticationAlgorithm().doIntegrityCheck(
294                 prepareIntegrityCheckBase(rakp1, rakp1ResponseData),
295                 integrityCheck, rakp1.calculateSik(rakp1ResponseData))) {
296             throw new IllegalArgumentException("Integrity check failed");
297         }
298 
299         return data;
300     }
301 
302     /**
303      * @return byte array holding prepared base for calculating Integrity Check
304      */
305     private byte[] prepareIntegrityCheckBase(Rakp1 rakp1,
306             Rakp1ResponseData responseData) {
307         byte[] icb = new byte[36];
308 
309         System.arraycopy(rakp1.getConsoleRandomNumber(), 0, icb, 0, 16);
310 
311         System.arraycopy(TypeConverter.intToLittleEndianByteArray(rakp1
312                 .getManagedSystemSessionId()), 0, icb, 16, 4);
313 
314         System.arraycopy(responseData.getManagedSystemGuid(), 0, icb, 20, 16);
315 
316         return icb;
317     }
318 
319     @Override
320     public boolean isCommandResponse(IpmiMessage message) {
321         return message instanceof Ipmiv20Message && ((Ipmiv20Message) message).getPayloadType() == PayloadType.Rakp4;
322     }
323 
324 }