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 java.security.InvalidKeyException;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.Arrays;
29
30 import javax.crypto.Mac;
31 import javax.crypto.spec.SecretKeySpec;
32
33 import org.metricshub.ipmi.core.coding.commands.session.Rakp1;
34
35 /**
36 * Interface for authentication algorithms. All classes extending this one must
37 * have a parameterless constructor.
38 */
39 public abstract class AuthenticationAlgorithm {
40
41 /**
42 * Block length of MD5, SHA-1 and SHA-256, the hash functions of the RAKP HMACs (RFC 2104 and FIPS 180-4).
43 */
44 private static final int HMAC_BLOCK_LENGTH = 64;
45
46 private final Mac mac;
47
48 /**
49 * Constructs an authentication algorithm.
50 */
51 protected AuthenticationAlgorithm(String algorithmName) {
52 this(CipherSuite.newMacInstance(algorithmName));
53 }
54
55 /**
56 * Constructs an authentication algorithm with the provided MAC.
57 *
58 * @param mac the MAC instance to use
59 */
60 private AuthenticationAlgorithm(Mac mac) {
61 this.mac = mac;
62 }
63
64 /**
65 * @return algorithm-specific code
66 */
67 public abstract byte getCode();
68
69 /**
70 * @return length of the key for the RAKP2 message
71 */
72 public abstract int getKeyLength();
73
74 /**
75 * @return length of the integrity check base for RAKP4 message
76 */
77 public abstract int getIntegrityCheckBaseLength();
78
79 /**
80 * Checks value of the Key Exchange Authentication Code in RAKP messages
81 *
82 * @param data - The base for authentication algorithm. Depends on RAKP
83 * Message.
84 * @param key - the Key Exchange Authentication Code to check.
85 * @param password - password of the user establishing a session, as bytes
86 * @return True if authentication check was successful, false otherwise.
87 * @throws NoSuchAlgorithmException when initiation of the algorithm fails
88 * @throws InvalidKeyException when creating of the algorithm key fails
89 */
90 public boolean checkKeyExchangeAuthenticationCode(byte[] data, byte[] key, byte[] password)
91 throws NoSuchAlgorithmException,
92 InvalidKeyException {
93 byte[] check = getKeyExchangeAuthenticationCode(data, password);
94 return MessageDigest.isEqual(check, key);
95 }
96
97 /**
98 * Calculates value of the Key Exchange Authentication Code in RAKP messages
99 *
100 * @param data - The base for authentication algorithm. Depends on RAKP
101 * Message.
102 * @param key - the password of the user establishing a session, or the BMC key (Kg), as bytes
103 * @return the Key Exchange Authentication Code
104 * @throws NoSuchAlgorithmException when initiation of the algorithm fails
105 * @throws InvalidKeyException when creating of the algorithm key fails
106 */
107 public byte[] getKeyExchangeAuthenticationCode(byte[] data, byte[] key)
108 throws NoSuchAlgorithmException,
109 InvalidKeyException {
110 // The HMAC of RFC 2104 computed here rather than with a Mac, whose SecretKeySpec would keep a copy of the
111 // password or of the BMC key that cannot be wiped: the padded key is wiped once used
112 MessageDigest digest = MessageDigest.getInstance(getDigestName());
113 byte[] pad = new byte[HMAC_BLOCK_LENGTH];
114 try {
115 System.arraycopy(key, 0, pad, 0, key.length);
116 for (int i = 0; i < pad.length; i++) {
117 pad[i] ^= 0x36;
118 }
119 digest.update(pad);
120 byte[] inner = digest.digest(data);
121 for (int i = 0; i < pad.length; i++) {
122 pad[i] ^= 0x36 ^ 0x5c;
123 }
124 digest.update(pad);
125 return digest.digest(inner);
126 } finally {
127 Arrays.fill(pad, (byte) 0);
128 }
129 }
130
131 /**
132 * @return the name of the {@link MessageDigest} of the HMAC of {@link #getAlgorithmName()}
133 */
134 private String getDigestName() throws NoSuchAlgorithmException {
135 switch (getAlgorithmName()) {
136 case "HmacMD5":
137 return "MD5";
138 case "HmacSHA1":
139 return "SHA-1";
140 case "HmacSHA256":
141 return "SHA-256";
142 default:
143 throw new NoSuchAlgorithmException("No digest for " + getAlgorithmName());
144 }
145 }
146
147 /**
148 * Validates Integrity Check Value in RAKP Message 4.
149 *
150 * @param data - The base for authentication algorithm.
151 * @param reference - The Integrity Check Value to validate.
152 * @param sik - The Session Integrity Key generated on base of RAKP
153 * Messages 1 and 2.
154 * @see Rakp1#calculateSik(org.metricshub.ipmi.core.coding.commands.session.Rakp1ResponseData)
155 * @return True if integrity check was successful, false otherwise.
156 * @throws NoSuchAlgorithmException when initiation of the algorithm fails
157 * @throws InvalidKeyException when creating of the algorithm key fails
158 */
159 public boolean doIntegrityCheck(byte[] data, byte[] reference, byte[] sik)
160 throws InvalidKeyException,
161 NoSuchAlgorithmException {
162
163 SecretKeySpec sKey = new SecretKeySpec(sik, getAlgorithmName());
164 mac.init(sKey);
165
166 final int integrityCheckLength = getIntegrityCheckBaseLength();
167 final byte[] result = new byte[integrityCheckLength];
168
169 System.arraycopy(mac.doFinal(data), 0, result, 0, integrityCheckLength);
170
171 return MessageDigest.isEqual(result, reference);
172 }
173
174 /**
175 * @return the name of the algorithm as a {@code String}.
176 */
177 public abstract String getAlgorithmName();
178 }