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 }