suricata/ike/
ikev2.rs

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
20use crate::applayer::*;
21use crate::direction::Direction;
22use crate::ike::ipsec_parser::*;
23
24use super::ipsec_parser::IkeV2Transform;
25use crate::ike::ike::{IKEState, IKETransaction, IkeEvent};
26use crate::ike::parser::IsakmpHeader;
27use ipsec_parser::{IkeExchangeType, IkePayloadType, IkeV2Header};
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30#[repr(u8)]
31pub enum IKEV2ConnectionState {
32    Init,
33    InitSASent,
34    InitKESent,
35    InitNonceSent,
36    RespSASent,
37    RespKESent,
38
39    ParsingDone,
40
41    Invalid,
42}
43
44impl IKEV2ConnectionState {
45    pub fn advance(&self, payload: &IkeV2Payload) -> IKEV2ConnectionState {
46        use self::IKEV2ConnectionState::*;
47        match (self, &payload.content) {
48            (&Init, &IkeV2PayloadContent::SA(_)) => InitSASent,
49            (&InitSASent, &IkeV2PayloadContent::KE(_)) => InitKESent,
50            (&InitKESent, &IkeV2PayloadContent::Nonce(_)) => InitNonceSent,
51            (&InitNonceSent, &IkeV2PayloadContent::SA(_)) => RespSASent,
52            (&RespSASent, &IkeV2PayloadContent::KE(_)) => RespKESent,
53            (&RespKESent, &IkeV2PayloadContent::Nonce(_)) => ParsingDone, // should go to RespNonceSent,
54            (&ParsingDone, _) => self.clone(),
55            (_, &IkeV2PayloadContent::Notify(_)) => self.clone(),
56            (_, &IkeV2PayloadContent::Dummy) => self.clone(),
57            (_, _) => Invalid,
58        }
59    }
60}
61
62pub 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
83impl Default for Ikev2Container {
84    fn default() -> Ikev2Container {
85        Ikev2Container {
86            connection_state: IKEV2ConnectionState::Init,
87            dh_group: IkeTransformDHType::None,
88            client_transforms: Vec::new(),
89            alg_enc: IkeTransformEncType::ENCR_NULL,
90            alg_auth: IkeTransformAuthType::NONE,
91            alg_prf: IkeTransformPRFType::PRF_NULL,
92            alg_dh: IkeTransformDHType::None,
93            alg_esn: IkeTransformESNType::NoESN,
94        }
95    }
96}
97
98pub fn handle_ikev2(
99    state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: Direction,
100) -> AppLayerResult {
101    let hdr = IkeV2Header {
102        init_spi: isakmp_header.init_spi,
103        resp_spi: isakmp_header.resp_spi,
104        next_payload: IkePayloadType(isakmp_header.next_payload),
105        maj_ver: isakmp_header.maj_ver,
106        min_ver: isakmp_header.min_ver,
107        exch_type: IkeExchangeType(isakmp_header.exch_type),
108        flags: isakmp_header.flags,
109        msg_id: isakmp_header.msg_id,
110        length: isakmp_header.length,
111    };
112
113    let mut tx = state.new_tx(direction);
114    tx.ike_version = 2;
115    // use init_spi as transaction identifier
116    // tx.xid = hdr.init_spi; todo is this used somewhere?
117    tx.hdr.ikev2_header = hdr.clone();
118    tx.hdr.spi_initiator = format!("{:016x}", isakmp_header.init_spi);
119    tx.hdr.spi_responder = format!("{:016x}", isakmp_header.resp_spi);
120    tx.hdr.maj_ver = isakmp_header.maj_ver;
121    tx.hdr.min_ver = isakmp_header.min_ver;
122    tx.hdr.msg_id = isakmp_header.msg_id;
123    tx.hdr.flags = isakmp_header.flags;
124    let mut payload_types = Vec::new();
125    let mut errors = 0;
126    let mut notify_types = Vec::new();
127    match parse_ikev2_payload_list(current, hdr.next_payload) {
128        Ok((_, Ok(ref p))) => {
129            for payload in p {
130                payload_types.push(payload.hdr.next_payload_type);
131                match payload.content {
132                    IkeV2PayloadContent::Dummy => (),
133                    IkeV2PayloadContent::SA(ref prop) => {
134                        // if hdr.flags & IKEV2_FLAG_INITIATOR != 0 {
135                        add_proposals(state, &mut tx, prop, direction);
136                        // }
137                    }
138                    IkeV2PayloadContent::KE(ref kex) => {
139                        SCLogDebug!("KEX {:?}", kex.dh_group);
140                        if direction == Direction::ToClient {
141                            state.ikev2_container.dh_group = kex.dh_group;
142                        }
143                    }
144                    IkeV2PayloadContent::Nonce(ref _n) => {
145                        SCLogDebug!("Nonce: {:?}", _n);
146                    }
147                    IkeV2PayloadContent::Notify(ref n) => {
148                        SCLogDebug!("Notify: {:?}", n);
149                        if n.notify_type.is_error() {
150                            errors += 1;
151                        }
152                        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                    _ => {
161                        SCLogDebug!("Unknown payload content {:?}", payload.content);
162                    }
163                }
164                state.ikev2_container.connection_state =
165                    state.ikev2_container.connection_state.advance(payload);
166                tx.payload_types
167                    .ikev2_payload_types
168                    .append(&mut payload_types);
169                tx.errors = errors;
170                tx.notify_types.append(&mut notify_types);
171            }
172        }
173        _e => {
174            SCLogDebug!("parse_ikev2_payload_with_type: {:?}", _e);
175        }
176    }
177    state.transactions.push(tx);
178    return AppLayerResult::ok();
179}
180
181fn add_proposals(
182    state: &mut IKEState, tx: &mut IKETransaction, prop: &Vec<IkeV2Proposal>, direction: Direction,
183) {
184    for p in prop {
185        let transforms: Vec<IkeV2Transform> = p.transforms.iter().map(|x| x.into()).collect();
186        // Rule 1: warn on weak or unknown transforms
187        for xform in &transforms {
188            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                ) => {
201                    // XXX send event only if direction == Direction::ToClient ?
202                    tx.set_event(IkeEvent::WeakCryptoEnc);
203                }
204                IkeV2Transform::PRF(ref prf) => match *prf {
205                    IkeTransformPRFType::PRF_NULL => {
206                        SCLogDebug!("'Null' PRF transform proposed");
207                        tx.set_event(IkeEvent::InvalidProposal);
208                    }
209                    IkeTransformPRFType::PRF_HMAC_MD5 | IkeTransformPRFType::PRF_HMAC_SHA1 => {
210                        SCLogDebug!("Weak PRF: {:?}", prf);
211                        tx.set_event(IkeEvent::WeakCryptoPrf);
212                    }
213                    _ => (),
214                },
215                IkeV2Transform::Auth(ref auth) => {
216                    match *auth {
217                        IkeTransformAuthType::NONE => {
218                            // Note: this could be expected with an AEAD encryption alg.
219                            // See rule 4
220                        }
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                        | IkeTransformAuthType::AUTH_HMAC_SHA1_160 => {
228                            SCLogDebug!("Weak auth: {:?}", auth);
229                            tx.set_event(IkeEvent::WeakCryptoAuth);
230                        }
231                        _ => (),
232                    }
233                }
234                IkeV2Transform::DH(ref dh) => match *dh {
235                    IkeTransformDHType::None => {
236                        SCLogDebug!("'None' DH transform proposed");
237                        tx.set_event(IkeEvent::InvalidProposal);
238                    }
239                    IkeTransformDHType::Modp768
240                    | IkeTransformDHType::Modp1024
241                    | IkeTransformDHType::Modp1024s160
242                    | IkeTransformDHType::Modp1536 => {
243                        SCLogDebug!("Weak DH: {:?}", dh);
244                        tx.set_event(IkeEvent::WeakCryptoDh);
245                    }
246                    _ => (),
247                },
248                IkeV2Transform::Unknown(_tx_type, _tx_id) => {
249                    SCLogDebug!("Unknown proposal: type={:?}, id={}", _tx_type, _tx_id);
250                    tx.set_event(IkeEvent::UnknownProposal);
251                }
252                _ => (),
253            }
254        }
255        // Rule 2: check if no DH was proposed
256        if !transforms.iter().any(|x| match *x {
257            IkeV2Transform::DH(_) => true,
258            _ => false,
259        }) {
260            SCLogDebug!("No DH transform found");
261            tx.set_event(IkeEvent::WeakCryptoNoDh);
262        }
263        // Rule 3: check if proposing AH ([RFC7296] section 3.3.1)
264        if p.protocol_id == ProtocolID::AH {
265            SCLogDebug!("Proposal uses protocol AH - no confidentiality");
266            tx.set_event(IkeEvent::NoEncryption);
267        }
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        if !transforms.iter().any(|x| match *x {
271            IkeV2Transform::Auth(IkeTransformAuthType::NONE) => false,
272            IkeV2Transform::Auth(_) => true,
273            _ => false,
274        }) && !transforms.iter().any(|x| match *x {
275            IkeV2Transform::Encryption(ref enc) => enc.is_aead(),
276            _ => false,
277        }) {
278            SCLogDebug!("No integrity transform found");
279            tx.set_event(IkeEvent::WeakCryptoNoAuth);
280        }
281        // Finally
282        if direction == Direction::ToClient {
283            transforms.iter().for_each(|t| match *t {
284                IkeV2Transform::Encryption(ref e) => {
285                    state.ikev2_container.alg_enc = *e;
286                    tx.hdr.ikev2_transforms.push(IkeV2Transform::Encryption(*e));
287                }
288                IkeV2Transform::Auth(ref a) => {
289                    state.ikev2_container.alg_auth = *a;
290                    tx.hdr.ikev2_transforms.push(IkeV2Transform::Auth(*a));
291                }
292                IkeV2Transform::PRF(ref p) => {
293                    state.ikev2_container.alg_prf = *p;
294                    tx.hdr.ikev2_transforms.push(IkeV2Transform::PRF(*p));
295                }
296                IkeV2Transform::DH(ref dh) => {
297                    state.ikev2_container.alg_dh = *dh;
298                    tx.hdr.ikev2_transforms.push(IkeV2Transform::DH(*dh));
299                }
300                IkeV2Transform::ESN(ref e) => {
301                    state.ikev2_container.alg_esn = *e;
302                    tx.hdr.ikev2_transforms.push(IkeV2Transform::ESN(*e));
303                }
304                _ => {}
305            });
306            SCLogDebug!("Selected transforms: {:?}", transforms);
307        } else {
308            SCLogDebug!("Proposed transforms: {:?}", transforms);
309            state.ikev2_container.client_transforms.push(transforms);
310        }
311    }
312}