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}