gl_client/
credentials.rs

1use crate::{
2    scheduler::Scheduler,
3    signer::Signer,
4    tls::{self, TlsConfig},
5    utils::get_node_id_from_tls_config,
6};
7/// Credentials is a collection of all relevant keys and attestations
8/// required to authenticate a device and authorize a command on the node.
9/// They represent the identity of a device and can be encoded into a byte
10/// format for easy storage.
11use log::debug;
12use std::{convert::TryFrom, path::Path};
13use thiserror;
14
15const CRED_VERSION: u32 = 1u32;
16const CA_RAW: &[u8] = include_str!("../.resources/tls/ca.pem").as_bytes();
17const NOBODY_CRT: &[u8] = include_str!(env!("GL_NOBODY_CRT")).as_bytes();
18const NOBODY_KEY: &[u8] = include_str!(env!("GL_NOBODY_KEY")).as_bytes();
19
20#[derive(thiserror::Error, Debug)]
21pub enum Error {
22    #[error("could not get from identity: {}", .0)]
23    GetFromIdentityError(String),
24    #[error("identity mismatch: {}", .0)]
25    IsIdentityError(String),
26    #[error("could not decode into credentials")]
27    DecodeCredentialsError(#[from] prost::DecodeError),
28    #[error("could not encode credentials")]
29    EncodeCredentialError(#[from] prost::EncodeError),
30    #[error("could not upgrade credentials: {}", .0)]
31    UpgradeCredentialsError(String),
32    #[error("could not build credentials {}", .0)]
33    BuildCredentialsError(String),
34    #[error("could not create create credentials from data: {}", .0)]
35    TransformDataIntoCredentialsError(String),
36    #[error("could not create tls config {}", .0)]
37    CreateTlsConfigError(#[source] anyhow::Error),
38    #[error("could not read from file: {}", .0)]
39    ReadFromFileError(#[from] std::io::Error),
40    #[error("could not fetch default nobody credentials: {}", .0)]
41    FetchDefaultNobodyCredentials(#[source] anyhow::Error),
42}
43
44pub type Result<T, E = Error> = std::result::Result<T, E>;
45
46pub trait TlsConfigProvider: Send + Sync {
47    fn tls_config(&self) -> TlsConfig;
48}
49
50pub trait RuneProvider {
51    fn rune(&self) -> String;
52}
53
54pub trait NodeIdProvider {
55    fn node_id(&self) -> Result<Vec<u8>>;
56}
57
58/// A helper struct to combine the Tls certificate and the corresponding private
59/// key.
60#[derive(Clone, Debug)]
61struct Identity {
62    cert: Vec<u8>,
63    key: Vec<u8>,
64}
65
66impl Default for Identity {
67    fn default() -> Self {
68        let key = load_file_or_default("GL_NOBODY_KEY", NOBODY_KEY)
69            .expect("Could not load file from GL_NOBODY_KEY");
70        let cert = load_file_or_default("GL_NOBODY_CRT", NOBODY_CRT)
71            .expect("Could not load file from GL_NOBODY_CRT");
72        Self { cert, key }
73    }
74}
75
76/// The `Nobody` credentials struct. This is an unauthenticated set of
77/// credentials and can only be used for registration and recovery.
78#[derive(Clone, Debug)]
79pub struct Nobody {
80    pub cert: Vec<u8>,
81    pub key: Vec<u8>,
82    pub ca: Vec<u8>,
83}
84
85impl Nobody {
86    /// Returns a new Nobody instance with default parameters.
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Returns a new Nobody instance with a custom set of parameters.
92    pub fn with<V>(cert: V, key: V) -> Self
93    where
94        V: Into<Vec<u8>>,
95    {
96        let ca =
97            load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
98
99        Self {
100            cert: cert.into(),
101            key: key.into(),
102            ca,
103        }
104    }
105
106    pub fn with_ca<V>(self, ca: V) -> Self
107    where
108        V: Into<Vec<u8>>,
109    {
110        Nobody {
111            ca: ca.into(),
112            ..self
113        }
114    }
115}
116
117impl TlsConfigProvider for Nobody {
118    fn tls_config(&self) -> TlsConfig {
119        tls::TlsConfig::with(&self.cert, &self.key, &self.ca)
120    }
121}
122
123impl Default for Nobody {
124    fn default() -> Self {
125        let ca =
126            load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
127        let identity = Identity::default();
128
129        Self {
130            cert: identity.cert,
131            key: identity.key,
132            ca,
133        }
134    }
135}
136
137/// The `Device` credentials store the device's certificate, the device's
138/// private key, the certificate authority and the device's rune.
139#[derive(Clone, Debug)]
140pub struct Device {
141    pub version: u32,
142    pub cert: Vec<u8>,
143    pub key: Vec<u8>,
144    pub ca: Vec<u8>,
145    pub rune: String,
146}
147
148impl Device {
149    /// Creates a new set of `Device` credentials from the given
150    /// credentials data blob. It defaults to the nobody credentials set.
151    pub fn from_bytes(data: impl AsRef<[u8]>) -> Self {
152        let mut creds = Self::default();
153        log::trace!("Build authenticated credentials from: {:?}", data.as_ref());
154        if let Ok(data) = model::Data::try_from(data.as_ref()) {
155            creds.version = data.version;
156            if let Some(cert) = data.cert {
157                creds.cert = cert
158            }
159            if let Some(key) = data.key {
160                creds.key = key
161            }
162            if let Some(ca) = data.ca {
163                creds.ca = ca
164            }
165            if let Some(rune) = data.rune {
166                creds.rune = rune
167            }
168        }
169        creds
170    }
171
172    /// Creates a new set of `Device` credentials from a path that
173    /// contains a credentials data blob. Defaults to the nobody
174    /// credentials set.
175    pub fn from_path(path: impl AsRef<Path>) -> Self {
176        debug!("Read credentials data from {:?}", path.as_ref());
177        let data = std::fs::read(path).unwrap_or_default();
178        Device::from_bytes(data)
179    }
180
181    /// Creates a new set of `Device` credentials from a complete set of
182    /// credentials.
183    pub fn with<V, S>(cert: V, key: V, rune: S) -> Self
184    where
185        V: Into<Vec<u8>>,
186        S: Into<String>,
187    {
188        let ca =
189            load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
190
191        Self {
192            version: CRED_VERSION,
193            cert: cert.into(),
194            key: key.into(),
195            rune: rune.into(),
196            ca
197        }
198    }
199
200    pub fn with_ca<V>(self, ca: V) -> Self 
201    where
202        V: Into<Vec<u8>>,
203    {
204        Device {
205            ca: ca.into(),
206            ..self
207        }
208    }
209
210    /// Asynchronously upgrades the credentials using the provided scheduler and
211    /// signer, potentially involving network operations or other async tasks.
212    pub async fn upgrade<T>(mut self, _scheduler: &Scheduler<T>, signer: &Signer) -> Result<Self>
213    where
214        T: TlsConfigProvider,
215    {
216        use Error::*;
217
218        self.version = CRED_VERSION;
219
220        if self.rune.is_empty() {
221            let node_id = self
222                .node_id()
223                .map_err(|e| UpgradeCredentialsError(e.to_string()))?;
224
225            let alt = runeauth::Alternative::new(
226                "pubkey".to_string(),
227                runeauth::Condition::Equal,
228                hex::encode(node_id),
229                false,
230            )
231            .map_err(|e| UpgradeCredentialsError(e.to_string()))?;
232
233            self.rune = signer
234                .create_rune(None, vec![vec![&alt.encode()]])
235                .map_err(|e| UpgradeCredentialsError(e.to_string()))?;
236        };
237        Ok(self)
238    }
239
240    /// Returns a byte encoded representation of the credentials. This
241    /// can be used to store the credentials in one single file.
242    pub fn to_bytes(&self) -> Vec<u8> {
243        self.to_owned().into()
244    }
245}
246
247impl TlsConfigProvider for Device {
248    fn tls_config(&self) -> TlsConfig {
249        tls::TlsConfig::with(&self.cert, &self.key, &self.ca)
250    }
251
252}
253
254impl RuneProvider for Device {
255    fn rune(&self) -> String {
256        self.to_owned().rune
257    }
258}
259
260impl NodeIdProvider for Device {
261    fn node_id(&self) -> Result<Vec<u8>> {
262        get_node_id_from_tls_config(&self.tls_config()).map_err(|_e| {
263            Error::GetFromIdentityError(
264                "node_id could not be retrieved from the certificate".to_string(),
265            )
266        })
267    }
268}
269
270impl From<Device> for Vec<u8> {
271    fn from(value: Device) -> Self {
272        let data: model::Data = value.into();
273        data.into()
274    }
275}
276
277impl From<Device> for model::Data {
278    fn from(device: Device) -> Self {
279        model::Data {
280            version: CRED_VERSION,
281            cert: Some(device.cert),
282            key: Some(device.key),
283            ca: Some(device.ca),
284            rune: Some(device.rune),
285        }
286    }
287}
288
289impl Default for Device {
290    fn default() -> Self {
291        let ca =
292            load_file_or_default("GL_CA_CRT", CA_RAW).expect("Could not load file from GL_CA_CRT");
293        let identity = Identity::default();
294        Self {
295            version: 0,
296            cert: identity.cert,
297            key: identity.key,
298            ca,
299            rune: Default::default(),
300        }
301    }
302}
303
304mod model {
305    use prost::Message;
306    use std::convert::TryFrom;
307
308    /// The Data struct is used for encoding and decoding of credentials. It
309    /// useses proto for byte encoding.
310    #[derive(Message, Clone)]
311    pub struct Data {
312        #[prost(uint32, tag = "1")]
313        pub version: u32,
314        #[prost(bytes, optional, tag = "2")]
315        pub cert: Option<Vec<u8>>,
316        #[prost(bytes, optional, tag = "3")]
317        pub key: Option<Vec<u8>>,
318        #[prost(bytes, optional, tag = "4")]
319        pub ca: Option<Vec<u8>>,
320        #[prost(string, optional, tag = "5")]
321        pub rune: Option<String>,
322    }
323
324    impl TryFrom<&[u8]> for Data {
325        type Error = super::Error;
326
327        fn try_from(buf: &[u8]) -> std::prelude::v1::Result<Self, Self::Error> {
328            let data: Data = Data::decode(buf)?;
329            Ok(data)
330        }
331    }
332
333    impl From<Data> for Vec<u8> {
334        fn from(value: Data) -> Self {
335            value.encode_to_vec()
336        }
337    }
338}
339
340/// Tries to load nobody credentials from a file that is passed by an envvar and
341/// defaults to the nobody cert and key paths that have been set during build-
342/// time.
343fn load_file_or_default(varname: &str, default: &[u8]) -> Result<Vec<u8>> {
344    match std::env::var(varname) {
345        Ok(fname) => {
346            debug!("Loading file {} for envvar {}", fname, varname);
347            let f = std::fs::read(fname.clone())?;
348            Ok(f)
349        }
350        Err(_) => Ok(default.to_vec()),
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_encode() {
360        let cert: Vec<u8> = vec![99, 98];
361        let key = vec![97, 96];
362        let ca = vec![95, 94];
363        let rune = "non_functional_rune".to_string();
364        let data = model::Data {
365            version: 1,
366            cert: Some(cert.clone()),
367            key: Some(key.clone()),
368            ca: Some(ca.clone()),
369            rune: Some(rune.clone()),
370        };
371        let buf: Vec<u8> = data.into();
372        print!("{:?}", buf);
373        for n in cert {
374            assert!(buf.contains(&n));
375        }
376        for n in key {
377            assert!(buf.contains(&n));
378        }
379        for n in ca {
380            assert!(buf.contains(&n));
381        }
382        for n in rune.as_bytes() {
383            assert!(buf.contains(n));
384        }
385    }
386
387    #[test]
388    fn test_decode() {
389        let data: Vec<u8> = vec![
390            8, 1, 18, 2, 99, 98, 26, 2, 97, 96, 34, 2, 95, 94, 42, 19, 110, 111, 110, 95, 102, 117,
391            110, 99, 116, 105, 111, 110, 97, 108, 95, 114, 117, 110, 101,
392        ];
393        let data = model::Data::try_from(&data[..]).unwrap();
394        assert!(data.version == 1);
395        assert!(data.cert.is_some_and(|d| d == vec![99, 98]));
396        assert!(data.key.is_some_and(|d| d == vec![97, 96]));
397        assert!(data.ca.is_some_and(|d| d == vec![95, 94]));
398        assert!(data.rune.is_some_and(|d| d == *"non_functional_rune"));
399    }
400}