Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/rust/src/ike/ikev2.rs
Line
Count
Source
1
/* Copyright (C) 2017-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
// written by Pierre Chifflier  <chifflier@wzdftpd.net>
19
20
use crate::applayer::*;
21
use crate::core::Direction;
22
use crate::ike::ipsec_parser::*;
23
24
use super::ipsec_parser::IkeV2Transform;
25
use crate::ike::ike::{IKEState, IKETransaction, IkeEvent};
26
use crate::ike::parser::IsakmpHeader;
27
use ipsec_parser::{IkeExchangeType, IkePayloadType, IkeV2Header};
28
29
#[derive(Clone, Debug, PartialEq, Eq)]
30
#[repr(u8)]
31
pub enum IKEV2ConnectionState {
32
    Init,
33
    InitSASent,
34
    InitKESent,
35
    InitNonceSent,
36
    RespSASent,
37
    RespKESent,
38
39
    ParsingDone,
40
41
    Invalid,
42
}
43
44
impl IKEV2ConnectionState {
45
1.33M
    pub fn advance(&self, payload: &IkeV2Payload) -> IKEV2ConnectionState {
46
        use self::IKEV2ConnectionState::*;
47
1.33M
        match (self, &payload.content) {
48
882
            (&Init, &IkeV2PayloadContent::SA(_)) => InitSASent,
49
56
            (&InitSASent, &IkeV2PayloadContent::KE(_)) => InitKESent,
50
37
            (&InitKESent, &IkeV2PayloadContent::Nonce(_)) => InitNonceSent,
51
26
            (&InitNonceSent, &IkeV2PayloadContent::SA(_)) => RespSASent,
52
18
            (&RespSASent, &IkeV2PayloadContent::KE(_)) => RespKESent,
53
9
            (&RespKESent, &IkeV2PayloadContent::Nonce(_)) => ParsingDone, // should go to RespNonceSent,
54
198
            (&ParsingDone, _) => self.clone(),
55
194k
            (_, &IkeV2PayloadContent::Notify(_)) => self.clone(),
56
53.0k
            (_, &IkeV2PayloadContent::Dummy) => self.clone(),
57
1.09M
            (_, _) => Invalid,
58
        }
59
1.33M
    }
60
}
61
62
pub struct Ikev2Container {
63
    /// The connection state
64
    pub connection_state: IKEV2ConnectionState,
65
66
    /// The transforms proposed by the initiator
67
    pub client_transforms: Vec<Vec<IkeV2Transform>>,
68
69
    /// The encryption algorithm selected by the responder
70
    pub alg_enc: IkeTransformEncType,
71
    /// The authentication algorithm selected by the responder
72
    pub alg_auth: IkeTransformAuthType,
73
    /// The PRF algorithm selected by the responder
74
    pub alg_prf: IkeTransformPRFType,
75
    /// The Diffie-Hellman algorithm selected by the responder
76
    pub alg_dh: IkeTransformDHType,
77
    /// The extended sequence numbers parameter selected by the responder
78
    pub alg_esn: IkeTransformESNType,
79
    /// The Diffie-Hellman group from the server KE message, if present.
80
    pub dh_group: IkeTransformDHType,
81
}
82
83
impl Default for Ikev2Container {
84
5.92k
    fn default() -> Ikev2Container {
85
5.92k
        Ikev2Container {
86
5.92k
            connection_state: IKEV2ConnectionState::Init,
87
5.92k
            dh_group: IkeTransformDHType::None,
88
5.92k
            client_transforms: Vec::new(),
89
5.92k
            alg_enc: IkeTransformEncType::ENCR_NULL,
90
5.92k
            alg_auth: IkeTransformAuthType::NONE,
91
5.92k
            alg_prf: IkeTransformPRFType::PRF_NULL,
92
5.92k
            alg_dh: IkeTransformDHType::None,
93
5.92k
            alg_esn: IkeTransformESNType::NoESN,
94
5.92k
        }
95
5.92k
    }
96
}
97
98
168k
pub fn handle_ikev2(
99
168k
    state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: Direction,
100
168k
) -> AppLayerResult {
101
168k
    let hdr = IkeV2Header {
102
168k
        init_spi: isakmp_header.init_spi,
103
168k
        resp_spi: isakmp_header.resp_spi,
104
168k
        next_payload: IkePayloadType(isakmp_header.next_payload),
105
168k
        maj_ver: isakmp_header.maj_ver,
106
168k
        min_ver: isakmp_header.min_ver,
107
168k
        exch_type: IkeExchangeType(isakmp_header.exch_type),
108
168k
        flags: isakmp_header.flags,
109
168k
        msg_id: isakmp_header.msg_id,
110
168k
        length: isakmp_header.length,
111
168k
    };
112
168k
113
168k
    let mut tx = state.new_tx(direction);
114
168k
    tx.ike_version = 2;
115
168k
    // use init_spi as transaction identifier
116
168k
    // tx.xid = hdr.init_spi; todo is this used somewhere?
117
168k
    tx.hdr.ikev2_header = hdr.clone();
118
168k
    tx.hdr.spi_initiator = format!("{:016x}", isakmp_header.init_spi);
119
168k
    tx.hdr.spi_responder = format!("{:016x}", isakmp_header.resp_spi);
120
168k
    tx.hdr.maj_ver = isakmp_header.maj_ver;
121
168k
    tx.hdr.min_ver = isakmp_header.min_ver;
122
168k
    tx.hdr.msg_id = isakmp_header.msg_id;
123
168k
    tx.hdr.flags = isakmp_header.flags;
124
168k
    let mut payload_types = Vec::new();
125
168k
    let mut errors = 0;
126
168k
    let mut notify_types = Vec::new();
127
168k
    match parse_ikev2_payload_list(current, hdr.next_payload) {
128
53.0k
        Ok((_, Ok(ref p))) => {
129
1.39M
            for payload in p {
130
1.33M
                payload_types.push(payload.hdr.next_payload_type);
131
1.33M
                match payload.content {
132
53.0k
                    IkeV2PayloadContent::Dummy => (),
133
14.4k
                    IkeV2PayloadContent::SA(ref prop) => {
134
14.4k
                        // if hdr.flags & IKEV2_FLAG_INITIATOR != 0 {
135
14.4k
                        add_proposals(state, &mut tx, prop, direction);
136
14.4k
                        // }
137
14.4k
                    }
138
3.67k
                    IkeV2PayloadContent::KE(ref kex) => {
139
3.67k
                        SCLogDebug!("KEX {:?}", kex.dh_group);
140
3.67k
                        if direction == Direction::ToClient {
141
1.82k
                            state.ikev2_container.dh_group = kex.dh_group;
142
1.84k
                        }
143
                    }
144
580
                    IkeV2PayloadContent::Nonce(ref _n) => {
145
580
                        SCLogDebug!("Nonce: {:?}", _n);
146
580
                    }
147
194k
                    IkeV2PayloadContent::Notify(ref n) => {
148
194k
                        SCLogDebug!("Notify: {:?}", n);
149
194k
                        if n.notify_type.is_error() {
150
188k
                            errors += 1;
151
188k
                        }
152
194k
                        notify_types.push(n.notify_type);
153
                    }
154
                    // XXX CertificateRequest
155
                    // XXX Certificate
156
                    // XXX Authentication
157
                    // XXX TSi
158
                    // XXX TSr
159
                    // XXX IDr
160
1.07M
                    _ => {
161
1.07M
                        SCLogDebug!("Unknown payload content {:?}", payload.content);
162
1.07M
                    }
163
                }
164
1.33M
                state.ikev2_container.connection_state =
165
1.33M
                    state.ikev2_container.connection_state.advance(payload);
166
1.33M
                tx.payload_types
167
1.33M
                    .ikev2_payload_types
168
1.33M
                    .append(&mut payload_types);
169
1.33M
                tx.errors = errors;
170
1.33M
                tx.notify_types.append(&mut notify_types);
171
            }
172
        }
173
115k
        _e => {
174
115k
            SCLogDebug!("parse_ikev2_payload_with_type: {:?}", _e);
175
115k
        }
176
    }
177
168k
    state.transactions.push(tx);
178
168k
    return AppLayerResult::ok();
179
168k
}
180
181
14.4k
fn add_proposals(
182
14.4k
    state: &mut IKEState, tx: &mut IKETransaction, prop: &Vec<IkeV2Proposal>, direction: Direction,
183
14.4k
) {
184
112k
    for p in prop {
185
98.2k
        let transforms: Vec<IkeV2Transform> = p.transforms.iter().map(|x| x.into()).collect();
186
        // Rule 1: warn on weak or unknown transforms
187
123k
        for xform in &transforms {
188
25.5k
            match *xform {
189
                IkeV2Transform::Encryption(
190
                    IkeTransformEncType::ENCR_DES_IV64
191
                    | IkeTransformEncType::ENCR_DES
192
                    | IkeTransformEncType::ENCR_3DES
193
                    | IkeTransformEncType::ENCR_RC5
194
                    | IkeTransformEncType::ENCR_IDEA
195
                    | IkeTransformEncType::ENCR_CAST
196
                    | IkeTransformEncType::ENCR_BLOWFISH
197
                    | IkeTransformEncType::ENCR_3IDEA
198
                    | IkeTransformEncType::ENCR_DES_IV32
199
                    | IkeTransformEncType::ENCR_NULL,
200
2.66k
                ) => {
201
2.66k
                    // XXX send event only if direction == Direction::ToClient ?
202
2.66k
                    tx.set_event(IkeEvent::WeakCryptoEnc);
203
2.66k
                }
204
2.11k
                IkeV2Transform::PRF(ref prf) => match *prf {
205
618
                    IkeTransformPRFType::PRF_NULL => {
206
618
                        SCLogDebug!("'Null' PRF transform proposed");
207
618
                        tx.set_event(IkeEvent::InvalidProposal);
208
618
                    }
209
720
                    IkeTransformPRFType::PRF_HMAC_MD5 | IkeTransformPRFType::PRF_HMAC_SHA1 => {
210
720
                        SCLogDebug!("Weak PRF: {:?}", prf);
211
720
                        tx.set_event(IkeEvent::WeakCryptoPrf);
212
720
                    }
213
772
                    _ => (),
214
                },
215
2.88k
                IkeV2Transform::Auth(ref auth) => {
216
2.88k
                    match *auth {
217
703
                        IkeTransformAuthType::NONE => {
218
703
                            // Note: this could be expected with an AEAD encryption alg.
219
703
                            // See rule 4
220
703
                        }
221
                        IkeTransformAuthType::AUTH_HMAC_MD5_96
222
                        | IkeTransformAuthType::AUTH_HMAC_SHA1_96
223
                        | IkeTransformAuthType::AUTH_DES_MAC
224
                        | IkeTransformAuthType::AUTH_KPDK_MD5
225
                        | IkeTransformAuthType::AUTH_AES_XCBC_96
226
                        | IkeTransformAuthType::AUTH_HMAC_MD5_128
227
666
                        | IkeTransformAuthType::AUTH_HMAC_SHA1_160 => {
228
666
                            SCLogDebug!("Weak auth: {:?}", auth);
229
666
                            tx.set_event(IkeEvent::WeakCryptoAuth);
230
666
                        }
231
1.51k
                        _ => (),
232
                    }
233
                }
234
7.41k
                IkeV2Transform::DH(ref dh) => match *dh {
235
1.90k
                    IkeTransformDHType::None => {
236
1.90k
                        SCLogDebug!("'None' DH transform proposed");
237
1.90k
                        tx.set_event(IkeEvent::InvalidProposal);
238
1.90k
                    }
239
                    IkeTransformDHType::Modp768
240
                    | IkeTransformDHType::Modp1024
241
                    | IkeTransformDHType::Modp1024s160
242
2.82k
                    | IkeTransformDHType::Modp1536 => {
243
2.82k
                        SCLogDebug!("Weak DH: {:?}", dh);
244
2.82k
                        tx.set_event(IkeEvent::WeakCryptoDh);
245
2.82k
                    }
246
2.68k
                    _ => (),
247
                },
248
4.76k
                IkeV2Transform::Unknown(_tx_type, _tx_id) => {
249
4.76k
                    SCLogDebug!("Unknown proposal: type={:?}, id={}", _tx_type, _tx_id);
250
4.76k
                    tx.set_event(IkeEvent::UnknownProposal);
251
4.76k
                }
252
5.74k
                _ => (),
253
            }
254
        }
255
        // Rule 2: check if no DH was proposed
256
98.2k
        if !transforms.iter().any(|x| match *x {
257
4.48k
            IkeV2Transform::DH(_) => true,
258
16.7k
            _ => false,
259
98.2k
        }) {
260
93.7k
            SCLogDebug!("No DH transform found");
261
93.7k
            tx.set_event(IkeEvent::WeakCryptoNoDh);
262
93.7k
        }
263
        // Rule 3: check if proposing AH ([RFC7296] section 3.3.1)
264
98.2k
        if p.protocol_id == ProtocolID::AH {
265
4.55k
            SCLogDebug!("Proposal uses protocol AH - no confidentiality");
266
4.55k
            tx.set_event(IkeEvent::NoEncryption);
267
93.6k
        }
268
        // Rule 4: lack of integrity is accepted only if using an AEAD proposal
269
        // Look if no auth was proposed, including if proposal is Auth::None
270
98.2k
        if !transforms.iter().any(|x| match *x {
271
689
            IkeV2Transform::Auth(IkeTransformAuthType::NONE) => false,
272
1.66k
            IkeV2Transform::Auth(_) => true,
273
22.4k
            _ => false,
274
98.2k
        }) && !transforms.iter().any(|x| match *x {
275
6.29k
            IkeV2Transform::Encryption(ref enc) => enc.is_aead(),
276
14.7k
            _ => false,
277
96.5k
        }) {
278
93.8k
            SCLogDebug!("No integrity transform found");
279
93.8k
            tx.set_event(IkeEvent::WeakCryptoNoAuth);
280
93.8k
        }
281
        // Finally
282
98.2k
        if direction == Direction::ToClient {
283
16.7k
            transforms.iter().for_each(|t| match *t {
284
3.73k
                IkeV2Transform::Encryption(ref e) => {
285
3.73k
                    state.ikev2_container.alg_enc = *e;
286
3.73k
                    tx.hdr.ikev2_transforms.push(IkeV2Transform::Encryption(*e));
287
3.73k
                }
288
1.87k
                IkeV2Transform::Auth(ref a) => {
289
1.87k
                    state.ikev2_container.alg_auth = *a;
290
1.87k
                    tx.hdr.ikev2_transforms.push(IkeV2Transform::Auth(*a));
291
1.87k
                }
292
1.59k
                IkeV2Transform::PRF(ref p) => {
293
1.59k
                    state.ikev2_container.alg_prf = *p;
294
1.59k
                    tx.hdr.ikev2_transforms.push(IkeV2Transform::PRF(*p));
295
1.59k
                }
296
5.23k
                IkeV2Transform::DH(ref dh) => {
297
5.23k
                    state.ikev2_container.alg_dh = *dh;
298
5.23k
                    tx.hdr.ikev2_transforms.push(IkeV2Transform::DH(*dh));
299
5.23k
                }
300
980
                IkeV2Transform::ESN(ref e) => {
301
980
                    state.ikev2_container.alg_esn = *e;
302
980
                    tx.hdr.ikev2_transforms.push(IkeV2Transform::ESN(*e));
303
980
                }
304
2.42k
                _ => {}
305
16.7k
            });
306
16.7k
            SCLogDebug!("Selected transforms: {:?}", transforms);
307
81.4k
        } else {
308
81.4k
            SCLogDebug!("Proposed transforms: {:?}", transforms);
309
81.4k
            state.ikev2_container.client_transforms.push(transforms);
310
81.4k
        }
311
    }
312
14.4k
}