View Javadoc
1   package org.metricshub.ipmi.core.coding.protocol.decoder;
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.protocol.AuthenticationType;
26  import org.metricshub.ipmi.core.coding.protocol.IpmiMessage;
27  import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
28  import org.metricshub.ipmi.core.coding.protocol.PayloadType;
29  import org.metricshub.ipmi.core.coding.rmcp.RmcpMessage;
30  import org.metricshub.ipmi.core.coding.security.CipherSuite;
31  import org.metricshub.ipmi.core.coding.security.ConfidentialityNone;
32  import org.metricshub.ipmi.core.common.TypeConverter;
33  
34  
35  import java.security.InvalidKeyException;
36  import java.util.Arrays;
37  
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Decodes IPMI v2.0 session header and retrieves encrypted payload.
43   */
44  public class Protocolv20Decoder extends ProtocolDecoder {
45  
46      private static Logger logger = LoggerFactory.getLogger(Protocolv20Decoder.class);
47      
48      private CipherSuite cipherSuite;
49  
50      /**
51       * Initiates IPMI v2.0 packet decoder.
52       *
53       * @param cipherSuite
54       *            - {@link CipherSuite} that will be used to decode the message
55       */
56      public Protocolv20Decoder(CipherSuite cipherSuite) {
57          super();
58          this.cipherSuite = cipherSuite;
59      }
60  
61      /**
62       * Decodes IPMI v2.0 message fields.
63       *
64       * @param rmcpMessage
65       *            - RMCP message to decode.
66       * @return decoded message
67       * @see Ipmiv20Message
68       * @throws IllegalArgumentException
69       *             when delivered RMCP message does not contain encapsulated
70       *             IPMI message or when AuthCode field is incorrect (integrity
71       *             check fails).
72       * @throws InvalidKeyException
73       *             - when initiation of the integrity algorithm fails
74       */
75      @Override
76      public IpmiMessage decode(RmcpMessage rmcpMessage) throws InvalidKeyException {
77          Ipmiv20Message message = new Ipmiv20Message(
78                  cipherSuite.getConfidentialityAlgorithm());
79  
80          byte[] raw = rmcpMessage.getData();
81  
82          message.setAuthenticationType(decodeAuthenticationType(raw[0]));
83  
84          message.setPayloadEncrypted(decodeEncryption(raw[1]));
85  
86          message.setPayloadAuthenticated(decodeAuthentication(raw[1]));
87  
88          message.setPayloadType(decodePayloadType(raw[1]));
89  
90          int offset = 2;
91  
92          if (message.getPayloadType() == PayloadType.Oem) {
93              message.setOemIANA(decodeOEMIANA(raw));
94              offset += 4;
95  
96              message.setOemPayloadID(decodeOEMPayloadId(raw, offset));
97              offset += 2;
98          }
99  
100         message.setSessionID(decodeSessionID(raw, offset));
101         offset += 4;
102 
103         message.setSessionSequenceNumber(decodeSessionSequenceNumber(raw,
104                 offset));
105         offset += 4;
106 
107         int payloadLength = decodePayloadLength(raw, offset);
108         offset += 2;
109 
110         if (message.isPayloadEncrypted()) {
111             message.setPayload(decodePayload(raw, offset, payloadLength,
112                     message.getConfidentialityAlgorithm(), message.getPayloadType()));
113         } else {
114             message.setPayload(decodePayload(raw, offset, payloadLength,
115                     new ConfidentialityNone(), message.getPayloadType()));
116         }
117 
118         offset += payloadLength;
119 
120         if (message.getAuthenticationType() != AuthenticationType.None
121                 && !(message.getAuthenticationType() == AuthenticationType.RMCPPlus && !message
122                         .isPayloadAuthenticated())
123                 && message.getSessionID() != 0) {
124             offset = skipIntegrityPAD(raw, offset);
125             message.setAuthCode(decodeAuthCode(raw, offset));
126             if (!validateAuthCode(raw, offset)) {
127                 logger.warn("Integrity check failed");
128             }
129         }
130 
131         return message;
132     }
133 
134     /**
135      * Decodes first bit of Payload Type.
136      *
137      * @param payloadType
138      * @return True if payload is encrypted, false otherwise.
139      */
140     private boolean decodeEncryption(byte payloadType) {
141         return (payloadType & TypeConverter.intToByte(0x80)) != 0;
142     }
143 
144     /**
145      * Decodes second bit of Payload Type.
146      *
147      * @param payloadType
148      * @return True if payload is authenticated, false otherwise.
149      */
150     public boolean decodeAuthentication(byte payloadType) {
151         return (payloadType & TypeConverter.intToByte(0x40)) != 0;
152     }
153 
154     public static PayloadType decodePayloadType(byte payloadType) {
155         return PayloadType.parseInt(TypeConverter.intToByte(payloadType
156                 & TypeConverter.intToByte(0x3f)));
157     }
158 
159     /**
160      * Decodes OEM IANA.
161      *
162      * @param rawMessage
163      *            - Byte array holding whole message data.
164      * @return OEM IANA number.
165      */
166     private int decodeOEMIANA(byte[] rawMessage) {
167         byte[] oemIANA = new byte[4];
168 
169         System.arraycopy(rawMessage, 3, oemIANA, 0, 3);
170         oemIANA[3] = 0;
171 
172         return TypeConverter.littleEndianByteArrayToInt(oemIANA);
173     }
174 
175     /**
176      * Decodes OEM payload ID. To implement manufacturer-specific OEM Payload ID
177      * decoding, override this function.
178      *
179      * @param rawMessage
180      *            - Byte array holding whole message data.
181      * @param offset
182      *            - Offset to OEM payload ID in header.
183      * @return Decoded OEM payload ID.
184      */
185     protected Object decodeOEMPayloadId(byte[] rawMessage, int offset) {
186         byte[] oemPayload = new byte[2];
187 
188         System.arraycopy(rawMessage, offset, oemPayload, 0, 2);
189 
190         return oemPayload;
191     }
192 
193     @Override
194     protected int decodePayloadLength(byte[] rawData, int offset) {
195         byte[] payloadLength = new byte[4];
196         System.arraycopy(rawData, offset, payloadLength, 0, 2);
197         payloadLength[2] = 0;
198         payloadLength[3] = 0;
199 
200         return TypeConverter.littleEndianByteArrayToInt(payloadLength);
201     }
202 
203     /**
204      * Skips the integrity pad and pad length fields.
205      *
206      * @param rawMessage
207      *            - Byte array holding whole message data.
208      * @param offset
209      *            - Offset to integrity pad.
210      * @return Offset to Auth Code
211      * @throws IndexOutOfBoundsException
212      *             when message is corrupted and pad length does not appear
213      *             after integrity pad or length is incorrect.
214      */
215     private int skipIntegrityPAD(final byte[] rawMessage, final int offset) {
216         int skip = 0;
217         while (TypeConverter.byteToInt(rawMessage[offset + skip]) == 0xff) {
218             ++skip;
219         }
220         int length = TypeConverter.byteToInt(rawMessage[offset + skip]);
221         if (length != skip) {
222             throw new IndexOutOfBoundsException("Message is corrupted.");
223         }
224 
225         int currentOffset = offset + skip + 2; // skip pad length and next header fields
226         if (currentOffset >= rawMessage.length) {
227             throw new IndexOutOfBoundsException("Message is corrupted.");
228         }
229         return currentOffset;
230     }
231 
232     /**
233      * Decodes the Auth Code.
234      *
235      * @param rawMessage
236      *            - Byte array holding whole message data.
237      * @param offset
238      *            - Offset to auth code.
239      * @return Auth Code
240      * @throws IndexOutOfBoundsException
241      *             when message is corrupted and pad length does not appear
242      *             after integrity pad or length is incorrect.
243      */
244     private byte[] decodeAuthCode(byte[] rawMessage, int offset) {
245         byte[] authCode = new byte[rawMessage.length - offset];
246         System.arraycopy(rawMessage, offset, authCode, 0, authCode.length);
247         return authCode;
248     }
249 
250     /**
251      * Checks if Auth Code of the received message is valid
252      *
253      * @param rawMessage
254      *            - received message
255      * @param offset
256      *            - offset to the AuthCode field in the message
257      * @return True if AuthCode is correct, false otherwise.
258      * @throws InvalidKeyException
259      *             - when initiation of the integrity algorithm fails
260      */
261     private boolean validateAuthCode(byte[] rawMessage, int offset) {
262         byte[] base = new byte[offset];
263 
264         System.arraycopy(rawMessage, 0, base, 0, offset);
265 
266         byte[] authCode = null;
267 
268         if (rawMessage.length > offset) {
269             authCode = new byte[rawMessage.length - offset];
270             System.arraycopy(rawMessage, offset, authCode, 0, authCode.length);
271         }
272 
273         return Arrays.equals(authCode, cipherSuite.getIntegrityAlgorithm()
274                 .generateAuthCode(base));
275     }
276 
277     /**
278      * Decodes session ID.
279      *
280      * @param message
281      *            - message to get session ID from
282      * @return Session ID.
283      */
284     public static int decodeSessionID(RmcpMessage message) {
285         int offset = 2;
286         if (decodePayloadType(message.getData()[1]) == PayloadType.Oem) {
287             offset += 6;
288         }
289         return decodeSessionID(message.getData(), offset);
290     }
291 }