Skip to main content

smb_server_csp/
lib_backend.rs

1//! Production backend: the maintained [RustCrypto] crates.
2//!
3//! Every function mirrors its bundled counterpart byte-for-byte; the
4//! facade-level vectors in `lib.rs` enforce that for whichever backend is
5//! compiled.
6//!
7//! [RustCrypto]: https://github.com/RustCrypto
8
9use hmac::Mac as _;
10use sha2::Digest as _;
11
12/// MD4 digest ([RFC 1320]).
13pub fn md4(data: &[u8]) -> [u8; 16] {
14    let mut h = md4::Md4::new();
15    h.update(data);
16    h.finalize().into()
17}
18
19/// MD5 digest ([RFC 1321]).
20pub fn md5(data: &[u8]) -> [u8; 16] {
21    let mut h = md5::Md5::new();
22    h.update(data);
23    h.finalize().into()
24}
25
26/// HMAC-MD5 of `data` under `key` ([RFC 2104]).
27pub fn hmac_md5(key: &[u8], data: &[u8]) -> [u8; 16] {
28    let mut m = <hmac::Hmac<md5::Md5> as hmac::KeyInit>::new_from_slice(key)
29        .expect("HMAC accepts any key length");
30    m.update(data);
31    m.finalize().into_bytes().into()
32}
33
34/// SHA-256 digest ([FIPS 180-4]).
35pub fn sha256(data: &[u8]) -> [u8; 32] {
36    let mut h = sha2::Sha256::new();
37    h.update(data);
38    h.finalize().into()
39}
40
41/// SHA-512 digest ([FIPS 180-4]).
42pub fn sha512(data: &[u8]) -> [u8; 64] {
43    let mut h = sha2::Sha512::new();
44    h.update(data);
45    h.finalize().into()
46}
47
48/// HMAC-SHA256 of `data` under `key`.
49pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
50    let mut m =
51        <hmac::Hmac<sha2::Sha256> as hmac::KeyInit>::new_from_slice(key).expect("any key length");
52    m.update(data);
53    m.finalize().into_bytes().into()
54}
55
56/// XOR `data` with the RC4 keystream generated from `key`
57/// ([MS-NLMP] §3.2.5.1.2 session-key transport).
58pub fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
59    /// The RustCrypto RC4 cipher is keyed by a compile-time key length;
60    /// dispatch over the sizes that occur in practice (NTLM uses 16).
61    macro_rules! rc4_dispatch {
62        ($($len:expr => $ty:ident),+ $(,)?) => {
63            match key.len() {
64                $(
65                    $len => {{
66                        type C = rc4::Rc4<typenum::$ty>;
67                        let mut c = <C as rc4::KeyInit>::new_from_slice(key)
68                            .expect("length checked by dispatch");
69                        let mut out = data.to_vec();
70                        use rc4::StreamCipher as _;
71                        c.apply_keystream(&mut out);
72                        out
73                    }}
74                )+
75                n => panic!("rc4: unsupported key length {n} (lib backend supports 1..=64)"),
76            }
77        };
78    }
79    rc4_dispatch! {
80        1 => U1, 2 => U2, 3 => U3, 4 => U4, 5 => U5, 6 => U6, 7 => U7, 8 => U8,
81        9 => U9, 10 => U10, 11 => U11, 12 => U12, 13 => U13, 14 => U14, 15 => U15,
82        16 => U16, 17 => U17, 18 => U18, 19 => U19, 20 => U20, 21 => U21, 22 => U22,
83        23 => U23, 24 => U24, 25 => U25, 26 => U26, 27 => U27, 28 => U28, 29 => U29,
84        30 => U30, 31 => U31, 32 => U32,
85    }
86}
87
88/// Expand a 56-bit NTLM key chunk (7 bytes) into an 8-byte DES key with
89/// zero parity bits — one bit inserted after every seven ([MS-NLMP]
90/// §3.3.1 / RFC 2437 style expansion used by LM/NTLMv1 responses).
91#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // DES key parity expansion
92fn key7_to_key8(k7: &[u8; 7]) -> [u8; 8] {
93    let mut bits = [0u8; 56];
94    let mut bit = 0usize;
95    for byte in k7.iter() {
96        for shift in (0..8).rev() {
97            if bit == 56 {
98                break;
99            }
100            bits[bit] = (byte >> shift) & 1;
101            bit += 1;
102        }
103    }
104    let mut out = [0u8; 8];
105    for i in 0..8 {
106        let mut b = 0u8;
107        for j in 0..7 {
108            b |= bits[i * 7 + j] << (7 - j);
109        }
110        out[i] = b; // LSB stays zero (parity position)
111    }
112    out
113}
114
115/// DES-ECB encrypt one block under a 56-bit NTLM key chunk.
116pub fn des_encrypt_key7(key56: [u8; 7], plaintext: [u8; 8]) -> [u8; 8] {
117    let key8 = key7_to_key8(&key56);
118    let cipher = <des::Des as des::cipher::KeyInit>::new_from_slice(&key8)
119        .expect("DES key is exactly 8 bytes");
120    let mut block = des::cipher::Array::from(plaintext);
121    use des::cipher::BlockCipherEncrypt as _;
122    cipher.encrypt_block(&mut block);
123    block.into()
124}
125
126/// NTLMv1/LM response: three DES encryptions of the challenge under
127/// successive 7-byte chunks of the 21-byte expanded hash ([MS-NLMP] §3.3.1).
128#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // NTLMv1 DES response
129pub fn ntlmv1_response(hash21: &[u8; 21], challenge: &[u8; 8]) -> [u8; 24] {
130    let mut out = [0u8; 24];
131    for i in 0..3 {
132        let mut k = [0u8; 7];
133        k.copy_from_slice(&hash21[i * 7..i * 7 + 7]);
134        out[i * 8..i * 8 + 8].copy_from_slice(&des_encrypt_key7(k, *challenge));
135    }
136    out
137}
138
139/// Encrypt one 16-byte AES-128 block.
140pub fn aes128_encrypt_block(key: &[u8; 16], block: &[u8; 16]) -> [u8; 16] {
141    let cipher =
142        <aes::Aes128 as aes::cipher::KeyInit>::new_from_slice(key).expect("AES-128 key is 16 bytes");
143    let mut b = aes::cipher::Array::from(*block);
144    use aes::cipher::BlockCipherEncrypt as _;
145    cipher.encrypt_block(&mut b);
146    b.into()
147}
148
149/// AES-CMAC over `data` under `key` ([RFC 4493]) — SMB 3.x signing.
150pub fn aes128_cmac(key: &[u8; 16], data: &[u8]) -> [u8; 16] {
151    let mut c =
152        <cmac::Cmac<aes::Aes128> as digest::KeyInit>::new_from_slice(key).expect("any key length");
153    use cmac::digest::Mac as _;
154    c.update(data);
155    c.finalize().into_bytes().into()
156}
157
158// ---------------- SMB3 encryption transform AEAD ([MS-SMB2] §3.1.4.2) ----
159
160/// AES-128-GCM seal: appends the 16-byte tag to the ciphertext.
161/// `nonce` = 12 bytes from the transform header's Nonce field;
162/// `aad`   = the 32-byte header tail (MsgSize..SessionId end).
163pub fn aes128gcm_seal(key: &[u8; 16], nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
164    use aes_gcm::aead::AeadInOut;
165    let c = <aes_gcm::Aes128Gcm as aes_gcm::KeyInit>::new_from_slice(key)
166        .expect("AES-GCM key is exactly 16 bytes");
167    let nonce = aes_gcm::Nonce::from(*nonce);
168    let mut buf = plaintext.to_vec();
169    c.encrypt_in_place(&nonce, aad, &mut buf).expect("GCM seal");
170    buf
171}
172
173/// AES-128-GCM open; `ciphertext` carries the trailing 16-byte tag.
174#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // AES-GCM AEAD
175pub fn aes128gcm_open(
176    key: &[u8; 16],
177    nonce: &[u8; 12],
178    aad: &[u8],
179    ciphertext: &[u8],
180) -> Option<Vec<u8>> {
181    use aes_gcm::aead::AeadInOut;
182    if ciphertext.len() < 16 {
183        return None;
184    }
185    let c = <aes_gcm::Aes128Gcm as aes_gcm::KeyInit>::new_from_slice(key).ok()?;
186    let nonce = aes_gcm::Nonce::from(*nonce);
187    let mut buf = ciphertext.to_vec();
188    c.decrypt_in_place(&nonce, aad, &mut buf).ok()?;
189    Some(buf)
190}
191
192/// AES-128-CCM seal with an 11-byte nonce ([MS-SMB2] CCM variant).
193pub fn aes128ccm_seal(key: &[u8; 16], nonce: &[u8; 11], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
194    use ccm::aead::AeadInOut;
195    type AesCcm = ccm::Ccm<aes::Aes128, typenum::U16, typenum::U11>;
196    let c = <AesCcm as ccm::KeyInit>::new_from_slice(key).expect("key is 16 bytes");
197    let nonce = ccm::Nonce::from(*nonce);
198    let mut buf = plaintext.to_vec();
199    c.encrypt_in_place(&nonce, aad, &mut buf).expect("CCM seal");
200    buf
201}
202
203/// AES-128-CCM open; `ciphertext` carries the trailing 16-byte tag.
204#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // AES-CCM AEAD
205pub fn aes128ccm_open(
206    key: &[u8; 16],
207    nonce: &[u8; 11],
208    aad: &[u8],
209    ciphertext: &[u8],
210) -> Option<Vec<u8>> {
211    use ccm::aead::AeadInOut;
212    if ciphertext.len() < 16 {
213        return None;
214    }
215    type AesCcm = ccm::Ccm<aes::Aes128, typenum::U16, typenum::U11>;
216    let c = <AesCcm as ccm::KeyInit>::new_from_slice(key).ok()?;
217    let nonce = ccm::Nonce::from(*nonce);
218    let mut buf = ciphertext.to_vec();
219    c.decrypt_in_place(&nonce, aad, &mut buf).ok()?;
220    Some(buf)
221}
222
223/// AES-256-GCM seal ([MS-SMB2] AES-256-GCM cipher); appends the 16-byte tag.
224pub fn aes256gcm_seal(key: &[u8; 32], nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
225    use aes_gcm::aead::AeadInOut;
226    let c = <aes_gcm::Aes256Gcm as aes_gcm::KeyInit>::new_from_slice(key)
227        .expect("AES-256-GCM key is exactly 32 bytes");
228    let nonce = aes_gcm::Nonce::from(*nonce);
229    let mut buf = plaintext.to_vec();
230    c.encrypt_in_place(&nonce, aad, &mut buf).expect("GCM seal");
231    buf
232}
233
234/// AES-256-GCM open; `ciphertext` carries the trailing 16-byte tag.
235#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // AES-GCM AEAD
236pub fn aes256gcm_open(
237    key: &[u8; 32],
238    nonce: &[u8; 12],
239    aad: &[u8],
240    ciphertext: &[u8],
241) -> Option<Vec<u8>> {
242    use aes_gcm::aead::AeadInOut;
243    if ciphertext.len() < 16 {
244        return None;
245    }
246    let c = <aes_gcm::Aes256Gcm as aes_gcm::KeyInit>::new_from_slice(key).ok()?;
247    let nonce = aes_gcm::Nonce::from(*nonce);
248    let mut buf = ciphertext.to_vec();
249    c.decrypt_in_place(&nonce, aad, &mut buf).ok()?;
250    Some(buf)
251}
252
253/// AES-256-CCM seal with an 11-byte nonce ([MS-SMB2] CCM variant).
254pub fn aes256ccm_seal(key: &[u8; 32], nonce: &[u8; 11], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
255    use ccm::aead::AeadInOut;
256    type AesCcm = ccm::Ccm<aes::Aes256, typenum::U16, typenum::U11>;
257    let c = <AesCcm as ccm::KeyInit>::new_from_slice(key).expect("key is 32 bytes");
258    let nonce = ccm::Nonce::from(*nonce);
259    let mut buf = plaintext.to_vec();
260    c.encrypt_in_place(&nonce, aad, &mut buf).expect("CCM seal");
261    buf
262}
263
264/// AES-256-CCM open; `ciphertext` carries the trailing 16-byte tag.
265#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // AES-CCM AEAD
266pub fn aes256ccm_open(
267    key: &[u8; 32],
268    nonce: &[u8; 11],
269    aad: &[u8],
270    ciphertext: &[u8],
271) -> Option<Vec<u8>> {
272    use ccm::aead::AeadInOut;
273    if ciphertext.len() < 16 {
274        return None;
275    }
276    type AesCcm = ccm::Ccm<aes::Aes256, typenum::U16, typenum::U11>;
277    let c = <AesCcm as ccm::KeyInit>::new_from_slice(key).ok()?;
278    let nonce = ccm::Nonce::from(*nonce);
279    let mut buf = ciphertext.to_vec();
280    c.decrypt_in_place(&nonce, aad, &mut buf).ok()?;
281    Some(buf)
282}
283
284/// NTLMSSP MIC for the SPNEGO mechListMIC ([RFC 4178] / [MS-NLMP] §3.4.6),
285/// matching samba's calc_ntlmv2_key + ntlmssp_make_packet_signature:
286///
287///   SignKey = MD5(SessionKey || SIGN_MAGIC_NUL)
288///   digest  = HMAC-MD5(SignKey, SeqNum_le || message)[0..8]
289///   Checksum= RC4(SendSealKey, digest)   // only when KEY_EXCH negotiated
290///   MAC     = 01000000 || Checksum || SeqNum_le
291#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] // NTLM MIC over SPNEGO mechListMIC
292pub fn ntlm_mech_list_mic(
293    session_key: &[u8; 16],
294    server_role: bool,
295    key_exch: bool,
296    seq: u32,
297    message: &[u8],
298) -> [u8; 16] {
299    const SRV_SIGN: &[u8] = b"session key to server-to-client signing key magic constant";
300    const CLI_SIGN: &[u8] = b"session key to client-to-server signing key magic constant";
301    const SRV_SEAL: &[u8] = b"session key to server-to-client sealing key magic constant";
302    const CLI_SEAL: &[u8] = b"session key to client-to-server sealing key magic constant";
303
304    let (sign_const, seal_const) =
305        if server_role { (SRV_SIGN, SRV_SEAL) } else { (CLI_SIGN, CLI_SEAL) };
306
307    let mut sk_in = session_key.to_vec();
308    sk_in.extend_from_slice(sign_const);
309    sk_in.push(0);
310    let send_sign_key = md5(&sk_in);
311
312    let mut input = Vec::with_capacity(4 + message.len());
313    input.extend_from_slice(&seq.to_le_bytes());
314    input.extend_from_slice(message);
315    let mut digest = hmac_md5(&send_sign_key, &input);
316
317    if key_exch {
318        let mut seal_in = session_key.to_vec();
319        seal_in.extend_from_slice(seal_const);
320        seal_in.push(0);
321        let send_seal_key = md5(&seal_in);
322        let first8: Vec<u8> = digest[..8].to_vec();
323        let enc = rc4(&send_seal_key, &first8);
324        digest[..8].copy_from_slice(&enc);
325    }
326
327    let mut out = [0u8; 16];
328    out[0..4].copy_from_slice(&[0x01, 0x00, 0x00, 0x00]);
329    out[4..12].copy_from_slice(&digest[..8]);
330    out[12..16].copy_from_slice(&seq.to_le_bytes());
331    out
332}