Skip to main content

ironcore_documents/
lib.rs

1pub mod aes;
2mod signing;
3pub mod v3;
4pub mod v4;
5pub mod v5;
6
7use hmac::{Hmac, KeyInit, Mac};
8use sha2::Sha256;
9use std::{
10    fmt::{Display, Formatter, Result as DisplayResult},
11    sync::{Mutex, MutexGuard},
12};
13use thiserror::Error;
14use v5::key_id_header::KEY_ID_HEADER_LEN;
15
16pub(crate) type Result<T> = core::result::Result<T, Error>;
17
18// Create alias for HMAC-SHA256
19type HmacSha256 = Hmac<Sha256>;
20
21// Compile-time key with override support
22const CRATE_DEBUG_KEY: &str = match option_env!("MY_CRATE_DEBUG_KEY") {
23    Some(val) => val,
24    // Default to a string that is unlikely to have a rainbow table or some prebuilt attack
25    None => "2025-10-28T17:23:36",
26};
27
28/// Produce a short keyed hash for display in Debug impls.
29pub fn redacted_hash(data: &[u8]) -> String {
30    let mut mac = HmacSha256::new_from_slice(CRATE_DEBUG_KEY.as_ref())
31        .expect("HMAC can take key of any size");
32    mac.update(data);
33    // slicing this way will always work because HmacSha256 will always be over 8 bytes
34    hex::encode(mac.finalize().into_bytes())[..8].to_string()
35}
36
37include!(concat!(env!("OUT_DIR"), "/mod.rs"));
38
39#[derive(Error, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
40pub enum Error {
41    /// EDOCs have a minimum size (at least the size of the pre-header)
42    EdocTooShort(usize),
43    /// Header could not be parsed as proto
44    HeaderParseErr(String),
45    /// EDOC version is not supported
46    InvalidVersion(u8),
47    /// 'IRON' as bytes
48    NoIronCoreMagic,
49    /// specified length of header is larger than the remaining data (proto header + payload)
50    SpecifiedLengthTooLong(u32),
51    /// Error occurred when serializing the header as proto
52    ProtoSerializationErr(String),
53    /// Serialized header is longer than allowed. Value is actual length in bytes.
54    HeaderLengthOverflow(u64),
55    /// Encryption of the edoc failed.
56    EncryptError(String),
57    /// Decryption of the edoc failed.
58    DecryptError(String),
59    // The next errors have to do with the key_id_header
60    /// EdekType was not recognized
61    EdekTypeError(String),
62    /// PayloadType was not recognized
63    PayloadTypeError(String),
64    /// key_id_header to short
65    KeyIdHeaderTooShort(usize),
66    /// key_id_header malformed
67    KeyIdHeaderMalformed(String),
68}
69
70impl Display for Error {
71    fn fmt(&self, f: &mut Formatter) -> DisplayResult {
72        match self {
73            Error::EdocTooShort(x) => write!(f, "EDOC too short. Found {x} bytes."),
74            Error::HeaderParseErr(x) => write!(f, "Header parse error: '{x}'"),
75            Error::InvalidVersion(x) => write!(f, "Invalid EDOC version: {x}"),
76            Error::NoIronCoreMagic => write!(f, "Missing IronCore Magic bytes in header."),
77            Error::SpecifiedLengthTooLong(x) => {
78                write!(f, "Header too short for specified length: {x} bytes")
79            }
80            Error::ProtoSerializationErr(x) => write!(f, "Protobuf serialization error: '{x}'"),
81            Error::HeaderLengthOverflow(x) => write!(f, "Header length too long: {x} bytes"),
82            Error::EncryptError(x) => write!(f, "{x}"),
83            Error::DecryptError(x) => write!(f, "{x}"),
84            Error::KeyIdHeaderTooShort(x) => write!(
85                f,
86                "Key ID header too short. Found: {x} bytes. Required: {KEY_ID_HEADER_LEN} bytes."
87            ),
88            Error::EdekTypeError(x) => write!(f, "EDEK type error: '{x}'"),
89            Error::PayloadTypeError(x) => write!(f, "Payload type error: '{x}'"),
90            Error::KeyIdHeaderMalformed(x) => write!(f, "Malformed key ID header: '{x}'"),
91        }
92    }
93}
94
95/// Acquire mutex in a blocking fashion. If the Mutex is or becomes poisoned, write out an error
96/// message and panic.
97///
98/// The lock is released when the returned MutexGuard falls out of scope.
99///
100/// # Usage:
101/// single statement (mut)
102/// `let result = take_lock(&t).deref_mut().call_method_on_t();`
103///
104/// multi-statement (mut)
105/// ```ignore
106/// let t = T {};
107/// let result = {
108///     let g = &mut *take_lock(&t);
109///     g.call_method_on_t()
110/// }; // lock released here
111/// ```
112///
113pub fn take_lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
114    m.lock().unwrap_or_else(|e| {
115        let error = format!("Error when acquiring lock: {e}");
116        panic!("{error}");
117    })
118}
119
120/// This macro will allow a newtype that has a single tuple field to use a fingerprint instead of
121/// printing the secret value.
122/// struct Foo([u8;32])
123/// impl_secret_debug!(Foo);
124/// Then the debug will print something like Foo(<redacted:bda33dd3...>).
125#[macro_export]
126macro_rules! impl_secret_debug {
127    ($t:ty) => {
128        impl std::fmt::Debug for $t {
129            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130                write!(
131                    f,
132                    "{}(<redacted:{}...>)",
133                    stringify!($t),
134                    $crate::redacted_hash(self.0.as_ref())
135                )
136            }
137        }
138    };
139}
140
141/// This macro will allow a newtype that has a single named field to use a fingerprint instead of
142/// printing the secret value.
143/// struct Foo {secret:[u8;32]}
144/// impl_secret_debug_named!(Foo, secret);
145/// Then the debug will print something like Foo{<redacted:bda33dd3...>}.
146#[macro_export]
147macro_rules! impl_secret_debug_named {
148    ($t:ty, $field:ident) => {
149        impl std::fmt::Debug for $t {
150            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151                write!(
152                    f,
153                    "{}{{<redacted:{}...>}}",
154                    stringify!($t),
155                    $crate::redacted_hash(self.$field.as_ref())
156                )
157            }
158        }
159    };
160}
161
162#[cfg(test)]
163mod test {
164    #[test]
165    fn impl_secret_debug_works() {
166        struct FooSecret([u8; 32]);
167        impl_secret_debug!(FooSecret);
168        let debug_str = format!("{:?}", FooSecret([1; 32]));
169        assert_eq!(debug_str, "FooSecret(<redacted:633f9067...>)");
170    }
171
172    #[test]
173    fn impl_secret_debug_named_works() {
174        struct FooSecret {
175            secret: String,
176        }
177        impl_secret_debug_named!(FooSecret, secret);
178        let debug_str = format!(
179            "{:?}",
180            FooSecret {
181                secret: "yes".to_string()
182            }
183        );
184        assert_eq!(debug_str, "FooSecret{<redacted:bda33dd3...>}");
185    }
186}