cli_bootstrap.rs (4487B)
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::MycEncryptedIdentityProvisioningMaterial; 8 9 pub(crate) const MYC_IDENTITY_PROVISIONING_DOCUMENT_BYTES: usize = 117; 10 const MYC_IDENTITY_PROVISIONING_MAGIC: &[u8; 4] = b"MYIP"; 11 const MYC_IDENTITY_PROVISIONING_VERSION: u8 = 1; 12 13 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 14 pub(crate) enum MycBootstrapDocumentError { 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<MycEncryptedIdentityProvisioningMaterial, MycBootstrapDocumentError> { 24 let mut document = Zeroizing::new([0_u8; MYC_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(MycBootstrapDocumentError::Io), 32 } 33 } 34 if length != MYC_IDENTITY_PROVISIONING_DOCUMENT_BYTES { 35 return Err(MycBootstrapDocumentError::InvalidLength); 36 } 37 if &document[..4] != MYC_IDENTITY_PROVISIONING_MAGIC 38 || document[4] != MYC_IDENTITY_PROVISIONING_VERSION 39 { 40 return Err(MycBootstrapDocumentError::InvalidHeader); 41 } 42 43 let mut identity_secret = [0_u8; 32]; 44 let mut data_key = [0_u8; 32]; 45 let mut envelope_nonce = [0_u8; 24]; 46 let mut wrapping_nonce = [0_u8; 24]; 47 identity_secret.copy_from_slice(&document[5..37]); 48 data_key.copy_from_slice(&document[37..69]); 49 envelope_nonce.copy_from_slice(&document[69..93]); 50 wrapping_nonce.copy_from_slice(&document[93..117]); 51 MycEncryptedIdentityProvisioningMaterial::new( 52 identity_secret, 53 data_key, 54 envelope_nonce, 55 wrapping_nonce, 56 ) 57 .map_err(|_| MycBootstrapDocumentError::InvalidMaterial) 58 } 59 60 #[cfg(test)] 61 mod tests { 62 use std::io::{self, Cursor}; 63 64 use super::*; 65 66 fn valid_document() -> [u8; MYC_IDENTITY_PROVISIONING_DOCUMENT_BYTES] { 67 let mut document = [0_u8; MYC_IDENTITY_PROVISIONING_DOCUMENT_BYTES]; 68 document[..4].copy_from_slice(MYC_IDENTITY_PROVISIONING_MAGIC); 69 document[4] = MYC_IDENTITY_PROVISIONING_VERSION; 70 document[5..37].copy_from_slice(&[1; 32]); 71 document[37..69].copy_from_slice(&[2; 32]); 72 document[69..93].copy_from_slice(&[3; 24]); 73 document[93..117].copy_from_slice(&[4; 24]); 74 document 75 } 76 77 #[test] 78 fn exact_document_is_admitted_and_debug_is_redacted() { 79 let material = read_identity_provisioning_document(Cursor::new(valid_document())) 80 .expect("exact provisioning document"); 81 assert_eq!( 82 format!("{material:?}"), 83 "MycEncryptedIdentityProvisioningMaterial([redacted])" 84 ); 85 } 86 87 #[test] 88 fn short_trailing_wrong_header_and_invalid_material_fail_closed() { 89 let valid = valid_document(); 90 assert_eq!( 91 read_identity_provisioning_document(Cursor::new(&valid[..116])).unwrap_err(), 92 MycBootstrapDocumentError::InvalidLength 93 ); 94 let mut trailing = valid.to_vec(); 95 trailing.push(0); 96 assert_eq!( 97 read_identity_provisioning_document(Cursor::new(trailing)).unwrap_err(), 98 MycBootstrapDocumentError::InvalidLength 99 ); 100 for index in [0, 4] { 101 let mut invalid = valid; 102 invalid[index] ^= 0xff; 103 assert_eq!( 104 read_identity_provisioning_document(Cursor::new(invalid)).unwrap_err(), 105 MycBootstrapDocumentError::InvalidHeader 106 ); 107 } 108 let mut invalid = valid; 109 invalid[37..69].fill(0); 110 assert_eq!( 111 read_identity_provisioning_document(Cursor::new(invalid)).unwrap_err(), 112 MycBootstrapDocumentError::InvalidMaterial 113 ); 114 } 115 116 struct FailingReader; 117 118 impl Read for FailingReader { 119 fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> { 120 Err(io::Error::other("sensitive source")) 121 } 122 } 123 124 #[test] 125 fn input_errors_discard_the_raw_source() { 126 assert_eq!( 127 read_identity_provisioning_document(FailingReader).unwrap_err(), 128 MycBootstrapDocumentError::Io 129 ); 130 } 131 }