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