Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/metricshub/ipmi/cli/CliArguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ enum Subcommand {
static final long DEFAULT_MESSAGE_TIMEOUT_MS = 5_000L;
static final int DEFAULT_RETRIES = 3;
/** The preference of the client, repeated here for the usage text and the {@code probe} output. */
static final int[] DEFAULT_CIPHER_SUITES = { 17, 3, 8, 16, 2, 7 };
static final int[] DEFAULT_CIPHER_SUITES = { 17, 3, 8, 16, 2, 7, 12, 11 };
static final char DEFAULT_ESCAPE = '~';
static final String SUBCOMMANDS = "probe, info, chassis, power, identify, sensors, fru, sel, sol or raw";
static final String POWER_ACTIONS = "status, on, off, cycle, reset, soft or diag";
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/metricshub/ipmi/cli/IpmiCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ static String usage() {
+ " -L, --privilege <level> user, operator, administrator or auto (default: the lowest the subcommand\n"
+ " needs); auto runs at user and raises the session when a command needs more\n"
+ " -C, --cipher-suite <id>[,..] One ID: use exactly this suite; several: preference order"
+ " (default: 17,3,8,16,2,7)\n"
+ " (default: 17,3,8,16,2,7,12,11)\n"
+ " -t, --timeout <ms> Deadline of the subcommand (default: 120000); for sol, of its setup\n"
+ " --message-timeout <ms> Per-message timeout (default: 5000)\n"
+ " --retries <n> Resends of a message without reply (default: 3)\n"
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/metricshub/ipmi/client/IpmiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,10 @@ public final class IpmiClient implements AutoCloseable {
* The cipher suites the client may choose when the builder sets none, by order of preference (IPMI 2.0 table
* 22-20): those that sign every message, and encrypt it when possible (17: RAKP-HMAC-SHA256, HMAC-SHA256-128,
* AES-CBC-128; 3: RAKP-HMAC-SHA1, HMAC-SHA1-96, AES-CBC-128; 8: RAKP-HMAC-MD5, HMAC-MD5-128, AES-CBC-128; then
* 16, 2 and 7, the same without encryption).
* 16, 2 and 7, the same without encryption; last 12 and 11, whose MD5-128 integrity is a plain MD5 of the
* password around the message, for the older BMCs that offer nothing better).
*/
private static final int[] DEFAULT_CIPHER_SUITES = { 17, 3, 8, 16, 2, 7 };
private static final int[] DEFAULT_CIPHER_SUITES = { 17, 3, 8, 16, 2, 7, 12, 11 };

/**
* The suites without integrity (0, 1, 6, 15), which the builder refuses: the list of suites comes unauthenticated,
Expand Down Expand Up @@ -1350,7 +1351,7 @@ public Builder privilege(PrivilegeLevel level) {

/**
* Sets the cipher suites the client may use, by order of preference: the first one the BMC offers is used.
* Default: 17, 3, 8, 16, 2, 7. The suites without integrity (0, 1, 6, 15) are refused.
* Default: 17, 3, 8, 16, 2, 7, 12, 11. The suites without integrity (0, 1, 6, 15) are refused.
*
* @param ids the suite IDs (IPMI 2.0 table 22-20)
* @return this builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,14 @@ public CipherSuite copy() {
}

/**
* Initializes algorithms contained in this {@link CipherSuite}.
* Initializes algorithms contained in this {@link CipherSuite}. A suite with MD5-128 integrity (11 to 14) is keyed
* with the password, which this method does not have: use {@link #initializeAlgorithms(byte[], byte[])} for it.
*
* @param sik
* - Session Integrity Key calculated during the opening of the
* session or user password if 'one-key' logins are enabled.
* @throws IllegalArgumentException
* - when an algorithm of the suite is not implemented
* - when an algorithm of the suite is not implemented, or when the suite uses MD5-128 integrity
* @throws InvalidKeyException
* - when initiation of the algorithm fails
* @throws NoSuchAlgorithmException
Expand All @@ -157,7 +158,35 @@ public void initializeAlgorithms(byte[] sik)
throws InvalidKeyException,
NoSuchAlgorithmException,
NoSuchPaddingException {
getIntegrityAlgorithm().initialize(sik);
if (integrityAlgorithm == SecurityConstants.IA_MD5_128) {
throw new IllegalArgumentException(
"Integrity algorithm MD5-128 is keyed with the password: use initializeAlgorithms(sik, password)");
}
initializeAlgorithms(sik, null);
Comment thread
bertysentry marked this conversation as resolved.
}

/**
* Initializes algorithms contained in this {@link CipherSuite}: the HMAC integrity algorithms and the
* confidentiality algorithm are keyed with the Session Integrity Key, MD5-128 integrity (suites 11 to 14) with the
* user's password (IPMI 2.0 section 13.28.4).
*
* @param sik the Session Integrity Key calculated during the opening of the session
* @param password the password of the user, as sent to the BMC; null for an empty password
* @throws IllegalArgumentException
* - when an algorithm of the suite is not implemented
* @throws InvalidKeyException
* - when initiation of the algorithm fails
* @throws NoSuchAlgorithmException
* - when initiation of the algorithm fails
* @throws NoSuchPaddingException
* - when initiation of the algorithm fails
*/
public void initializeAlgorithms(byte[] sik, byte[] password)
throws InvalidKeyException,
NoSuchAlgorithmException,
NoSuchPaddingException {
IntegrityAlgorithm integrity = getIntegrityAlgorithm();
integrity.initialize(integrity.getCode() == SecurityConstants.IA_MD5_128 ? password : sik);
getConfidentialityAlgorithm().initialize(sik, getAuthenticationAlgorithm());
}

Expand Down Expand Up @@ -202,8 +231,7 @@ public IntegrityAlgorithm getIntegrityAlgorithm() {
case SecurityConstants.IA_HMAC_SHA1_96:
return instantiateIntegrityAlgorithm(IntegrityHmacSha1_96::new);
case SecurityConstants.IA_MD5_128:
// TODO: MD5-128
throw new IllegalArgumentException("Integrity algorithm MD5-128 is not yet implemented.");
return instantiateIntegrityAlgorithm(IntegrityMd5_128::new);
case SecurityConstants.IA_HMAC_MD5_128:
return instantiateIntegrityAlgorithm(IntegrityHmacMd5_128::new);
case SecurityConstants.IA_HMAC_SHA256_128:
Expand Down Expand Up @@ -237,11 +265,11 @@ public ConfidentialityAlgorithm getConfidentialityAlgorithm() {
}
return ca;
case SecurityConstants.CA_XRC4_40:
// TODO: XRc4-40
throw new IllegalArgumentException("Confidentiality algorithm XRC4-40 is not yet implemented.");
// xRC4 is deprecated by the IPMI 2.0 specification (section 13.29) and no current BMC requires it: not
// implemented, the suites 4, 5, 9, 10, 13, 14, 18 and 19 are reported as unsupported
throw new IllegalArgumentException("Confidentiality algorithm XRC4-40 is not implemented.");
case SecurityConstants.CA_XRC4_128:
// TODO: XRc4-128
throw new IllegalArgumentException("Confidentiality algorithm XRC4-128 is not yet implemented.");
throw new IllegalArgumentException("Confidentiality algorithm XRC4-128 is not implemented.");
default:
throw new IllegalArgumentException(
"Invalid confidentiality algorithm.");
Expand Down Expand Up @@ -300,8 +328,8 @@ public static List<CipherSuite> getCipherSuites(byte[] bytes) {

/**
* Tells whether this library implements the algorithms of this suite. The suites listed by
* {@link #getCipherSuites(byte[])} include the ones it does not implement (MD5-128 integrity, xRC4
* confidentiality), which fail when the session is opened.
* {@link #getCipherSuites(byte[])} include the ones it does not implement (xRC4 confidentiality: suites 4, 5, 9,
* 10, 13, 14, 18 and 19), which fail when the session is opened.
*
* @return true when the authentication, integrity and confidentiality algorithms are all implemented
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.metricshub.ipmi.core.coding.security;

/*-
* ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
* IPMI Java Client
* ჻჻჻჻჻჻
* Copyright 2023 MetricsHub
* ჻჻჻჻჻჻
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
*/

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

// Name mirrors the IPMI 2.0 integrity algorithm name (MD5-128), like the HMAC-xxx-nn classes
// CHECKSTYLE.OFF: TypeName
/**
* MD5-128 integrity algorithm (IPMI 2.0 section 13.28.4): a plain MD5 of the password around the message,
* {@code AuthCode = MD5(password + AuthType/Format ... Next Header + password)}, where the password is the user's key
* (Kuid) zero-padded to 20 bytes as the BMC stores it. Unlike the HMAC algorithms, it is not keyed with the Session
* Integrity Key: {@link #initialize(byte[])} takes the password.
*/
public class IntegrityMd5_128 extends IntegrityAlgorithm {
// CHECKSTYLE.ON: TypeName

public static final String ALGORITHM_NAME = "MD5";

private static final int AUTH_CODE_LENGTH = 16;

private static final int PASSWORD_LENGTH = 20;

private final MessageDigest md5;

/**
* Initiates the MD5-128 integrity algorithm.
*/
public IntegrityMd5_128() {
super("");
try {
md5 = MessageDigest.getInstance(ALGORITHM_NAME);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Algorithm " + ALGORITHM_NAME + " is not available", e);
}
}

/**
* Keys the algorithm with the user's password (not the Session Integrity Key), zero-padded to 20 bytes.
*
* @param password the password of the user, as sent to the BMC; null for an empty password
*/
@Override
public synchronized void initialize(byte[] password) {
setSik(Arrays.copyOf(password == null ? new byte[0] : password, PASSWORD_LENGTH));
}

@Override
public synchronized byte[] generateAuthCode(byte[] base) {
byte[] password = getSik();
if (password == null) {
throw new NullPointerException("Algorithm not initialized.");
}
byte[] data = base[base.length - 2] == 0 ? injectIntegrityPad(base, AUTH_CODE_LENGTH) : base;
md5.reset();
md5.update(password);
md5.update(data);
md5.update(password);
return md5.digest();
}

@Override
public byte getCode() {
return SecurityConstants.IA_MD5_128;
}

@Override
public String getAlgorithmName() {
return ALGORITHM_NAME;
}

@Override
public int getAuthCodeLength() {
return AUTH_CODE_LENGTH;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ public int startSession(
throw new ConnectionException("Session Integrity Key is null");
}

cipherSuite.initializeAlgorithms(sik);
cipherSuite.initializeAlgorithms(sik, password);

lastAction = null;

Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ either option can be repeated or given a comma-separated list.
| `-kf, --bmc-key-file <file>` | Read the hexadecimal BMC key from a file. |
| `-P, --port <port>` | UDP port of the BMC. Default: 623. |
| `-L, --privilege <level>` | `user`, `operator`, `administrator` or `auto` (case-insensitive). Default: the lowest level the subcommand needs (see [below](#default-privilege-per-subcommand)). |
| `-C, --cipher-suite <id>[,<id>...]` | One ID: use exactly this cipher suite, without discovery. Several: the preference order among the suites the BMC offers. Default: `17,3,8,16,2,7`. Suites without integrity (0, 1, 6, 15) are refused: their replies could be forged. |
| `-C, --cipher-suite <id>[,<id>...]` | One ID: use exactly this cipher suite, without discovery. Several: the preference order among the suites the BMC offers. Default: `17,3,8,16,2,7,12,11`. Suites without integrity (0, 1, 6, 15) are refused: their replies could be forged. |
| `-t, --timeout <ms>` | Deadline of the subcommand, in milliseconds. Default: 120000. For `sol`, it bounds the session setup and the payload activation only. |
| `--message-timeout <ms>` | Per-message timeout. Default: 5000. |
| `--retries <n>` | Resends of a message that got no reply. Default: 3. |
Expand Down
12 changes: 7 additions & 5 deletions src/site/markdown/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ try (IpmiClient bmc = IpmiClient.builder("bmc.example.com")
.bmcKey(kg) // optional: two-key login (Kg), raw bytes
.port(623) // default
.privilege(PrivilegeLevel.Operator) // maximum privilege of the session; default User
.cipherSuites(17, 3) // preference among the suites the BMC offers; default 17, 3, 8, 16, 2, 7
.cipherSuites(17, 3) // preference among the suites the BMC offers; default 17, 3, 8, 16, 2, 7, 12, 11
.timeout(Duration.ofMinutes(2)) // overall deadline of each operation; default 2 min
.messageTimeout(Duration.ofSeconds(5)) // per-message timeout; default 5 s
.retries(3) // resends of a message without reply; default 3
Expand All @@ -39,7 +39,7 @@ message timeouts, retries or keep-alive periods. Nothing is configured globally.
| `credentials(user, password)` | required by every operation but `capabilities()` | [Credentials](#credentials) |
| `bmcKey(byte[])` | none | [BMC key](#bmc-key) |
| `privilege(PrivilegeLevel)` | `User` | [Privilege level](#privilege-level) |
| `cipherSuites(int...)`, `cipherSuite(int)` | `17, 3, 8, 16, 2, 7` | [Cipher suites](#cipher-suites) |
| `cipherSuites(int...)`, `cipherSuite(int)` | `17, 3, 8, 16, 2, 7, 12, 11` | [Cipher suites](#cipher-suites) |
| `timeout(Duration)` | 2 minutes | [Timeout](#timeout) |
| `messageTimeout(Duration)` | 5 seconds | [Message timeout and retries](#message-timeout-and-retries) |
| `retries(int)` | `3` | [Message timeout and retries](#message-timeout-and-retries) |
Expand Down Expand Up @@ -122,10 +122,12 @@ command, Set Session Privilege Level included: the raise is then refused once an

`cipherSuites(int...)` lists the suites the client may use, by order of preference: before opening
the session, the client asks the BMC which suites it offers (Get Channel Cipher Suites) and takes
the first of the list that the BMC offers. The default, `17, 3, 8, 16, 2, 7`, prefers the suites
that sign and encrypt every message (17: RAKP-HMAC-SHA256, HMAC-SHA256-128, AES-CBC-128; 3:
the first of the list that the BMC offers. The default, `17, 3, 8, 16, 2, 7, 12, 11`, prefers the
suites that sign and encrypt every message (17: RAKP-HMAC-SHA256, HMAC-SHA256-128, AES-CBC-128; 3:
RAKP-HMAC-SHA1, HMAC-SHA1-96, AES-CBC-128; 8: RAKP-HMAC-MD5, HMAC-MD5-128, AES-CBC-128), then
the same without encryption. A BMC that offers none of the listed suites fails the first operation
the same without encryption, and last the MD5-128 suites (12 with AES-CBC-128, 11 without), whose
signature is a plain MD5 of the password around the message. A BMC that offers none of the listed
suites fails the first operation
with `IpmiClientException: The BMC ... offers none of the cipher suites ...`.

`cipherSuite(int)` uses exactly that suite without asking the BMC, which saves the Get Channel
Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/low-level-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ being delivered to the listeners, and its timeout is not reported.
the algorithms of the [table](preparing-the-bmc.html#cipher-suites) (the OEM suites are left
out), including suites the library does not implement. Choose one it implements, with integrity:
**3** or **17** in practice; `CipherSuite.isSupported()` tells whether the library implements the
algorithms of a suite, and a suite with an xRC4 or MD5-128 algorithm fails when the session is
algorithms of a suite, and a suite with an xRC4 algorithm fails when the session is
opened. Do not fall back to a suite without integrity (0, 1, 6, 15) just because the BMC lists
nothing else: the list is not authenticated. To skip the discovery when you already know the
suite, build it with `CipherSuite.forId()` and pass it to `createConnection()` (Get Channel
Expand Down
2 changes: 1 addition & 1 deletion src/site/markdown/migrating-from-verax.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Code written for the Verax library keeps working after a package rename.

| Area | Change |
| --- | --- |
| Cipher suites | RAKP-HMAC-SHA256 and RAKP-HMAC-MD5 authentication, HMAC-SHA256-128 and HMAC-MD5-128 integrity: suites 6 to 8 and 15 to 17 work, in addition to 0 to 3. |
| Cipher suites | RAKP-HMAC-SHA256 and RAKP-HMAC-MD5 authentication, HMAC-SHA256-128, HMAC-MD5-128 and MD5-128 integrity: suites 6 to 8, 11, 12 and 15 to 17 work, in addition to 0 to 3. |
| Keep-alive | `IpmiConnector(int port, long pingPeriod)` sets the keep-alive period of the connector, or disables it (`0`). |
| SDR repository | Every OEM record type (`C0h` – `FFh`, not only `C0h`) is decoded as `OemRecord`, and a record that cannot be decoded is skipped instead of aborting the walk. Truncated Get SDR replies are read again in chunks. |
| Sensor records | Full, Compact and Event-Only records share `AbstractSensorRecord` ([Upgrading](upgrading.html#sensor-records)). |
Expand Down
22 changes: 14 additions & 8 deletions src/site/markdown/preparing-the-bmc.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,30 +87,36 @@ them). The client implements:
| 7 | RAKP-HMAC-MD5 | HMAC-MD5-128 | none | yes |
| 8 | RAKP-HMAC-MD5 | HMAC-MD5-128 | AES-CBC-128 | yes |
| 9, 10 | RAKP-HMAC-MD5 | HMAC-MD5-128 | xRC4-128, xRC4-40 | no |
| 11 – 14 | RAKP-HMAC-MD5 | MD5-128 | none, AES-CBC-128, xRC4 | no |
| 11 | RAKP-HMAC-MD5 | MD5-128 | none | yes |
| 12 | RAKP-HMAC-MD5 | MD5-128 | AES-CBC-128 | yes |
| 13, 14 | RAKP-HMAC-MD5 | MD5-128 | xRC4-128, xRC4-40 | no |
| 15 | RAKP-HMAC-SHA256 | none | none | yes |
| 16 | RAKP-HMAC-SHA256 | HMAC-SHA256-128 | none | yes |
| **17** | RAKP-HMAC-SHA256 | HMAC-SHA256-128 | AES-CBC-128 | **yes** |
| 18, 19 | RAKP-HMAC-SHA256 | HMAC-SHA256-128 | xRC4-128, xRC4-40 | no |

Suites **3** and **17** sign and encrypt every message, and every current BMC offers at least
one of them. Prefer **17** where available.
one of them. Prefer **17** where available. The xRC4 suites are not implemented: the IPMI 2.0
specification deprecates xRC4, and no BMC requires it. MD5-128 (suites 11 and 12) is a plain MD5 of
the password around each message rather than an HMAC of the session key: it is implemented for the
older BMCs that offer nothing better, and comes last in the default preference.

### How `IpmiClient` chooses the suite

By default (`cipherSuites(17, 3, 8, 16, 2, 7)`), the client first asks the BMC for its list of
cipher suites (Get Channel Cipher Suites) when it opens its session, then picks the first of these
suites that the BMC offers:
By default (`cipherSuites(17, 3, 8, 16, 2, 7, 12, 11)`), the client first asks the BMC for its
list of cipher suites (Get Channel Cipher Suites) when it opens its session, then picks the first of
these suites that the BMC offers:

1. **17**, then **3**, then 8: messages signed and encrypted;
2. 16, then 2, then 7: messages signed, not encrypted.
2. 16, then 2, then 7: messages signed, not encrypted;
3. 12, then 11: messages signed with MD5-128, encrypted or not.

A Lenovo XCC that offers suites 1 to 19 gets suite 17, a Cisco IMC that offers 0 to 14 gets
suite 3. The client never chooses a suite without integrity (0, 1, 6, 15), even when the BMC
offers nothing else: the list of suites is not authenticated, so an attacker on the network
could otherwise make the client fall back to a session whose replies can be forged. A BMC that
offers none of the six fails the first operation with `IpmiClientException: The BMC ... offers
none of the cipher suites [17, 3, 8, 16, 2, 7] (offered: ...)`. The algorithms of a suite are those of the
offers none of the eight fails the first operation with `IpmiClientException: The BMC ... offers
none of the cipher suites [17, 3, 8, 16, 2, 7, 12, 11] (offered: ...)`. The algorithms of a suite are those of the
table above, whatever the BMC's record of the suite says, and the OEM suites a BMC may list (as
Cisco IMC does) are ignored.

Expand Down
Loading
Loading