ironcore_documents/
lib.rs

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