Skip to main content

smb_server_csp/
lib.rs

1//! Crypto service provider for smb-server-rs.
2//!
3//! One stable API surface, two interchangeable backends selected at compile
4//! time:
5//!
6//! * **`lib` (default)** — maintained [RustCrypto] crates. Hardware
7//!   acceleration (AES-NI et al.) where available; the production choice.
8//! * **`handrolled`** — the bundled from-scratch implementations. Zero
9//!   external dependencies, useful for auditing or constrained builds.
10//!
11//! ```text
12//! cargo build -p smb-server-csp                 # RustCrypto backend
13//! cargo build -p smb-server-csp --no-default-features --features handrolled
14//! ```
15//!
16//! Both backends must produce byte-identical output; the test vectors in
17//! this file are asserted for whichever backend is compiled.
18//!
19//! [RustCrypto]: https://github.com/RustCrypto
20
21#![forbid(unsafe_code)]
22#![deny(missing_docs)]
23#![warn(missing_debug_implementations)]
24
25#[cfg(all(feature = "lib", feature = "handrolled"))]
26compile_error!("select exactly one CSP backend: 'lib' (default) or 'handrolled'");
27
28#[cfg(not(any(feature = "lib", feature = "handrolled")))]
29compile_error!("select exactly one CSP backend: 'lib' (default) or 'handrolled'");
30
31#[cfg(feature = "lib")]
32mod lib_backend;
33
34#[cfg(feature = "lib")]
35pub use lib_backend::*;
36
37#[cfg(feature = "lib")]
38pub(crate) mod csp_call {
39    pub fn md4(data: &[u8]) -> [u8; 16] { crate::lib_backend::md4(data) }
40    pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] { crate::lib_backend::hmac_sha256(key, data) }
41}
42
43#[cfg(feature = "handrolled")]
44mod bundled {
45    pub mod aes128;
46    pub mod des;
47    pub mod hmac;
48    pub mod hmac_sha256;
49    pub mod md4;
50    pub mod md5;
51    pub mod rc4;
52    pub mod sha256;
53    pub mod sha512;
54}
55
56#[cfg(feature = "handrolled")]
57mod backend {
58    pub use super::bundled::aes128::{aes128_cmac, aes128_encrypt_block};
59    pub use super::bundled::des::{des_encrypt_key7, ntlmv1_response};
60    pub use super::bundled::hmac::hmac_md5;
61    pub use super::bundled::hmac_sha256::hmac_sha256;
62    pub use super::bundled::md4::md4;
63    pub use super::bundled::md5::md5;
64    pub use super::bundled::rc4::rc4;
65    pub use super::bundled::sha256::sha256;
66    pub use super::bundled::sha512::sha512;
67}
68
69#[cfg(feature = "handrolled")]
70pub use backend::*;
71
72#[cfg(feature = "handrolled")]
73pub(crate) mod csp_call {
74    pub fn md4(data: &[u8]) -> [u8; 16] { super::backend::md4(data) }
75    pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] { super::backend::hmac_sha256(key, data) }
76}
77
78
79/// NT hash = MD4 of the UTF-16LE encoded password ([MS-NLMP] §3.3.1.1).
80#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // MD4 of UTF-16LE password
81pub fn nt_hash(password: &str) -> [u8; 16] {
82    let mut bytes = Vec::with_capacity(password.len() * 2);
83    for u in password.encode_utf16() {
84        bytes.extend_from_slice(&u.to_le_bytes());
85    }
86    crate::csp_call::md4(&bytes)
87}
88
89/// SP800-108 counter-mode KDF with HMAC-SHA256 PRF ([MS-SMB2] §3.1.4.1):
90///   K(i) = HMAC-SHA256(key, [i]_32BE || label || 0x00 || context || [L]_32BE)
91///
92/// This is protocol plumbing on top of the backend primitive rather than a
93/// primitive itself, so it is shared by both backends unchanged.
94#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // SP800-108 counter-mode KDF
95pub fn kdf_counter_mode_hmac_sha256(
96    key: &[u8],
97    label: &[u8],
98    context: &[u8],
99    result_len: usize,
100) -> Vec<u8> {
101    let bitlen = (result_len as u32) * 8;
102    let mut out = Vec::with_capacity(result_len);
103    let mut counter: u32 = 1;
104    while out.len() < result_len {
105        let mut input = Vec::with_capacity(4 + label.len() + 1 + context.len() + 4);
106        input.extend_from_slice(&counter.to_be_bytes());
107        input.extend_from_slice(label);
108        input.push(0);
109        input.extend_from_slice(context);
110        input.extend_from_slice(&bitlen.to_be_bytes());
111        let tag = crate::csp_call::hmac_sha256(key, &input);
112        out.extend_from_slice(&tag);
113        counter += 1;
114    }
115    out.truncate(result_len);
116    out
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    fn hex(b: &[u8]) -> String {
124        b.iter().map(|x| format!("{:02x}", x)).collect()
125    }
126
127    // Golden values captured from the bundled implementations and, where
128    // applicable, cross-checked against FIPS / RFC / well-known references.
129    // BOTH backends must satisfy every assertion below.
130
131    #[test]
132    fn nt_hash_vector() {
133        assert_eq!(hex(&nt_hash("password")), "8846f7eaee8fb117ad06bdd830b7586c");
134    }
135
136    #[test]
137    fn md4_vector() {
138        assert_eq!(hex(&md4(b"abc")), "a448017aaf21d8525fc10ae87aa6729d");
139        assert_eq!(
140            hex(&md4(b"")),
141            "31d6cfe0d16ae931b73c59d7e0c089c0"
142        );
143    }
144
145    #[test]
146    fn md5_vector() {
147        assert_eq!(hex(&md5(b"abc")), "900150983cd24fb0d6963f7d28e17f72");
148    }
149
150    #[test]
151    fn hmac_md5_rfc2202() {
152        assert_eq!(
153            hex(&hmac_md5(b"Jefe", b"what do ya want for nothing?")),
154            "750c783e6ab0b503eaa86e310a5db738"
155        );
156    }
157
158    #[test]
159    fn sha2_vectors() {
160        assert_eq!(
161            hex(&sha256(b"abc")),
162            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
163        );
164        assert_eq!(
165            hex(&sha512(b"abc")),
166            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
167             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
168        );
169    }
170
171    #[test]
172    fn hmac_sha256_rfc4231() {
173        assert_eq!(
174            hex(&hmac_sha256(b"Jefe", b"what do ya want for nothing?")),
175            "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
176        );
177    }
178
179    #[test]
180    fn rc4_vector() {
181        assert_eq!(
182            hex(&rc4(&[1u8, 2, 3, 4, 5], &[0u8; 16])),
183            "b2396305f03dc027ccc3524a0a1118a8"
184        );
185    }
186
187    #[test]
188    fn ntlmv1_golden() {
189        // Verified against OpenSSL DES-ECB with the standard 7->8 bit
190        // parity-expanded key (the original bundled schedule was wrong).
191        let h21 = [7u8; 21];
192        let challenge = [3u8; 8];
193        assert_eq!(
194            hex(&ntlmv1_response(&h21, &challenge)),
195            "23f68ad3636db29e23f68ad3636db29e23f68ad3636db29e" // verified vs impacket get_ntlmv1_response
196        );
197    }
198
199    #[test]
200    fn aes_block_fips197() {
201        let key: [u8; 16] = [
202            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
203            0x0e, 0x0f,
204        ];
205        let pt: [u8; 16] = [
206            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
207            0xee, 0xff,
208        ];
209        assert_eq!(
210            hex(&aes128_encrypt_block(&key, &pt)),
211            "69c4e0d86a7b0430d8cdb78070b4c55a"
212        );
213    }
214
215    #[test]
216    fn cmac_vectors() {
217        let key: [u8; 16] = [
218            0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
219            0x42, 0x42,
220        ];
221        // Goldens captured from the original bundled implementation.
222        assert_eq!(hex(&aes128_cmac(&key, &[])), "565aa8245bc3cf6d60f4f3e9d52921dd");
223        let msg16: [u8; 16] = [
224            0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93,
225            0x17, 0x2a,
226        ];
227        assert_eq!(
228            hex(&aes128_cmac(&key, &msg16)),
229            "de1d864d75b61033c28628c18f6048f8"
230        );
231        // RFC 4493 canonical key cross-check (validated against an external
232        // CMAC implementation during development).
233        let rfc_key: [u8; 16] = [
234            0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
235            0x4f, 0x3c,
236        ];
237        assert_eq!(
238            hex(&aes128_cmac(&rfc_key, &msg16)),
239            "070a16b46b4d4144f79bdd9dd04a287c"
240        );
241    }
242
243    #[test]
244    fn kdf_cross_checked() {
245        // Value cross-checked against an independent Python reference
246        // (hmac/hashlib) implementing SP800-108 counter mode.
247        let key: [u8; 16] = [
248            0x0d, 0xd2, 0xe3, 0xd0, 0xf7, 0x5e, 0xfb, 0x08, 0xc6, 0xfc, 0x14, 0xd6, 0x15, 0x9f,
249            0x3c, 0xc4,
250        ];
251        assert_eq!(
252            hex(&kdf_counter_mode_hmac_sha256(&key, b"label", b"context", 16)),
253            "2b59530a759817720bf9ef519983621c"
254        );
255    }
256
257    #[cfg(feature = "lib")]
258    #[test]
259    fn aes256_seal_open_roundtrip() {
260        let key = [0x11u8; 32];
261        let aad = b"transform-header-aad";
262        let pt = b"AES-256 sealed SMB payload";
263
264        let g = aes256gcm_seal(&key, &[0x22u8; 12], aad, pt);
265        assert_eq!(
266            aes256gcm_open(&key, &[0x22u8; 12], aad, &g).as_deref(),
267            Some(&pt[..])
268        );
269        // A wrong key must not open the frame.
270        assert!(aes256gcm_open(&[0u8; 32], &[0x22u8; 12], aad, &g).is_none());
271
272        let c = aes256ccm_seal(&key, &[0x33u8; 11], aad, pt);
273        assert_eq!(
274            aes256ccm_open(&key, &[0x33u8; 11], aad, &c).as_deref(),
275            Some(&pt[..])
276        );
277        // Tampered AAD must fail authentication.
278        assert!(aes256ccm_open(&key, &[0x33u8; 11], b"other-aad", &c).is_none());
279    }
280}