ipmi_rs/rmcp/v2_0/
mod.rs

1use std::{num::NonZeroU32, ops::Add};
2
3use crate::app::auth::PrivilegeLevel;
4
5mod crypto;
6use crypto::CryptoState;
7
8mod messages;
9use ipmi_rs_core::{
10    app::auth::{AuthenticationAlgorithm, ConfidentialityAlgorithm, IntegrityAlgorithm},
11    connection::Response,
12};
13pub(super) use messages::*;
14pub use messages::{
15    ParseSessionResponseError, RakpMessage2ErrorStatusCode, RakpMessage2ParseError,
16    RakpMessage4ErrorStatusCode, RakpMessage4ParseError,
17};
18
19use self::crypto::CryptoUnwrapError;
20
21use super::{
22    internal::IpmbState, socket::RmcpIpmiSocket, v1_5, RmcpIpmiError, RmcpIpmiReceiveError,
23    UnwrapSessionError,
24};
25
26#[derive(Debug)]
27pub enum ValidateSessionResponseError {
28    MessageTagMismatch,
29    RemoteConsoleSessionIdMismatch,
30}
31
32#[derive(Debug)]
33pub enum ValidateRakpMessage2Error {
34    MessageTagMismatch,
35    RemoteConsoleSessionIdMismatch,
36}
37
38#[derive(Debug)]
39pub enum ValidateRakpMessage4Error {
40    MessageTagMismatch,
41    ManagedSystemSessionIdMismatch,
42}
43
44#[derive(Debug)]
45pub enum ActivationError {
46    Io(std::io::Error),
47    InvalidKeyExchangeAuthCodeLen(usize, AuthenticationAlgorithm),
48    OpenSessionRequestSend(WriteError),
49    OpenSessionResponseReceive(RmcpIpmiReceiveError),
50    OpenSessionResponseRead(UnwrapSessionError),
51    OpenSessionResponseParse(ParseSessionResponseError),
52    OpenSessionResponseValidate(ValidateSessionResponseError),
53    SendRakpMessage1(WriteError),
54    RakpMessage2Receive(RmcpIpmiReceiveError),
55    RakpMessage2Read(UnwrapSessionError),
56    RakpMessage2Parse(RakpMessage2ParseError),
57    RakpMessage2Validate(ValidateRakpMessage2Error),
58    RakpMessage3Send(WriteError),
59    RakpMessage4Receive(RmcpIpmiReceiveError),
60    RakpMessage4Read(UnwrapSessionError),
61    RakpMessage4Parse(RakpMessage4ParseError),
62    RakpMessage4Validate(ValidateRakpMessage4Error),
63    RakpMessage4InvalidIntegrityCheckValue,
64    ServerAuthenticationFailed,
65}
66
67impl From<ParseSessionResponseError> for ActivationError {
68    fn from(value: ParseSessionResponseError) -> Self {
69        Self::OpenSessionResponseParse(value)
70    }
71}
72
73impl From<ValidateSessionResponseError> for ActivationError {
74    fn from(value: ValidateSessionResponseError) -> Self {
75        Self::OpenSessionResponseValidate(value)
76    }
77}
78
79impl From<ValidateRakpMessage2Error> for ActivationError {
80    fn from(value: ValidateRakpMessage2Error) -> Self {
81        Self::RakpMessage2Validate(value)
82    }
83}
84
85impl From<ValidateRakpMessage4Error> for ActivationError {
86    fn from(value: ValidateRakpMessage4Error) -> Self {
87        Self::RakpMessage4Validate(value)
88    }
89}
90
91#[derive(Debug)]
92pub enum WriteError {
93    Io(std::io::Error),
94    PayloadTooLong,
95    EncryptedPayloadTooLong,
96}
97
98impl From<std::io::Error> for WriteError {
99    fn from(value: std::io::Error) -> Self {
100        Self::Io(value)
101    }
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub enum ReadError {
106    NotIpmiV2_0,
107    NotEnoughData,
108    NotRmcpPlus,
109    InvalidPayloadType(u8),
110    DecryptionError(CryptoUnwrapError),
111}
112
113impl From<CryptoUnwrapError> for ReadError {
114    fn from(value: CryptoUnwrapError) -> Self {
115        Self::DecryptionError(value)
116    }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq)]
120pub enum PayloadType {
121    IpmiMessage,
122    Sol,
123    RmcpPlusOpenSessionRequest,
124    RmcpPlusOpenSessionResponse,
125    RakpMessage1,
126    RakpMessage2,
127    RakpMessage3,
128    RakpMessage4,
129}
130
131#[derive(Debug, Clone)]
132pub struct Message {
133    pub ty: PayloadType,
134    pub session_id: u32,
135    pub session_sequence_number: u32,
136    pub payload: Vec<u8>,
137}
138
139impl TryFrom<u8> for PayloadType {
140    type Error = ();
141
142    fn try_from(value: u8) -> Result<Self, Self::Error> {
143        let ty = match value {
144            0x00 => PayloadType::IpmiMessage,
145            0x01 => PayloadType::Sol,
146            0x10 => PayloadType::RmcpPlusOpenSessionRequest,
147            0x11 => PayloadType::RmcpPlusOpenSessionResponse,
148            0x12 => PayloadType::RakpMessage1,
149            0x13 => PayloadType::RakpMessage2,
150            0x14 => PayloadType::RakpMessage3,
151            0x15 => PayloadType::RakpMessage4,
152            _ => return Err(()),
153        };
154
155        Ok(ty)
156    }
157}
158
159impl From<PayloadType> for u8 {
160    fn from(value: PayloadType) -> Self {
161        match value {
162            PayloadType::IpmiMessage => 0x00,
163            PayloadType::Sol => 0x01,
164            PayloadType::RmcpPlusOpenSessionRequest => 0x10,
165            PayloadType::RmcpPlusOpenSessionResponse => 0x11,
166            PayloadType::RakpMessage1 => 0x12,
167            PayloadType::RakpMessage2 => 0x13,
168            PayloadType::RakpMessage3 => 0x14,
169            PayloadType::RakpMessage4 => 0x15,
170        }
171    }
172}
173
174#[derive(Debug)]
175pub struct State {
176    socket: RmcpIpmiSocket,
177    session_id: NonZeroU32,
178    session_sequence_number: NonZeroU32,
179    state: CryptoState,
180    ipmb_state: IpmbState,
181}
182
183impl State {
184    fn validate_open_session(
185        req: &OpenSessionRequest,
186        resp: &OpenSessionResponse,
187    ) -> Result<(), ValidateSessionResponseError> {
188        if resp.message_tag != req.message_tag {
189            return Err(ValidateSessionResponseError::MessageTagMismatch);
190        }
191
192        if resp.remote_console_session_id != req.remote_console_session_id {
193            return Err(ValidateSessionResponseError::RemoteConsoleSessionIdMismatch);
194        }
195
196        Ok(())
197    }
198
199    fn validate_rm1_rm2(
200        remote_console_session_id: NonZeroU32,
201        rm1: &RakpMessage1,
202        rm2: &RakpMessage2,
203    ) -> Result<(), ValidateRakpMessage2Error> {
204        if rm1.message_tag != rm2.message_tag {
205            return Err(ValidateRakpMessage2Error::MessageTagMismatch);
206        }
207
208        if remote_console_session_id != rm2.remote_console_session_id {
209            return Err(ValidateRakpMessage2Error::RemoteConsoleSessionIdMismatch);
210        }
211
212        Ok(())
213    }
214
215    fn validate_rm3_rm4(
216        managed_system_session_id: NonZeroU32,
217        rm3: &RakpMessage3,
218        rm4: &RakpMessage4,
219    ) -> Result<(), ValidateRakpMessage4Error> {
220        if rm3.message_tag != rm4.message_tag {
221            return Err(ValidateRakpMessage4Error::MessageTagMismatch);
222        }
223
224        if rm4.managed_system_session_id != managed_system_session_id {
225            return Err(ValidateRakpMessage4Error::ManagedSystemSessionIdMismatch);
226        }
227
228        Ok(())
229    }
230
231    pub fn activate(
232        state: v1_5::State,
233        privilege_level: Option<PrivilegeLevel>,
234        username: &Username,
235        password: &[u8],
236    ) -> Result<Self, ActivationError> {
237        use rand::{CryptoRng, Rng};
238
239        let mut rng = rand::thread_rng();
240
241        // For good measure, add a compile time assert that
242        // makes sure thread_rng is a crypto rng.
243        fn assert_crypto_rng<T: CryptoRng>(_: &T) {}
244        assert_crypto_rng(&rng);
245
246        fn send(
247            socket: &mut RmcpIpmiSocket,
248            ty: PayloadType,
249            payload: Vec<u8>,
250        ) -> Result<(), WriteError> {
251            let message = Message {
252                ty,
253                session_id: 0,
254                session_sequence_number: 0,
255                payload,
256            };
257
258            socket.send(|buffer| CryptoState::write_unencrypted(&message, buffer))
259        }
260
261        fn recv(data: &mut [u8]) -> Result<Message, UnwrapSessionError> {
262            CryptoState::default()
263                .read_payload(data)
264                .map_err(UnwrapSessionError::V2_0)
265        }
266
267        let mut socket = RmcpIpmiSocket::new(state.release_socket());
268
269        let remote_console_session_id: NonZeroU32 = rng.gen();
270
271        let open_session_request = OpenSessionRequest {
272            message_tag: 0,
273            requested_max_privilege: privilege_level,
274            remote_console_session_id,
275            authentication_algorithms: AuthenticationAlgorithm::RakpHmacSha1,
276            confidentiality_algorithms: ConfidentialityAlgorithm::AesCbc128,
277            integrity_algorithms: IntegrityAlgorithm::HmacSha1_96,
278        };
279
280        log::debug!("Sending RMCP+ Open Session Request. {open_session_request:X?}");
281
282        let mut payload = Vec::new();
283        open_session_request.write_data(&mut payload);
284        send(
285            &mut socket,
286            PayloadType::RmcpPlusOpenSessionRequest,
287            payload,
288        )
289        .map_err(ActivationError::OpenSessionRequestSend)?;
290
291        let data = socket
292            .recv()
293            .map_err(ActivationError::OpenSessionResponseReceive)?;
294
295        let response_data = recv(data).map_err(ActivationError::OpenSessionResponseRead)?;
296
297        let response = match OpenSessionResponse::from_data(&response_data.payload) {
298            Ok(r) => r,
299            Err(ParseSessionResponseError::HaveErrorCode(error_code)) => {
300                log::warn!("RMCP+ error occurred. Status code: '{error_code:?}'");
301                return Err(ParseSessionResponseError::HaveErrorCode(error_code).into());
302            }
303            Err(e) => return Err(e.into()),
304        };
305
306        log::debug!("Received RMCP+ Open Session Response: {response:X?}.");
307
308        Self::validate_open_session(&open_session_request, &response)?;
309
310        let random_data = rng.gen();
311
312        let rm1 = RakpMessage1 {
313            message_tag: 0x0D,
314            managed_system_session_id: response.managed_system_session_id,
315            remote_console_random_number: random_data,
316            requested_maximum_privilege_level: PrivilegeLevel::Administrator,
317            username,
318        };
319
320        let mut payload = Vec::new();
321        rm1.write(&mut payload);
322
323        log::debug!("Sending RMCP+ RAKP Message 1. {rm1:X?}");
324
325        send(&mut socket, PayloadType::RakpMessage1, payload)
326            .map_err(ActivationError::SendRakpMessage1)?;
327
328        let data = socket
329            .recv()
330            .map_err(ActivationError::RakpMessage2Receive)?;
331
332        let v2_message = recv(data).map_err(ActivationError::RakpMessage2Read)?;
333        let rm2 = RakpMessage2::from_data(&v2_message.payload)
334            .map_err(ActivationError::RakpMessage2Parse)?;
335
336        log::debug!("Received RMCP+ RAKP Message 2. {rm2:X?}");
337
338        Self::validate_rm1_rm2(remote_console_session_id, &rm1, &rm2)?;
339
340        let kex_auth_code = rm2.key_exchange_auth_code;
341
342        let required_kex_auth_code_len = match response.authentication_payload {
343            AuthenticationAlgorithm::RakpNone => 0,
344            AuthenticationAlgorithm::RakpHmacSha1 => 20,
345            AuthenticationAlgorithm::RakpHmacSha256 => 32,
346            AuthenticationAlgorithm::RakpHmacMd5 => 16,
347        };
348
349        if kex_auth_code.len() != required_kex_auth_code_len {
350            return Err(ActivationError::InvalidKeyExchangeAuthCodeLen(
351                kex_auth_code.len(),
352                response.authentication_payload,
353            ));
354        }
355
356        let mut crypto_state = CryptoState::new(None, password);
357        let message_3_value = crypto_state.calculate_rakp3_data(&response, &rm1, &rm2);
358
359        let rm3 = if let Some(m3) = message_3_value.as_ref() {
360            RakpMessage3 {
361                message_tag: 0x0A,
362                managed_system_session_id: response.managed_system_session_id,
363                contents: RakpMessage3Contents::Succes(m3),
364            }
365        } else {
366            log::warn!("Received RAKP message 2 with invalid integrity check value.");
367
368            RakpMessage3 {
369                message_tag: 0x0A,
370                managed_system_session_id: response.managed_system_session_id,
371                contents: RakpMessage3Contents::Failure(
372                    RakpMessage3ErrorStatusCode::InvalidIntegrityCheckValue,
373                ),
374            }
375        };
376
377        let mut payload = Vec::new();
378        rm3.write(&mut payload);
379
380        log::debug!("Sending RAKP message 3. {rm3:X?}");
381
382        send(&mut socket, PayloadType::RakpMessage3, payload)
383            .map_err(ActivationError::RakpMessage3Send)?;
384
385        if rm3.is_failure() {
386            return Err(ActivationError::ServerAuthenticationFailed)?;
387        }
388
389        let data = socket
390            .recv()
391            .map_err(ActivationError::RakpMessage4Receive)?;
392
393        let message = recv(data).unwrap();
394        let rm4 = RakpMessage4::from_data(&message.payload)
395            .map_err(ActivationError::RakpMessage4Parse)?;
396
397        log::debug!("Received RAKP Message 4: {rm4:X?}");
398
399        Self::validate_rm3_rm4(response.remote_console_session_id, &rm3, &rm4)?;
400
401        if !crypto_state.verify(
402            response.authentication_payload,
403            &rm1.remote_console_random_number,
404            rm3.managed_system_session_id.get(),
405            &rm2.managed_system_guid,
406            rm4.integrity_check_value,
407        ) {
408            log::error!("Received incorrect/invalid integrity check value in RAKP Message 4.");
409            return Err(ActivationError::RakpMessage4InvalidIntegrityCheckValue);
410        }
411
412        let session_id = rm3.managed_system_session_id;
413        let session_sequence_number = NonZeroU32::new(1).unwrap();
414
415        Ok(Self {
416            socket,
417            session_id,
418            session_sequence_number,
419            state: crypto_state,
420            ipmb_state: IpmbState::default(),
421        })
422    }
423
424    pub fn send(&mut self, request: &mut crate::connection::Request) -> Result<(), RmcpIpmiError> {
425        let session_sequence_number = self.session_sequence_number.get();
426
427        if session_sequence_number == u32::MAX {
428            todo!("Handle wrapping session number by re-activating?");
429        }
430
431        self.session_sequence_number =
432            NonZeroU32::new(self.session_sequence_number.get().add(1)).unwrap();
433
434        let payload = super::internal::next_ipmb_message(request, &mut self.ipmb_state);
435
436        let message = Message {
437            ty: PayloadType::IpmiMessage,
438            session_id: self.session_id.get(),
439            session_sequence_number,
440            payload,
441        };
442
443        self.socket
444            .send(|buffer| self.state.write_message(&message, buffer))
445            .unwrap();
446
447        Ok(())
448    }
449
450    // TODO: validate session sequence number
451    // TODO: Validate session sequence ID
452    pub fn recv(&mut self) -> Result<crate::connection::Response, RmcpIpmiReceiveError> {
453        let data = self.socket.recv()?;
454
455        let data = self
456            .state
457            .read_payload(data)
458            .map_err(|e| RmcpIpmiReceiveError::Session(UnwrapSessionError::V2_0(e)))?
459            .payload;
460
461        if data.len() < 7 {
462            return Err(RmcpIpmiReceiveError::NotEnoughData);
463        }
464
465        let _req_addr = data[0];
466        let netfn = data[1] >> 2;
467        let _checksum1 = data[2];
468        let _rs_addr = data[3];
469        let _rqseq = data[4];
470        let cmd = data[5];
471        let response_data: Vec<_> = data[6..data.len() - 1].to_vec();
472        let _checksum2 = data[data.len() - 1];
473
474        // TODO: validate sequence, checksums, etc.
475
476        let response = if let Some(resp) = Response::new(
477            crate::connection::Message::new_raw(netfn, cmd, response_data),
478            0,
479        ) {
480            resp
481        } else {
482            // TODO: need better message here :)
483            return Err(RmcpIpmiReceiveError::EmptyMessage);
484        };
485
486        Ok(response)
487    }
488
489    pub fn send_recv(
490        &mut self,
491        request: &mut crate::connection::Request,
492    ) -> Result<crate::connection::Response, RmcpIpmiError> {
493        self.send(request)?;
494        self.recv().map_err(Into::into)
495    }
496}