Coverage Report

Created: 2026-06-07 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/ffi/hashing.rs
Line
Count
Source
1
/* Copyright (C) 2020 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
use digest::{Digest, Update};
19
use md5::Md5;
20
use sha1::Sha1;
21
use sha2::Sha256;
22
use std::os::raw::c_char;
23
24
pub const SC_SHA1_LEN: usize = 20;
25
pub const SC_SHA256_LEN: usize = 32;
26
27
// Length of hex digests without trailing NUL.
28
pub const SC_MD5_HEX_LEN: usize = 32;
29
pub const SC_SHA256_HEX_LEN: usize = 64;
30
31
// Wrap the Rust Sha256 in a new type named SCSha256 to give this type
32
// the "SC" prefix. The one drawback is we must access the actual context
33
// with .0.
34
pub struct SCSha256(Sha256);
35
36
#[no_mangle]
37
587k
pub extern "C" fn SCSha256New() -> *mut SCSha256 {
38
587k
    let hasher = Box::new(SCSha256(Sha256::new()));
39
587k
    Box::into_raw(hasher)
40
587k
}
41
42
#[no_mangle]
43
5.87M
pub unsafe extern "C" fn SCSha256Update(hasher: &mut SCSha256, bytes: *const u8, len: u32) {
44
5.87M
    update(&mut hasher.0, bytes, len);
45
5.87M
}
46
47
#[no_mangle]
48
473k
pub unsafe extern "C" fn SCSha256Finalize(hasher: &mut SCSha256, out: *mut u8, len: u32) {
49
473k
    let hasher: Box<SCSha256> = Box::from_raw(hasher);
50
473k
    finalize(hasher.0, out, len);
51
473k
}
52
53
/// C function to finalize the Sha256 hasher to a hex string.
54
///
55
/// Notes:
56
/// - There is probably room for optimization here, by iterating the result and writing
57
///   the output directly to the output buffer.
58
///
59
/// But even given the notes, this appears to be faster than the equivalent that we
60
/// did in C using NSS.
61
#[no_mangle]
62
0
pub unsafe extern "C" fn SCSha256FinalizeToHex(hasher: &mut SCSha256, out: *mut c_char, len: u32) {
63
0
    let hasher: Box<SCSha256> = Box::from_raw(hasher);
64
0
    let result = hasher.0.finalize();
65
0
    let hex = format!("{:x}", &result);
66
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
67
0
}
68
69
/// Free an unfinalized Sha256 context.
70
#[no_mangle]
71
113k
pub unsafe extern "C" fn SCSha256Free(hasher: &mut SCSha256) {
72
    // Drop.
73
113k
    let _: Box<SCSha256> = Box::from_raw(hasher);
74
113k
}
75
76
#[no_mangle]
77
10
pub unsafe extern "C" fn SCSha256HashBuffer(
78
10
    buf: *const u8, buf_len: u32, out: *mut u8, len: u32,
79
10
) -> bool {
80
10
    if len as usize != SC_SHA256_LEN {
81
0
        return false;
82
10
    }
83
10
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
84
10
    let output = std::slice::from_raw_parts_mut(out, len as usize);
85
10
    let hash = Sha256::new().chain(data).finalize();
86
10
    output.copy_from_slice(&hash);
87
10
    return true;
88
10
}
89
90
// Start of SHA1 C bindings.
91
92
pub struct SCSha1(Sha1);
93
94
#[no_mangle]
95
515k
pub extern "C" fn SCSha1New() -> *mut SCSha1 {
96
515k
    let hasher = Box::new(SCSha1(Sha1::new()));
97
515k
    Box::into_raw(hasher)
98
515k
}
99
100
#[no_mangle]
101
5.34M
pub unsafe extern "C" fn SCSha1Update(hasher: &mut SCSha1, bytes: *const u8, len: u32) {
102
5.34M
    update(&mut hasher.0, bytes, len);
103
5.34M
}
104
105
#[no_mangle]
106
405k
pub unsafe extern "C" fn SCSha1Finalize(hasher: &mut SCSha1, out: *mut u8, len: u32) {
107
405k
    let hasher: Box<SCSha1> = Box::from_raw(hasher);
108
405k
    finalize(hasher.0, out, len);
109
405k
}
110
111
/// Free an unfinalized Sha1 context.
112
#[no_mangle]
113
109k
pub unsafe extern "C" fn SCSha1Free(hasher: &mut SCSha1) {
114
    // Drop.
115
109k
    let _: Box<SCSha1> = Box::from_raw(hasher);
116
109k
}
117
118
#[no_mangle]
119
2.29k
pub unsafe extern "C" fn SCSha1HashBuffer(
120
2.29k
    buf: *const u8, buf_len: u32, out: *mut u8, len: u32,
121
2.29k
) -> bool {
122
2.29k
    if len as usize != SC_SHA1_LEN {
123
0
        return false;
124
2.29k
    }
125
2.29k
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
126
2.29k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
127
2.29k
    let hash = Sha1::new().chain(data).finalize();
128
2.29k
    output.copy_from_slice(&hash);
129
2.29k
    return true;
130
2.29k
}
131
132
// Start of MD5 C bindings.
133
134
pub struct SCMd5(Md5);
135
136
#[no_mangle]
137
515k
pub extern "C" fn SCMd5New() -> *mut SCMd5 {
138
515k
    let hasher = Box::new(SCMd5(Md5::new()));
139
515k
    Box::into_raw(hasher)
140
515k
}
141
142
#[no_mangle]
143
5.34M
pub unsafe extern "C" fn SCMd5Update(hasher: &mut SCMd5, bytes: *const u8, len: u32) {
144
5.34M
    update(&mut hasher.0, bytes, len);
145
5.34M
}
146
147
/// Finalize the MD5 hash placing the digest in the provided out buffer.
148
///
149
/// This function consumes the SCMd5 hash context.
150
#[no_mangle]
151
405k
pub unsafe extern "C" fn SCMd5Finalize(hasher: &mut SCMd5, out: *mut u8, len: u32) {
152
405k
    let hasher: Box<SCMd5> = Box::from_raw(hasher);
153
405k
    finalize(hasher.0, out, len);
154
405k
}
155
156
/// Finalize MD5 context to a hex string.
157
///
158
/// Consumes the hash context and cannot be re-used.
159
#[no_mangle]
160
0
pub unsafe extern "C" fn SCMd5FinalizeToHex(hasher: &mut SCMd5, out: *mut c_char, len: u32) {
161
0
    let hasher: Box<SCMd5> = Box::from_raw(hasher);
162
0
    let result = hasher.0.finalize();
163
0
    let hex = format!("{:x}", &result);
164
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
165
0
}
166
167
/// Free an unfinalized Sha1 context.
168
#[no_mangle]
169
109k
pub unsafe extern "C" fn SCMd5Free(hasher: &mut SCMd5) {
170
    // Drop.
171
109k
    let _: Box<SCMd5> = Box::from_raw(hasher);
172
109k
}
173
174
#[no_mangle]
175
88
pub unsafe extern "C" fn SCMd5HashBuffer(buf: *const u8, buf_len: u32, out: *mut u8, len: u32) {
176
88
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
177
88
    let output = std::slice::from_raw_parts_mut(out, len as usize);
178
88
    let hash = Md5::new().chain(data).finalize();
179
88
    output.copy_from_slice(&hash);
180
88
}
181
182
/// C binding for a function to MD5 hash a single buffer to a hex string.
183
#[no_mangle]
184
10.1k
pub unsafe extern "C" fn SCMd5HashBufferToHex(
185
10.1k
    buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
186
10.1k
) {
187
10.1k
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
188
10.1k
    let hash = Md5::new().chain(data).finalize();
189
10.1k
    let hex = format!("{:x}", &hash);
190
10.1k
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
191
10.1k
}
192
193
// Functions that are generic over Digest. For the most part the C bindings are
194
// just wrappers around these.
195
196
16.5M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
197
16.5M
    let data = std::slice::from_raw_parts(bytes, len as usize);
198
16.5M
    digest.update(data);
199
16.5M
}
suricata::ffi::hashing::update::<digest::core_api::wrapper::CoreWrapper<digest::core_api::ct_variable::CtVariableCoreWrapper<sha2::core_api::Sha256VarCore, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, sha2::OidSha256>>>
Line
Count
Source
196
5.87M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
197
5.87M
    let data = std::slice::from_raw_parts(bytes, len as usize);
198
5.87M
    digest.update(data);
199
5.87M
}
suricata::ffi::hashing::update::<digest::core_api::wrapper::CoreWrapper<sha1::Sha1Core>>
Line
Count
Source
196
5.34M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
197
5.34M
    let data = std::slice::from_raw_parts(bytes, len as usize);
198
5.34M
    digest.update(data);
199
5.34M
}
suricata::ffi::hashing::update::<digest::core_api::wrapper::CoreWrapper<md5::Md5Core>>
Line
Count
Source
196
5.34M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
197
5.34M
    let data = std::slice::from_raw_parts(bytes, len as usize);
198
5.34M
    digest.update(data);
199
5.34M
}
200
201
1.28M
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
202
1.28M
    let result = digest.finalize();
203
1.28M
    let output = std::slice::from_raw_parts_mut(out, len as usize);
204
    // This will panic if the sizes differ.
205
1.28M
    output.copy_from_slice(&result);
206
1.28M
}
suricata::ffi::hashing::finalize::<digest::core_api::wrapper::CoreWrapper<digest::core_api::ct_variable::CtVariableCoreWrapper<sha2::core_api::Sha256VarCore, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, sha2::OidSha256>>>
Line
Count
Source
201
473k
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
202
473k
    let result = digest.finalize();
203
473k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
204
    // This will panic if the sizes differ.
205
473k
    output.copy_from_slice(&result);
206
473k
}
suricata::ffi::hashing::finalize::<digest::core_api::wrapper::CoreWrapper<sha1::Sha1Core>>
Line
Count
Source
201
405k
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
202
405k
    let result = digest.finalize();
203
405k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
204
    // This will panic if the sizes differ.
205
405k
    output.copy_from_slice(&result);
206
405k
}
suricata::ffi::hashing::finalize::<digest::core_api::wrapper::CoreWrapper<md5::Md5Core>>
Line
Count
Source
201
405k
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
202
405k
    let result = digest.finalize();
203
405k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
204
    // This will panic if the sizes differ.
205
405k
    output.copy_from_slice(&result);
206
405k
}
207
208
#[cfg(test)]
209
mod test {
210
    use super::*;
211
212
    // A test around SCSha256 primarily to check that the output is
213
    // correctly copied into a C string.
214
    #[test]
215
    fn test_sha256() {
216
        unsafe {
217
            let hasher = SCSha256New();
218
            assert!(!hasher.is_null());
219
            let hasher = &mut *hasher as &mut SCSha256;
220
            let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41];
221
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
222
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
223
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
224
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
225
            let hex = [0_u8; SC_SHA256_HEX_LEN + 1];
226
            SCSha256FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_SHA256_HEX_LEN + 1) as u32);
227
            let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap();
228
            assert_eq!(string, "22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153");
229
        }
230
    }
231
232
    // A test around SCSha256 primarily to check that the output is
233
    // correctly copied into a C string.
234
    #[test]
235
    fn test_md5() {
236
        unsafe {
237
            let hasher = SCMd5New();
238
            assert!(!hasher.is_null());
239
            let hasher = &mut *hasher as &mut SCMd5;
240
            let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41];
241
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
242
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
243
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
244
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
245
            let hex = [0_u8; SC_MD5_HEX_LEN + 1];
246
            SCMd5FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_MD5_HEX_LEN + 1) as u32);
247
            let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap();
248
            assert_eq!(string, "5216ddcc58e8dade5256075e77f642da");
249
        }
250
    }
251
252
}