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 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 }