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.Rakp1;
26  import org.metricshub.ipmi.core.common.TypeConverter;
27  
28  import java.security.InvalidKeyException;
29  import java.util.Arrays;
30  
31  import javax.crypto.Mac;
32  import javax.crypto.spec.SecretKeySpec;
33  
34  /**
35   * Interface for Integrity Algorithms. All classes extending this one must
36   * implement constructor(byte[]).
37   */
38  public abstract class IntegrityAlgorithm {
39  
40  	private static final byte[] CONST1 = new byte[20];
41  	static {
42  		Arrays.fill(CONST1, (byte) 1);
43  	}
44  
45  	private byte[] sik;
46  	private final Mac mac;
47  
48  	/**
49  	 * Constructs an integrity algorithm.
50  	 */
51  	protected IntegrityAlgorithm(String algorithmName) {
52  		this(CipherSuite.newMacInstance(algorithmName));
53  	}
54  
55  	/**
56  	 * Constructs an integrity algorithm with the provided MAC.
57  	 *
58  	 * @param mac the MAC instance to use
59  	 */
60  	private IntegrityAlgorithm(Mac mac) {
61  		this.mac = mac;
62  	}
63  
64  	/**
65  	 * Initializes Integrity Algorithm
66  	 *
67  	 * @param key - Session Integrity Key calculated during the opening of the
68  	 *        session or user password if 'one-key' logins are enabled.
69  	 */
70  	public synchronized void initialize(byte[] key) throws InvalidKeyException {
71  		this.sik = key;
72  		final String algorithmName = getAlgorithmName();
73  
74  		SecretKeySpec k1 = new SecretKeySpec(key, algorithmName);
75  
76  		mac.init(k1);
77  		k1 = new SecretKeySpec(mac.doFinal(CONST1), algorithmName);
78  
79  		mac.init(k1);
80  	}
81  
82  	/**
83  	 * Returns the Session Integrity Key passed to {@link #initialize(byte[])}.
84  	 *
85  	 * @return the Session Integrity Key, or null if not initialized
86  	 */
87  	protected synchronized byte[] getSik() {
88  		return sik;
89  	}
90  
91  	/**
92  	 * Stores the Session Integrity Key without initializing the MAC, for algorithms that do not use one.
93  	 *
94  	 * @param sik the Session Integrity Key
95  	 */
96  	protected synchronized void setSik(byte[] sik) {
97  		this.sik = sik;
98  	}
99  
100 	/**
101 	 * Wipes the key passed to {@link #initialize(byte[])} (the password for MD5-128 integrity) once the session is
102 	 * over, and keys the MAC with the wiped key: the codes the algorithm generates no longer match the session.
103 	 */
104 	public synchronized void wipeKey() {
105 		if (sik == null) {
106 			return;
107 		}
108 		Arrays.fill(sik, (byte) 0);
109 		if (mac != null && sik.length > 0) {
110 			try {
111 				mac.init(new SecretKeySpec(sik, getAlgorithmName()));
112 			} catch (InvalidKeyException e) {
113 				// Cannot happen: HMAC takes a key of any non-zero length, and the session is over anyway
114 			}
115 		}
116 	}
117 
118 	/**
119 	 * Returns the algorithm's ID.
120 	 */
121 	public abstract byte getCode();
122 
123 	/**
124 	 * Creates AuthCode field for message. Synchronized: the sending, receiving and keep-alive threads share this
125 	 * instance and its {@link Mac}.
126 	 *
127 	 * @param base - data starting with the AuthType/Format field up to and
128 	 *        including the field that immediately precedes the AuthCode field
129 	 * @return AuthCode field. Might be null if empty AuthCOde field is generated.
130 	 * @see Rakp1#calculateSik(org.metricshub.ipmi.core.coding.commands.session.Rakp1ResponseData)
131 	 */
132 	public synchronized byte[] generateAuthCode(final byte[] base) {
133 
134 		if (sik == null) {
135 			throw new NullPointerException("Algorithm not initialized.");
136 		}
137 
138 		final int authCodeLength = getAuthCodeLength();
139 		final byte[] result = new byte[authCodeLength];
140 		byte[] updatedBase;
141 
142 		if (base[base.length - 2] == 0) { // No padding
143 			updatedBase = injectIntegrityPad(base, authCodeLength);
144 		} else {
145 			updatedBase = base;
146 		}
147 
148 		System.arraycopy(mac.doFinal(updatedBase), 0, result, 0, authCodeLength);
149 
150 		return result;
151 	}
152 
153 	/**
154 	 * Modifies the algorithm base since with null Auth Code during encoding
155 	 * Integrity Pad isn't calculated.
156 	 *
157 	 * @param base - integrity algorithm base without Integrity Pad.
158 	 * @param authCodeLength - expected length of the Auth Code field.
159 	 * @return - integrity algorithm base with Integrity Pad and updated Pad Length
160 	 *         field.
161 	 */
162 	protected byte[] injectIntegrityPad(byte[] base, int authCodeLength) {
163 		int pad = 0;
164 		if ((base.length + authCodeLength) % 4 != 0) {
165 			pad = 4 - (base.length + authCodeLength) % 4;
166 		}
167 
168 		if (pad != 0) {
169 			byte[] newBase = new byte[base.length + pad];
170 
171 			System.arraycopy(base, 0, newBase, 0, base.length - 2);
172 
173 			for (int i = base.length - 2; i < base.length - 2 + pad; ++i) {
174 				newBase[i] = TypeConverter.intToByte(0xff);
175 			}
176 
177 			newBase[newBase.length - 2] = TypeConverter.intToByte(pad);
178 
179 			newBase[newBase.length - 1] = base[base.length - 1];
180 
181 			return newBase;
182 		} else {
183 			return base;
184 		}
185 	}
186 
187 	/**
188 	 * Returns the name of the algorithm.
189 	 *
190 	 * @return The algorithm name as a {@code String}.
191 	 */
192 	public abstract String getAlgorithmName();
193 
194 	/**
195 	 * Returns the length of the authentication code
196 	 *
197 	 * @return The length of the authentication code in bytes.
198 	 */
199 	public abstract int getAuthCodeLength();
200 
201 }