Skip to main content

product_os_command_control/
authentication.rs

1//! Authentication module
2//!
3//! Provides authentication headers, error types, and key exchange mechanisms
4//! for secure communication between Product OS nodes.
5
6use std::prelude::v1::*;
7
8use headers::{ Header, HeaderName };
9
10use serde::{ Deserialize, Serialize };
11use once_cell::sync::Lazy;
12use product_os_request::ProductOSClient;
13
14
15/// Error returned when command-control authentication fails.
16#[derive(Debug, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct CommandControlAuthenticateError {
19    /// The specific error state.
20    pub error: CommandControlAuthenticateErrorState
21}
22
23/// Describes the kind of authentication error that occurred.
24#[derive(Debug, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub enum CommandControlAuthenticateErrorState {
27    /// A key validation error with a descriptive message.
28    KeyError(String),
29    /// No error (used as a default/sentinel value).
30    None
31}
32
33impl std::error::Error for CommandControlAuthenticateError {}
34
35impl std::fmt::Display for CommandControlAuthenticateError {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match &self.error {
38            CommandControlAuthenticateErrorState::KeyError(m) => write!(f, "{}", m),
39            CommandControlAuthenticateErrorState::None => write!(f, "No error")
40        }
41    }
42}
43
44static X_INTERACT_COMMAND: Lazy<HeaderName> = Lazy::new(|| HeaderName::from_static("x-product-os-command"));
45static X_INTERACT_CONTROL: Lazy<HeaderName> = Lazy::new(|| HeaderName::from_static("x-product-os-control"));
46static X_INTERACT_VERIFY: Lazy<HeaderName> = Lazy::new(|| HeaderName::from_static("x-product-os-verify"));
47
48/// Custom header for the Product OS command identifier.
49pub struct XProductOSCommandHeader(String);
50
51impl XProductOSCommandHeader {
52    /// Consumes the header and returns the inner value.
53    pub fn value(self) -> String {
54        self.0
55    }
56}
57
58impl Header for XProductOSCommandHeader {
59    fn name() -> &'static HeaderName {
60        &X_INTERACT_COMMAND
61    }
62
63    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
64        where
65            I: Iterator<Item = &'i headers::HeaderValue> {
66        let value = values
67            .next()
68            .ok_or_else(headers::Error::invalid)?;
69
70        Ok(XProductOSCommandHeader(value.to_str().unwrap().to_string()))
71    }
72
73    fn encode<E>(&self, values: &mut E)
74        where
75            E: Extend<headers::HeaderValue> {
76        let value = headers::HeaderValue::from_str(self.0.as_str()).unwrap();
77        values.extend(std::iter::once(value));
78    }
79}
80
81/// Custom header for the Product OS control identifier.
82pub struct XProductOSControlHeader(String);
83
84impl XProductOSControlHeader {
85    /// Consumes the header and returns the inner value.
86    pub fn value(self) -> String {
87        self.0
88    }
89}
90
91impl Header for XProductOSControlHeader {
92    fn name() -> &'static HeaderName {
93        &X_INTERACT_CONTROL
94    }
95
96    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
97        where
98            I: Iterator<Item = &'i headers::HeaderValue> {
99        let value = values
100            .next()
101            .ok_or_else(headers::Error::invalid)?;
102
103        Ok(XProductOSControlHeader(value.to_str().unwrap().to_string()))
104    }
105
106    fn encode<E>(&self, values: &mut E)
107        where
108            E: Extend<headers::HeaderValue> {
109        let value = headers::HeaderValue::from_str(self.0.as_str()).unwrap();
110        values.extend(std::iter::once(value));
111    }
112}
113
114/// Custom header for the Product OS verification tag.
115pub struct XProductOSVerifyHeader(String);
116
117impl XProductOSVerifyHeader {
118    /// Consumes the header and returns the inner value.
119    pub fn value(self) -> String {
120        self.0
121    }
122}
123
124impl Header for XProductOSVerifyHeader {
125    fn name() -> &'static HeaderName {
126        &X_INTERACT_VERIFY
127    }
128
129    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
130        where
131            I: Iterator<Item = &'i headers::HeaderValue> {
132        let value = values
133            .next()
134            .ok_or_else(headers::Error::invalid)?;
135
136        Ok(XProductOSVerifyHeader(value.to_str().unwrap().to_string()))
137    }
138
139    fn encode<E>(&self, values: &mut E)
140        where
141            E: Extend<headers::HeaderValue> {
142        let value = headers::HeaderValue::from_str(self.0.as_str()).unwrap();
143        values.extend(std::iter::once(value));
144    }
145}
146
147/// Data exchanged during a Diffie-Hellman key exchange between nodes.
148#[derive(Debug, Deserialize, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct AuthExchangeKeyData {
151    /// The identifier of the node initiating the exchange.
152    pub identifier: String,
153    /// The session identifier for this key exchange.
154    pub session: String,
155    /// The public key bytes for the Diffie-Hellman exchange.
156    pub public_key: Vec<u8>
157}
158
159
160/// Generates a self-trust key pair for the local node.
161///
162/// Creates a Diffie-Hellman session and immediately completes it with itself,
163/// establishing a key that can be used for self-verification. Also adds the
164/// controller's own certificates as trusted.
165pub fn perform_self_trust(controller: &mut crate::ProductOSController) {
166    tracing::info!("Generating own key exchange...");
167
168    let (self_key_session, self_public_key) = controller.create_key_session();
169    controller.generate_key(self_key_session.as_str(), self_public_key.as_slice(), controller.registry.get_me().get_identifier().to_string(), None);
170
171    let certificates: Vec<Vec<u8>> = controller.certificates.certificates.to_vec();
172
173    for cert in certificates {
174        controller.requester.add_trusted_certificate_der(cert);
175        controller.client.build(&controller.requester);
176    }
177}
178
179
180
181
182/// Performs Diffie-Hellman key exchange with all remote nodes that don't yet have a shared key.
183pub async fn perform_key_exchange(controller: &mut crate::ProductOSController) {
184    tracing::info!("Performing key exchanges...");
185
186    let self_identifier = controller.registry.get_me().get_identifier().to_string();
187    #[allow(deprecated)]
188    let control_url = controller.registry.get_me().get_address();
189
190    let nodes = controller.registry.get_nodes_endpoints(0, true);
191
192    tracing::info!("Performing key exchange {:?}", nodes);
193    for (identifier, (url, key)) in nodes {
194        if url != control_url {
195            match key {
196                Some(_) => (),
197                None => {
198                    tracing::info!("Authenticating {}: {}", identifier, url);
199
200                    let (key_session, public_key) = controller.create_key_session();
201                    let key_exchange = AuthExchangeKeyData {
202                        identifier: self_identifier.to_owned(),
203                        session: key_session,
204                        public_key: public_key.to_vec()
205                    };
206
207                    tracing::trace!("Sending authentication exchange {:?}", key_exchange);
208
209                    match crate::commands::command(&controller.client, url.clone(), vec!(), "auth", "exchange", Some(serde_json::value::to_value(key_exchange).unwrap())).await {
210                        Ok(response) => {
211                            let Some(status) = response.try_status() else {
212                                tracing::error!("Failed to get status from key exchange response for {}", url);
213                                continue;
214                            };
215
216                            match status {
217                                product_os_request::StatusCode::CONFLICT => {
218                                    let auth: CommandControlAuthenticateError = match serde_json::from_str(controller.client.text(response).await.unwrap().as_str()) {
219                                        Ok(auth_error) => auth_error,
220                                        Err(_) => CommandControlAuthenticateError { error: CommandControlAuthenticateErrorState::None }
221                                    };
222
223                                    tracing::error!("Error object auth {:?}", auth);
224
225                                    match auth.error {
226                                        CommandControlAuthenticateErrorState::KeyError(_) => {
227                                            tracing::info!("Error from remote node - keys already exist - problem {}", identifier);
228                                        },
229                                        CommandControlAuthenticateErrorState::None => ()
230                                    };
231                                },
232                                product_os_request::StatusCode::OK => {
233                                    let body = controller.client.text(response).await.unwrap();
234                                    tracing::info!("Response received from {}: {} {:?}", url, status, body);
235
236                                    let key_exchange: AuthExchangeKeyData = serde_json::from_str(body.as_str()).unwrap();
237                                    controller.generate_key(key_exchange.session.as_str(), key_exchange.public_key.as_slice(), key_exchange.identifier, None);
238                                }
239                                _ => {
240                                    let body = controller.client.bytes(response).await.unwrap();
241                                    tracing::error!("Error response received from {}: {} {:?}", url, status, body);
242                                }
243                            }
244                        },
245                        Err(e) => {
246                            tracing::error!("Error encountered {:?} from {}", e, url);
247                        }
248                    }
249                }
250            }
251        }
252    }
253
254    tracing::info!("Finished key exchanges...");
255}