///|
priv struct Handshake {
username : Bytes
password_key : Bytes
console_id : UInt32
console_random : Bytes
}
///|
fn handshake(
username : Bytes,
password : Bytes,
console_id : UInt32,
console_random : Bytes,
) -> Handshake raise ProtocolError {
guard username.length() >= 1 &&
username.length() <= 16 &&
password.length() >= 1 &&
password.length() <= 20 &&
console_id != 0U &&
console_random.length() == 16 else {
raise invalid(
"username must contain 1..16 bytes and password 1..20 bytes; nonzero ID and 16 random bytes required",
)
}
let padded = Array::make(20, b'\x00')
for i in 0.. SessionKeys raise {
let input : Array[Byte] = []
append(input, h.console_random)
append(input, challenge.bmc_random)
input.append([2, byte(h.username.length())])
append(input, h.username)
let sik = @crypto.hmac_sha256(h.password_key, Bytes::from_array(input))
{
sik,
k1: @crypto.hmac_sha256(sik, Bytes::make(20, 1)),
k2: slice(@crypto.hmac_sha256(sik, Bytes::make(20, 2)), 0, 16),
}
}
///|
fn verify_rakp2(
h : Handshake,
bmc_id : UInt32,
challenge : RakpChallenge,
) -> SessionKeys raise {
let input : Array[Byte] = []
put32(input, h.console_id)
put32(input, bmc_id)
append(input, h.console_random)
append(input, challenge.bmc_random)
append(input, challenge.bmc_guid)
input.append([2, byte(h.username.length())])
append(input, h.username)
let expected = @crypto.hmac_sha256(h.password_key, Bytes::from_array(input))
guard @crypto.constant_time_equal(expected, challenge.authentication_code) else {
raise invalid("RAKP2 authentication failed")
}
derive_keys(h, challenge)
}
///|
fn rakp3_code(h : Handshake, challenge : RakpChallenge) -> Bytes raise {
let input : Array[Byte] = []
append(input, challenge.bmc_random)
put32(input, h.console_id)
input.append([2, byte(h.username.length())])
append(input, h.username)
@crypto.hmac_sha256(h.password_key, Bytes::from_array(input))
}
///|
fn verify_rakp4(
h : Handshake,
bmc_id : UInt32,
challenge : RakpChallenge,
keys : SessionKeys,
code : Bytes,
) -> Unit raise {
let input : Array[Byte] = []
append(input, h.console_random)
put32(input, bmc_id)
append(input, challenge.bmc_guid)
let expected = slice(
@crypto.hmac_sha256(keys.sik, Bytes::from_array(input)),
0,
16,
)
guard @crypto.constant_time_equal(expected, code) else {
raise invalid("RAKP4 authentication failed")
}
}
///|
fn pad_payload(payload : Bytes) -> Bytes raise ProtocolError {
guard payload.length() >= 1 && payload.length() <= 1024 else {
raise invalid("invalid plaintext payload length")
}
let padding = (16 - (payload.length() + 1) % 16) % 16
let out = payload.to_array()
for i in 1..<=padding {
out.push(byte(i))
}
out.push(byte(padding))
Bytes::from_array(out)
}
///|
fn unpad_payload(data : Bytes) -> Bytes raise ProtocolError {
guard data.length() >= 16 && data.length() % 16 == 0 else {
raise invalid("invalid encrypted payload size")
}
let padding = data[data.length() - 1].to_int()
guard padding <= 15 && padding + 1 < data.length() else {
raise invalid("invalid IPMI confidentiality padding")
}
let end = data.length() - padding - 1
for i in 0.. Bytes raise {
guard session_id != 0U && sequence != 0U else {
raise invalid("zero active session ID or sequence")
}
let cipher = @crypto.aes_cbc(true, keys.k2, iv, pad_payload(payload))
let out : Array[Byte] = [6, 0, 0xff, 7, 6, 0xc0]
put32(out, session_id)
put32(out, sequence)
let length = 16 + cipher.length()
out.append([byte(length & 255), byte(length >> 8)])
append(out, iv)
append(out, cipher)
let pad = (4 - (out.length() - 4 + 2) % 4) % 4
for _ in 0.. (UInt32, Bytes) raise {
guard data.length() >= 66 && data.length() <= 1120 else {
raise invalid("invalid protected datagram length")
}
guard data[0] == 6 &&
data[1] == 0 &&
data[2] == 0xff &&
data[3] == 7 &&
data[4] == 6 &&
data[5] == 0xc0 else {
raise invalid("expected encrypted and authenticated RMCP+ IPMI packet")
}
guard u32(data, 6) == session_id && session_id != 0U else {
raise invalid("session identity mismatch")
}
let sequence = u32(data, 10)
guard sequence != 0U && sequence > previous_sequence else {
raise invalid("stale session sequence")
}
let length = data[14].to_int() | (data[15].to_int() << 8)
let end = 16 + length
guard length >= 32 && length % 16 == 0 && end + 18 <= data.length() else {
raise invalid("invalid confidentiality payload length")
}
let authenticated_end = data.length() - 16
let mac = @crypto.hmac_sha256(keys.k1, slice(data, 4, authenticated_end))
guard @crypto.constant_time_equal(
slice(mac, 0, 16),
slice(data, authenticated_end, data.length()),
) else {
raise invalid("packet integrity check failed")
}
// All parsing below is of authenticated bytes. In particular, never expose
// a CBC padding oracle by decrypting an unauthenticated datagram.
let padding = data[authenticated_end - 2].to_int()
guard padding <= 3 &&
end + padding + 2 == authenticated_end &&
(authenticated_end - 4) % 4 == 0 &&
data[authenticated_end - 1] == 7 else {
raise invalid("invalid integrity trailer")
}
for i in end..<(end + padding) {
guard data[i] == 0xff else { raise invalid("invalid integrity padding") }
}
let iv = slice(data, 16, 32)
let clear = @crypto.aes_cbc(false, keys.k2, iv, slice(data, 32, end))
(sequence, unpad_payload(clear))
}