Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/rust/src/krb/detect.rs
Line
Count
Source
1
/* Copyright (C) 2018 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
// written by Pierre Chifflier  <chifflier@wzdftpd.net>
19
20
use crate::krb::krb5::{test_weak_encryption, KRB5Transaction};
21
use suricata_sys::sys::DetectEngineThreadCtx;
22
23
use kerberos_parser::krb5::EncryptionType;
24
25
use nom7::branch::alt;
26
use nom7::bytes::complete::{is_a, tag, take_while, take_while1};
27
use nom7::character::complete::char;
28
use nom7::combinator::{all_consuming, map_res, opt};
29
use nom7::multi::many1;
30
use nom7::IResult;
31
32
use std::ffi::CStr;
33
use std::os::raw::c_void;
34
35
#[no_mangle]
36
0
pub unsafe extern "C" fn SCKrb5TxGetMsgType(tx: &KRB5Transaction, ptr: *mut u32) {
37
0
    *ptr = tx.msg_type.0;
38
0
}
39
40
/// Get error code, if present in transaction
41
/// Return 0 if error code was filled, else 1
42
#[no_mangle]
43
0
pub unsafe extern "C" fn SCKrb5TxGetErrorCode(tx: &KRB5Transaction, ptr: *mut i32) -> u32 {
44
0
    match tx.error_code {
45
0
        Some(ref e) => {
46
0
            *ptr = e.0;
47
0
            0
48
        }
49
0
        None => 1,
50
    }
51
0
}
52
53
#[no_mangle]
54
0
pub unsafe extern "C" fn SCKrb5TxGetCname(
55
0
    _de: *mut DetectEngineThreadCtx, tx: *const c_void, _flags: u8, i: u32, buffer: *mut *const u8,
56
0
    buffer_len: *mut u32,
57
0
) -> bool {
58
0
    let tx = cast_pointer!(tx, KRB5Transaction);
59
0
    if let Some(ref s) = tx.cname {
60
0
        if (i as usize) < s.name_string.len() {
61
0
            let value = &s.name_string[i as usize];
62
0
            *buffer = value.as_ptr();
63
0
            *buffer_len = value.len() as u32;
64
0
            return true;
65
0
        }
66
0
    }
67
0
    false
68
0
}
69
70
#[no_mangle]
71
0
pub unsafe extern "C" fn SCKrb5TxGetSname(
72
0
    _de: *mut DetectEngineThreadCtx, tx: *const c_void, _flags: u8, i: u32, buffer: *mut *const u8,
73
0
    buffer_len: *mut u32,
74
0
) -> bool {
75
0
    let tx = cast_pointer!(tx, KRB5Transaction);
76
0
    if let Some(ref s) = tx.sname {
77
0
        if (i as usize) < s.name_string.len() {
78
0
            let value = &s.name_string[i as usize];
79
0
            *buffer = value.as_ptr();
80
0
            *buffer_len = value.len() as u32;
81
0
            return true;
82
0
        }
83
0
    }
84
0
    false
85
0
}
86
87
const KRB_TICKET_FASTARRAY_SIZE: usize = 256;
88
89
#[derive(Debug)]
90
pub struct DetectKrb5TicketEncryptionList {
91
    positive: [bool; KRB_TICKET_FASTARRAY_SIZE],
92
    negative: [bool; KRB_TICKET_FASTARRAY_SIZE],
93
    other: Vec<EncryptionType>,
94
}
95
96
impl Default for DetectKrb5TicketEncryptionList {
97
0
    fn default() -> Self {
98
0
        Self::new()
99
0
    }
100
}
101
102
impl DetectKrb5TicketEncryptionList {
103
850
    pub fn new() -> Self {
104
850
        Self {
105
850
            positive: [false; KRB_TICKET_FASTARRAY_SIZE],
106
850
            negative: [false; KRB_TICKET_FASTARRAY_SIZE],
107
850
            other: Vec::new(),
108
850
        }
109
850
    }
110
}
111
112
// Suppress large enum variant lint as the LIST is very large compared
113
// to the boolean variant.
114
#[derive(Debug)]
115
#[allow(clippy::large_enum_variant)]
116
pub enum DetectKrb5TicketEncryptionData {
117
    WEAK(bool),
118
    LIST(DetectKrb5TicketEncryptionList),
119
}
120
121
872
pub fn detect_parse_encryption_weak(i: &str) -> IResult<&str, DetectKrb5TicketEncryptionData> {
122
872
    let (i, neg) = opt(char('!'))(i)?;
123
872
    let (i, _) = tag("weak")(i)?;
124
22
    let value = neg.is_none();
125
22
    return Ok((i, DetectKrb5TicketEncryptionData::WEAK(value)));
126
872
}
127
128
trait MyFromStr {
129
    fn from_str(s: &str) -> Result<Self, String>
130
    where
131
        Self: Sized;
132
}
133
134
impl MyFromStr for EncryptionType {
135
13.1k
    fn from_str(s: &str) -> Result<Self, String> {
136
13.1k
        let su_slice: &str = s;
137
13.1k
        match su_slice {
138
13.1k
            "des-cbc-crc" => Ok(EncryptionType::DES_CBC_CRC),
139
13.1k
            "des-cbc-md4" => Ok(EncryptionType::DES_CBC_MD4),
140
13.1k
            "des-cbc-md5" => Ok(EncryptionType::DES_CBC_MD5),
141
13.1k
            "des3-cbc-md5" => Ok(EncryptionType::DES3_CBC_MD5),
142
13.1k
            "des3-cbc-sha1" => Ok(EncryptionType::DES3_CBC_SHA1),
143
13.1k
            "dsaWithSHA1-CmsOID" => Ok(EncryptionType::DSAWITHSHA1_CMSOID),
144
13.1k
            "md5WithRSAEncryption-CmsOID" => Ok(EncryptionType::MD5WITHRSAENCRYPTION_CMSOID),
145
13.1k
            "sha1WithRSAEncryption-CmsOID" => Ok(EncryptionType::SHA1WITHRSAENCRYPTION_CMSOID),
146
13.1k
            "rc2CBC-EnvOID" => Ok(EncryptionType::RC2CBC_ENVOID),
147
13.1k
            "rsaEncryption-EnvOID" => Ok(EncryptionType::RSAENCRYPTION_ENVOID),
148
13.1k
            "rsaES-OAEP-ENV-OID" => Ok(EncryptionType::RSAES_OAEP_ENV_OID),
149
13.1k
            "des-ede3-cbc-Env-OID" => Ok(EncryptionType::DES_EDE3_CBC_ENV_OID),
150
13.1k
            "des3-cbc-sha1-kd" => Ok(EncryptionType::DES3_CBC_SHA1_KD),
151
13.1k
            "aes128-cts-hmac-sha1-96" => Ok(EncryptionType::AES128_CTS_HMAC_SHA1_96),
152
13.1k
            "aes256-cts-hmac-sha1-96" => Ok(EncryptionType::AES256_CTS_HMAC_SHA1_96),
153
13.1k
            "aes128-cts-hmac-sha256-128" => Ok(EncryptionType::AES128_CTS_HMAC_SHA256_128),
154
13.1k
            "aes256-cts-hmac-sha384-192" => Ok(EncryptionType::AES256_CTS_HMAC_SHA384_192),
155
13.1k
            "rc4-hmac" => Ok(EncryptionType::RC4_HMAC),
156
13.1k
            "rc4-hmac-exp" => Ok(EncryptionType::RC4_HMAC_EXP),
157
13.1k
            "camellia128-cts-cmac" => Ok(EncryptionType::CAMELLIA128_CTS_CMAC),
158
13.1k
            "camellia256-cts-cmac" => Ok(EncryptionType::CAMELLIA256_CTS_CMAC),
159
13.1k
            "subkey-keymaterial" => Ok(EncryptionType::SUBKEY_KEYMATERIAL),
160
13.1k
            "rc4-md4" => Ok(EncryptionType::RC4_MD4),
161
13.0k
            "rc4-plain2" => Ok(EncryptionType::RC4_PLAIN2),
162
13.0k
            "rc4-lm" => Ok(EncryptionType::RC4_LM),
163
12.8k
            "rc4-sha" => Ok(EncryptionType::RC4_SHA),
164
12.8k
            "des-plain" => Ok(EncryptionType::DES_PLAIN),
165
12.8k
            "rc4-hmac-OLD" => Ok(EncryptionType::RC4_HMAC_OLD),
166
12.8k
            "rc4-plain-OLD" => Ok(EncryptionType::RC4_PLAIN_OLD),
167
12.8k
            "rc4-hmac-OLD-exp" => Ok(EncryptionType::RC4_HMAC_OLD_EXP),
168
12.8k
            "rc4-plain-OLD-exp" => Ok(EncryptionType::RC4_PLAIN_OLD_EXP),
169
12.8k
            "rc4-plain" => Ok(EncryptionType::RC4_PLAIN),
170
12.8k
            "rc4-plain-exp" => Ok(EncryptionType::RC4_PLAIN_EXP),
171
            _ => {
172
12.8k
                if let Ok(num) = s.parse::<i32>() {
173
12.1k
                    return Ok(EncryptionType(num));
174
                } else {
175
624
                    return Err(format!("'{}' is not a valid value for EncryptionType", s));
176
                }
177
            }
178
        }
179
13.1k
    }
180
}
181
182
98.4k
pub fn is_alphanumeric_or_dash(chr: char) -> bool {
183
98.4k
    return chr.is_alphanumeric() || chr == '-';
184
98.4k
}
185
186
13.4k
pub fn detect_parse_encryption_item(i: &str) -> IResult<&str, EncryptionType> {
187
13.4k
    let (i, _) = opt(is_a(" "))(i)?;
188
13.4k
    let (i, e) = map_res(take_while1(is_alphanumeric_or_dash), |s: &str| {
189
13.1k
        EncryptionType::from_str(s)
190
13.4k
    })(i)?;
191
12.5k
    let (i, _) = opt(is_a(" "))(i)?;
192
12.5k
    let (i, _) = opt(char(','))(i)?;
193
12.5k
    return Ok((i, e));
194
13.4k
}
195
196
850
pub fn detect_parse_encryption_list(i: &str) -> IResult<&str, DetectKrb5TicketEncryptionData> {
197
850
    let mut l = DetectKrb5TicketEncryptionList::new();
198
850
    let (i, v) = many1(detect_parse_encryption_item)(i)?;
199
12.5k
    for &val in v.iter() {
200
12.5k
        let vali = val.0;
201
        // KRB_TICKET_FASTARRAY_SIZE is a constant typed usize but which fits in a i32
202
12.5k
        if vali < 0 && vali > -(KRB_TICKET_FASTARRAY_SIZE as i32) {
203
1.25k
            l.negative[(-vali) as usize] = true;
204
11.2k
        } else if vali >= 0 && (vali as usize) < KRB_TICKET_FASTARRAY_SIZE {
205
1.49k
            l.positive[vali as usize] = true;
206
9.80k
        } else {
207
9.80k
            l.other.push(val);
208
9.80k
        }
209
    }
210
341
    return Ok((i, DetectKrb5TicketEncryptionData::LIST(l)));
211
850
}
212
213
872
pub fn detect_parse_encryption(i: &str) -> IResult<&str, DetectKrb5TicketEncryptionData> {
214
872
    let (i, _) = opt(is_a(" "))(i)?;
215
872
    let (i, parsed) = alt((detect_parse_encryption_weak, detect_parse_encryption_list))(i)?;
216
517
    let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?;
217
216
    return Ok((i, parsed));
218
872
}
219
220
#[no_mangle]
221
872
pub unsafe extern "C" fn SCKrb5DetectEncryptionParse(
222
872
    ustr: *const std::os::raw::c_char,
223
872
) -> *mut DetectKrb5TicketEncryptionData {
224
872
    let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
225
872
    if let Ok(s) = ft_name.to_str() {
226
872
        if let Ok((_, ctx)) = detect_parse_encryption(s) {
227
216
            let boxed = Box::new(ctx);
228
216
            return Box::into_raw(boxed) as *mut _;
229
656
        }
230
0
    }
231
656
    return std::ptr::null_mut();
232
872
}
233
234
#[no_mangle]
235
0
pub unsafe extern "C" fn SCKrb5DetectEncryptionMatch(
236
0
    tx: &KRB5Transaction, ctx: &DetectKrb5TicketEncryptionData,
237
0
) -> std::os::raw::c_int {
238
0
    if let Some(x) = tx.ticket_etype {
239
0
        match ctx {
240
0
            DetectKrb5TicketEncryptionData::WEAK(w) => {
241
0
                if (test_weak_encryption(x) && *w) || (!test_weak_encryption(x) && !*w) {
242
0
                    return 1;
243
0
                }
244
            }
245
0
            DetectKrb5TicketEncryptionData::LIST(l) => {
246
0
                let vali = x.0;
247
0
                if vali < 0 && ((-vali) as usize) < KRB_TICKET_FASTARRAY_SIZE {
248
0
                    if l.negative[(-vali) as usize] {
249
0
                        return 1;
250
0
                    }
251
0
                } else if vali >= 0 && (vali as usize) < KRB_TICKET_FASTARRAY_SIZE {
252
0
                    if l.positive[vali as usize] {
253
0
                        return 1;
254
0
                    }
255
                } else {
256
0
                    for &val in l.other.iter() {
257
0
                        if x == val {
258
0
                            return 1;
259
0
                        }
260
                    }
261
                }
262
            }
263
        }
264
0
    }
265
0
    return 0;
266
0
}
267
268
#[no_mangle]
269
216
pub unsafe extern "C" fn SCKrb5DetectEncryptionFree(ctx: &mut DetectKrb5TicketEncryptionData) {
270
    // Just unbox...
271
216
    std::mem::drop(Box::from_raw(ctx));
272
216
}
273
274
#[cfg(test)]
275
mod tests {
276
277
    use super::*;
278
279
    #[test]
280
    fn test_detect_parse_encryption() {
281
        match detect_parse_encryption(" weak  ") {
282
            Ok((rem, ctx)) => {
283
                match ctx {
284
                    DetectKrb5TicketEncryptionData::WEAK(w) => {
285
                        assert!(w);
286
                    }
287
                    _ => {
288
                        panic!("Result should have been weak.");
289
                    }
290
                }
291
                // And we should have no bytes left.
292
                assert_eq!(rem.len(), 0);
293
            }
294
            _ => {
295
                panic!("Result should have been ok.");
296
            }
297
        }
298
        match detect_parse_encryption("!weak") {
299
            Ok((rem, ctx)) => {
300
                match ctx {
301
                    DetectKrb5TicketEncryptionData::WEAK(w) => {
302
                        assert!(!w);
303
                    }
304
                    _ => {
305
                        panic!("Result should have been weak.");
306
                    }
307
                }
308
                // And we should have no bytes left.
309
                assert_eq!(rem.len(), 0);
310
            }
311
            _ => {
312
                panic!("Result should have been ok.");
313
            }
314
        }
315
        match detect_parse_encryption(" des-cbc-crc , -128,2 257") {
316
            Ok((rem, ctx)) => {
317
                match ctx {
318
                    DetectKrb5TicketEncryptionData::LIST(l) => {
319
                        assert!(l.positive[EncryptionType::DES_CBC_CRC.0 as usize]);
320
                        assert!(l.negative[128]);
321
                        assert!(l.positive[2]);
322
                        assert_eq!(l.other.len(), 1);
323
                        assert_eq!(l.other[0], EncryptionType(257));
324
                    }
325
                    _ => {
326
                        panic!("Result should have been list.");
327
                    }
328
                }
329
                // And we should have no bytes left.
330
                assert_eq!(rem.len(), 0);
331
            }
332
            _ => {
333
                panic!("Result should have been ok.");
334
            }
335
        }
336
        let ctx = detect_parse_encryption("-2147483648").unwrap().1;
337
        match ctx {
338
            DetectKrb5TicketEncryptionData::LIST(l) => {
339
                assert_eq!(l.other.len(), 1);
340
                assert_eq!(l.other[0], EncryptionType(i32::MIN));
341
            }
342
            _ => {
343
                panic!("Result should have been list.");
344
            }
345
        }
346
    }
347
}