diff --git a/Cargo.lock b/Cargo.lock index 6657e8e..cbfe4a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "bare-metal" version = "0.2.5" @@ -11,12 +46,49 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "bitfield" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cortex-m" version = "0.7.7" @@ -29,6 +101,99 @@ dependencies = [ "volatile-register", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -64,6 +229,84 @@ dependencies = [ "nb 1.1.0", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + [[package]] name = "nb" version = "0.1.3" @@ -79,6 +322,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openprot" version = "0.1.0" @@ -153,6 +402,51 @@ version = "0.1.0" name = "openprot-services-telemetry" version = "0.1.0" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.97" @@ -177,6 +471,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -186,6 +490,24 @@ dependencies = [ "semver", ] +[[package]] +name = "rustcrypto" +version = "0.1.0" +dependencies = [ + "aes", + "aes-gcm", + "cipher", + "ctr", + "generic-array", + "k256", + "openprot-hal-blocking", + "p256", + "p384", + "rand_core", + "zerocopy", + "zeroize", +] + [[package]] name = "same-file" version = "1.0.6" @@ -195,6 +517,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "0.9.0" @@ -210,6 +545,27 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "subtle" version = "2.6.1" @@ -227,18 +583,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "vcell" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "void" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 658311a..489c699 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "platform/impls/linux", "platform/impls/tock", "platform/impls/hubris", + "platform/impls/rustcrypto", "services/telemetry", "services/storage", ] diff --git a/hal/blocking/src/cipher.rs b/hal/blocking/src/cipher.rs new file mode 100644 index 0000000..4983c4e --- /dev/null +++ b/hal/blocking/src/cipher.rs @@ -0,0 +1,919 @@ +// Licensed under the Apache-2.0 license + +use core::fmt::Debug; +use zerocopy::{FromBytes, IntoBytes}; + +/// Marker trait for all cipher modes. +pub trait CipherMode: core::fmt::Debug + Clone + Copy {} + +/// Marker trait for block cipher modes (e.g., CBC, CTR). +pub trait BlockCipherMode: CipherMode {} + +/// Marker trait for AEAD modes (e.g., GCM, CCM). +pub trait AeadCipherMode: CipherMode {} + +/// Marker trait for stream cipher modes (e.g., ChaCha20). +pub trait StreamCipherMode: CipherMode {} + +/// Common error kinds for symmetric cipher operations. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[non_exhaustive] +pub enum ErrorKind { + /// Failed to initialize the cipher context. + InitializationError, + + /// General hardware failure during cipher operation. + HardwareFailure, + + /// Insufficient permissions to access hardware or perform the operation. + PermissionDenied, + + /// The cipher context is in an invalid or uninitialized state. + InvalidState, + + /// The input data is invalid (e.g., wrong length or format). + InvalidInput, + + /// The specified algorithm or mode is not supported. + UnsupportedAlgorithm, + + /// Key or IV is invalid or missing. + KeyError, +} + +/// Trait for converting implementation-specific errors into a generic [`ErrorKind`]. +pub trait Error: Debug { + /// Returns a generic error kind corresponding to the specific error. + fn kind(&self) -> ErrorKind; +} + +/// Trait for associating a type with an error type. +pub trait ErrorType { + /// The associated error type. + type Error: Error; +} + +/// Trait for symmetric cipher algorithms. +/// +/// This trait defines the core types and constraints for symmetric cipher implementations. +/// All types must support zero-copy serialization via the `zerocopy` crate traits, +/// enabling efficient operation with both software and hardware implementations. +/// +/// # Zero-Copy Requirements +/// +/// The `FromBytes` and `IntoBytes` trait bounds ensure that: +/// - Types can be safely constructed from byte arrays without validation +/// - Types can be converted to byte arrays for hardware or network transmission +/// - No unnecessary copying occurs during cryptographic operations +/// - Memory layout is well-defined and predictable +/// +/// # Security Considerations +/// +/// - Key types should implement `Zeroize` for secure memory cleanup +/// - Plaintext and ciphertext types should be handled securely in memory +/// - Consider using `secrecy` crate for sensitive data protection +/// +/// # Example Implementation +/// +/// ```ignore +/// impl SymmetricCipher for MyAesCipher { +/// type Key = [u8; 32]; // AES-256 key +/// type Nonce = [u8; 16]; // 128-bit nonce/IV +/// type PlainText = [u8; 64]; // Fixed-size plaintext buffer +/// type CipherText = [u8; 80]; // Fixed-size ciphertext buffer (with padding) +/// type Error = CryptoError; +/// } +/// ``` +pub trait SymmetricCipher: ErrorType { + /// The cryptographic key type. + /// + /// This type represents the secret key material used for encryption and decryption. + /// The key can be provided through various mechanisms including software keys, + /// key vault references, or hardware-managed keys. + /// + /// # Security Requirements + /// + /// - Should implement `Zeroize` for secure memory cleanup when stored in software + /// - May reference keys stored in secure hardware or key vaults + /// - Size should match the algorithm's key requirements (e.g., 32 bytes for AES-256) + /// - Consider using masked key shares for side-channel protection in hardware + /// + /// # Key Sources + /// + /// - **Software Keys**: `[u8; 32]` for AES-256, `[u8; 16]` for AES-128 + /// - **Key Vault References**: `KeyVaultHandle`, `KeyId`, or similar abstract types + /// - **Hardware Keys**: Sideloaded keys from key managers or crypto coprocessors + /// - **Masked Keys**: Split key shares for side-channel attack resistance + /// + /// # Common Types + /// + /// - `[u8; 32]` for AES-256, ChaCha20 (software keys) + /// - `[u8; 16]` for AES-128 (software keys) + /// - `KeyVaultId` for key vault managed keys + /// - `HardwareKeyHandle` for hardware-managed keys + /// - Custom types for hardware-specific key formats or masked implementations + type Key; + + /// The nonce or initialization vector type. + /// + /// This type represents the nonce (number used once) or initialization vector + /// for the cipher operation. It must be unique for each encryption with the same key. + /// + /// # Security Requirements + /// + /// - Must never be reused with the same key (critical for CTR mode and stream ciphers) + /// - Should be generated using cryptographically secure random number generation + /// - Size must match the algorithm's requirements + /// + /// # Common Types + /// + /// - `[u8; 16]` for AES-CTR, AES-CBC + /// - `[u8; 12]` for ChaCha20, AES-GCM + /// - `[u8; 8]` for some legacy ciphers + type Nonce: FromBytes + IntoBytes; + + /// The plaintext data type. + /// + /// This type represents the unencrypted data input to the cipher. + /// It must support zero-copy operations for efficient processing. + /// + /// # Performance Considerations + /// + /// - Should minimize copying and allocation + /// - Consider using slices or references where possible + /// - Support both fixed-size and variable-length data + /// + /// # Common Types + /// + /// - `&[u8]` for read-only operations + /// - `[u8; N]` for fixed-size owned data + /// - Custom container types for variable-length data + type PlainText: FromBytes + IntoBytes; + + /// The ciphertext data type. + /// + /// This type represents the encrypted data output from the cipher. + /// It must support zero-copy operations for efficient processing. + /// + /// # Size Considerations + /// + /// - For stream ciphers: same size as plaintext + /// - For block ciphers with padding: may be larger than plaintext + /// - For AEAD modes: includes authentication tag + /// + /// # Common Types + /// + /// - `[u8; N]` for fixed-size encrypted blocks + /// - Custom container types for variable-length encrypted data + /// - Types that include metadata or authentication tags + type CipherText: FromBytes + IntoBytes; +} + +/// Trait for initializing a cipher with a specific mode. +pub trait CipherInit: SymmetricCipher { + /// The operational context for performing encryption/decryption. + type CipherContext<'a>: CipherOp + where + Self: 'a; + + /// Initializes the cipher with the given parameters. + /// + /// # Parameters + /// + /// - `key`: A reference to the key used for the cipher. + /// - `nonce`: A reference to the nonce or IV used for the cipher. + /// - `mode`: The cipher mode to use. + /// + /// # Returns + /// + /// A result containing the operational context or an error. + fn init<'a>( + &'a mut self, + key: &Self::Key, + nonce: &Self::Nonce, + mode: M, + ) -> Result, Self::Error>; +} + +/// Trait for basic encryption/decryption operations. +pub trait CipherOp: SymmetricCipher + ErrorType { + /// Encrypts the given plaintext. + /// + /// # Parameters + /// + /// - `plaintext`: The data to encrypt. + /// + /// # Returns + /// + /// A result containing the ciphertext or an error. + fn encrypt(&mut self, plaintext: Self::PlainText) -> Result; + + /// Decrypts the given ciphertext. + /// + /// # Parameters + /// + /// - `ciphertext`: The data to decrypt. + /// + /// # Returns + /// + /// A result containing the plaintext or an error. + fn decrypt(&mut self, ciphertext: Self::CipherText) -> Result; +} + +/// Optional trait for cipher contexts that support resetting to their initial state. +pub trait ResettableCipherOp: ErrorType { + /// Resets the cipher context. + /// + /// # Returns + /// + /// A result indicating success or failure. + fn reset(&mut self) -> Result<(), Self::Error>; +} + +/// Optional trait for cipher contexts that support rekeying. +pub trait CipherRekey: ErrorType { + /// Rekeys the cipher context with a new key. + /// + /// # Parameters + /// + /// - `new_key`: A reference to the new key. + /// + /// # Returns + /// + /// A result indicating success or failure. + fn rekey(&mut self, new_key: &K) -> Result<(), Self::Error>; +} + +/// Error type for block-aligned container operations. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BlockAlignedError { + /// The container has reached its maximum capacity. + CapacityExceeded, + /// The input data is too large for the container. + DataTooLarge, +} + +impl core::fmt::Display for BlockAlignedError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::CapacityExceeded => write!(f, "block container has reached maximum capacity"), + Self::DataTooLarge => write!(f, "input data exceeds container capacity"), + } + } +} + +/// Block-aligned data container that guarantees correct block sizing at compile time. +/// +/// This type wrapper ensures that data is always properly aligned to block boundaries, +/// preventing runtime errors from incorrectly sized cipher inputs. It uses fixed-size +/// arrays suitable for embedded systems without heap allocation. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlockAligned { + blocks: [[u8; BLOCK_SIZE]; MAX_BLOCKS], + block_count: usize, +} + +impl Default + for BlockAligned +{ + fn default() -> Self { + Self::new() + } +} + +impl BlockAligned { + /// Create a new empty block-aligned container + pub const fn new() -> Self { + Self { + blocks: [[0u8; BLOCK_SIZE]; MAX_BLOCKS], + block_count: 0, + } + } + + /// Create block-aligned data from a byte slice, padding if necessary. + /// + /// # Parameters + /// - `data`: Input data that will be padded to block boundaries + /// - `padding_byte`: Byte value to use for padding (typically 0) + /// + /// # Returns + /// - `Ok(BlockAligned)`: Successfully created block-aligned data + /// - `Err(BlockAlignedError)`: Input data exceeds maximum capacity + /// + /// # Errors + /// Returns an error if the input data would require more than `MAX_BLOCKS` blocks. + pub fn from_slice_padded(data: &[u8], padding_byte: u8) -> Result { + let required_blocks = data.len().div_ceil(BLOCK_SIZE); + + if required_blocks > MAX_BLOCKS { + return Err(BlockAlignedError::DataTooLarge); + } + + let mut result = Self::new(); + result.block_count = required_blocks; + + // Fill complete blocks + for (i, chunk) in data.chunks(BLOCK_SIZE).enumerate() { + let block = result + .blocks + .get_mut(i) + .ok_or(BlockAlignedError::DataTooLarge)?; + block.fill(padding_byte); + let slice = block + .get_mut(..chunk.len()) + .ok_or(BlockAlignedError::DataTooLarge)?; + slice.copy_from_slice(chunk); + } + + Ok(result) + } + + /// Add a complete block to the container. + /// + /// # Parameters + /// - `block`: Block data to add + /// + /// # Returns + /// - `Ok(())`: Block successfully added + /// - `Err(BlockAlignedError)`: Container is at maximum capacity + pub fn push_block(&mut self, block: [u8; BLOCK_SIZE]) -> Result<(), BlockAlignedError> { + if self.block_count >= MAX_BLOCKS { + return Err(BlockAlignedError::CapacityExceeded); + } + + let block_slot = self + .blocks + .get_mut(self.block_count) + .ok_or(BlockAlignedError::CapacityExceeded)?; + *block_slot = block; + self.block_count = self.block_count.saturating_add(1); + Ok(()) + } + + /// Get the blocks as a slice containing only the valid blocks. + pub fn blocks(&self) -> &[[u8; BLOCK_SIZE]] { + &self.blocks[..self.block_count] + } + + /// Get the total number of bytes in valid blocks. + pub const fn len(&self) -> usize { + self.block_count.saturating_mul(BLOCK_SIZE) + } + + /// Check if the container is empty. + pub const fn is_empty(&self) -> bool { + self.block_count == 0 + } + + /// Get the number of blocks currently stored. + pub const fn block_count(&self) -> usize { + self.block_count + } + + /// Get the maximum number of blocks this container can hold. + pub const fn capacity(&self) -> usize { + MAX_BLOCKS + } + + /// Get a specific block by index. + pub fn get_block(&self, index: usize) -> Option<&[u8; BLOCK_SIZE]> { + if index < self.block_count { + self.blocks.get(index) + } else { + None + } + } + + /// Iterate over all valid blocks. + pub fn iter_blocks(&self) -> impl Iterator { + self.blocks[..self.block_count].iter() + } +} + +/// Trait for secure cipher operations and cleanup. +/// +/// This trait provides security-focused operations that are orthogonal to basic +/// cipher functionality. It enables secure state management, cleanup, and +/// zeroization without requiring full cipher operation capabilities. +/// +/// # Security Operations +/// +/// - Secure state clearing and zeroization +/// - Emergency cleanup procedures +/// - Security policy enforcement +/// - Sensitive data lifecycle management +/// +/// # Independence from CipherOp +/// +/// This trait is deliberately independent of `CipherOp` to allow: +/// - Security managers that don't perform encryption/decryption +/// - Key stores and vaults with secure cleanup +/// - Flexible composition with other cipher traits +pub trait SecureCipherOp: ErrorType { + /// Securely clear internal state and zeroize sensitive data. + /// + /// This method performs a secure cleanup of all internal state, including: + /// - Zeroization of key material in memory + /// - Clearing of intermediate computation values + /// - Resetting hardware registers (for hardware implementations) + /// - Invalidating cached data or contexts + /// + /// # Security Guarantees + /// + /// - All sensitive data must be cryptographically erased + /// - Memory containing keys or intermediate values must be zeroized + /// - Hardware registers must be cleared if applicable + /// - The operation should be resistant to compiler optimizations + /// + /// # Returns + /// + /// A result indicating whether the secure cleanup was successful. + /// + /// # Errors + /// + /// - `HardwareFailure`: Hardware cleanup operations failed + /// - `PermissionDenied`: Insufficient privileges for secure operations + /// - `InvalidState`: Cipher is in a state that prevents secure cleanup + /// + /// # Example + /// + /// ```ignore + /// let mut cipher = SecureAesCipher::new(); + /// // ... perform cipher operations ... + /// cipher.clear_state()?; // Secure cleanup before dropping + /// ``` + fn clear_state(&mut self) -> Result<(), Self::Error>; +} + +/// Trait for querying cipher status and hardware state. +/// +/// This trait provides status monitoring capabilities that are useful for +/// hardware implementations, performance optimization, and error detection. +/// It's independent of cipher operations to allow status monitoring without +/// requiring operation capabilities. +/// +/// # Status Monitoring +/// +/// - Hardware readiness and availability +/// - Output data availability +/// - Error and alert conditions +/// - Performance and state information +/// +/// # Hardware Integration +/// +/// - Allows polling-based operation models +/// - Supports interrupt-driven architectures +/// - Enables efficient resource utilization +/// - Provides visibility into hardware state +pub trait CipherStatus: ErrorType { + /// Check if the cipher is ready to accept new input data. + /// + /// This method indicates whether the cipher implementation can accept + /// new input for processing. For hardware implementations, this typically + /// corresponds to input buffer availability. + /// + /// # Returns + /// + /// - `Ok(true)`: Cipher is ready for new input + /// - `Ok(false)`: Cipher is busy and cannot accept input + /// - `Err(_)`: Error occurred while checking status + /// + /// # Use Cases + /// + /// - Polling loops waiting for hardware readiness + /// - Flow control in streaming operations + /// - Performance optimization by avoiding blocking calls + fn is_ready(&self) -> Result; + + /// Check if processed output data is available for reading. + /// + /// This method indicates whether the cipher has completed processing + /// and has output data available. For hardware implementations, this + /// typically corresponds to output buffer status. + /// + /// # Returns + /// + /// - `Ok(true)`: Output data is available + /// - `Ok(false)`: No output data is currently available + /// - `Err(_)`: Error occurred while checking status + /// + /// # Use Cases + /// + /// - Polling for completion of asynchronous operations + /// - Avoiding blocking reads when no data is available + /// - Implementing efficient producer-consumer patterns + fn has_output(&self) -> Result; + + /// Check if the cipher is idle and available for new operations. + /// + /// This method indicates whether the cipher is in an idle state and + /// can be used for new operations. This is useful for determining + /// when to start new transactions or perform maintenance operations. + /// + /// # Returns + /// + /// - `Ok(true)`: Cipher is idle and available + /// - `Ok(false)`: Cipher is busy with ongoing operations + /// - `Err(_)`: Error occurred while checking status + /// + /// # Use Cases + /// + /// - Determining when to begin new cipher transactions + /// - Resource management and scheduling + /// - Power management decisions + /// - Maintenance and diagnostic operations + fn is_idle(&self) -> Result; +} + +/// Trait for AEAD (Authenticated Encryption with Associated Data) operations. +/// +/// This trait extends symmetric cipher operations to provide authenticated encryption, +/// which combines confidentiality (encryption) with authenticity and integrity +/// (authentication). AEAD modes like AES-GCM and ChaCha20-Poly1305 are the +/// recommended approach for modern cryptographic applications. +/// +/// # AEAD Benefits +/// +/// - **Confidentiality**: Data is encrypted and unreadable without the key +/// - **Authenticity**: Verifies the data came from the expected sender +/// - **Integrity**: Detects any tampering or corruption of the data +/// - **Associated Data**: Can authenticate additional data without encrypting it +/// +/// # Security Guarantees +/// +/// - Prevents chosen-ciphertext attacks +/// - Provides semantic security +/// - Detects message tampering +/// - Supports additional authenticated data (AAD) that remains in plaintext +/// +/// # Common Algorithms +/// +/// - **AES-GCM**: High performance, hardware acceleration available +/// - **ChaCha20-Poly1305**: Software-friendly, constant-time implementation +/// - **AES-CCM**: Suited for resource-constrained environments +/// +/// # Example Usage +/// +/// ```ignore +/// // Encrypt with associated data +/// let plaintext = b"secret message"; +/// let aad = b"public header info"; +/// let (ciphertext, tag) = cipher.encrypt_aead(plaintext, aad)?; +/// +/// // Decrypt and verify +/// let decrypted = cipher.decrypt_aead(ciphertext, aad, tag)?; +/// ``` +pub trait AeadCipherOp: SymmetricCipher + ErrorType { + /// The associated data type for AEAD operations. + /// + /// Associated data (AAD) is additional information that is authenticated + /// but not encrypted. It provides integrity protection for data that must + /// remain in plaintext, such as packet headers or metadata. + /// + /// # Security Properties + /// + /// - **Authenticated but not encrypted**: AAD is included in authentication tag calculation + /// - **Integrity protected**: Any modification to AAD will cause decryption to fail + /// - **No confidentiality**: AAD remains visible in plaintext + /// + /// # Use Cases + /// + /// - Network packet headers that must be readable by intermediary devices + /// - File metadata that must remain accessible + /// - Protocol version information + /// - Sequence numbers or timestamps + /// + /// # Common Types + /// + /// - `&[u8]` for read-only associated data + /// - `[u8; N]` for fixed-size owned associated data + /// - `()` or empty slice if no associated data is needed + type AssociatedData: FromBytes + IntoBytes; + + /// The authentication tag type for AEAD operations. + /// + /// The authentication tag is a cryptographic checksum that provides + /// integrity and authenticity verification for both the ciphertext + /// and associated data. + /// + /// # Security Properties + /// + /// - **Unforgeable**: Cannot be created without the secret key + /// - **Tamper-evident**: Any modification to protected data changes the tag + /// - **Algorithm-specific size**: Fixed size determined by the AEAD mode + /// + /// # Tag Sizes + /// + /// - **AES-GCM**: 16 bytes (128 bits) recommended, can be truncated + /// - **ChaCha20-Poly1305**: 16 bytes (128 bits) fixed + /// - **AES-CCM**: Variable (4, 6, 8, 10, 12, 14, or 16 bytes) + /// + /// # Security Warning + /// + /// Tags must be compared in constant time to prevent timing attacks. + /// Use cryptographic comparison functions, not standard equality operators. + /// + /// # Common Types + /// + /// - `[u8; 16]` for most AEAD modes + /// - `[u8; N]` for variable-length tags + /// - Custom types that include metadata + type Tag: FromBytes + IntoBytes; + + /// Encrypts the given plaintext with associated data. + /// + /// # Parameters + /// + /// - `plaintext`: The data to encrypt. + /// - `associated_data`: The associated data to authenticate. + /// + /// # Returns + /// + /// A result containing the ciphertext and authentication tag or an error. + fn encrypt_aead( + &mut self, + plaintext: Self::PlainText, + associated_data: Self::AssociatedData, + ) -> Result<(Self::CipherText, Self::Tag), Self::Error>; + + /// Decrypts the given ciphertext with associated data and authentication tag. + /// + /// # Parameters + /// + /// - `ciphertext`: The data to decrypt. + /// - `associated_data`: The associated data to authenticate. + /// - `tag`: The authentication tag. + /// + /// # Returns + /// + /// A result containing the plaintext or an error. + fn decrypt_aead( + &mut self, + ciphertext: Self::CipherText, + associated_data: Self::AssociatedData, + tag: Self::Tag, + ) -> Result; +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] // Allow unwrap in tests for cleaner test code +mod tests { + use super::*; + + #[test] + fn test_block_aligned_creation() { + let container = BlockAligned::<16, 4>::new(); + assert_eq!(container.block_count(), 0); + assert_eq!(container.capacity(), 4); + assert_eq!(container.len(), 0); + assert!(container.is_empty()); + } + + #[test] + fn test_block_aligned_default() { + let container: BlockAligned<16, 4> = Default::default(); + assert_eq!(container.block_count(), 0); + assert_eq!(container.capacity(), 4); + assert!(container.is_empty()); + } + + #[test] + fn test_push_block_success() { + let mut container = BlockAligned::<16, 4>::new(); + + let block1 = [0x42u8; 16]; + let result = container.push_block(block1); + assert!(result.is_ok()); + assert_eq!(container.block_count(), 1); + assert_eq!(container.len(), 16); + assert!(!container.is_empty()); + + let block2 = [0x33u8; 16]; + let result = container.push_block(block2); + assert!(result.is_ok()); + assert_eq!(container.block_count(), 2); + assert_eq!(container.len(), 32); + } + + #[test] + fn test_push_block_capacity_exceeded() { + let mut container = BlockAligned::<16, 2>::new(); + + // Fill to capacity + container.push_block([0x01u8; 16]).unwrap(); + container.push_block([0x02u8; 16]).unwrap(); + assert_eq!(container.block_count(), 2); + + // Try to exceed capacity + let result = container.push_block([0x03u8; 16]); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), BlockAlignedError::CapacityExceeded); + assert_eq!(container.block_count(), 2); // Should remain unchanged + } + + #[test] + fn test_get_block() { + let mut container = BlockAligned::<16, 4>::new(); + + let block1 = [0x42u8; 16]; + let block2 = [0x33u8; 16]; + container.push_block(block1).unwrap(); + container.push_block(block2).unwrap(); + + // Test valid indices + assert_eq!(container.get_block(0), Some(&block1)); + assert_eq!(container.get_block(1), Some(&block2)); + + // Test invalid indices + assert_eq!(container.get_block(2), None); + assert_eq!(container.get_block(100), None); + } + + #[test] + fn test_blocks_slice() { + let mut container = BlockAligned::<16, 4>::new(); + + let block1 = [0x42u8; 16]; + let block2 = [0x33u8; 16]; + container.push_block(block1).unwrap(); + container.push_block(block2).unwrap(); + + let blocks = container.blocks(); + assert_eq!(blocks.len(), 2); + assert_eq!(blocks.first().unwrap(), &block1); + assert_eq!(blocks.get(1).unwrap(), &block2); + } + + #[test] + fn test_iter_blocks() { + let mut container = BlockAligned::<16, 4>::new(); + + let block1 = [0x42u8; 16]; + let block2 = [0x33u8; 16]; + let block3 = [0x11u8; 16]; + + container.push_block(block1).unwrap(); + container.push_block(block2).unwrap(); + container.push_block(block3).unwrap(); + + // Test iterator manually + let mut iter = container.iter_blocks(); + assert_eq!(iter.next(), Some(&block1)); + assert_eq!(iter.next(), Some(&block2)); + assert_eq!(iter.next(), Some(&block3)); + assert_eq!(iter.next(), None); + + // Test that iterator only includes valid blocks + let empty_container = BlockAligned::<16, 4>::new(); + let mut empty_iter = empty_container.iter_blocks(); + assert_eq!(empty_iter.next(), None); + } + + #[test] + fn test_from_slice_padded_exact_fit() { + // Test data that exactly fits one block + let data = [0x42u8; 16]; + let container = BlockAligned::<16, 4>::from_slice_padded(&data, 0x00).unwrap(); + + assert_eq!(container.block_count(), 1); + assert_eq!(container.get_block(0), Some(&data)); + } + + #[test] + fn test_from_slice_padded_partial_block() { + // Test data that requires padding + let data = b"Hello, World!"; // 13 bytes + let container = BlockAligned::<16, 4>::from_slice_padded(data, 0x00).unwrap(); + + assert_eq!(container.block_count(), 1); + + let block = container.get_block(0).unwrap(); + // First 13 bytes should match input + assert_eq!(&block[..13], data); + // Last 3 bytes should be padding + assert_eq!(&block[13..], &[0x00; 3]); + } + + #[test] + fn test_from_slice_padded_multiple_blocks() { + // Test data that spans multiple blocks + let data = [0x42u8; 33]; // 33 bytes = 3 blocks (16 + 16 + 1) + let container = BlockAligned::<16, 4>::from_slice_padded(&data, 0xFF).unwrap(); + + assert_eq!(container.block_count(), 3); + + // First two blocks should be all 0x42 + assert_eq!(container.get_block(0), Some(&[0x42u8; 16])); + assert_eq!(container.get_block(1), Some(&[0x42u8; 16])); + + // Third block should have one byte of data and 15 bytes of padding + let third_block = container.get_block(2).unwrap(); + assert_eq!(third_block.first().unwrap(), &0x42); + assert_eq!(&third_block[1..], &[0xFF; 15]); + } + + #[test] + fn test_from_slice_padded_empty_data() { + let data = &[]; + let container = BlockAligned::<16, 4>::from_slice_padded(data, 0x00).unwrap(); + + assert_eq!(container.block_count(), 0); + assert!(container.is_empty()); + } + + #[test] + fn test_from_slice_padded_data_too_large() { + // Test data that exceeds capacity + let data = [0x42u8; 100]; // 100 bytes = 7 blocks (16*6 + 4), but capacity is only 4 + let result = BlockAligned::<16, 4>::from_slice_padded(&data, 0x00); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), BlockAlignedError::DataTooLarge); + } + + #[test] + fn test_from_slice_padded_different_padding() { + let data = b"test"; // 4 bytes + let container = BlockAligned::<8, 2>::from_slice_padded(data, 0xAA).unwrap(); + + assert_eq!(container.block_count(), 1); + + let block = container.get_block(0).unwrap(); + assert_eq!(&block[..4], data); + assert_eq!(&block[4..], &[0xAA; 4]); + } + + #[test] + fn test_different_block_sizes() { + // Test with 8-byte blocks + let mut container8 = BlockAligned::<8, 4>::new(); + container8.push_block([0x11u8; 8]).unwrap(); + assert_eq!(container8.len(), 8); + + // Test with 32-byte blocks + let mut container32 = BlockAligned::<32, 2>::new(); + container32.push_block([0x22u8; 32]).unwrap(); + assert_eq!(container32.len(), 32); + + // Test with 1-byte blocks + let mut container1 = BlockAligned::<1, 16>::new(); + container1.push_block([0x33]).unwrap(); + assert_eq!(container1.len(), 1); + } + + #[test] + fn test_clone_and_equality() { + let mut container1 = BlockAligned::<16, 4>::new(); + let block = [0x42u8; 16]; + container1.push_block(block).unwrap(); + + let container2 = container1.clone(); + assert_eq!(container1, container2); + assert_eq!(container1.block_count(), container2.block_count()); + assert_eq!(container1.get_block(0), container2.get_block(0)); + + // Test inequality + let mut container3 = BlockAligned::<16, 4>::new(); + container3.push_block([0x33u8; 16]).unwrap(); + assert_ne!(container1, container3); + } + + #[test] + fn test_edge_cases() { + // Test with maximum capacity + let mut container = BlockAligned::<1, 8>::new(); + for i in 0..8 { + container.push_block([i as u8]).unwrap(); + } + assert_eq!(container.block_count(), 8); + assert_eq!(container.len(), 8); + + // Verify all blocks are correct + for i in 0..8 { + assert_eq!(container.get_block(i), Some(&[i as u8])); + } + } + + #[test] + fn test_error_display() { + let capacity_error = BlockAlignedError::CapacityExceeded; + let data_error = BlockAlignedError::DataTooLarge; + + // Test that the errors are created correctly + assert_eq!(capacity_error, BlockAlignedError::CapacityExceeded); + assert_eq!(data_error, BlockAlignedError::DataTooLarge); + + // Test that they are not equal to each other + assert_ne!(capacity_error, data_error); + } + + #[test] + fn test_debug_formatting() { + let mut container = BlockAligned::<4, 2>::new(); + container.push_block([1, 2, 3, 4]).unwrap(); + + // Test that the container was created successfully + assert_eq!(container.block_count(), 1); + assert_eq!(container.get_block(0), Some(&[1, 2, 3, 4])); + } +} diff --git a/hal/blocking/src/digest.rs b/hal/blocking/src/digest.rs index 742b368..b4b3412 100644 --- a/hal/blocking/src/digest.rs +++ b/hal/blocking/src/digest.rs @@ -25,17 +25,17 @@ //! //! ## Key Components //! -//! - [`Digest`] - A generic container for digest output values -//! - [`DigestAlgorithm`] - Trait defining digest algorithm properties +//! - `Digest` - A generic container for digest output values +//! - `DigestAlgorithm` - Trait defining digest algorithm properties //! //! ### Scoped API -//! - [`scoped::DigestInit`] - Trait for initializing digest operations (borrowed contexts) -//! - [`scoped::DigestOp`] - Trait for performing digest computations (borrowed contexts) -//! - [`scoped::DigestCtrlReset`] - Trait for resetting digest contexts +//! - `scoped::DigestInit` - Trait for initializing digest operations (borrowed contexts) +//! - `scoped::DigestOp` - Trait for performing digest computations (borrowed contexts) +//! - `scoped::DigestCtrlReset` - Trait for resetting digest contexts //! //! ### Owned API (Typestate) -//! - [`owned::DigestInit`] - Trait for initializing digest operations (owned contexts) -//! - [`owned::DigestOp`] - Trait for performing digest computations (owned contexts) +//! - `owned::DigestInit` - Trait for initializing digest operations (owned contexts) +//! - `owned::DigestOp` - Trait for performing digest computations (owned contexts) //! //! ## Supported Algorithms //! diff --git a/hal/blocking/src/i2c_device.rs b/hal/blocking/src/i2c_device.rs index 8cdf59a..6ae0ea8 100644 --- a/hal/blocking/src/i2c_device.rs +++ b/hal/blocking/src/i2c_device.rs @@ -90,12 +90,12 @@ //! //! The traits are organized in a composable hierarchy: //! -//! - [`I2CCoreTarget`]: Core transaction lifecycle (required for all devices) -//! - [`WriteTarget`]: Handle write operations from master -//! - [`ReadTarget`]: Handle read operations from master -//! - [`WriteReadTarget`]: Handle combined write-read transactions -//! - [`RegisterAccess`]: Higher-level register-based access patterns -//! - [`I2CTarget`]: Convenience trait combining all capabilities +//! - `I2CCoreTarget`: Core transaction lifecycle (required for all devices) +//! - `WriteTarget`: Handle write operations from master +//! - `ReadTarget`: Handle read operations from master +//! - `WriteReadTarget`: Handle combined write-read transactions +//! - `RegisterAccess`: Higher-level register-based access patterns +//! - `I2CTarget`: Convenience trait combining all capabilities //! //! ## Transaction Flow //! diff --git a/hal/blocking/src/lib.rs b/hal/blocking/src/lib.rs index 02217ae..48bbbc8 100644 --- a/hal/blocking/src/lib.rs +++ b/hal/blocking/src/lib.rs @@ -32,6 +32,9 @@ pub mod system_control; /// Key management traits pub mod key_vault; +/// Symmetric cipher abstractions with zero-copy support +pub mod cipher; + // Re-export embedded-hal 1.0 traits pub use embedded_hal::delay::DelayNs; pub use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin}; diff --git a/platform/impls/baremetal/mock/src/i2c_hardware.rs b/platform/impls/baremetal/mock/src/i2c_hardware.rs index 5e89503..0455dd8 100644 --- a/platform/impls/baremetal/mock/src/i2c_hardware.rs +++ b/platform/impls/baremetal/mock/src/i2c_hardware.rs @@ -328,7 +328,7 @@ pub struct MockI2cHardware { // Slave mode fields /// Whether slave mode is currently enabled (1 byte) slave_enabled: bool, - /// Currently configured slave address (1 byte: Option) + /// Currently configured slave address (1 byte: `Option`) slave_address: Option, /// Slave receive buffer (64 bytes) - realistic I2C message size slave_rx_buffer: [u8; 64], @@ -338,7 +338,7 @@ pub struct MockI2cHardware { slave_tx_buffer: [u8; 64], /// Number of valid bytes in transmit buffer (8 bytes: usize on 64-bit) slave_tx_count: usize, - /// Most recent slave event that occurred (1 byte: Option) + /// Most recent slave event that occurred (1 byte: `Option`) last_slave_event: Option, } diff --git a/platform/impls/rustcrypto/Cargo.toml b/platform/impls/rustcrypto/Cargo.toml new file mode 100644 index 0000000..d36a153 --- /dev/null +++ b/platform/impls/rustcrypto/Cargo.toml @@ -0,0 +1,28 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "rustcrypto" +version = "0.1.0" +license.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +openprot-hal-blocking = { path = "../../../hal/blocking" } + +# RustCrypto ECDSA crates +p256 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic"] } +p384 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic"] } +k256 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic"] } + +# RustCrypto cipher crates +aes = { version = "0.8", default-features = false } +ctr = { version = "0.9", default-features = false } +aes-gcm = { version = "0.10", default-features = false, features = ["aes"] } +cipher = { version = "0.4", default-features = false } +generic-array = { version = "0.14", default-features = false } + +# Traits and utilities +zerocopy = { workspace = true } +zeroize = { workspace = true } +rand_core = { workspace = true } diff --git a/platform/impls/rustcrypto/src/cipher.rs b/platform/impls/rustcrypto/src/cipher.rs new file mode 100644 index 0000000..2c749df --- /dev/null +++ b/platform/impls/rustcrypto/src/cipher.rs @@ -0,0 +1,582 @@ +// Licensed under the Apache-2.0 license + +use openprot_hal_blocking::cipher::{ + AeadCipherMode, BlockCipherMode, CipherInit, CipherMode, CipherOp, CipherStatus, Error, + ErrorKind, ErrorType, SymmetricCipher, +}; + +// RustCrypto imports for AES-CTR implementation +use aes::Aes256; +use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; +use ctr::Ctr64BE; +use generic_array::GenericArray; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RustCryptoCipherError { + InvalidKey, + InvalidNonce, + EncryptionFailed, + DecryptionFailed, + AuthenticationFailed, + MessageTooLarge, + InvalidState, + HardwareFailure, +} + +impl core::fmt::Display for RustCryptoCipherError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidKey => write!(f, "invalid key length or format"), + Self::InvalidNonce => write!(f, "invalid nonce/IV length or format"), + Self::EncryptionFailed => write!(f, "encryption operation failed"), + Self::DecryptionFailed => write!(f, "decryption operation failed"), + Self::AuthenticationFailed => write!(f, "authentication verification failed"), + Self::MessageTooLarge => write!(f, "message too large for configured buffer size"), + Self::InvalidState => write!(f, "cipher context is in an invalid state"), + Self::HardwareFailure => write!(f, "hardware failure during cipher operation"), + } + } +} + +impl Error for RustCryptoCipherError { + fn kind(&self) -> ErrorKind { + match self { + Self::InvalidKey => ErrorKind::KeyError, + Self::InvalidNonce => ErrorKind::InvalidInput, + Self::EncryptionFailed | Self::DecryptionFailed => ErrorKind::HardwareFailure, + Self::AuthenticationFailed => ErrorKind::InvalidInput, + Self::MessageTooLarge => ErrorKind::InvalidInput, + Self::InvalidState => ErrorKind::InvalidState, + Self::HardwareFailure => ErrorKind::HardwareFailure, + } + } +} + +// +// Cipher mode markers +// + +/// AES-256 in CTR mode marker +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Aes256CtrMode; + +impl CipherMode for Aes256CtrMode {} +impl BlockCipherMode for Aes256CtrMode {} + +/// AES-256 in GCM mode marker +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Aes256GcmMode; + +impl CipherMode for Aes256GcmMode {} +impl AeadCipherMode for Aes256GcmMode {} + +// +// Basic cipher implementations for type system foundation +// + +/// Basic AES-256-CTR cipher implementation +pub struct Aes256CtrCipher; + +/// Basic AES-256-GCM AEAD cipher implementation +pub struct Aes256GcmCipher; + +// +// ErrorType trait implementations +// + +impl ErrorType for Aes256CtrCipher { + type Error = RustCryptoCipherError; +} + +impl ErrorType for Aes256GcmCipher { + type Error = RustCryptoCipherError; +} + +// +// SymmetricCipher trait implementations +// + +impl SymmetricCipher for Aes256CtrCipher { + type Key = [u8; 32]; // AES-256 key + type Nonce = [u8; 16]; // 128-bit IV for CTR mode + type PlainText = [u8; 256]; // Fixed-size plaintext buffer + type CipherText = [u8; 256]; // Fixed-size ciphertext buffer +} + +impl SymmetricCipher for Aes256GcmCipher { + type Key = [u8; 32]; // AES-256 key + type Nonce = [u8; 12]; // 96-bit nonce for GCM + type PlainText = [u8; 256]; // Fixed-size plaintext buffer + type CipherText = [u8; 272]; // 256 + 16 bytes for authentication tag +} + +// +// AES-CTR Implementation using RustCrypto +// + +/// AES-256-CTR cipher context wrapping RustCrypto's implementation. +/// +/// This struct provides a secure, zeroizing wrapper around the RustCrypto +/// AES-256-CTR implementation, suitable for embedded systems and security-critical applications. +pub struct AesCtrContext { + /// The underlying RustCrypto AES-256-CTR cipher + cipher: Ctr64BE, + /// Current state for status tracking + state: CipherState, +} + +/// Cipher state for tracking operational status +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CipherState { + /// Cipher is ready to accept new operations + Ready, + /// Cipher is currently processing data + Processing, + /// Cipher has completed processing and has output available + HasOutput, +} + +impl AesCtrContext { + /// Create a new AES-CTR context with the given key and IV. + /// + /// # Parameters + /// - `key`: 32-byte AES-256 key + /// - `iv`: 16-byte initialization vector + /// + /// # Returns + /// A new AES-CTR context ready for encryption/decryption operations. + /// + /// # Security Note + /// The IV must be unique for each encryption operation with the same key. + pub fn new(key: &[u8; 32], iv: &[u8; 16]) -> Result { + let key_array = GenericArray::from_slice(key); + let iv_array = GenericArray::from_slice(iv); + + let cipher = Ctr64BE::::new(key_array, iv_array); + Ok(Self { + cipher, + state: CipherState::Ready, + }) + } + + /// Reset the cipher context to its initial state with new key/IV. + /// + /// This securely clears the previous state and reinitializes with new parameters. + pub fn reset(&mut self, key: &[u8; 32], iv: &[u8; 16]) -> Result<(), RustCryptoCipherError> { + // Create new cipher instance (this effectively clears the old one) + let key_array = GenericArray::from_slice(key); + let iv_array = GenericArray::from_slice(iv); + + self.cipher = Ctr64BE::::new(key_array, iv_array); + self.state = CipherState::Ready; + + Ok(()) + } + + /// Get the current position in the keystream (for CTR mode). + /// + /// This can be useful for resuming operations or implementing seek functionality. + pub fn position(&self) -> u64 { + self.cipher.current_pos() + } + + /// Seek to a specific position in the keystream. + /// + /// # Parameters + /// - `pos`: Position to seek to in the keystream + /// + /// # Security Warning + /// Seeking in CTR mode can be dangerous if not done carefully. + /// Never reuse keystream positions with the same key/IV combination. + pub fn seek(&mut self, pos: u64) { + self.cipher.seek(pos); + } +} + +// +// ErrorType implementation for AES-CTR context +// + +impl ErrorType for AesCtrContext { + type Error = RustCryptoCipherError; +} + +// +// CipherInit implementation for AES-256-CTR +// + +impl CipherInit for Aes256CtrCipher { + type CipherContext<'a> = AesCtrContext; + + /// Initialize a new AES-CTR cipher context. + /// + /// # Parameters + /// - `key`: Reference to the AES-256 key (32 bytes) + /// - `nonce`: Reference to the initialization vector (16 bytes) + /// - `mode`: The cipher mode (Aes256CtrMode) + /// + /// # Returns + /// A new AES-CTR context ready for encryption/decryption operations. + /// + /// # Errors + /// - `InvalidKey`: If the key is not exactly 32 bytes + /// - `InvalidNonce`: If the IV is not exactly 16 bytes + /// - `InitializationError`: If the cipher cannot be initialized + /// + /// # Security Notes + /// - The IV must be unique for each encryption with the same key + /// - Never reuse the same key/IV combination + /// - Consider using a counter or random IV generation + fn init<'a>( + &'a mut self, + key: &Self::Key, + nonce: &Self::Nonce, + _mode: Aes256CtrMode, + ) -> Result, Self::Error> { + // Validate key and nonce lengths (compile-time guaranteed by types) + // Create new context with the provided key and IV + AesCtrContext::new(key, nonce) + } +} + +// +// CipherOp implementation for AES-CTR context +// + +impl CipherOp for AesCtrContext { + /// Encrypt plaintext using AES-256-CTR mode. + /// + /// In CTR mode, encryption and decryption are the same operation: + /// XOR the plaintext with the keystream generated by encrypting the counter. + /// + /// # Parameters + /// - `plaintext`: Fixed-size plaintext buffer to encrypt + /// + /// # Returns + /// The encrypted ciphertext with the same size as the input. + /// + /// # Security Notes + /// - Never reuse the same key/IV combination + /// - The counter state is advanced after each operation + /// - CTR mode provides semantic security when IVs are unique + fn encrypt(&mut self, mut plaintext: Self::PlainText) -> Result { + self.state = CipherState::Processing; + + // In CTR mode, encryption is just XOR with keystream + // The apply_keystream method modifies the buffer in-place + self.cipher.apply_keystream(&mut plaintext); + + self.state = CipherState::HasOutput; + + // Return the modified buffer as ciphertext + Ok(plaintext) + } + + /// Decrypt ciphertext using AES-256-CTR mode. + /// + /// In CTR mode, decryption is identical to encryption: + /// XOR the ciphertext with the keystream. + /// + /// # Parameters + /// - `ciphertext`: Fixed-size ciphertext buffer to decrypt + /// + /// # Returns + /// The decrypted plaintext with the same size as the input. + /// + /// # Security Notes + /// - The IV/counter state must match what was used for encryption + /// - Position in the keystream is automatically tracked + /// - Ensure the cipher context is properly initialized + fn decrypt( + &mut self, + mut ciphertext: Self::CipherText, + ) -> Result { + self.state = CipherState::Processing; + + // In CTR mode, decryption is identical to encryption + // XOR the ciphertext with the same keystream + self.cipher.apply_keystream(&mut ciphertext); + + self.state = CipherState::HasOutput; + + // Return the modified buffer as plaintext + Ok(ciphertext) + } +} + +impl SymmetricCipher for AesCtrContext { + type Key = [u8; 32]; + type Nonce = [u8; 16]; + type PlainText = [u8; 256]; + type CipherText = [u8; 256]; +} + +// +// CipherStatus implementation for AES-CTR context +// + +impl CipherStatus for AesCtrContext { + /// Check if the cipher is ready to accept new input data. + /// + /// For software-based AES-CTR, this is typically always true unless + /// the cipher is in an error state. + /// + /// # Returns + /// - `Ok(true)`: Cipher is ready for new operations + /// - `Ok(false)`: Cipher is in error state or not ready + /// - `Err(_)`: Error occurred while checking status + fn is_ready(&self) -> Result { + match self.state { + CipherState::Ready | CipherState::HasOutput => Ok(true), + CipherState::Processing => Ok(false), // Busy processing + } + } + + /// Check if processed output data is available for reading. + /// + /// For streaming ciphers like CTR mode, output is typically available + /// immediately after processing completes. + /// + /// # Returns + /// - `Ok(true)`: Output data is available + /// - `Ok(false)`: No output data is currently available + /// - `Err(_)`: Error occurred while checking status + fn has_output(&self) -> Result { + match self.state { + CipherState::HasOutput => Ok(true), + CipherState::Ready | CipherState::Processing => Ok(false), + } + } + + /// Check if the cipher is idle and available for new operations. + /// + /// For software implementations, this is similar to `is_ready()` but + /// provides semantic clarity for different use cases. + /// + /// # Returns + /// - `Ok(true)`: Cipher is idle and available + /// - `Ok(false)`: Cipher is busy with ongoing operations + /// - `Err(_)`: Error occurred while checking status + fn is_idle(&self) -> Result { + match self.state { + CipherState::Ready => Ok(true), + CipherState::Processing => Ok(false), + CipherState::HasOutput => Ok(true), // Can start new operations + } + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] // Allow unwrap in tests for cleaner test code +mod tests { + use super::*; + use openprot_hal_blocking::cipher::BlockAligned; + + // Test vectors for AES-256-CTR mode + const TEST_KEY: [u8; 32] = [ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, + 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, + 0xdf, 0xf4, + ]; + + const TEST_IV: [u8; 16] = [ + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0xff, + ]; + + #[test] + fn test_aes_ctr_context_creation() { + let context = AesCtrContext::new(&TEST_KEY, &TEST_IV); + assert!(context.is_ok(), "Failed to create AES-CTR context"); + + let ctx = context.unwrap(); + assert!( + ctx.is_ready().unwrap(), + "Context should be ready after creation" + ); + assert!( + ctx.is_idle().unwrap(), + "Context should be idle after creation" + ); + assert!( + !ctx.has_output().unwrap(), + "Context should have no output initially" + ); + } + + #[test] + fn test_cipher_init_trait() { + let mut cipher = Aes256CtrCipher; + let result = cipher.init(&TEST_KEY, &TEST_IV, Aes256CtrMode); + assert!( + result.is_ok(), + "Failed to create context via CipherInit trait" + ); + + let context = result.unwrap(); + assert!(context.is_ready().unwrap(), "Context should be ready"); + } + + #[test] + fn test_basic_cipher_operations() { + let mut context = AesCtrContext::new(&TEST_KEY, &TEST_IV).unwrap(); + + // Test encryption - CTR mode uses fixed-size arrays + let plaintext: [u8; 256] = [0x42; 256]; // Test plaintext + let result = context.encrypt(plaintext); + assert!(result.is_ok(), "Encryption should succeed"); + + let ciphertext = result.unwrap(); + assert_ne!( + plaintext, ciphertext, + "Ciphertext should differ from plaintext" + ); + + // Reset context for decryption (CTR uses same operation for encrypt/decrypt) + context.reset(&TEST_KEY, &TEST_IV).unwrap(); + + // Test decryption + let result = context.decrypt(ciphertext); + assert!(result.is_ok(), "Decryption should succeed"); + + let decrypted = result.unwrap(); + assert_eq!(plaintext, decrypted, "Decrypted text should match original"); + } + + #[test] + fn test_block_aligned_container_operations() { + let mut container = BlockAligned::<16, 4>::new(); + assert_eq!(container.block_count(), 0, "New container should be empty"); + + // Add a block + let test_block = [0x42u8; 16]; + let result = container.push_block(test_block); + assert!(result.is_ok(), "Adding block should succeed"); + assert_eq!(container.block_count(), 1, "Should have 1 block"); + + // Check the block content + let stored_block = container.get_block(0).unwrap(); + assert_eq!(stored_block, &test_block, "Stored block should match input"); + + // Test from_slice_padded + let test_data = b"Hello, World!"; // 13 bytes + let padded_container = BlockAligned::<16, 2>::from_slice_padded(test_data, 0x00); + assert!(padded_container.is_ok(), "from_slice_padded should succeed"); + + let container = padded_container.unwrap(); + assert_eq!( + container.block_count(), + 1, + "Should have 1 block for 13 bytes" + ); + + let block = container.get_block(0).unwrap(); + assert_eq!(&block[..13], test_data, "First 13 bytes should match input"); + assert_eq!(&block[13..], &[0x00; 3], "Last 3 bytes should be padding"); + } + + #[test] + fn test_cipher_status_tracking() { + let context = AesCtrContext::new(&TEST_KEY, &TEST_IV).unwrap(); + + // Initial state + assert!(context.is_ready().unwrap(), "Should be ready initially"); + assert!(context.is_idle().unwrap(), "Should be idle initially"); + assert!( + !context.has_output().unwrap(), + "Should have no output initially" + ); + + // CTR mode is always ready since it's a streaming cipher + assert!( + context.is_ready().unwrap(), + "CTR mode should always be ready" + ); + } + + #[test] + fn test_context_reset() { + let mut context = AesCtrContext::new(&TEST_KEY, &TEST_IV).unwrap(); + + // Encrypt some data + let plaintext: [u8; 256] = [0x42; 256]; + let output1 = context.encrypt(plaintext).unwrap(); + + // Reset with same key/IV + let result = context.reset(&TEST_KEY, &TEST_IV); + assert!(result.is_ok(), "Reset should succeed"); + + // Encrypt same data again - should produce same result + let output2 = context.encrypt(plaintext).unwrap(); + assert_eq!(output1, output2, "Reset should restore initial state"); + + // Reset with different IV + let new_iv = [0x00u8; 16]; + context.reset(&TEST_KEY, &new_iv).unwrap(); + let output3 = context.encrypt(plaintext).unwrap(); + assert_ne!( + output1, output3, + "Different IV should produce different output" + ); + } + + #[test] + fn test_error_conditions() { + // Test capacity exceeded for BlockAligned + let large_data = [0x42u8; 100]; + let result = BlockAligned::<16, 2>::from_slice_padded(&large_data, 0x00); + assert!( + result.is_err(), + "Should fail when data requires too many blocks" + ); + + let mut container = BlockAligned::<16, 2>::new(); + container.push_block([0u8; 16]).unwrap(); + container.push_block([0u8; 16]).unwrap(); + + // Try to add one more block + let result = container.push_block([0u8; 16]); + assert!(result.is_err(), "Should fail when capacity exceeded"); + } + + #[test] + fn test_symmetric_cipher_associated_types() { + // Test that our cipher implements the expected types + fn check_cipher_types() { + // This function just checks that the types are correctly associated + } + + check_cipher_types::(); + check_cipher_types::(); + } + + #[test] + fn test_error_type_consistency() { + let context = AesCtrContext::new(&TEST_KEY, &TEST_IV).unwrap(); + + // Test that error types are consistent across traits + let _ready_result: Result = context.is_ready(); + let _idle_result: Result = context.is_idle(); + let _output_result: Result = context.has_output(); + } + + #[test] + fn test_block_aligned_container_functionality() { + // Empty BlockAligned container + let empty_input = BlockAligned::<16, 4>::new(); + assert_eq!( + empty_input.block_count(), + 0, + "Empty container should have 0 blocks" + ); + assert!( + empty_input.is_empty(), + "Empty container should report as empty" + ); + assert_eq!( + empty_input.capacity(), + 4, + "Container should have correct capacity" + ); + } +} diff --git a/platform/impls/rustcrypto/src/lib.rs b/platform/impls/rustcrypto/src/lib.rs new file mode 100644 index 0000000..d6f2285 --- /dev/null +++ b/platform/impls/rustcrypto/src/lib.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache-2.0 license + +//! RustCrypto implementation of OpenPRoT HAL traits +//! +//! This crate provides implementations of OpenPRoT HAL traits using the +//! RustCrypto ecosystem, offering high-quality, audited cryptographic +//! implementations for embedded and general-purpose use. +//! +//! # Features + +#![no_std] + +pub mod cipher; + +// Re-export commonly used ECDSA types + +// Re-export commonly used cipher types +pub use cipher::{Aes256CtrCipher, Aes256GcmCipher, RustCryptoCipherError};