View Javadoc
1   package org.sentrysoftware.ipmi.core.coding.security;
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.session.GetChannelCipherSuites;
26  import org.sentrysoftware.ipmi.core.coding.commands.session.GetChannelCipherSuitesResponseData;
27  import org.sentrysoftware.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      public static final String NOT_YET_IMPLEMENTED_MESSAGE = "Not yet implemented.";
44  
45      private byte id;
46  
47      private byte authenticationAlgorithm;
48      private byte integrityAlgorithm;
49      private byte confidentialityAlgorithm;
50  
51      private AuthenticationAlgorithm aa;
52      private ConfidentialityAlgorithm ca;
53      private IntegrityAlgorithm ia;
54  
55      public byte getId() {
56          return id;
57      }
58  
59      public CipherSuite(byte id, byte authenticationAlgorithm,
60              byte confidentialityAlgorithm, byte integrityAlgorithm) {
61          this.id = id;
62          this.authenticationAlgorithm = (authenticationAlgorithm);
63          this.confidentialityAlgorithm = (confidentialityAlgorithm);
64          this.integrityAlgorithm = (integrityAlgorithm);
65      }
66  
67      /**
68       * Initializes algorithms contained in this {@link CipherSuite}.
69       *
70       * @param sik
71       *            - Session Integrity Key calculated during the opening of the
72       *            session or user password if 'one-key' logins are enabled.
73       * @throws IllegalArgumentException
74       * @throws InvalidKeyException
75       *             - when initiation of the algorithm fails
76       * @throws NoSuchAlgorithmException
77       *             - when initiation of the algorithm fails
78       * @throws NoSuchPaddingException
79       *             - when initiation of the algorithm fails
80       */
81      public void initializeAlgorithms(byte[] sik) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
82          getIntegrityAlgorithm().initialize(sik);
83          getConfidentialityAlgorithm().initialize(sik, getAuthenticationAlgorithm());
84      }
85  
86  	/**
87  	 * Returns instance of AuthenticationAlgorithm class.
88  	 *
89  	 * @throws IllegalArgumentException when authentication algorithm code is
90  	 *                                  incorrect.
91  	 */
92  	public AuthenticationAlgorithm getAuthenticationAlgorithm() {
93  		if (aa != null && aa.getCode() != authenticationAlgorithm) {
94  			throw new IllegalArgumentException("Invalid authentication algorithm code");
95  		}
96  		switch (authenticationAlgorithm) {
97  		case SecurityConstants.AA_RAKP_NONE:
98  			return instantiateAuthenticationAlgorithm(AuthenticationRakpNone::new);
99  		case SecurityConstants.AA_RAKP_HMAC_SHA1:
100 			return instantiateAuthenticationAlgorithm(AuthenticationRakpHmacSha1::new);
101 		case SecurityConstants.AA_RAKP_HMAC_MD5:
102 			return instantiateAuthenticationAlgorithm(AuthenticationRakpHmacMd5::new);
103 		case SecurityConstants.AA_RAKP_HMAC_SHA256:
104 			return instantiateAuthenticationAlgorithm(AuthenticationRakpHmacSha256::new);
105 		default:
106 			throw new IllegalArgumentException("Invalid authentication algorithm.");
107 		}
108 	}
109 
110 	/**
111 	 * Returns instance of IntegrityAlgorithm class.
112 	 *
113 	 * @throws IllegalArgumentException when integrity algorithm code is incorrect.
114 	 */
115 	public IntegrityAlgorithm getIntegrityAlgorithm() {
116 		if (ia != null && ia.getCode() != integrityAlgorithm) {
117 			throw new IllegalArgumentException("Invalid integrity algorithm code");
118 		}
119 		switch (integrityAlgorithm) {
120 		case SecurityConstants.IA_NONE:
121 			return instantiateIntegrityAlgorithm(IntegrityNone::new);
122 		case SecurityConstants.IA_HMAC_SHA1_96:
123 			return instantiateIntegrityAlgorithm(IntegrityHmacSha1_96::new);
124 		case SecurityConstants.IA_MD5_128:
125 			// TODO: MD5-128
126 			throw new IllegalArgumentException("Integrity algorithm MD5-128 is not yet implemented.");
127 		case SecurityConstants.IA_HMAC_MD5_128:
128 			return instantiateIntegrityAlgorithm(IntegrityHmacMd5_128::new);
129 		case SecurityConstants.IA_HMAC_SHA256_128:
130 			return instantiateIntegrityAlgorithm(IntegrityHmacSha256_128::new);
131 		default:
132 			throw new IllegalArgumentException("Invalid integrity algorithm.");
133 		}
134 	}
135 
136     /**
137      * Returns instance of ConfidentialityAlgorithm class.
138      *
139      * @throws IllegalArgumentException
140      *             when confidentiality algorithm code is incorrect.
141      */
142     public ConfidentialityAlgorithm getConfidentialityAlgorithm() {
143         if (ca != null && ca.getCode() != confidentialityAlgorithm) {
144             throw new IllegalArgumentException(
145                     "Invalid confidentiality algorithm code");
146         }
147         switch (confidentialityAlgorithm) {
148         case SecurityConstants.CA_NONE:
149             if (ca == null) {
150                 ca = new ConfidentialityNone();
151             }
152             return ca;
153         case SecurityConstants.CA_AES_CBC128:
154             if (ca == null) {
155                 ca = new ConfidentialityAesCbc128();
156             }
157             return ca;
158         case SecurityConstants.CA_XRC4_40:
159             // TODO: XRc4-40
160             throw new IllegalArgumentException("Confidentiality algorithm XRC4-40 is not yet implemented.");
161         case SecurityConstants.CA_XRC4_128:
162             // TODO: XRc4-128
163             throw new IllegalArgumentException("Confidentiality algorithm XRC4-128 is not yet implemented.");
164         default:
165             throw new IllegalArgumentException(
166                     "Invalid confidentiality algorithm.");
167 
168         }
169     }
170 
171     /**
172      * Builds Cipher Suites collection from raw data received by
173      * {@link GetChannelCipherSuites} commands. Cannot be executed in
174      * {@link GetChannelCipherSuitesResponseData} since data comes in 16-byte
175      * packets and is fragmented. Supports only one integrity and one
176      * confidentiality algorithm per suite.
177      *
178      * @param bytes
179      *            - concatenated Cipher Suite Records received by
180      *            {@link GetChannelCipherSuites} commands.
181      * @return list of Cipher Suites supported by BMC.
182      */
183     public static List<CipherSuite> getCipherSuites(byte[] bytes) {
184         ArrayList<CipherSuite> suites = new ArrayList<CipherSuite>();
185 
186         int offset = 0;
187 
188         while (offset < bytes.length) {
189             byte id = bytes[offset + 1];
190             if (bytes[offset] == TypeConverter.intToByte(0xC0)) {
191                 offset += 2;
192             } else {
193                 offset += 5;
194             }
195             byte aa = bytes[offset];
196             byte ca = -1;
197             byte ia = -1;
198             ++offset;
199             while (offset < bytes.length
200                     && bytes[offset] != TypeConverter.intToByte(0xC0)
201                     && bytes[offset] != TypeConverter.intToByte(0xC1)) {
202                 if ((TypeConverter.byteToInt(bytes[offset]) & 0xC0) == 0x80) {
203                     ca = TypeConverter.intToByte(TypeConverter
204                             .byteToInt(bytes[offset]) & 0x3f);
205                 } else if ((TypeConverter.byteToInt(bytes[offset]) & 0xC0) == 0x40) {
206                     ia = TypeConverter.intToByte(TypeConverter
207                             .byteToInt(bytes[offset]) & 0x3f);
208                 }
209                 ++offset;
210             }
211             suites.add(new CipherSuite(id, aa, ca, ia));
212         }
213 
214         return suites;
215     }
216 
217     /**
218      * @return {@link CipherSuite} with algorithms set to
219      *         {@link AuthenticationRakpNone}, {@link ConfidentialityNone} and
220      *         {@link IntegrityNone}.
221      */
222     public static CipherSuite getEmpty() {
223         return new CipherSuite((byte) 0, (byte) 0, (byte) 0, (byte) 0);
224     }
225 
226 	/**
227 	 * Creates an instance of AuthenticationAlgorithm.
228 	 *
229 	 * @param supplier constructor of the algorithm
230 	 */
231 	private AuthenticationAlgorithm instantiateAuthenticationAlgorithm(
232 			final Supplier<AuthenticationAlgorithm> constructor) {
233 		if (aa == null) {
234 			aa = constructor.get();
235 		}
236 		return aa;
237 	}
238 	
239 	/**
240 	 * Creates an instance of IntegrityAlgorithm.
241 	 *
242 	 * @param supplier constructor of the algorithm
243 	 */
244 	private IntegrityAlgorithm instantiateIntegrityAlgorithm(final Supplier<IntegrityAlgorithm> constructor) {
245 		if (ia == null) {
246 			ia = constructor.get();
247 		}
248 		return ia;
249 	}
250 
251 	/**
252 	 * Constructs a Mac object that implements the given MAC algorithm.
253 	 *
254 	 * @param algorithmName the name of the algorithm to use
255 	 * @return The Mac object that implements the specified MAC algorithm.
256 	 */
257 	public static Mac newMacInstance(final String algorithmName) {
258 		if (algorithmName == null || algorithmName.trim().isEmpty()) {
259 			return null;
260 		}
261 		try {
262 			return Mac.getInstance(algorithmName);
263 		} catch (NoSuchAlgorithmException e) {
264 			throw new IllegalArgumentException("Algorithm " + algorithmName + " is not available", e);
265 		}
266 	}
267 }