rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

cli_bootstrap.rs (3418B)


      1 //! Fixed, zeroizing bootstrap documents consumed only from standard input.
      2 
      3 use std::io::Read;
      4 
      5 use zeroize::Zeroizing;
      6 
      7 use crate::RhiEncryptedIdentityProvisioningMaterial;
      8 
      9 pub(crate) const RHI_IDENTITY_PROVISIONING_DOCUMENT_BYTES: usize = 117;
     10 const RHI_IDENTITY_PROVISIONING_MAGIC: &[u8; 4] = b"RHIP";
     11 const RHI_IDENTITY_PROVISIONING_VERSION: u8 = 1;
     12 
     13 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     14 pub(crate) enum RhiBootstrapDocumentError {
     15     Io,
     16     InvalidLength,
     17     InvalidHeader,
     18     InvalidMaterial,
     19 }
     20 
     21 pub(crate) fn read_identity_provisioning_document(
     22     mut reader: impl Read,
     23 ) -> Result<RhiEncryptedIdentityProvisioningMaterial, RhiBootstrapDocumentError> {
     24     let mut document = Zeroizing::new([0_u8; RHI_IDENTITY_PROVISIONING_DOCUMENT_BYTES + 1]);
     25     let mut length = 0_usize;
     26     while length < document.len() {
     27         match reader.read(&mut document[length..]) {
     28             Ok(0) => break,
     29             Ok(read) => length = length.saturating_add(read),
     30             Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
     31             Err(_) => return Err(RhiBootstrapDocumentError::Io),
     32         }
     33     }
     34     if length != RHI_IDENTITY_PROVISIONING_DOCUMENT_BYTES {
     35         return Err(RhiBootstrapDocumentError::InvalidLength);
     36     }
     37     if &document[..4] != RHI_IDENTITY_PROVISIONING_MAGIC
     38         || document[4] != RHI_IDENTITY_PROVISIONING_VERSION
     39     {
     40         return Err(RhiBootstrapDocumentError::InvalidHeader);
     41     }
     42     let mut identity_secret = [0_u8; 32];
     43     let mut data_key = [0_u8; 32];
     44     let mut envelope_nonce = [0_u8; 24];
     45     let mut wrapping_nonce = [0_u8; 24];
     46     identity_secret.copy_from_slice(&document[5..37]);
     47     data_key.copy_from_slice(&document[37..69]);
     48     envelope_nonce.copy_from_slice(&document[69..93]);
     49     wrapping_nonce.copy_from_slice(&document[93..117]);
     50     RhiEncryptedIdentityProvisioningMaterial::new(
     51         identity_secret,
     52         data_key,
     53         envelope_nonce,
     54         wrapping_nonce,
     55     )
     56     .map_err(|_| RhiBootstrapDocumentError::InvalidMaterial)
     57 }
     58 
     59 #[cfg(test)]
     60 mod tests {
     61     use std::io::Cursor;
     62 
     63     use super::*;
     64 
     65     fn document() -> [u8; RHI_IDENTITY_PROVISIONING_DOCUMENT_BYTES] {
     66         let mut document = [0_u8; RHI_IDENTITY_PROVISIONING_DOCUMENT_BYTES];
     67         document[..4].copy_from_slice(RHI_IDENTITY_PROVISIONING_MAGIC);
     68         document[4] = RHI_IDENTITY_PROVISIONING_VERSION;
     69         document[5..37].copy_from_slice(&[1; 32]);
     70         document[37..69].copy_from_slice(&[2; 32]);
     71         document[69..93].copy_from_slice(&[3; 24]);
     72         document[93..117].copy_from_slice(&[4; 24]);
     73         document
     74     }
     75 
     76     #[test]
     77     fn exact_document_is_admitted_and_bounds_are_fail_closed() {
     78         let valid = document();
     79         let material = read_identity_provisioning_document(Cursor::new(valid))
     80             .expect("exact provisioning document");
     81         assert_eq!(
     82             format!("{material:?}"),
     83             "RhiEncryptedIdentityProvisioningMaterial([redacted])"
     84         );
     85         assert_eq!(
     86             read_identity_provisioning_document(Cursor::new(&valid[..116])).unwrap_err(),
     87             RhiBootstrapDocumentError::InvalidLength
     88         );
     89         let mut trailing = valid.to_vec();
     90         trailing.push(0);
     91         assert_eq!(
     92             read_identity_provisioning_document(Cursor::new(trailing)).unwrap_err(),
     93             RhiBootstrapDocumentError::InvalidLength
     94         );
     95     }
     96 }