Skip to main content

async_snmp/v3/crypto/
mod.rs

1//! Pluggable cryptographic provider for SNMPv3 security operations.
2//!
3//! This module defines the [`CryptoProvider`] trait that captures the primitive
4//! cryptographic operations needed by the USM layer, and provides two built-in
5//! implementations:
6//!
7//! - [`RustCryptoProvider`] (feature `crypto-rustcrypto`, **default**) - backed by
8//!   the RustCrypto crate ecosystem. Supports all auth and privacy protocols.
9//! - [`AwsLcFipsProvider`] (feature `crypto-fips`) - backed by aws-lc-rs for
10//!   FIPS 140-3 compliance. Rejects MD5, DES, and 3DES as non-FIPS algorithms.
11//!
12//! The active provider is selected at compile time via mutually exclusive feature
13//! flags. Only one provider can be active per build. See the crate-level
14//! documentation for usage.
15
16use super::{AuthProtocol, PrivProtocol};
17
18/// Error type for cryptographic provider operations.
19///
20/// This covers failures that originate from the crypto backend itself:
21/// unsupported algorithms, invalid key material, and cipher-level errors.
22/// Protocol-level framing errors (e.g., wrong privParameters length) live
23/// in [`PrivacyError`](super::privacy::PrivacyError).
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum CryptoError {
26    /// The crypto backend does not support the requested algorithm.
27    ///
28    /// For example, the FIPS provider does not support MD5 or DES.
29    UnsupportedAlgorithm(&'static str),
30    /// The key length is invalid for the requested operation.
31    InvalidKeyLength,
32    /// The cipher operation failed internally.
33    CipherError,
34    /// The OS random source is unavailable.
35    RandomSource,
36}
37
38impl std::fmt::Display for CryptoError {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            Self::UnsupportedAlgorithm(name) => {
42                write!(f, "unsupported algorithm: {}", name)
43            }
44            Self::InvalidKeyLength => write!(f, "invalid key length"),
45            Self::CipherError => write!(f, "cipher operation failed"),
46            Self::RandomSource => write!(f, "OS random source unavailable"),
47        }
48    }
49}
50
51impl std::error::Error for CryptoError {}
52
53/// Result type for cryptographic provider operations.
54pub type CryptoResult<T> = Result<T, CryptoError>;
55
56#[cfg(all(feature = "crypto-rustcrypto", feature = "crypto-fips"))]
57compile_error!(
58    "Features \"crypto-rustcrypto\" and \"crypto-fips\" are mutually exclusive. If you used --all-features, specify features explicitly instead."
59);
60
61#[cfg(not(any(feature = "crypto-rustcrypto", feature = "crypto-fips")))]
62compile_error!(
63    "A crypto backend is required. Enable either \"crypto-rustcrypto\" (default) or \"crypto-fips\"."
64);
65
66#[cfg(feature = "crypto-rustcrypto")]
67mod rustcrypto;
68#[cfg(feature = "crypto-rustcrypto")]
69pub use rustcrypto::RustCryptoProvider;
70
71#[cfg(feature = "crypto-fips")]
72mod fips;
73#[cfg(feature = "crypto-fips")]
74pub use fips::AwsLcFipsProvider;
75
76/// Trait defining the cryptographic primitives needed by the SNMPv3 USM layer.
77///
78/// This trait captures the six core operations that vary between crypto backends:
79/// hashing, password-to-key derivation, key localization, HMAC computation, and
80/// symmetric encryption/decryption.
81///
82/// # Implementors
83///
84/// Methods take `&self` to allow stateful providers (HSM handles, FFI contexts).
85/// The default [`RustCryptoProvider`] is a stateless unit struct.
86///
87/// # Thread Safety
88///
89/// Implementations must be `Send + Sync + 'static` to support use across
90/// async tasks and threads.
91///
92/// # Security Requirements
93///
94/// Implementations must uphold the following properties for correct and secure
95/// SNMPv3 operation:
96///
97/// - **Constant-time HMAC comparison.** The caller (`verify_message`) performs
98///   the constant-time comparison, but `compute_hmac` must not short-circuit
99///   or leak timing information about intermediate state.
100/// - **IV/salt uniqueness.** Encryption callers supply the IV, but if your
101///   provider wraps a stateful cipher context that generates IVs internally,
102///   it must guarantee uniqueness across calls (RFC 3826 Section 3.1.4.1).
103/// - **Key material hygiene.** Intermediate key material (e.g., expanded
104///   password hashes) should be zeroized after use. Implementations backed
105///   by HSMs or FIPS modules should keep keys in hardware where possible.
106/// - **Algorithm support errors.** Return [`CryptoError::UnsupportedAlgorithm`]
107///   rather than panicking when asked to perform an operation the backend
108///   does not support.
109pub trait CryptoProvider: Send + Sync + 'static {
110    /// Derive a master key from a password using the RFC 3414 Section A.2.1 algorithm.
111    ///
112    /// Expands the password to 1MB by repetition and hashes it with the protocol's
113    /// hash function. Returns the raw digest bytes.
114    ///
115    /// Empty passwords should return an all-zero key of the protocol's digest length.
116    ///
117    /// Returns [`CryptoError::UnsupportedAlgorithm`] if the backend does not
118    /// support the requested authentication protocol.
119    fn password_to_key(&self, protocol: AuthProtocol, password: &[u8]) -> CryptoResult<Vec<u8>>;
120
121    /// Localize a master key to a specific engine ID (RFC 3414 Section A.2.2).
122    ///
123    /// Computes: `H(master_key || engine_id || master_key)`
124    ///
125    /// Returns [`CryptoError::UnsupportedAlgorithm`] if the backend does not
126    /// support the requested authentication protocol.
127    fn localize_key(
128        &self,
129        protocol: AuthProtocol,
130        master_key: &[u8],
131        engine_id: &[u8],
132    ) -> CryptoResult<Vec<u8>>;
133
134    /// Compute HMAC over one or more data slices, truncated to `truncate_len` bytes.
135    ///
136    /// The multi-slice interface avoids allocations when computing HMACs over
137    /// non-contiguous data (e.g., message verification with zeroed auth params).
138    ///
139    /// Returns [`CryptoError::UnsupportedAlgorithm`] if the backend does not
140    /// support the requested authentication protocol.
141    fn compute_hmac(
142        &self,
143        protocol: AuthProtocol,
144        key: &[u8],
145        slices: &[&[u8]],
146        truncate_len: usize,
147    ) -> CryptoResult<Vec<u8>>;
148
149    /// Encrypt data in place using the specified privacy protocol.
150    ///
151    /// The caller is responsible for key extraction, IV construction, and
152    /// padding (for block ciphers). This method performs only the raw cipher
153    /// operation.
154    fn encrypt(
155        &self,
156        protocol: PrivProtocol,
157        key: &[u8],
158        iv: &[u8],
159        data: &mut [u8],
160    ) -> CryptoResult<()>;
161
162    /// Compute a bare hash digest using the protocol's hash function.
163    ///
164    /// Returns [`CryptoError::UnsupportedAlgorithm`] if the backend does not
165    /// support the requested authentication protocol.
166    fn hash(&self, protocol: AuthProtocol, data: &[u8]) -> CryptoResult<Vec<u8>>;
167
168    /// Decrypt data in place using the specified privacy protocol.
169    ///
170    /// The caller is responsible for key extraction and IV reconstruction.
171    /// This method performs only the raw cipher operation.
172    fn decrypt(
173        &self,
174        protocol: PrivProtocol,
175        key: &[u8],
176        iv: &[u8],
177        data: &mut [u8],
178    ) -> CryptoResult<()>;
179}
180
181/// Returns the active crypto provider.
182///
183/// The provider is selected at compile time. The default uses [`RustCryptoProvider`].
184/// Alternative backends can be feature-gated here.
185#[cfg(feature = "crypto-rustcrypto")]
186pub(crate) fn provider() -> &'static RustCryptoProvider {
187    &RustCryptoProvider
188}
189
190#[cfg(feature = "crypto-fips")]
191pub(crate) fn provider() -> &'static AwsLcFipsProvider {
192    &AwsLcFipsProvider
193}