/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 | } |