View Javadoc
1   package org.metricshub.ipmi.core.coding.security;
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.session.GetChannelCipherSuites;
26  import org.metricshub.ipmi.core.coding.commands.session.GetChannelCipherSuitesResponseData;
27  import org.metricshub.ipmi.core.common.TypeConverter;
28  
29  import javax.crypto.Mac;
30  import javax.crypto.NoSuchPaddingException;
31  import java.security.InvalidKeyException;
32  import java.security.NoSuchAlgorithmException;
33  import java.util.ArrayList;
34  import java.util.List;
35  import java.util.function.Supplier;
36  
37  /**
38   * Provides cipher suite (authentication, confidentiality and integrity
39   * algorithms used during the session).
40   */
41  public class CipherSuite {
42  
43  	/**
44  	 * A message no exception carries: the exceptions about the algorithms this library does not implement name the
45  	 * algorithm, such as {@code Confidentiality algorithm XRC4-40 is not implemented.}
46  	 *
47  	 * @deprecated unused since 2.0.00; this constant will be removed in 3.0.
48  	 */
49  	@Deprecated
50  	public static final String NOT_YET_IMPLEMENTED_MESSAGE = "Not yet implemented.";
51  
52  	/**
53  	 * The authentication, confidentiality and integrity algorithms of the standard cipher suites 0 to 19 (IPMI 2.0
54  	 * table 22-20).
55  	 */
56  	private static final byte[][] STANDARD_SUITES = {
57  			{ SecurityConstants.AA_RAKP_NONE, SecurityConstants.CA_NONE, SecurityConstants.IA_NONE },
58  			{ SecurityConstants.AA_RAKP_HMAC_SHA1, SecurityConstants.CA_NONE, SecurityConstants.IA_NONE },
59  			{ SecurityConstants.AA_RAKP_HMAC_SHA1, SecurityConstants.CA_NONE, SecurityConstants.IA_HMAC_SHA1_96 },
60  			{ SecurityConstants.AA_RAKP_HMAC_SHA1, SecurityConstants.CA_AES_CBC128, SecurityConstants.IA_HMAC_SHA1_96 },
61  			{ SecurityConstants.AA_RAKP_HMAC_SHA1, SecurityConstants.CA_XRC4_128, SecurityConstants.IA_HMAC_SHA1_96 },
62  			{ SecurityConstants.AA_RAKP_HMAC_SHA1, SecurityConstants.CA_XRC4_40, SecurityConstants.IA_HMAC_SHA1_96 },
63  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_NONE, SecurityConstants.IA_NONE },
64  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_NONE, SecurityConstants.IA_HMAC_MD5_128 },
65  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_AES_CBC128, SecurityConstants.IA_HMAC_MD5_128 },
66  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_XRC4_128, SecurityConstants.IA_HMAC_MD5_128 },
67  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_XRC4_40, SecurityConstants.IA_HMAC_MD5_128 },
68  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_NONE, SecurityConstants.IA_MD5_128 },
69  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_AES_CBC128, SecurityConstants.IA_MD5_128 },
70  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_XRC4_128, SecurityConstants.IA_MD5_128 },
71  			{ SecurityConstants.AA_RAKP_HMAC_MD5, SecurityConstants.CA_XRC4_40, SecurityConstants.IA_MD5_128 },
72  			{ SecurityConstants.AA_RAKP_HMAC_SHA256, SecurityConstants.CA_NONE, SecurityConstants.IA_NONE },
73  			{ SecurityConstants.AA_RAKP_HMAC_SHA256, SecurityConstants.CA_NONE, SecurityConstants.IA_HMAC_SHA256_128 },
74  			{ SecurityConstants.AA_RAKP_HMAC_SHA256, SecurityConstants.CA_AES_CBC128, SecurityConstants.IA_HMAC_SHA256_128 },
75  			{ SecurityConstants.AA_RAKP_HMAC_SHA256, SecurityConstants.CA_XRC4_128, SecurityConstants.IA_HMAC_SHA256_128 },
76  			{ SecurityConstants.AA_RAKP_HMAC_SHA256, SecurityConstants.CA_XRC4_40, SecurityConstants.IA_HMAC_SHA256_128 } };
77  
78  	private byte id;
79  
80  	private byte authenticationAlgorithm;
81  	private byte integrityAlgorithm;
82  	private byte confidentialityAlgorithm;
83  
84  	private AuthenticationAlgorithm aa;
85  	private ConfidentialityAlgorithm ca;
86  	private IntegrityAlgorithm ia;
87  
88  	/**
89  	 * @return the ID of the cipher suite (IPMI 2.0 table 22-20)
90  	 */
91  	public byte getId() {
92  		return id;
93  	}
94  
95  	/**
96  	 * @return the code of the authentication algorithm (IPMI 2.0 table 13-17: {@code SecurityConstants.AA_*}), whether
97  	 *         the library implements it or not
98  	 */
99  	public byte getAuthenticationAlgorithmCode() {
100 		return authenticationAlgorithm;
101 	}
102 
103 	/**
104 	 * @return the code of the integrity algorithm (IPMI 2.0 table 13-18: {@code SecurityConstants.IA_*}), whether the
105 	 *         library implements it or not
106 	 */
107 	public byte getIntegrityAlgorithmCode() {
108 		return integrityAlgorithm;
109 	}
110 
111 	/**
112 	 * @return the code of the confidentiality algorithm (IPMI 2.0 table 13-19: {@code SecurityConstants.CA_*}), whether
113 	 *         the library implements it or not
114 	 */
115 	public byte getConfidentialityAlgorithmCode() {
116 		return confidentialityAlgorithm;
117 	}
118 
119 	/**
120 	 * Creates a cipher suite.
121 	 *
122 	 * @param id the ID of the cipher suite
123 	 * @param authenticationAlgorithm the code of the authentication algorithm ({@link SecurityConstants})
124 	 * @param confidentialityAlgorithm the code of the confidentiality algorithm
125 	 * @param integrityAlgorithm the code of the integrity algorithm
126 	 */
127 	public CipherSuite(byte id, byte authenticationAlgorithm, byte confidentialityAlgorithm, byte integrityAlgorithm) {
128 		this.id = id;
129 		this.authenticationAlgorithm = authenticationAlgorithm;
130 		this.confidentialityAlgorithm = confidentialityAlgorithm;
131 		this.integrityAlgorithm = integrityAlgorithm;
132 	}
133 
134 	/**
135 	 * Returns a new instance of this suite, with uninitialized algorithms: opening a session keys the algorithms of its
136 	 * suite, so two sessions must never share an instance.
137 	 *
138 	 * @return a cipher suite with the same ID and algorithms
139 	 */
140 	public CipherSuite copy() {
141 		return new CipherSuite(id, authenticationAlgorithm, confidentialityAlgorithm, integrityAlgorithm);
142 	}
143 
144 	/**
145 	 * Initializes algorithms contained in this {@link CipherSuite}. A suite with MD5-128 integrity (11 to 14) is keyed
146 	 * with the password, which this method does not have: use {@link #initializeAlgorithms(byte[], byte[])} for it.
147 	 *
148 	 * @param sik
149 	 *        - Session Integrity Key calculated during the opening of the
150 	 *        session or user password if 'one-key' logins are enabled.
151 	 * @throws IllegalArgumentException
152 	 *         - when an algorithm of the suite is not implemented, or when the suite uses MD5-128 integrity
153 	 * @throws InvalidKeyException
154 	 *         - when initiation of the algorithm fails
155 	 * @throws NoSuchAlgorithmException
156 	 *         - when initiation of the algorithm fails
157 	 * @throws NoSuchPaddingException
158 	 *         - when initiation of the algorithm fails
159 	 */
160 	public void initializeAlgorithms(byte[] sik)
161 			throws InvalidKeyException,
162 			NoSuchAlgorithmException,
163 			NoSuchPaddingException {
164 		if (integrityAlgorithm == SecurityConstants.IA_MD5_128) {
165 			throw new IllegalArgumentException(
166 					"Integrity algorithm MD5-128 is keyed with the password: use initializeAlgorithms(sik, password)");
167 		}
168 		initializeAlgorithms(sik, null);
169 	}
170 
171 	/**
172 	 * Initializes algorithms contained in this {@link CipherSuite}: the HMAC integrity algorithms and the
173 	 * confidentiality algorithm are keyed with the Session Integrity Key, MD5-128 integrity (suites 11 to 14) with the
174 	 * user's password (IPMI 2.0 section 13.28.4).
175 	 *
176 	 * @param sik the Session Integrity Key calculated during the opening of the session
177 	 * @param password the password of the user, as sent to the BMC; null for an empty password
178 	 * @throws IllegalArgumentException
179 	 *         - when an algorithm of the suite is not implemented
180 	 * @throws InvalidKeyException
181 	 *         - when initiation of the algorithm fails
182 	 * @throws NoSuchAlgorithmException
183 	 *         - when initiation of the algorithm fails
184 	 * @throws NoSuchPaddingException
185 	 *         - when initiation of the algorithm fails
186 	 */
187 	public void initializeAlgorithms(byte[] sik, byte[] password)
188 			throws InvalidKeyException,
189 			NoSuchAlgorithmException,
190 			NoSuchPaddingException {
191 		IntegrityAlgorithm integrity = getIntegrityAlgorithm();
192 		integrity.initialize(integrity.getCode() == SecurityConstants.IA_MD5_128 ? password : sik);
193 		getConfidentialityAlgorithm().initialize(sik, getAuthenticationAlgorithm());
194 	}
195 
196 	/**
197 	 * Returns instance of AuthenticationAlgorithm class.
198 	 *
199 	 * @return the authentication algorithm of the suite
200 	 * @throws IllegalArgumentException when authentication algorithm code is
201 	 *         incorrect.
202 	 */
203 	public AuthenticationAlgorithm getAuthenticationAlgorithm() {
204 		if (aa != null && aa.getCode() != authenticationAlgorithm) {
205 			throw new IllegalArgumentException("Invalid authentication algorithm code");
206 		}
207 		switch (authenticationAlgorithm) {
208 		case SecurityConstants.AA_RAKP_NONE:
209 			return instantiateAuthenticationAlgorithm(AuthenticationRakpNone::new);
210 		case SecurityConstants.AA_RAKP_HMAC_SHA1:
211 			return instantiateAuthenticationAlgorithm(AuthenticationRakpHmacSha1::new);
212 		case SecurityConstants.AA_RAKP_HMAC_MD5:
213 			return instantiateAuthenticationAlgorithm(AuthenticationRakpHmacMd5::new);
214 		case SecurityConstants.AA_RAKP_HMAC_SHA256:
215 			return instantiateAuthenticationAlgorithm(AuthenticationRakpHmacSha256::new);
216 		default:
217 			throw new IllegalArgumentException("Invalid authentication algorithm.");
218 		}
219 	}
220 
221 	/**
222 	 * Returns instance of IntegrityAlgorithm class.
223 	 *
224 	 * @return the integrity algorithm of the suite
225 	 * @throws IllegalArgumentException when integrity algorithm code is incorrect.
226 	 */
227 	public IntegrityAlgorithm getIntegrityAlgorithm() {
228 		if (ia != null && ia.getCode() != integrityAlgorithm) {
229 			throw new IllegalArgumentException("Invalid integrity algorithm code");
230 		}
231 		switch (integrityAlgorithm) {
232 		case SecurityConstants.IA_NONE:
233 			return instantiateIntegrityAlgorithm(IntegrityNone::new);
234 		case SecurityConstants.IA_HMAC_SHA1_96:
235 			return instantiateIntegrityAlgorithm(IntegrityHmacSha1_96::new);
236 		case SecurityConstants.IA_MD5_128:
237 			return instantiateIntegrityAlgorithm(IntegrityMd5_128::new);
238 		case SecurityConstants.IA_HMAC_MD5_128:
239 			return instantiateIntegrityAlgorithm(IntegrityHmacMd5_128::new);
240 		case SecurityConstants.IA_HMAC_SHA256_128:
241 			return instantiateIntegrityAlgorithm(IntegrityHmacSha256_128::new);
242 		default:
243 			throw new IllegalArgumentException("Invalid integrity algorithm.");
244 		}
245 	}
246 
247 	/**
248 	 * Returns instance of ConfidentialityAlgorithm class.
249 	 *
250 	 * @return the confidentiality algorithm of the suite
251 	 * @throws IllegalArgumentException
252 	 *         when confidentiality algorithm code is incorrect.
253 	 */
254 	public ConfidentialityAlgorithm getConfidentialityAlgorithm() {
255 		if (ca != null && ca.getCode() != confidentialityAlgorithm) {
256 			throw new IllegalArgumentException("Invalid confidentiality algorithm code");
257 		}
258 		switch (confidentialityAlgorithm) {
259 		case SecurityConstants.CA_NONE:
260 			if (ca == null) {
261 				ca = new ConfidentialityNone();
262 			}
263 			return ca;
264 		case SecurityConstants.CA_AES_CBC128:
265 			if (ca == null) {
266 				ca = new ConfidentialityAesCbc128();
267 			}
268 			return ca;
269 		case SecurityConstants.CA_XRC4_40:
270 			// xRC4 is optional for BMCs in IPMI 2.0 (table 13-19, section 13.30) and no current BMC requires it: not
271 			// implemented, the suites 4, 5, 9, 10, 13, 14, 18 and 19 are reported as unsupported
272 			throw new IllegalArgumentException("Confidentiality algorithm XRC4-40 is not implemented.");
273 		case SecurityConstants.CA_XRC4_128:
274 			throw new IllegalArgumentException("Confidentiality algorithm XRC4-128 is not implemented.");
275 		default:
276 			throw new IllegalArgumentException("Invalid confidentiality algorithm.");
277 
278 		}
279 	}
280 
281 	/**
282 	 * Builds Cipher Suites collection from raw data received by
283 	 * {@link GetChannelCipherSuites} commands. Cannot be executed in
284 	 * {@link GetChannelCipherSuitesResponseData} since data comes in 16-byte
285 	 * packets and is fragmented.
286 	 * <p>
287 	 * A standard record (IPMI 2.0 table 22-19) is C0h, the suite ID, then the algorithm tags. The algorithms of the
288 	 * standard suites 0 to 19 are those of IPMI 2.0 table 22-20, whatever the tags say, as in ipmitool and FreeIPMI:
289 	 * the records come unauthenticated, and a suite 17 whose tags were stripped must not become a suite without
290 	 * integrity. OEM records (C1h, the OEM suite ID, a 3-byte IANA, then the tags), whose suite IDs are vendor-specific,
291 	 * records with a reserved suite ID, and malformed or truncated records are skipped.
292 	 * </p>
293 	 *
294 	 * @param bytes
295 	 *        - concatenated Cipher Suite Records received by
296 	 *        {@link GetChannelCipherSuites} commands.
297 	 * @return list of the standard Cipher Suites offered by BMC, in the BMC's order (including suites whose
298 	 *         algorithms this library does not implement, see {@link #isSupported()})
299 	 */
300 	public static List<CipherSuite> getCipherSuites(byte[] bytes) {
301 		ArrayList<CipherSuite> suites = new ArrayList<CipherSuite>();
302 
303 		int offset = 0;
304 
305 		while (offset < bytes.length) {
306 			int start = TypeConverter.byteToInt(bytes[offset]);
307 			if (start != 0xC0 && start != 0xC1) {
308 				++offset; // not the start of a record: resynchronize on the next one
309 				continue;
310 			}
311 			int tags = offset + (start == 0xC0 ? 2 : 5);
312 			if (tags >= bytes.length) {
313 				break; // truncated record
314 			}
315 			int id = TypeConverter.byteToInt(bytes[offset + 1]);
316 			// The tags end at the next record: their bits 7:6 are never 11b
317 			offset = tags;
318 			while (offset < bytes.length && (bytes[offset] & 0xC0) != 0xC0) {
319 				++offset;
320 			}
321 			if (start == 0xC0 && id < STANDARD_SUITES.length) {
322 				byte[] algorithms = STANDARD_SUITES[id];
323 				suites.add(new CipherSuite((byte) id, algorithms[0], algorithms[1], algorithms[2]));
324 			}
325 		}
326 
327 		return suites;
328 	}
329 
330 	/**
331 	 * Tells whether this library implements the algorithms of this suite. The suites listed by
332 	 * {@link #getCipherSuites(byte[])} include the ones it does not implement (xRC4 confidentiality: suites 4, 5, 9,
333 	 * 10, 13, 14, 18 and 19), which fail when the session is opened.
334 	 *
335 	 * @return true when the authentication, integrity and confidentiality algorithms are all implemented
336 	 */
337 	public boolean isSupported() {
338 		try {
339 			getAuthenticationAlgorithm();
340 			getIntegrityAlgorithm();
341 			getConfidentialityAlgorithm();
342 			return true;
343 		} catch (IllegalArgumentException e) {
344 			return false;
345 		}
346 	}
347 
348 	/**
349 	 * @return {@link CipherSuite} with algorithms set to
350 	 *         {@link AuthenticationRakpNone}, {@link ConfidentialityNone} and
351 	 *         {@link IntegrityNone}.
352 	 */
353 	public static CipherSuite getEmpty() {
354 		return new CipherSuite((byte) 0, (byte) 0, (byte) 0, (byte) 0);
355 	}
356 
357 	/**
358 	 * Creates one of the standard cipher suites (IPMI 2.0 table 22-20) from its ID, with the algorithms the
359 	 * specification assigns to it: the suite to use with a BMC whose offer is not read.
360 	 *
361 	 * @param id the ID of the suite, 0 to 19
362 	 * @return the suite, with uninitialized algorithms
363 	 * @throws IllegalArgumentException when the ID is not a standard suite
364 	 */
365 	public static CipherSuite forId(int id) {
366 		if (id < 0 || id >= STANDARD_SUITES.length) {
367 			throw new IllegalArgumentException("Cipher suite " + id + " is not a standard suite (0 to 19)");
368 		}
369 		byte[] algorithms = STANDARD_SUITES[id];
370 		return new CipherSuite((byte) id, algorithms[0], algorithms[1], algorithms[2]);
371 	}
372 
373 	/**
374 	 * Lists the IDs of the given suites, for messages.
375 	 *
376 	 * @param suites the suites, or null
377 	 * @return the comma-separated IDs, or "none" when there is no suite
378 	 */
379 	public static String listIds(List<CipherSuite> suites) {
380 		StringBuilder ids = new StringBuilder();
381 		if (suites != null) {
382 			for (CipherSuite suite : suites) {
383 				ids.append(ids.length() == 0 ? "" : ", ").append(suite.getId());
384 			}
385 		}
386 		return ids.length() == 0 ? "none" : ids.toString();
387 	}
388 
389 	/**
390 	 * Creates an instance of AuthenticationAlgorithm.
391 	 *
392 	 * @param constructor constructor of the algorithm
393 	 */
394 	private AuthenticationAlgorithm instantiateAuthenticationAlgorithm(
395 			final Supplier<AuthenticationAlgorithm> constructor) {
396 		if (aa == null) {
397 			aa = constructor.get();
398 		}
399 		return aa;
400 	}
401 
402 	/**
403 	 * Creates an instance of IntegrityAlgorithm.
404 	 *
405 	 * @param constructor constructor of the algorithm
406 	 */
407 	/**
408 	 * Wipes the key of the integrity algorithm of the suite, if it was keyed (the password for MD5-128 integrity), once
409 	 * the session is over.
410 	 */
411 	public void wipeKeys() {
412 		if (ia != null) {
413 			ia.wipeKey();
414 		}
415 	}
416 
417 	private IntegrityAlgorithm instantiateIntegrityAlgorithm(final Supplier<IntegrityAlgorithm> constructor) {
418 		if (ia == null) {
419 			ia = constructor.get();
420 		}
421 		return ia;
422 	}
423 
424 	/**
425 	 * Constructs a Mac object that implements the given MAC algorithm.
426 	 *
427 	 * @param algorithmName the name of the algorithm to use
428 	 * @return The Mac object that implements the specified MAC algorithm.
429 	 */
430 	public static Mac newMacInstance(final String algorithmName) {
431 		if (algorithmName == null || algorithmName.trim().isEmpty()) {
432 			return null;
433 		}
434 		try {
435 			return Mac.getInstance(algorithmName);
436 		} catch (NoSuchAlgorithmException e) {
437 			throw new IllegalArgumentException("Algorithm " + algorithmName + " is not available", e);
438 		}
439 	}
440 }