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 }