├── .github ├── dependabot.yml └── workflows │ ├── clippy-check.yml │ ├── rust.yml │ └── security-audit.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── ike-sa-init-req.bin └── ike-sa-init-resp.bin ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── fuzzer_ikev2_payload_list.rs │ └── fuzzer_ikev2_payload_sa.rs ├── src ├── error.rs ├── esp.rs ├── ikev2.rs ├── ikev2_debug.rs ├── ikev2_notify.rs ├── ikev2_parser.rs ├── ikev2_transforms.rs └── lib.rs └── tests └── ikev2.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/clippy-check.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: Clippy check 3 | jobs: 4 | clippy_check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - run: rustup component add clippy 9 | - uses: actions-rs/clippy-check@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | args: --all-features 13 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | rust: 12 | - stable 13 | - 1.46.0 14 | - nightly 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: ${{ matrix.rust }} 21 | override: true 22 | - name: Cargo update 23 | run: cargo update 24 | - uses: actions-rs/cargo@v1 25 | with: 26 | command: check 27 | 28 | test: 29 | name: Test Suite 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: stable 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | command: test 41 | 42 | fmt: 43 | name: Rustfmt 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v2 47 | - uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: stable 51 | override: true 52 | - run: rustup component add rustfmt 53 | - uses: actions-rs/cargo@v1 54 | with: 55 | command: fmt 56 | args: --all -- --check 57 | 58 | clippy: 59 | name: Clippy 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v2 63 | - uses: actions-rs/toolchain@v1 64 | with: 65 | profile: minimal 66 | toolchain: stable 67 | override: true 68 | - run: rustup component add clippy 69 | - uses: actions-rs/cargo@v1 70 | with: 71 | command: clippy 72 | args: -- -D warnings 73 | 74 | doc: 75 | name: Build documentation 76 | runs-on: ubuntu-latest 77 | env: 78 | RUSTDOCFLAGS: --cfg docsrs 79 | steps: 80 | - uses: actions/checkout@v2 81 | - uses: actions-rs/toolchain@v1 82 | with: 83 | profile: minimal 84 | toolchain: nightly 85 | override: true 86 | - uses: actions-rs/cargo@v1 87 | with: 88 | command: doc 89 | args: --workspace --no-deps --all-features 90 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: "0 8 * * *" 5 | push: 6 | paths: 7 | - "**/Cargo.*" 8 | jobs: 9 | security_audit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - uses: actions-rs/audit-check@v1 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | matrix: 4 | include: 5 | - rust: stable 6 | env: 7 | - NAME="stable" 8 | - FEATURES='' 9 | - rust: stable 10 | env: 11 | - NAME="stable,fmt" 12 | - FEATURES='' 13 | - RUSTFMT=yes 14 | - rust: stable 15 | env: 16 | - NAME="stable,clippy" 17 | - FEATURES='' 18 | - CLIPPY=yes 19 | - rust: nightly 20 | env: 21 | - NAME="nightly" 22 | - FEATURES='' 23 | before_script: 24 | - rustc --version 25 | - ([ "$CLIPPY" != yes ] || rustup component add clippy) 26 | - ([ "$RUSTFMT" != yes ] || rustup component add rustfmt) 27 | script: 28 | - ([ "$RUSTFMT" != yes ] || cargo fmt --all -- --check) 29 | - ([ "$CLIPPY" != yes ] || cargo clippy -- -D clippy::all) 30 | - | 31 | cargo build --verbose --features "$FEATURES" && 32 | cargo test --verbose --features "$FEATURES" && 33 | ([ "$BENCH" != 1 ] || cargo bench --verbose --features "$FEATURES") 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Parser for the IKEv2 protocol" 3 | license = "MIT/Apache-2.0" 4 | keywords = ["IPsec","IKEv2","protocol","parser","nom"] 5 | homepage = "https://github.com/rusticata/ipsec-parser" 6 | repository = "https://github.com/rusticata/ipsec-parser.git" 7 | documentation = "https://docs.rs/ipsec-parser" 8 | name = "ipsec-parser" 9 | version = "0.7.0" 10 | authors = ["Pierre Chifflier "] 11 | categories = ["parser-implementations"] 12 | readme = "README.md" 13 | edition = "2018" 14 | 15 | include = [ 16 | "LICENSE-*", 17 | ".gitignore", 18 | ".travis.yml", 19 | "Cargo.toml", 20 | "src/*.rs", 21 | "assets/*" 22 | ] 23 | 24 | [dependencies] 25 | nom = "7.0" 26 | rusticata-macros = "4.0" 27 | 28 | [badges] 29 | travis-ci = { repository = "rusticata/ipsec-parser" } 30 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Pierre Chifflier 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ipsec-parser 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) 4 | [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 5 | [![Build Status](https://travis-ci.org/rusticata/ipsec-parser.svg?branch=master)](https://travis-ci.org/rusticata/ipsec-parser) 6 | 7 | 8 | 9 | # IPsec parsers 10 | 11 | This crate contains several parsers using for IPsec: IKEv2, and reading the envelope of ESP 12 | encapsulated messages. 13 | This parser provides the base functions to read and analyze messages, but does not handle the 14 | interpretation of messages. 15 | 16 | ESP is supported, but only to read the envelope of the payload. 17 | 18 | Encapsulated ESP is supported, to differentiate between IKE and ESP headers. 19 | 20 | # IKEv2 parser 21 | 22 | An IKEv2 (RFC7296) parser, implemented with the [nom](https://github.com/Geal/nom) 23 | parser combinator framework. 24 | 25 | The code is available on [Github](https://github.com/rusticata/ipsec-parser) 26 | and is part of the [Rusticata](https://github.com/rusticata) project. 27 | 28 | To parse an IKE packet, first read the header using `parse_ikev2_header`, then use the type 29 | from the header to parse the remaining part: 30 | 31 | 32 | ```rust 33 | use ipsec_parser::*; 34 | use nom::IResult; 35 | 36 | static IKEV2_INIT_RESP: &'static [u8] = include_bytes!("../assets/ike-sa-init-resp.bin"); 37 | 38 | fn test_ikev2_init_resp() { 39 | let bytes = IKEV2_INIT_RESP; 40 | match parse_ikev2_header(&bytes) { 41 | Ok( (rem, ref hdr) ) => { 42 | match parse_ikev2_payload_list(rem,hdr.next_payload) { 43 | Ok( (_, Ok(ref p)) ) => { 44 | // p is a list of payloads 45 | // first one is always dummy 46 | assert!(p.len() > 0); 47 | assert_eq!(p[0].content, IkeV2PayloadContent::Dummy); 48 | for payload in p { 49 | match payload.content { 50 | IkeV2PayloadContent::SA(ref sa) => { /* .. */ }, 51 | _ => () 52 | } 53 | } 54 | }, 55 | e => { eprintln!("Parsing payload failed: {:?}", e); }, 56 | } 57 | }, 58 | _ => { eprintln!("Parsing header failed"); }, 59 | } 60 | } 61 | ``` 62 | 63 | 64 | 65 | ## Changelog 66 | 67 | ### 0.7.0 68 | 69 | - Upgrade to nom 7 70 | - Set MSRV to 1.46 71 | 72 | ### 0.6.0 73 | 74 | - Upgrade to nom 6 75 | - Convert all macro-based parsers to functions 76 | 77 | ### 0.5.0 78 | 79 | - Upgrade to nom 5 80 | 81 | ### 0.4.1 82 | 83 | - o not use glob imports in `use` groups (compatibility with rust 1.24) 84 | 85 | ### 0.4.0 86 | 87 | - Upgrade to nom 4 88 | 89 | ### 0.3.0 90 | 91 | * Add function `parse_ikev2_message` to read header and payload list 92 | * `init_spi` and `resp_spi` fields have been changed from `&[u8]` to `u64` 93 | 94 | ## Rusticata 95 | 96 | This parser is part of the [rusticata](https://github.com/rusticata) project. 97 | The goal of this project is to provide **safe** parsers, that can be used in other projects. 98 | 99 | Testing of the parser is done manually, and also using unit tests and 100 | [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz). Please fill a bugreport if you find any issue. 101 | 102 | Feel free to contribute: tests, feedback, doc, suggestions (or code) of new parsers etc. are welcome. 103 | 104 | ## License 105 | 106 | Licensed under either of 107 | 108 | * Apache License, Version 2.0 109 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 110 | * MIT license 111 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 112 | 113 | at your option. 114 | 115 | ## Contribution 116 | 117 | Unless you explicitly state otherwise, any contribution intentionally submitted 118 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 119 | dual licensed as above, without any additional terms or conditions. 120 | -------------------------------------------------------------------------------- /assets/ike-sa-init-req.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/ipsec-parser/613d81c875e0de0d19ded7990fb3d1a8342c6a40/assets/ike-sa-init-req.bin -------------------------------------------------------------------------------- /assets/ike-sa-init-resp.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/ipsec-parser/613d81c875e0de0d19ded7990fb3d1a8342c6a40/assets/ike-sa-init-resp.bin -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ipsec-parser-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | 12 | [dependencies.ipsec-parser] 13 | path = ".." 14 | [dependencies.libfuzzer-sys] 15 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 16 | 17 | # Prevent this from interfering with workspaces 18 | [workspace] 19 | members = ["."] 20 | 21 | [[bin]] 22 | name = "fuzzer_ikev2_payload_sa" 23 | path = "fuzz_targets/fuzzer_ikev2_payload_sa.rs" 24 | 25 | [[bin]] 26 | name = "fuzzer_ikev2_payload_list" 27 | path = "fuzz_targets/fuzzer_ikev2_payload_list.rs" 28 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzzer_ikev2_payload_list.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate ipsec_parser; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | // fuzzed code goes here 7 | if data.len() > 0 { 8 | let _ = ipsec_parser::parse_ikev2_payload_list(&data[1..],ipsec_parser::IkePayloadType(data[0])); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzzer_ikev2_payload_sa.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate ipsec_parser; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | // fuzzed code goes here 7 | let _ = ipsec_parser::parse_ikev2_payload_sa(data,0); 8 | }); 9 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use nom::error::{ErrorKind, ParseError}; 2 | 3 | #[derive(Debug)] 4 | pub enum IPsecError { 5 | PayloadTooSmall, 6 | ExtraBytesInPayload, 7 | PayloadParseError, 8 | 9 | NomError(ErrorKind), 10 | } 11 | 12 | impl ParseError for IPsecError { 13 | fn from_error_kind(_input: I, kind: ErrorKind) -> Self { 14 | IPsecError::NomError(kind) 15 | } 16 | fn append(_input: I, _kind: ErrorKind, other: Self) -> Self { 17 | other 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/esp.rs: -------------------------------------------------------------------------------- 1 | use crate::ikev2::IkeV2Header; 2 | use crate::ikev2_parser::parse_ikev2_header; 3 | use nom::bytes::streaming::take; 4 | use nom::combinator::rest; 5 | use nom::number::streaming::be_u32; 6 | use nom::IResult; 7 | 8 | /// Encapsulating Security Payload Packet Format 9 | /// 10 | /// Defined in [RFC2406](https://tools.ietf.org/html/rfc2406) section 2 11 | #[derive(Debug)] 12 | pub struct ESPHeader<'a> { 13 | pub spi_index: &'a [u8], 14 | pub seq: u32, 15 | pub data: &'a [u8], 16 | } 17 | 18 | /// UDP-encapsulated Packet Formats 19 | /// 20 | /// Defined in [RFC3948](https://tools.ietf.org/html/rfc3948) section 2 21 | #[derive(Debug)] 22 | pub enum ESPData<'a> { 23 | ESP(ESPHeader<'a>), 24 | IKE(IkeV2Header), 25 | } 26 | 27 | /// Parse an encapsulated ESP packet 28 | /// 29 | /// The type of encapsulated data depends on the first field (`spi_index`): 0 is a forbidden SPI 30 | /// index, and indicates that the header is an IKE header. 31 | /// Any other value indicates an ESP header. 32 | /// 33 | /// *Note: input is entirely consumed* 34 | pub fn parse_esp_encapsulated(i: &[u8]) -> IResult<&[u8], ESPData> { 35 | if be_u32(i)?.1 == 0 { 36 | parse_ikev2_header(i).map(|x| (x.0, ESPData::IKE(x.1))) 37 | } else { 38 | parse_esp_header(i).map(|x| (x.0, ESPData::ESP(x.1))) 39 | } 40 | } 41 | 42 | /// Parse an ESP packet 43 | /// 44 | /// The ESP header contains: 45 | /// 46 | /// - the SPI index 47 | /// - the sequence number 48 | /// - the payload data (which can be encrypted) 49 | /// 50 | /// *Note: input is entirely consumed* 51 | pub fn parse_esp_header(i: &[u8]) -> IResult<&[u8], ESPHeader> { 52 | let (i, spi_index) = take(4usize)(i)?; 53 | let (i, seq) = be_u32(i)?; 54 | let (i, data) = rest(i)?; 55 | let hdr = ESPHeader { 56 | spi_index, 57 | seq, 58 | data, 59 | }; 60 | Ok((i, hdr)) 61 | } 62 | -------------------------------------------------------------------------------- /src/ikev2.rs: -------------------------------------------------------------------------------- 1 | use crate::ikev2_notify::NotifyType; 2 | use crate::ikev2_transforms::*; 3 | use rusticata_macros::newtype_enum; 4 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 5 | 6 | /// Payload exchange type: SA, Auth, CreateChildSA, etc. 7 | #[derive(Copy, Clone, PartialEq, Eq)] 8 | pub struct IkeExchangeType(pub u8); 9 | 10 | newtype_enum! { 11 | impl debug IkeExchangeType { 12 | IKE_SA_INIT = 34, 13 | IKE_AUTH = 35, 14 | CREATE_CHILD_SA = 36, 15 | INFORMATIONAL = 37, 16 | } 17 | } 18 | 19 | /// Protocol type: IKE, AH or ESP 20 | /// 21 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.1 22 | #[derive(Clone, Copy, PartialEq, Eq)] 23 | pub struct ProtocolID(pub u8); 24 | 25 | newtype_enum! { 26 | impl debug ProtocolID { 27 | IKE = 1, 28 | AH = 2, 29 | ESP = 3, 30 | } 31 | } 32 | 33 | pub const IKEV2_FLAG_INITIATOR: u8 = 0b1000; 34 | pub const IKEV2_FLAG_VERSION: u8 = 0b1_0000; 35 | pub const IKEV2_FLAG_RESPONSE: u8 = 0b10_0000; 36 | 37 | /// The IKE Header 38 | /// 39 | /// IKE messages use UDP ports 500 and/or 4500, with one IKE message per 40 | /// UDP datagram. Information from the beginning of the packet through 41 | /// the UDP header is largely ignored except that the IP addresses and 42 | /// UDP ports from the headers are reversed and used for return packets. 43 | /// When sent on UDP port 500, IKE messages begin immediately following 44 | /// the UDP header. When sent on UDP port 4500, IKE messages have 45 | /// prepended four octets of zeros. These four octets of zeros are not 46 | /// part of the IKE message and are not included in any of the length 47 | /// fields or checksums defined by IKE. Each IKE message begins with the 48 | /// IKE header, denoted HDR in this document. Following the header are 49 | /// one or more IKE payloads each identified by a Next Payload field in 50 | /// the preceding payload. Payloads are identified in the order in which 51 | /// they appear in an IKE message by looking in the Next Payload field in 52 | /// the IKE header, and subsequently according to the Next Payload field 53 | /// in the IKE payload itself until a Next Payload field of zero 54 | /// indicates that no payloads follow. If a payload of type "Encrypted" 55 | /// is found, that payload is decrypted and its contents parsed as 56 | /// additional payloads. An Encrypted payload MUST be the last payload 57 | /// in a packet and an Encrypted payload MUST NOT contain another 58 | /// Encrypted payload. 59 | /// 60 | /// The responder's SPI in the header identifies an instance of an IKE 61 | /// Security Association. It is therefore possible for a single instance 62 | /// of IKE to multiplex distinct sessions with multiple peers, including 63 | /// multiple sessions per peer. 64 | /// 65 | /// All multi-octet fields representing integers are laid out in big 66 | /// endian order (also known as "most significant byte first", or 67 | /// "network byte order"). 68 | /// 69 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.1 70 | #[derive(Clone, Debug, PartialEq)] 71 | pub struct IkeV2Header { 72 | pub init_spi: u64, 73 | pub resp_spi: u64, 74 | pub next_payload: IkePayloadType, 75 | pub maj_ver: u8, 76 | pub min_ver: u8, 77 | pub exch_type: IkeExchangeType, 78 | pub flags: u8, 79 | pub msg_id: u32, 80 | pub length: u32, 81 | } 82 | 83 | /// Payload type 84 | #[derive(Clone, Copy, PartialEq, Eq)] 85 | pub struct IkePayloadType(pub u8); 86 | 87 | newtype_enum! { 88 | impl debug IkePayloadType { 89 | NoNextPayload = 0, 90 | SecurityAssociation = 33, 91 | KeyExchange = 34, 92 | IdentInitiator = 35, 93 | IdentResponder = 36, 94 | Certificate = 37, 95 | CertificateRequest = 38, 96 | Authentication = 39, 97 | Nonce = 40, 98 | Notify = 41, 99 | Delete = 42, 100 | VendorID = 43, 101 | TrafficSelectorInitiator = 44, 102 | TrafficSelectorResponder = 45, 103 | EncryptedAndAuthenticated = 46, 104 | Configuration = 47, 105 | ExtensibleAuthentication = 48, 106 | } 107 | } 108 | 109 | /// Generic (unparsed payload) 110 | /// 111 | /// Defined in [RFC7296] 112 | #[derive(Debug, PartialEq)] 113 | pub struct IkeV2GenericPayload<'a> { 114 | pub hdr: IkeV2PayloadHeader, 115 | pub payload: &'a [u8], 116 | } 117 | 118 | /// Ciphersuite Proposal 119 | /// 120 | /// The Proposal structure contains within it a Proposal Num and an IPsec 121 | /// protocol ID. Each structure MUST have a proposal number one (1) 122 | /// greater than the previous structure. The first Proposal in the 123 | /// initiator's SA payload MUST have a Proposal Num of one (1). One 124 | /// reason to use multiple proposals is to propose both standard crypto 125 | /// ciphers and combined-mode ciphers. Combined-mode ciphers include 126 | /// both integrity and encryption in a single encryption algorithm, and 127 | /// MUST either offer no integrity algorithm or a single integrity 128 | /// algorithm of "NONE", with no integrity algorithm being the 129 | /// RECOMMENDED method. If an initiator wants to propose both combined- 130 | /// mode ciphers and normal ciphers, it must include two proposals: one 131 | /// will have all the combined-mode ciphers, and the other will have all 132 | /// the normal ciphers with the integrity algorithms. For example, one 133 | /// such proposal would have two proposal structures. Proposal 1 is ESP 134 | /// with AES-128, AES-192, and AES-256 bits in Cipher Block Chaining 135 | /// (CBC) mode, with either HMAC-SHA1-96 or XCBC-96 as the integrity 136 | /// algorithm; Proposal 2 is AES-128 or AES-256 in GCM mode with an 137 | /// 8-octet Integrity Check Value (ICV). Both proposals allow but do not 138 | /// require the use of ESNs (Extended Sequence Numbers). This can be 139 | /// illustrated as: 140 | /// 141 | /// ```ignore 142 | /// SA Payload 143 | /// | 144 | /// +--- Proposal #1 ( Proto ID = ESP(3), SPI size = 4, 145 | /// | | 7 transforms, SPI = 0x052357bb ) 146 | /// | | 147 | /// | +-- Transform ENCR ( Name = ENCR_AES_CBC ) 148 | /// | | +-- Attribute ( Key Length = 128 ) 149 | /// | | 150 | /// | +-- Transform ENCR ( Name = ENCR_AES_CBC ) 151 | /// | | +-- Attribute ( Key Length = 192 ) 152 | /// | | 153 | /// | +-- Transform ENCR ( Name = ENCR_AES_CBC ) 154 | /// | | +-- Attribute ( Key Length = 256 ) 155 | /// | | 156 | /// | +-- Transform INTEG ( Name = AUTH_HMAC_SHA1_96 ) 157 | /// | +-- Transform INTEG ( Name = AUTH_AES_XCBC_96 ) 158 | /// | +-- Transform ESN ( Name = ESNs ) 159 | /// | +-- Transform ESN ( Name = No ESNs ) 160 | /// | 161 | /// +--- Proposal #2 ( Proto ID = ESP(3), SPI size = 4, 162 | /// | 4 transforms, SPI = 0x35a1d6f2 ) 163 | /// | 164 | /// +-- Transform ENCR ( Name = AES-GCM with a 8 octet ICV ) 165 | /// | +-- Attribute ( Key Length = 128 ) 166 | /// | 167 | /// +-- Transform ENCR ( Name = AES-GCM with a 8 octet ICV ) 168 | /// | +-- Attribute ( Key Length = 256 ) 169 | /// | 170 | /// +-- Transform ESN ( Name = ESNs ) 171 | /// +-- Transform ESN ( Name = No ESNs ) 172 | /// ``` 173 | /// 174 | /// Each Proposal/Protocol structure is followed by one or more transform 175 | /// structures. The number of different transforms is generally 176 | /// determined by the Protocol. AH generally has two transforms: 177 | /// Extended Sequence Numbers (ESNs) and an integrity check algorithm. 178 | /// ESP generally has three: ESN, an encryption algorithm, and an 179 | /// integrity check algorithm. IKE generally has four transforms: a 180 | /// Diffie-Hellman group, an integrity check algorithm, a PRF algorithm, 181 | /// and an encryption algorithm. For each Protocol, the set of 182 | /// permissible transforms is assigned Transform ID numbers, which appear 183 | /// in the header of each transform. 184 | /// 185 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.1 186 | #[derive(Clone, Debug, PartialEq)] 187 | pub struct IkeV2Proposal<'a> { 188 | pub last: u8, 189 | pub reserved: u8, 190 | pub proposal_length: u16, 191 | pub proposal_num: u8, 192 | pub protocol_id: ProtocolID, 193 | pub spi_size: u8, 194 | pub num_transforms: u8, 195 | pub spi: Option<&'a [u8]>, 196 | pub transforms: Vec>, 197 | } 198 | 199 | /// Key Exchange Payload 200 | /// 201 | /// The Key Exchange payload, denoted KE in this document, is used to 202 | /// exchange Diffie-Hellman public numbers as part of a Diffie-Hellman 203 | /// key exchange. The Key Exchange payload consists of the IKE generic 204 | /// payload header followed by the Diffie-Hellman public value itself. 205 | /// 206 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.4 207 | #[derive(Debug, PartialEq)] 208 | pub struct KeyExchangePayload<'a> { 209 | pub dh_group: IkeTransformDHType, 210 | pub reserved: u16, 211 | pub kex_data: &'a [u8], 212 | } 213 | 214 | /// Identification Payloads 215 | /// 216 | /// The Identification payloads, denoted IDi and IDr in this document, 217 | /// allow peers to assert an identity to one another. This identity may 218 | /// be used for policy lookup, but does not necessarily have to match 219 | /// anything in the CERT payload; both fields may be used by an 220 | /// implementation to perform access control decisions. When using the 221 | /// ID_IPV4_ADDR/ID_IPV6_ADDR identity types in IDi/IDr payloads, IKEv2 222 | /// does not require this address to match the address in the IP header 223 | /// of IKEv2 packets, or anything in the TSi/TSr payloads. The contents 224 | /// of IDi/IDr are used purely to fetch the policy and authentication 225 | /// data related to the other party. 226 | /// 227 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.5 228 | #[derive(Debug, PartialEq)] 229 | pub struct IdentificationPayload<'a> { 230 | pub id_type: IdentificationType, 231 | pub reserved1: u8, 232 | pub reserved2: u16, 233 | pub ident_data: &'a [u8], 234 | } 235 | 236 | /// Type of Identification 237 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 238 | pub struct IdentificationType(pub u8); 239 | 240 | #[rustfmt::skip] 241 | impl IdentificationType { 242 | /// A single four (4) octet IPv4 address. 243 | pub const ID_IPV4_ADDR : IdentificationType = IdentificationType(1); 244 | /// A fully-qualified domain name string. An example of an ID_FQDN 245 | /// is "example.com". The string MUST NOT contain any terminators 246 | /// (e.g., NULL, CR, etc.). All characters in the ID_FQDN are ASCII; 247 | /// for an "internationalized domain name", the syntax is as defined 248 | /// in [IDNA], for example "xn--tmonesimerkki-bfbb.example.net". 249 | pub const ID_FQDN : IdentificationType = IdentificationType(2); 250 | /// A fully-qualified RFC 822 email address string. An example of a 251 | /// ID_RFC822_ADDR is "jsmith@example.com". The string MUST NOT 252 | /// contain any terminators. Because of [EAI], implementations would 253 | /// be wise to treat this field as UTF-8 encoded text, not as 254 | /// pure ASCII. 255 | pub const ID_RFC822_ADDR : IdentificationType = IdentificationType(3); 256 | /// A single sixteen (16) octet IPv6 address. 257 | pub const ID_IPV6_ADDR : IdentificationType = IdentificationType(5); 258 | /// The binary Distinguished Encoding Rules (DER) encoding of an ASN.1 X.500 Distinguished 259 | /// Name. 260 | pub const ID_DER_ASN1_DN : IdentificationType = IdentificationType(9); 261 | /// The binary DER encoding of an ASN.1 X.509 GeneralName. 262 | pub const ID_DER_ASN1_GN : IdentificationType = IdentificationType(10); 263 | /// An opaque octet stream that may be used to pass vendor-specific information necessary to do 264 | /// certain proprietary types of identification. 265 | pub const ID_KEY_ID : IdentificationType = IdentificationType(11); 266 | } 267 | 268 | /// Certificate Payload 269 | /// 270 | /// The Certificate payload, denoted CERT in this document, provides a 271 | /// means to transport certificates or other authentication-related 272 | /// information via IKE. Certificate payloads SHOULD be included in an 273 | /// exchange if certificates are available to the sender. The Hash and 274 | /// URL formats of the Certificate payloads should be used in case the 275 | /// peer has indicated an ability to retrieve this information from 276 | /// elsewhere using an HTTP_CERT_LOOKUP_SUPPORTED Notify payload. Note 277 | /// that the term "Certificate payload" is somewhat misleading, because 278 | /// not all authentication mechanisms use certificates and data other 279 | /// than certificates may be passed in this payload. 280 | /// 281 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.6 282 | #[derive(Debug, PartialEq)] 283 | pub struct CertificatePayload<'a> { 284 | pub cert_encoding: CertificateEncoding, 285 | pub cert_data: &'a [u8], 286 | } 287 | 288 | /// Certificate Encoding 289 | /// 290 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.6 291 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 292 | pub struct CertificateEncoding(pub u8); 293 | 294 | #[allow(non_upper_case_globals)] 295 | #[rustfmt::skip] 296 | impl CertificateEncoding { 297 | /// PKCS #7 wrapped X.509 certificate 298 | pub const Pkcs7_X509 : CertificateEncoding = CertificateEncoding(1); 299 | /// PGP Certificate 300 | pub const PgpCert : CertificateEncoding = CertificateEncoding(2); 301 | /// DNS Signed Key 302 | pub const DnsKey : CertificateEncoding = CertificateEncoding(3); 303 | /// X.509 Certificate - Signature 304 | pub const X509Sig : CertificateEncoding = CertificateEncoding(4); 305 | /// Kerberos Token 306 | pub const Kerberos : CertificateEncoding = CertificateEncoding(6); 307 | /// Certificate Revocation List (CRL) 308 | pub const Crl : CertificateEncoding = CertificateEncoding(7); 309 | /// Authority Revocation List (ARL) 310 | pub const Arl : CertificateEncoding = CertificateEncoding(8); 311 | /// SPKI Certificate 312 | pub const SpkiCert : CertificateEncoding = CertificateEncoding(9); 313 | /// X.509 Certificate - Attribute 314 | pub const X509CertAttr : CertificateEncoding = CertificateEncoding(10); 315 | /// Deprecated (was Raw RSA Key) 316 | pub const OldRsaKey : CertificateEncoding = CertificateEncoding(11); 317 | /// Hash and URL of X.509 certificate 318 | pub const X509Cert_HashUrl : CertificateEncoding = CertificateEncoding(12); 319 | /// Hash and URL of X.509 bundle 320 | pub const X509Bundle_HashUrl : CertificateEncoding = CertificateEncoding(13); 321 | /// OCSP Content ([RFC4806](https://tools.ietf.org/html/rfc4806)) 322 | pub const OCSPContent : CertificateEncoding = CertificateEncoding(14); 323 | /// Raw Public Key ([RFC7670](https://tools.ietf.org/html/rfc7670)) 324 | pub const RawPublicKey : CertificateEncoding = CertificateEncoding(15); 325 | } 326 | 327 | /// Certificate Request Payload 328 | /// 329 | /// The Certificate Request payload, denoted CERTREQ in this document, 330 | /// provides a means to request preferred certificates via IKE and can 331 | /// appear in the IKE_INIT_SA response and/or the IKE_AUTH request. 332 | /// Certificate Request payloads MAY be included in an exchange when the 333 | /// sender needs to get the certificate of the receiver. 334 | /// 335 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.7 336 | #[derive(Debug, PartialEq)] 337 | pub struct CertificateRequestPayload<'a> { 338 | pub cert_encoding: CertificateEncoding, 339 | pub ca_data: &'a [u8], 340 | } 341 | 342 | /// Authentication Payload 343 | /// 344 | /// The Authentication payload, denoted AUTH in this document, contains 345 | /// data used for authentication purposes. 346 | /// 347 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.8 348 | #[derive(Debug, PartialEq)] 349 | pub struct AuthenticationPayload<'a> { 350 | pub auth_method: AuthenticationMethod, 351 | pub auth_data: &'a [u8], 352 | } 353 | 354 | /// Method of authentication used. 355 | /// 356 | /// See also [IKEV2IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml) for the latest values. 357 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 358 | pub struct AuthenticationMethod(pub u8); 359 | 360 | #[allow(non_upper_case_globals)] 361 | #[rustfmt::skip] 362 | impl AuthenticationMethod { 363 | /// RSA Digital Signature 364 | pub const RsaSig : AuthenticationMethod = AuthenticationMethod(1); 365 | /// Shared Key Message Integrity Code 366 | pub const SharedKeyMIC : AuthenticationMethod = AuthenticationMethod(2); 367 | /// DSS Digital Signature 368 | pub const DssSig : AuthenticationMethod = AuthenticationMethod(3); 369 | /// ECDSA with SHA-256 on the P-256 curve 370 | pub const EcdsaSha256P256 : AuthenticationMethod = AuthenticationMethod(9); 371 | /// ECDSA with SHA-384 on the P-384 curve 372 | pub const EcdsaSha384P384 : AuthenticationMethod = AuthenticationMethod(10); 373 | /// ECDSA with SHA-512 on the P-512 curve 374 | pub const EcdsaSha512P512 : AuthenticationMethod = AuthenticationMethod(11); 375 | /// Generic Secure Password Authentication Method 376 | pub const GenericPass : AuthenticationMethod = AuthenticationMethod(12); 377 | /// NULL Authentication 378 | pub const Null : AuthenticationMethod = AuthenticationMethod(13); 379 | /// Digital Signature 380 | pub const DigitalSig : AuthenticationMethod = AuthenticationMethod(14); 381 | 382 | /// Test if value is in unassigned range 383 | pub fn is_unassigned(self) -> bool { 384 | (self.0 >= 4 && self.0 <= 8) || 385 | (self.0 >= 15 && self.0 <= 200) 386 | } 387 | 388 | /// Test if value is in private use range 389 | pub fn is_private_use(self) -> bool { 390 | self.0 >= 201 391 | } 392 | } 393 | 394 | /// Nonce Payload 395 | /// 396 | /// The Nonce payload, denoted as Ni and Nr in this document for the 397 | /// initiator's and responder's nonce, respectively, contains random data used to guarantee 398 | /// liveness during an exchange and protect against replay attacks. 399 | /// 400 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.9 401 | #[derive(PartialEq)] 402 | pub struct NoncePayload<'a> { 403 | pub nonce_data: &'a [u8], 404 | } 405 | 406 | /// Notify Payload 407 | /// 408 | /// The Notify payload, denoted N in this document, is used to transmit informational data, such as 409 | /// error conditions and state transitions, to an IKE peer. A Notify payload may appear in a 410 | /// response message (usually specifying why a request was rejected), in an INFORMATIONAL exchange 411 | /// (to report an error not in an IKE request), or in any other message to indicate sender 412 | /// capabilities or to modify the meaning of the request. 413 | /// 414 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.10 415 | #[derive(PartialEq)] 416 | pub struct NotifyPayload<'a> { 417 | pub protocol_id: ProtocolID, 418 | pub spi_size: u8, 419 | pub notify_type: NotifyType, 420 | pub spi: Option<&'a [u8]>, 421 | pub notify_data: Option<&'a [u8]>, 422 | } 423 | 424 | /// Delete Payload 425 | /// 426 | /// The Delete payload, denoted D in this document, contains a 427 | /// protocol-specific Security Association identifier that the sender has 428 | /// removed from its Security Association database and is, therefore, no 429 | /// longer valid. Figure 17 shows the format of the Delete payload. It 430 | /// is possible to send multiple SPIs in a Delete payload; however, each 431 | /// SPI MUST be for the same protocol. Mixing of protocol identifiers 432 | /// MUST NOT be performed in the Delete payload. It is permitted, 433 | /// however, to include multiple Delete payloads in a single 434 | /// INFORMATIONAL exchange where each Delete payload lists SPIs for a 435 | /// different protocol. 436 | /// 437 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.11 438 | #[derive(Debug, PartialEq)] 439 | pub struct DeletePayload<'a> { 440 | pub protocol_id: ProtocolID, 441 | pub spi_size: u8, 442 | pub num_spi: u16, 443 | pub spi: &'a [u8], 444 | } 445 | 446 | /// Vendor ID Payload 447 | /// 448 | /// The Vendor ID payload, denoted V in this document, contains a vendor- 449 | /// defined constant. The constant is used by vendors to identify and 450 | /// recognize remote instances of their implementations. This mechanism 451 | /// allows a vendor to experiment with new features while maintaining 452 | /// backward compatibility. 453 | /// 454 | /// A Vendor ID payload MAY announce that the sender is capable of 455 | /// accepting certain extensions to the protocol, or it MAY simply 456 | /// identify the implementation as an aid in debugging. A Vendor ID 457 | /// payload MUST NOT change the interpretation of any information defined 458 | /// in this specification (i.e., the critical bit MUST be set to 0). 459 | /// Multiple Vendor ID payloads MAY be sent. An implementation is not 460 | /// required to send any Vendor ID payload at all. 461 | /// 462 | /// A Vendor ID payload may be sent as part of any message. Reception of 463 | /// a familiar Vendor ID payload allows an implementation to make use of 464 | /// private use numbers described throughout this document, such as 465 | /// private payloads, private exchanges, private notifications, etc. 466 | /// Unfamiliar Vendor IDs MUST be ignored. 467 | /// 468 | /// Writers of documents who wish to extend this protocol MUST define a 469 | /// Vendor ID payload to announce the ability to implement the extension 470 | /// in the document. It is expected that documents that gain acceptance 471 | /// and are standardized will be given "magic numbers" out of the Future 472 | /// Use range by IANA, and the requirement to use a Vendor ID will go 473 | /// away. 474 | /// 475 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.12 476 | #[derive(Debug, PartialEq)] 477 | pub struct VendorIDPayload<'a> { 478 | pub vendor_id: &'a [u8], 479 | } 480 | 481 | /// Type of Traffic Selector 482 | /// 483 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.13.1 484 | /// 485 | /// See also [IKEV2IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml) for the latest values. 486 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 487 | pub struct TSType(pub u8); 488 | 489 | #[allow(non_upper_case_globals)] 490 | impl TSType { 491 | /// A range of IPv4 addresses 492 | pub const IPv4AddrRange: TSType = TSType(7); 493 | /// A range of IPv6 addresses 494 | pub const IPv6AddrRange: TSType = TSType(8); 495 | /// Fibre Channel Traffic Selectors ([RFC4595](https://tools.ietf.org/html/rfc4595)) 496 | pub const FcAddrRange: TSType = TSType(9); 497 | } 498 | 499 | /// Traffic Selector 500 | /// 501 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.13.1 502 | #[derive(Debug, PartialEq)] 503 | pub struct TrafficSelector<'a> { 504 | pub ts_type: TSType, 505 | pub ip_proto_id: u8, 506 | pub sel_length: u16, 507 | pub start_port: u16, 508 | pub end_port: u16, 509 | pub start_addr: &'a [u8], 510 | pub end_addr: &'a [u8], 511 | } 512 | 513 | fn ipv4_from_slice(b: &[u8]) -> Ipv4Addr { 514 | Ipv4Addr::new(b[0], b[1], b[2], b[3]) 515 | } 516 | 517 | fn ipv6_from_slice(b: &[u8]) -> Ipv6Addr { 518 | Ipv6Addr::new( 519 | (b[0] as u16) << 8 | (b[1] as u16), 520 | (b[2] as u16) << 8 | (b[3] as u16), 521 | (b[4] as u16) << 8 | (b[5] as u16), 522 | (b[6] as u16) << 8 | (b[7] as u16), 523 | (b[8] as u16) << 8 | (b[9] as u16), 524 | (b[10] as u16) << 8 | (b[11] as u16), 525 | (b[12] as u16) << 8 | (b[13] as u16), 526 | (b[14] as u16) << 8 | (b[15] as u16), 527 | ) 528 | } 529 | 530 | impl<'a> TrafficSelector<'a> { 531 | pub fn get_ts_type(&self) -> TSType { 532 | self.ts_type 533 | } 534 | 535 | pub fn get_start_addr(&self) -> Option { 536 | match self.ts_type { 537 | TSType::IPv4AddrRange => Some(IpAddr::V4(ipv4_from_slice(self.start_addr))), 538 | TSType::IPv6AddrRange => Some(IpAddr::V6(ipv6_from_slice(self.start_addr))), 539 | _ => None, 540 | } 541 | } 542 | 543 | pub fn get_end_addr(&self) -> Option { 544 | match self.ts_type { 545 | TSType::IPv4AddrRange => Some(IpAddr::V4(ipv4_from_slice(self.end_addr))), 546 | TSType::IPv6AddrRange => Some(IpAddr::V6(ipv6_from_slice(self.end_addr))), 547 | _ => None, 548 | } 549 | } 550 | } 551 | 552 | /// Traffic Selector Payload 553 | /// 554 | /// The Traffic Selector payload, denoted TS in this document, allows 555 | /// peers to identify packet flows for processing by IPsec security 556 | /// services. The Traffic Selector payload consists of the IKE generic 557 | /// payload header followed by individual Traffic Selectors. 558 | /// 559 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.13 560 | #[derive(Debug, PartialEq)] 561 | pub struct TrafficSelectorPayload<'a> { 562 | pub num_ts: u8, 563 | pub reserved: &'a [u8], // 3 bytes 564 | pub ts: Vec>, 565 | } 566 | 567 | /// Encrypted Payload 568 | /// 569 | /// The Encrypted payload, denoted SK {...} in this document, contains 570 | /// other payloads in encrypted form. The Encrypted payload, if present 571 | /// in a message, MUST be the last payload in the message. Often, it is 572 | /// the only payload in the message. This payload is also called the 573 | /// "Encrypted and Authenticated" payload. 574 | #[derive(Debug, PartialEq)] 575 | pub struct EncryptedPayload<'a>(pub &'a [u8]); 576 | 577 | /// IKE Message Payload Content 578 | /// 579 | /// The content of an IKE message is one of the defined payloads. 580 | /// 581 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.2 582 | #[derive(Debug, PartialEq)] 583 | pub enum IkeV2PayloadContent<'a> { 584 | SA(Vec>), 585 | KE(KeyExchangePayload<'a>), 586 | IDi(IdentificationPayload<'a>), 587 | IDr(IdentificationPayload<'a>), 588 | Certificate(CertificatePayload<'a>), 589 | CertificateRequest(CertificateRequestPayload<'a>), 590 | Authentication(AuthenticationPayload<'a>), 591 | Nonce(NoncePayload<'a>), 592 | Notify(NotifyPayload<'a>), 593 | Delete(DeletePayload<'a>), 594 | VendorID(VendorIDPayload<'a>), 595 | TSi(TrafficSelectorPayload<'a>), 596 | TSr(TrafficSelectorPayload<'a>), 597 | Encrypted(EncryptedPayload<'a>), 598 | 599 | Unknown(&'a [u8]), 600 | 601 | Dummy, 602 | } 603 | 604 | /// Generic Payload Header 605 | /// 606 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.2 607 | #[derive(Clone, Debug, PartialEq)] 608 | pub struct IkeV2PayloadHeader { 609 | pub next_payload_type: IkePayloadType, 610 | pub critical: bool, 611 | pub reserved: u8, 612 | pub payload_length: u16, 613 | } 614 | 615 | /// IKE Message Payload 616 | /// 617 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3 618 | #[derive(Debug, PartialEq)] 619 | pub struct IkeV2Payload<'a> { 620 | pub hdr: IkeV2PayloadHeader, 621 | pub content: IkeV2PayloadContent<'a>, 622 | } 623 | -------------------------------------------------------------------------------- /src/ikev2_debug.rs: -------------------------------------------------------------------------------- 1 | use crate::ikev2::*; 2 | use crate::ikev2_transforms::*; 3 | use rusticata_macros::debug::HexSlice; 4 | use std::fmt; 5 | 6 | // ------------------------- ikev2_transforms.rs ------------------------------ 7 | // 8 | impl<'a> fmt::Debug for IkeV2RawTransform<'a> { 9 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 10 | let (tf_type, tf_id) = match self.transform_type { 11 | IkeTransformType::EncryptionAlgorithm => ( 12 | "EncryptionAlgorithm".to_string(), 13 | format!("{:?}", self.transform_id), 14 | ), 15 | IkeTransformType::PseudoRandomFunction => ( 16 | "PseudoRandomFunction".to_string(), 17 | format!("{:?}", self.transform_id), 18 | ), 19 | IkeTransformType::IntegrityAlgorithm => ( 20 | "IntegrityAlgorithm".to_string(), 21 | format!("{:?}", self.transform_id), 22 | ), 23 | IkeTransformType::DiffieHellmanGroup => ( 24 | "DiffieHellmanGroup".to_string(), 25 | format!("{:?}", self.transform_id), 26 | ), 27 | IkeTransformType::ExtendedSequenceNumbers => ( 28 | "ExtendedSequenceNumbers".to_string(), 29 | format!("{:?}", self.transform_id), 30 | ), 31 | _ => ( 32 | format!("", self.transform_type.0), 33 | "".to_string(), 34 | ), 35 | }; 36 | fmt.debug_struct("IkeV2RawTransform") 37 | .field("last", &self.last) 38 | .field("reserved1", &self.reserved1) 39 | .field("transform_length", &self.transform_length) 40 | .field("transform_type", &tf_type) 41 | .field("reserved2", &self.reserved2) 42 | .field("transform_id", &tf_id) 43 | .field("attributes", &self.attributes) 44 | .finish() 45 | } 46 | } 47 | 48 | // ------------------------- ikev2.rs ------------------------------ 49 | 50 | impl<'a> fmt::Debug for NoncePayload<'a> { 51 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 52 | fmt.debug_struct("NoncePayload") 53 | .field("nonce_data", &HexSlice(self.nonce_data)) 54 | .finish() 55 | } 56 | } 57 | 58 | impl<'a> fmt::Debug for NotifyPayload<'a> { 59 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 60 | fmt.debug_struct("NotifyPayload") 61 | .field("protocol_id", &self.protocol_id) 62 | .field("spi_size", &self.spi_size) 63 | .field("notify_type", &self.notify_type) 64 | .field("spi", &self.spi) 65 | .field("notify_data", &self.notify_data.map(HexSlice)) 66 | .finish() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ikev2_notify.rs: -------------------------------------------------------------------------------- 1 | use rusticata_macros::newtype_enum; 2 | 3 | /// Notify Message Type 4 | /// 5 | /// Notification information can be error messages specifying why an SA 6 | /// could not be established. It can also be status data that a process 7 | /// managing an SA database wishes to communicate with a peer process. 8 | /// 9 | /// The table below lists the notification messages and their 10 | /// corresponding values. The number of different error statuses was 11 | /// greatly reduced from IKEv1 both for simplification and to avoid 12 | /// giving configuration information to probers. 13 | /// 14 | /// Types in the range 0 - 16383 are intended for reporting errors. An 15 | /// implementation receiving a Notify payload with one of these types 16 | /// that it does not recognize in a response MUST assume that the 17 | /// corresponding request has failed entirely. Unrecognized error types 18 | /// in a request and status types in a request or response MUST be 19 | /// ignored, and they should be logged. 20 | /// 21 | /// Notify payloads with status types MAY be added to any message and 22 | /// MUST be ignored if not recognized. They are intended to indicate 23 | /// capabilities, and as part of SA negotiation, are used to negotiate 24 | /// non-cryptographic parameters. 25 | /// 26 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.10.1 27 | /// 28 | /// Extensions: 29 | /// 30 | /// - [RFC4555](https://tools.ietf.org/html/rfc4555) IKEv2 Mobility and Multihoming Protocol (MOBIKE) 31 | /// - [RFC4739](https://tools.ietf.org/html/rfc4739) Multiple Authentication Exchanges in the Internet Key Exchange (IKEv2) Protocol 32 | /// - [RFC5685](https://tools.ietf.org/html/rfc5685) Redirect Mechanism for the Internet Key Exchange Protocol Version 2 (IKEv2) 33 | /// - [RFC5723](https://tools.ietf.org/html/rfc5723) Internet Key Exchange Protocol Version 2 (IKEv2) Session Resumption 34 | /// - [RFC7427](https://tools.ietf.org/html/rfc7427) Signature Authentication in the Internet Key Exchange Version 2 (IKEv2) 35 | /// 36 | /// See also [IKEV2IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml) for the latest values. 37 | #[derive(Clone, Copy, PartialEq, Eq)] 38 | pub struct NotifyType(pub u16); 39 | 40 | newtype_enum! { 41 | impl debug NotifyType { 42 | // error types 43 | UNSUPPORTED_CRITICAL_PAYLOAD = 1, 44 | INVALID_IKE_SPI = 4, 45 | INVALID_MAJOR_VERSION = 5, 46 | INVALID_SYNTAX = 7, 47 | INVALID_MESSAGE_ID = 9, 48 | INVALID_SPI = 11, 49 | NO_PROPOSAL_CHOSEN = 14, 50 | INVALID_KE_PAYLOAD = 17, 51 | AUTHENTICATION_FAILED = 24, 52 | SINGLE_PAIR_REQUIRED = 34, 53 | NO_ADDITIONAL_SAS = 35, 54 | INTERNAL_ADDRESS_FAILURE = 36, 55 | FAILED_CP_REQUIRED = 37, 56 | TS_UNACCEPTABLE = 38, 57 | INVALID_SELECTORS = 39, 58 | TEMPORARY_FAILURE = 43, 59 | CHILD_SA_NOT_FOUND = 44, 60 | // status types 61 | INITIAL_CONTACT = 16384, 62 | SET_WINDOW_SIZE = 16385, 63 | ADDITIONAL_TS_POSSIBLE = 16386, 64 | IPCOMP_SUPPORTED = 16387, 65 | NAT_DETECTION_SOURCE_IP = 16388, 66 | NAT_DETECTION_DESTINATION_IP = 16389, 67 | COOKIE = 16390, 68 | USE_TRANSPORT_MODE = 16391, 69 | HTTP_CERT_LOOKUP_SUPPORTED = 16392, 70 | REKEY_SA = 16393, 71 | ESP_TFC_PADDING_NOT_SUPPORTED = 16394, 72 | NON_FIRST_FRAGMENTS_ALSO = 16395, 73 | // 74 | MULTIPLE_AUTH_SUPPORTED = 16404, 75 | ANOTHER_AUTH_FOLLOWS = 16405, 76 | REDIRECT_SUPPORTED = 16406, 77 | // 78 | IKEV2_FRAGMENTATION_SUPPORTED = 16430, 79 | SIGNATURE_HASH_ALGORITHMS = 16431, 80 | } 81 | } 82 | 83 | impl NotifyType { 84 | pub fn is_error(self) -> bool { 85 | self.0 < 16384 86 | } 87 | pub fn is_status(self) -> bool { 88 | self.0 > 16384 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ikev2_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::error::IPsecError; 2 | use crate::ikev2::*; 3 | use crate::ikev2_notify::NotifyType; 4 | use crate::ikev2_transforms::*; 5 | use nom::bytes::streaming::take; 6 | use nom::combinator::{complete, cond, map, map_parser, verify}; 7 | use nom::error::{make_error, ErrorKind}; 8 | use nom::multi::{count, many1}; 9 | use nom::number::streaming::{be_u16, be_u32, be_u64, be_u8}; 10 | use nom::{Err, IResult, Needed}; 11 | 12 | pub fn parse_ikev2_header(i: &[u8]) -> IResult<&[u8], IkeV2Header> { 13 | if i.len() < 28 { 14 | return Err(Err::Incomplete(Needed::new(28))); 15 | } 16 | let (i, init_spi) = be_u64(i)?; 17 | let (i, resp_spi) = be_u64(i)?; 18 | let (i, next_payload) = map(be_u8, IkePayloadType)(i)?; 19 | let (i, vers) = be_u8(i)?; 20 | let maj_ver = vers >> 4; 21 | let min_ver = vers & 0b1111; 22 | let (i, exch_type) = map(be_u8, IkeExchangeType)(i)?; 23 | let (i, flags) = be_u8(i)?; 24 | let (i, msg_id) = be_u32(i)?; 25 | let (i, length) = be_u32(i)?; 26 | let hdr = IkeV2Header { 27 | init_spi, 28 | resp_spi, 29 | next_payload, 30 | maj_ver, 31 | min_ver, 32 | exch_type, 33 | flags, 34 | msg_id, 35 | length, 36 | }; 37 | Ok((i, hdr)) 38 | } 39 | 40 | #[inline] 41 | fn bits_split_1(i: &[u8]) -> IResult<&[u8], (u8, u8)> { 42 | let (i, b) = be_u8(i)?; 43 | let b1 = b >> 7; 44 | let b2_7 = b & 0b_0111_1111; 45 | Ok((i, (b1, b2_7))) 46 | } 47 | 48 | pub fn parse_ikev2_payload_generic(i: &[u8]) -> IResult<&[u8], IkeV2GenericPayload> { 49 | let (i, next_payload_type) = map(be_u8, IkePayloadType)(i)?; 50 | let (i, b) = bits_split_1(i)?; 51 | let (i, payload_length) = verify(be_u16, |&n| n >= 4)(i)?; 52 | let (i, payload) = take(payload_length - 4)(i)?; 53 | let hdr = IkeV2PayloadHeader { 54 | next_payload_type, 55 | critical: b.0 == 1, 56 | reserved: b.1, 57 | payload_length, 58 | }; 59 | let payload = IkeV2GenericPayload { hdr, payload }; 60 | Ok((i, payload)) 61 | } 62 | 63 | pub fn parse_ikev2_transform(i: &[u8]) -> IResult<&[u8], IkeV2RawTransform> { 64 | let (i, last) = be_u8(i)?; 65 | let (i, reserved1) = be_u8(i)?; 66 | let (i, transform_length) = be_u16(i)?; 67 | let (i, transform_type) = be_u8(i)?; 68 | let (i, reserved2) = be_u8(i)?; 69 | let (i, transform_id) = be_u16(i)?; 70 | // we have to specify a callback here to force lazy evaluation, 71 | // because the function arguments are evaluated *before* the test (causing underflow) 72 | let (i, attributes) = cond(transform_length > 8, |d| take(transform_length - 8)(d))(i)?; 73 | let transform = IkeV2RawTransform { 74 | last, 75 | reserved1, 76 | transform_length, 77 | transform_type: IkeTransformType(transform_type), 78 | reserved2, 79 | transform_id, 80 | attributes, 81 | }; 82 | Ok((i, transform)) 83 | } 84 | 85 | pub fn parse_ikev2_proposal(i: &[u8]) -> IResult<&[u8], IkeV2Proposal> { 86 | if i.len() < 8 { 87 | return Err(Err::Incomplete(Needed::new(8))); 88 | } 89 | let (i, last) = be_u8(i)?; 90 | let (i, reserved) = be_u8(i)?; 91 | let (i, proposal_length) = be_u16(i)?; 92 | let (i, proposal_num) = be_u8(i)?; 93 | let (i, protocol_id) = map(be_u8, ProtocolID)(i)?; 94 | let (i, spi_size) = be_u8(i)?; 95 | let (i, num_transforms) = be_u8(i)?; 96 | let (i, spi) = cond(spi_size > 0, take(spi_size))(i)?; 97 | if proposal_length < (8u16 + spi_size as u16) { 98 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 99 | } 100 | let (i, transforms) = map_parser( 101 | take(proposal_length - 8 - (spi_size as u16)), 102 | count(parse_ikev2_transform, num_transforms as usize), 103 | )(i)?; 104 | let proposal = IkeV2Proposal { 105 | last, 106 | reserved, 107 | proposal_length, 108 | proposal_num, 109 | protocol_id, 110 | spi_size, 111 | num_transforms, 112 | spi, 113 | transforms, 114 | }; 115 | Ok((i, proposal)) 116 | } 117 | 118 | pub fn parse_ikev2_payload_sa(i: &[u8], _length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 119 | map( 120 | many1(complete(parse_ikev2_proposal)), 121 | IkeV2PayloadContent::SA, 122 | )(i) 123 | } 124 | 125 | pub fn parse_ikev2_payload_kex(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 126 | if length < 4 { 127 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 128 | } 129 | let (i, dh_group) = map(be_u16, IkeTransformDHType)(i)?; 130 | let (i, reserved) = be_u16(i)?; 131 | let (i, kex_data) = take(length - 4)(i)?; 132 | let payload = KeyExchangePayload { 133 | dh_group, 134 | reserved, 135 | kex_data, 136 | }; 137 | Ok((i, IkeV2PayloadContent::KE(payload))) 138 | } 139 | 140 | pub fn parse_ikev2_payload_ident_init( 141 | i: &[u8], 142 | length: u16, 143 | ) -> IResult<&[u8], IkeV2PayloadContent> { 144 | if length < 4 { 145 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 146 | } 147 | let (i, id_type) = map(be_u8, IdentificationType)(i)?; 148 | let (i, reserved1) = be_u8(i)?; 149 | let (i, reserved2) = be_u16(i)?; 150 | let (i, ident_data) = take(length - 4)(i)?; 151 | let payload = IdentificationPayload { 152 | id_type, 153 | reserved1, 154 | reserved2, 155 | ident_data, 156 | }; 157 | Ok((i, IkeV2PayloadContent::IDi(payload))) 158 | } 159 | 160 | pub fn parse_ikev2_payload_ident_resp( 161 | i: &[u8], 162 | length: u16, 163 | ) -> IResult<&[u8], IkeV2PayloadContent> { 164 | if length < 4 { 165 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 166 | } 167 | let (i, id_type) = map(be_u8, IdentificationType)(i)?; 168 | let (i, reserved1) = be_u8(i)?; 169 | let (i, reserved2) = be_u16(i)?; 170 | let (i, ident_data) = take(length - 4)(i)?; 171 | let payload = IdentificationPayload { 172 | id_type, 173 | reserved1, 174 | reserved2, 175 | ident_data, 176 | }; 177 | Ok((i, IkeV2PayloadContent::IDr(payload))) 178 | } 179 | 180 | pub fn parse_ikev2_payload_certificate( 181 | i: &[u8], 182 | length: u16, 183 | ) -> IResult<&[u8], IkeV2PayloadContent> { 184 | if length < 1 { 185 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 186 | } 187 | let (i, cert_encoding) = map(be_u8, CertificateEncoding)(i)?; 188 | let (i, cert_data) = take(length - 1)(i)?; 189 | let payload = CertificatePayload { 190 | cert_encoding, 191 | cert_data, 192 | }; 193 | Ok((i, IkeV2PayloadContent::Certificate(payload))) 194 | } 195 | 196 | pub fn parse_ikev2_payload_certificate_request( 197 | i: &[u8], 198 | length: u16, 199 | ) -> IResult<&[u8], IkeV2PayloadContent> { 200 | if length < 1 { 201 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 202 | } 203 | let (i, cert_encoding) = map(be_u8, CertificateEncoding)(i)?; 204 | let (i, ca_data) = take(length - 1)(i)?; 205 | let payload = CertificateRequestPayload { 206 | cert_encoding, 207 | ca_data, 208 | }; 209 | Ok((i, IkeV2PayloadContent::CertificateRequest(payload))) 210 | } 211 | 212 | pub fn parse_ikev2_payload_authentication( 213 | i: &[u8], 214 | length: u16, 215 | ) -> IResult<&[u8], IkeV2PayloadContent> { 216 | if length < 4 { 217 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 218 | } 219 | let (i, auth_method) = map(be_u8, AuthenticationMethod)(i)?; 220 | let (i, auth_data) = take(length - 4)(i)?; 221 | let payload = AuthenticationPayload { 222 | auth_method, 223 | auth_data, 224 | }; 225 | Ok((i, IkeV2PayloadContent::Authentication(payload))) 226 | } 227 | 228 | pub fn parse_ikev2_payload_nonce(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 229 | let (i, nonce_data) = take(length)(i)?; 230 | Ok((i, IkeV2PayloadContent::Nonce(NoncePayload { nonce_data }))) 231 | } 232 | 233 | pub fn parse_ikev2_payload_notify(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 234 | let (i, protocol_id) = map(be_u8, ProtocolID)(i)?; 235 | let (i, spi_size) = be_u8(i)?; 236 | let (i, notify_type) = map(be_u16, NotifyType)(i)?; 237 | let (i, spi) = cond(spi_size > 0, take(spi_size))(i)?; 238 | let (i, notify_data) = cond( 239 | length > 8 + spi_size as u16, 240 | // we have to specify a callback here to force lazy evaluation, 241 | // because the function arguments are evaluated *before* the test (causing underflow) 242 | |d| take(length - (8 + spi_size as u16))(d), 243 | )(i)?; 244 | let payload = NotifyPayload { 245 | protocol_id, 246 | spi_size, 247 | notify_type, 248 | spi, 249 | notify_data, 250 | }; 251 | Ok((i, IkeV2PayloadContent::Notify(payload))) 252 | } 253 | 254 | pub fn parse_ikev2_payload_vendor_id(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 255 | if length < 8 { 256 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 257 | } 258 | let (i, vendor_id) = take(length - 8)(i)?; 259 | Ok(( 260 | i, 261 | IkeV2PayloadContent::VendorID(VendorIDPayload { vendor_id }), 262 | )) 263 | } 264 | 265 | pub fn parse_ikev2_payload_delete(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 266 | if length < 8 { 267 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 268 | } 269 | let (i, protocol_id) = map(be_u8, ProtocolID)(i)?; 270 | let (i, spi_size) = be_u8(i)?; 271 | let (i, num_spi) = be_u16(i)?; 272 | let (i, spi) = take(length - 8)(i)?; 273 | let payload = DeletePayload { 274 | protocol_id, 275 | spi_size, 276 | num_spi, 277 | spi, 278 | }; 279 | Ok((i, IkeV2PayloadContent::Delete(payload))) 280 | } 281 | 282 | fn parse_ts_addr(i: &[u8], t: TSType) -> IResult<&[u8], &[u8]> { 283 | match t { 284 | TSType::IPv4AddrRange => take(4usize)(i), 285 | TSType::IPv6AddrRange => take(16usize)(i), 286 | _ => Err(nom::Err::Error(make_error(i, ErrorKind::Switch))), 287 | } 288 | } 289 | 290 | fn parse_ikev2_ts(i: &[u8]) -> IResult<&[u8], TrafficSelector> { 291 | let (i, ts_type) = map(be_u8, TSType)(i)?; 292 | let (i, ip_proto_id) = be_u8(i)?; 293 | let (i, sel_length) = be_u16(i)?; 294 | let (i, start_port) = be_u16(i)?; 295 | let (i, end_port) = be_u16(i)?; 296 | let (i, start_addr) = parse_ts_addr(i, ts_type)?; 297 | let (i, end_addr) = parse_ts_addr(i, ts_type)?; 298 | let ts = TrafficSelector { 299 | ts_type, 300 | ip_proto_id, 301 | sel_length, 302 | start_port, 303 | end_port, 304 | start_addr, 305 | end_addr, 306 | }; 307 | Ok((i, ts)) 308 | } 309 | 310 | pub fn parse_ikev2_payload_ts(i: &[u8], length: u16) -> IResult<&[u8], TrafficSelectorPayload> { 311 | if length < 4 { 312 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 313 | } 314 | let (i, num_ts) = be_u8(i)?; 315 | let (i, reserved) = take(3usize)(i)?; 316 | let (i, ts) = map_parser(take(length - 4), many1(complete(parse_ikev2_ts)))(i)?; 317 | let payload = TrafficSelectorPayload { 318 | num_ts, 319 | reserved, 320 | ts, 321 | }; 322 | Ok((i, payload)) 323 | } 324 | 325 | pub fn parse_ikev2_payload_ts_init(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 326 | map( 327 | |d| parse_ikev2_payload_ts(d, length), 328 | IkeV2PayloadContent::TSi, 329 | )(i) 330 | } 331 | 332 | pub fn parse_ikev2_payload_ts_resp(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 333 | map( 334 | |d| parse_ikev2_payload_ts(d, length), 335 | IkeV2PayloadContent::TSr, 336 | )(i) 337 | } 338 | 339 | pub fn parse_ikev2_payload_encrypted(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 340 | map(take(length), |d| { 341 | IkeV2PayloadContent::Encrypted(EncryptedPayload(d)) 342 | })(i) 343 | } 344 | 345 | pub fn parse_ikev2_payload_unknown(i: &[u8], length: u16) -> IResult<&[u8], IkeV2PayloadContent> { 346 | map(take(length), IkeV2PayloadContent::Unknown)(i) 347 | } 348 | 349 | #[rustfmt::skip] 350 | pub fn parse_ikev2_payload_with_type( 351 | i: &[u8], 352 | length: u16, 353 | next_payload_type: IkePayloadType, 354 | ) -> IResult<&[u8], IkeV2PayloadContent> { 355 | let f = match next_payload_type { 356 | // IkePayloadType::NoNextPayload => parse_ikev2_payload_unknown, // XXX ? 357 | IkePayloadType::SecurityAssociation => parse_ikev2_payload_sa, 358 | IkePayloadType::KeyExchange => parse_ikev2_payload_kex, 359 | IkePayloadType::IdentInitiator => parse_ikev2_payload_ident_init, 360 | IkePayloadType::IdentResponder => parse_ikev2_payload_ident_resp, 361 | IkePayloadType::Certificate => parse_ikev2_payload_certificate, 362 | IkePayloadType::CertificateRequest => parse_ikev2_payload_certificate_request, 363 | IkePayloadType::Authentication => parse_ikev2_payload_authentication, 364 | IkePayloadType::Nonce => parse_ikev2_payload_nonce, 365 | IkePayloadType::Notify => parse_ikev2_payload_notify, 366 | IkePayloadType::Delete => parse_ikev2_payload_delete, 367 | IkePayloadType::VendorID => parse_ikev2_payload_vendor_id, 368 | IkePayloadType::TrafficSelectorInitiator => parse_ikev2_payload_ts_init, 369 | IkePayloadType::TrafficSelectorResponder => parse_ikev2_payload_ts_resp, 370 | IkePayloadType::EncryptedAndAuthenticated => parse_ikev2_payload_encrypted, 371 | // None => parse_ikev2_payload_unknown, 372 | _ => parse_ikev2_payload_unknown, 373 | // _ => panic!("unknown type {}",next_payload_type), 374 | }; 375 | map_parser(take(length),move |d| f(d, length))(i) 376 | } 377 | 378 | fn parse_ikev2_payload_list_fold<'a>( 379 | res_v: Result>, IPsecError>, 380 | p: IkeV2GenericPayload<'a>, 381 | ) -> Result>, IPsecError> { 382 | let mut v = res_v?; 383 | // println!("parse_payload_list_fold: v.len={} p={:?}",v.len(),p); 384 | debug_assert!(!v.is_empty()); 385 | let last_payload = v 386 | .last() 387 | .expect("parse_payload_list_fold: called with empty input"); 388 | let next_payload_type = last_payload.hdr.next_payload_type; 389 | if p.hdr.payload_length < 4 { 390 | return Err(IPsecError::PayloadTooSmall); 391 | } 392 | match parse_ikev2_payload_with_type(p.payload, p.hdr.payload_length - 4, next_payload_type) { 393 | Ok((rem, p2)) => { 394 | // let (rem, p2) = parse_ikev2_payload_with_type(p.payload, p.hdr.payload_length - 4, next_payload_type)?; 395 | if !rem.is_empty() { 396 | return Err(IPsecError::ExtraBytesInPayload); // XXX should this be only a warning? 397 | } 398 | let payload = IkeV2Payload { 399 | hdr: p.hdr.clone(), 400 | content: p2, 401 | }; 402 | v.push(payload); 403 | Ok(v) 404 | } 405 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(IPsecError::NomError(e.code)), 406 | Err(nom::Err::Incomplete(_)) => Err(IPsecError::NomError(ErrorKind::Complete)), 407 | } 408 | } 409 | 410 | pub fn parse_ikev2_payload_list( 411 | i: &[u8], 412 | initial_type: IkePayloadType, 413 | ) -> IResult<&[u8], Result, IPsecError>> { 414 | // XXX fold manually, because fold_many1 requires accumulator to have Clone, and we don't want 415 | // XXX to implement that for IkeV2Payload 416 | let mut acc = Ok(vec![IkeV2Payload { 417 | hdr: IkeV2PayloadHeader { 418 | next_payload_type: initial_type, 419 | critical: false, 420 | reserved: 0, 421 | payload_length: 0, 422 | }, 423 | content: IkeV2PayloadContent::Dummy, 424 | }]); 425 | #[allow(clippy::clone_double_ref)] 426 | let mut i = i.clone(); 427 | loop { 428 | if i.is_empty() { 429 | break; 430 | } 431 | 432 | let (rem, p) = complete(parse_ikev2_payload_generic)(i)?; 433 | 434 | acc = parse_ikev2_payload_list_fold(acc, p); 435 | 436 | i = rem; 437 | } 438 | Ok((i, acc)) 439 | // XXX should we split_first() the vector and return all but the first element ? 440 | } 441 | 442 | /// Parse an IKEv2 message 443 | /// 444 | /// Parse the IKEv2 header and payload list 445 | #[allow(clippy::type_complexity)] 446 | pub fn parse_ikev2_message( 447 | i: &[u8], 448 | ) -> IResult<&[u8], (IkeV2Header, Result, IPsecError>)> { 449 | let (i, hdr) = parse_ikev2_header(i)?; 450 | if hdr.length < 28 { 451 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 452 | } 453 | let (i, msg) = map_parser(take(hdr.length - 28), |d| { 454 | parse_ikev2_payload_list(d, hdr.next_payload) 455 | })(i)?; 456 | Ok((i, (hdr, msg))) 457 | } 458 | 459 | #[cfg(test)] 460 | mod tests { 461 | use crate::ikev2_parser::*; 462 | 463 | #[rustfmt::skip] 464 | static IKEV2_INIT_REQ: &[u8] = &[ 465 | 0x01, 0xf8, 0xc3, 0xd4, 0xbb, 0x77, 0x3f, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 466 | 0x21, 0x20, 0x22, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x48, 0x22, 0x00, 0x00, 0x30, 467 | 0x00, 0x00, 0x00, 0x2c, 0x01, 0x01, 0x00, 0x04, 0x03, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x14, 468 | 0x80, 0x0e, 0x00, 0x80, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x08, 469 | 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x1e, 0x28, 0x00, 0x00, 0x88, 470 | 0x00, 0x1e, 0x00, 0x00, 0x8f, 0xe6, 0xf3, 0x6e, 0x88, 0x7b, 0x18, 0x9b, 0x5e, 0xce, 0xf2, 0x56, 471 | 0xf9, 0x8d, 0x76, 0xaa, 0xcb, 0x07, 0xb3, 0xb9, 0x58, 0xee, 0x73, 0xea, 0x7b, 0x73, 0xb1, 0x04, 472 | 0x7e, 0xa4, 0x2a, 0x4e, 0x44, 0x1f, 0xb9, 0x3e, 0xf9, 0xa9, 0xab, 0x0c, 0x54, 0x5a, 0xa7, 0x46, 473 | 0x2e, 0x58, 0x3c, 0x06, 0xb2, 0xed, 0x91, 0x8d, 0x11, 0xca, 0x67, 0xdb, 0x21, 0x6b, 0xb8, 0xad, 474 | 0xbf, 0x57, 0x3f, 0xba, 0x5a, 0xa6, 0x7d, 0x49, 0x83, 0x4b, 0xa9, 0x93, 0x6f, 0x4c, 0xe9, 0x66, 475 | 0xcd, 0x57, 0x5c, 0xba, 0x07, 0x42, 0xfa, 0x0b, 0xe8, 0xb9, 0xd0, 0x25, 0xc4, 0xb9, 0xdf, 0x29, 476 | 0xd7, 0xe4, 0x6e, 0xd6, 0x54, 0x78, 0xaa, 0x95, 0x02, 0xbf, 0x25, 0x55, 0x71, 0xfa, 0x9e, 0xcb, 477 | 0x05, 0xea, 0x8f, 0x7b, 0x14, 0x0e, 0x1d, 0xdf, 0xb4, 0x03, 0x5f, 0x2d, 0x21, 0x66, 0x58, 0x6e, 478 | 0x42, 0x72, 0x32, 0x03, 0x29, 0x00, 0x00, 0x24, 0xe3, 0x3b, 0x52, 0xaa, 0x6f, 0x6d, 0x62, 0x87, 479 | 0x16, 0xd7, 0xab, 0xc6, 0x45, 0xa6, 0xcc, 0x97, 0x07, 0x43, 0x3d, 0x85, 0x83, 0xde, 0xab, 0x97, 480 | 0xdb, 0xbf, 0x08, 0xce, 0x0f, 0xad, 0x59, 0x71, 0x29, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x40, 0x04, 481 | 0xcc, 0xc0, 0x64, 0x5c, 0x1e, 0xeb, 0xc2, 0x1d, 0x09, 0x2b, 0xf0, 0x7f, 0xca, 0x34, 0xc3, 0xe6, 482 | 0x2b, 0x20, 0xec, 0x8f, 0x29, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x40, 0x05, 0x15, 0x39, 0x75, 0x77, 483 | 0xf5, 0x54, 0x87, 0xa3, 0x8f, 0xd8, 0xaf, 0x70, 0xb0, 0x9c, 0x20, 0x9c, 0xff, 0x4a, 0x37, 0xd1, 484 | 0x29, 0x00, 0x00, 0x10, 0x00, 0x00, 0x40, 0x2f, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 485 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x40, 0x16 486 | ]; 487 | 488 | #[test] 489 | fn test_ikev2_init_req() { 490 | let empty = &b""[..]; 491 | let bytes = &IKEV2_INIT_REQ[0..28]; 492 | let expected = Ok(( 493 | empty, 494 | IkeV2Header { 495 | init_spi: 0x01f8c3d4bb773f2f, 496 | resp_spi: 0x0, 497 | next_payload: IkePayloadType::SecurityAssociation, 498 | maj_ver: 2, 499 | min_ver: 0, 500 | exch_type: IkeExchangeType::IKE_SA_INIT, 501 | flags: 0x8, 502 | msg_id: 0, 503 | length: 328, 504 | }, 505 | )); 506 | let res = parse_ikev2_header(bytes); 507 | assert_eq!(res, expected); 508 | } 509 | 510 | static IKEV2_INIT_RESP: &[u8] = include_bytes!("../assets/ike-sa-init-resp.bin"); 511 | 512 | #[test] 513 | fn test_ikev2_init_resp() { 514 | let bytes = IKEV2_INIT_RESP; 515 | let (rem, ref hdr) = parse_ikev2_header(bytes).expect("parsing header failed"); 516 | let (rem2, res_p) = 517 | parse_ikev2_payload_list(rem, hdr.next_payload).expect("parsing payload failed"); 518 | assert!(rem2.is_empty()); 519 | let p = res_p.expect("parsing payload failed"); 520 | // there are 5 items + dummy => 6 521 | assert_eq!(p.len(), 6); 522 | // first one is always dummy 523 | assert_eq!(p[0].content, IkeV2PayloadContent::Dummy); 524 | } 525 | 526 | #[rustfmt::skip] 527 | static IKEV2_PAYLOAD_SA: &[u8] = &[ 528 | 0x22, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x24, 0x01, 0x01, 0x00, 0x03, 0x03, 0x00, 0x00, 0x0c, 529 | 0x01, 0x00, 0x00, 0x14, 0x80, 0x0e, 0x00, 0x80, 0x03, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x05, 530 | 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x1e 531 | ]; 532 | 533 | #[test] 534 | fn test_ikev2_payload_sa() { 535 | let bytes = IKEV2_PAYLOAD_SA; 536 | let expected1 = IkeV2GenericPayload { 537 | hdr: IkeV2PayloadHeader { 538 | next_payload_type: IkePayloadType::KeyExchange, 539 | critical: false, 540 | reserved: 0, 541 | payload_length: 40, 542 | }, 543 | payload: &bytes[4..], 544 | }; 545 | let (_, res) = parse_ikev2_payload_generic(bytes).expect("Failed to parse"); 546 | assert_eq!(res, expected1); 547 | let attrs1 = &[0x80, 0x0e, 0x00, 0x80]; 548 | let expected2 = IkeV2PayloadContent::SA(vec![IkeV2Proposal { 549 | last: 0, 550 | reserved: 0, 551 | proposal_length: 36, 552 | proposal_num: 1, 553 | protocol_id: ProtocolID::IKE, 554 | spi_size: 0, 555 | num_transforms: 3, 556 | spi: None, 557 | transforms: vec![ 558 | IkeV2RawTransform { 559 | last: 3, 560 | reserved1: 0, 561 | transform_length: 12, 562 | transform_type: IkeTransformType::EncryptionAlgorithm, 563 | reserved2: 0, 564 | transform_id: 20, 565 | attributes: Some(attrs1), 566 | }, 567 | IkeV2RawTransform { 568 | last: 3, 569 | reserved1: 0, 570 | transform_length: 8, 571 | transform_type: IkeTransformType::PseudoRandomFunction, 572 | reserved2: 0, 573 | transform_id: 5, 574 | attributes: None, 575 | }, 576 | IkeV2RawTransform { 577 | last: 0, 578 | reserved1: 0, 579 | transform_length: 8, 580 | transform_type: IkeTransformType::DiffieHellmanGroup, 581 | reserved2: 0, 582 | transform_id: 30, 583 | attributes: None, 584 | }, 585 | ], 586 | }]); 587 | 588 | let (rem, res2) = parse_ikev2_payload_sa(res.payload, 0).expect("Failed to parse"); 589 | assert!(rem.is_empty()); 590 | assert_eq!(res2, expected2); 591 | } 592 | 593 | #[test] 594 | fn test_ikev2_parse_payload_many() { 595 | // let empty = &b""[..]; 596 | let bytes = &IKEV2_INIT_REQ[28..]; 597 | let res = parse_ikev2_payload_list(bytes, IkePayloadType::SecurityAssociation); 598 | println!("{:?}", res); 599 | } 600 | } 601 | -------------------------------------------------------------------------------- /src/ikev2_transforms.rs: -------------------------------------------------------------------------------- 1 | use rusticata_macros::newtype_enum; 2 | use std::convert::From; 3 | 4 | /// Transform (cryptographic algorithm) type 5 | /// 6 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.2 7 | #[derive(Clone, Copy, PartialEq, Eq)] 8 | pub struct IkeTransformType(pub u8); 9 | 10 | newtype_enum! { 11 | impl debug IkeTransformType { 12 | EncryptionAlgorithm = 1, 13 | PseudoRandomFunction = 2, 14 | IntegrityAlgorithm = 3, 15 | DiffieHellmanGroup = 4, 16 | ExtendedSequenceNumbers = 5, 17 | } 18 | } 19 | 20 | /// Encryption values 21 | /// 22 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.2 23 | /// 24 | /// See also [IKEV2IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml) for the latest values. 25 | #[derive(Clone, Copy, PartialEq, Eq)] 26 | pub struct IkeTransformEncType(pub u16); 27 | 28 | newtype_enum! { 29 | impl debug IkeTransformEncType { 30 | // 0 is reserved 31 | ENCR_DES_IV64 = 1, 32 | ENCR_DES = 2, 33 | ENCR_3DES = 3, 34 | ENCR_RC5 = 4, 35 | ENCR_IDEA = 5, 36 | ENCR_CAST = 6, 37 | ENCR_BLOWFISH = 7, 38 | ENCR_3IDEA = 8, 39 | ENCR_DES_IV32 = 9, 40 | // 10 is reserved 41 | ENCR_NULL = 11, 42 | ENCR_AES_CBC = 12, 43 | ENCR_AES_CTR = 13, 44 | ENCR_AES_CCM_8 = 14, 45 | ENCR_AES_CCM_12 = 15, 46 | ENCR_AES_CCM_16 = 16, 47 | // 17 is unassigned 48 | ENCR_AES_GCM_8 = 18, 49 | ENCR_AES_GCM_12 = 19, 50 | ENCR_AES_GCM_16 = 20, 51 | ENCR_NULL_AUTH_AES_GMAC = 21, 52 | // 22 is reserved for IEEE P1619 XTS-AES 53 | ENCR_CAMELLIA_CBC = 23, 54 | ENCR_CAMELLIA_CTR = 24, 55 | ENCR_CAMELLIA_CCM_8 = 25, 56 | ENCR_CAMELLIA_CCM_12 = 26, 57 | ENCR_CAMELLIA_CCM_16 = 27, 58 | ENCR_CHACHA20_POLY1305 = 28, // [RFC7634] 59 | } 60 | } 61 | 62 | impl IkeTransformEncType { 63 | pub fn is_aead(self) -> bool { 64 | matches!( 65 | self, 66 | IkeTransformEncType::ENCR_AES_CCM_8 67 | | IkeTransformEncType::ENCR_AES_CCM_12 68 | | IkeTransformEncType::ENCR_AES_CCM_16 69 | | IkeTransformEncType::ENCR_AES_GCM_8 70 | | IkeTransformEncType::ENCR_AES_GCM_12 71 | | IkeTransformEncType::ENCR_AES_GCM_16 72 | | IkeTransformEncType::ENCR_CAMELLIA_CCM_8 73 | | IkeTransformEncType::ENCR_CAMELLIA_CCM_12 74 | | IkeTransformEncType::ENCR_CAMELLIA_CCM_16 75 | | IkeTransformEncType::ENCR_CHACHA20_POLY1305 76 | ) 77 | } 78 | 79 | pub fn is_unassigned(self) -> bool { 80 | self.0 >= 23 && self.0 <= 1023 81 | } 82 | pub fn is_private_use(self) -> bool { 83 | self.0 >= 1024 84 | } 85 | } 86 | 87 | /// Pseudo-Random Function values 88 | /// 89 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.2 90 | /// 91 | /// See also [IKEV2IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml) for the latest values. 92 | #[derive(Clone, Copy, PartialEq, Eq)] 93 | pub struct IkeTransformPRFType(pub u16); 94 | 95 | newtype_enum! { 96 | impl debug IkeTransformPRFType { 97 | PRF_NULL = 0, 98 | PRF_HMAC_MD5 = 1, 99 | PRF_HMAC_SHA1 = 2, 100 | PRF_HMAC_TIGER = 3, 101 | PRF_AES128_XCBC = 4, 102 | PRF_HMAC_SHA2_256 = 5, 103 | PRF_HMAC_SHA2_384 = 6, 104 | PRF_HMAC_SHA2_512 = 7, 105 | PRF_AES128_CMAC = 8, 106 | } 107 | } 108 | 109 | impl IkeTransformPRFType { 110 | pub fn is_unassigned(self) -> bool { 111 | self.0 >= 9 && self.0 <= 1023 112 | } 113 | pub fn is_private_use(self) -> bool { 114 | self.0 >= 1024 115 | } 116 | } 117 | 118 | /// Authentication / Integrity values 119 | /// 120 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.2 121 | #[derive(Clone, Copy, PartialEq, Eq)] 122 | pub struct IkeTransformAuthType(pub u16); 123 | 124 | newtype_enum! { 125 | impl debug IkeTransformAuthType { 126 | NONE = 0, 127 | AUTH_HMAC_MD5_96 = 1, 128 | AUTH_HMAC_SHA1_96 = 2, 129 | AUTH_DES_MAC = 3, 130 | AUTH_KPDK_MD5 = 4, 131 | AUTH_AES_XCBC_96 = 5, 132 | AUTH_HMAC_MD5_128 = 6, 133 | AUTH_HMAC_SHA1_160 = 7, 134 | AUTH_AES_CMAC_96 = 8, 135 | AUTH_AES_128_GMAC = 9, 136 | AUTH_AES_192_GMAC = 10, 137 | AUTH_AES_256_GMAC = 11, 138 | AUTH_HMAC_SHA2_256_128 = 12, 139 | AUTH_HMAC_SHA2_384_192 = 13, 140 | AUTH_HMAC_SHA2_512_256 = 14, 141 | } 142 | } 143 | 144 | impl IkeTransformAuthType { 145 | pub fn is_unassigned(self) -> bool { 146 | self.0 >= 15 && self.0 <= 1023 147 | } 148 | pub fn is_private_use(self) -> bool { 149 | self.0 >= 1024 150 | } 151 | } 152 | 153 | /// Diffie-Hellman values 154 | /// 155 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.2 156 | /// 157 | /// See also [IKEV2IANA](https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml) for the latest values. 158 | #[derive(Clone, Copy, PartialEq, Eq)] 159 | pub struct IkeTransformDHType(pub u16); 160 | 161 | newtype_enum! { 162 | impl debug IkeTransformDHType { 163 | None = 0, 164 | Modp768 = 1, 165 | Modp1024 = 2, 166 | Modp1536 = 5, 167 | Modp2048 = 14, 168 | Modp3072 = 15, 169 | Modp4096 = 16, 170 | Modp6144 = 17, 171 | Modp8192 = 18, 172 | Ecp256 = 19, 173 | Ecp384 = 20, 174 | Ecp521 = 21, 175 | Modp1024s160 = 22, 176 | Modp2048s224 = 23, 177 | Modp2048s256 = 24, 178 | Ecp192 = 25, 179 | Ecp224 = 26, 180 | BrainpoolP224r1 = 27, 181 | BrainpoolP256r1 = 28, 182 | BrainpoolP384r1 = 29, 183 | BrainpoolP512r1 = 30, 184 | Curve25519 = 31, 185 | Curve448 = 32, 186 | } 187 | } 188 | 189 | impl IkeTransformDHType { 190 | pub fn is_unassigned(self) -> bool { 191 | self.0 >= 15 && self.0 <= 1023 192 | } 193 | pub fn is_private_use(self) -> bool { 194 | self.0 >= 1024 195 | } 196 | } 197 | 198 | /// Extended Sequence Number values 199 | /// 200 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3.2 201 | #[derive(Clone, Copy, PartialEq, Eq)] 202 | pub struct IkeTransformESNType(pub u16); 203 | 204 | newtype_enum! { 205 | impl debug IkeTransformESNType { 206 | NoESN = 0, 207 | ESN = 1, 208 | } 209 | } 210 | 211 | /// Raw representation of a transform (cryptographic algorithm) and parameters 212 | /// 213 | /// Use the `From` method to convert it to a [`IkeV2Transform`](enum.IkeV2Transform.html) 214 | /// 215 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3 216 | #[derive(Clone, PartialEq)] 217 | pub struct IkeV2RawTransform<'a> { 218 | pub last: u8, 219 | pub reserved1: u8, 220 | pub transform_length: u16, 221 | pub transform_type: IkeTransformType, 222 | pub reserved2: u8, 223 | pub transform_id: u16, 224 | pub attributes: Option<&'a [u8]>, 225 | } 226 | 227 | /// IKEv2 Transform (cryptographic algorithm) 228 | /// 229 | /// This structure is a simple representation of a transform, containing only the type (encryption, 230 | /// etc.). To store the parameters, use [`IkeV2RawTransform`](struct.IkeV2RawTransform.html). 231 | /// 232 | /// Defined in [RFC7296](https://tools.ietf.org/html/rfc7296) section 3.3 233 | #[derive(Debug, PartialEq)] 234 | pub enum IkeV2Transform { 235 | Encryption(IkeTransformEncType), 236 | PRF(IkeTransformPRFType), 237 | Auth(IkeTransformAuthType), 238 | DH(IkeTransformDHType), 239 | ESN(IkeTransformESNType), 240 | /// Unknown tranform (type,id) 241 | Unknown(IkeTransformType, u16), 242 | } 243 | 244 | impl<'a> From<&'a IkeV2RawTransform<'a>> for IkeV2Transform { 245 | fn from(r: &IkeV2RawTransform) -> IkeV2Transform { 246 | match r.transform_type { 247 | IkeTransformType::EncryptionAlgorithm => { 248 | IkeV2Transform::Encryption(IkeTransformEncType(r.transform_id)) 249 | } 250 | IkeTransformType::PseudoRandomFunction => { 251 | IkeV2Transform::PRF(IkeTransformPRFType(r.transform_id)) 252 | } 253 | IkeTransformType::IntegrityAlgorithm => { 254 | IkeV2Transform::Auth(IkeTransformAuthType(r.transform_id)) 255 | } 256 | IkeTransformType::DiffieHellmanGroup => { 257 | IkeV2Transform::DH(IkeTransformDHType(r.transform_id)) 258 | } 259 | IkeTransformType::ExtendedSequenceNumbers => { 260 | IkeV2Transform::ESN(IkeTransformESNType(r.transform_id)) 261 | } 262 | _ => IkeV2Transform::Unknown(r.transform_type, r.transform_id), 263 | } 264 | } 265 | } 266 | 267 | impl<'a> From> for IkeV2Transform { 268 | fn from(r: IkeV2RawTransform) -> IkeV2Transform { 269 | (&r).into() 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # IPsec parsers 2 | //! 3 | //! This crate contains several parsers using for IPsec: IKEv2, and reading the envelope of ESP 4 | //! encapsulated messages. 5 | //! This parser provides the base functions to read and analyze messages, but does not handle the 6 | //! interpretation of messages. 7 | //! 8 | //! ESP is supported, but only to read the envelope of the payload. 9 | //! 10 | //! Encapsulated ESP is supported, to differentiate between IKE and ESP headers. 11 | //! 12 | //! # IKEv2 parser 13 | //! 14 | //! An IKEv2 (RFC7296) parser, implemented with the [nom](https://github.com/Geal/nom) 15 | //! parser combinator framework. 16 | //! 17 | //! The code is available on [Github](https://github.com/rusticata/ipsec-parser) 18 | //! and is part of the [Rusticata](https://github.com/rusticata) project. 19 | //! 20 | //! To parse an IKE packet, first read the header using `parse_ikev2_header`, then use the type 21 | //! from the header to parse the remaining part: 22 | //! 23 | //! 24 | //! ```rust 25 | //! # extern crate nom; 26 | //! # extern crate ipsec_parser; 27 | //! use ipsec_parser::*; 28 | //! use nom::IResult; 29 | //! 30 | //! static IKEV2_INIT_RESP: &'static [u8] = include_bytes!("../assets/ike-sa-init-resp.bin"); 31 | //! 32 | //! # fn main() { 33 | //! fn test_ikev2_init_resp() { 34 | //! let bytes = IKEV2_INIT_RESP; 35 | //! match parse_ikev2_header(&bytes) { 36 | //! Ok( (rem, ref hdr) ) => { 37 | //! match parse_ikev2_payload_list(rem,hdr.next_payload) { 38 | //! Ok( (_, Ok(ref p)) ) => { 39 | //! // p is a list of payloads 40 | //! // first one is always dummy 41 | //! assert!(p.len() > 0); 42 | //! assert_eq!(p[0].content, IkeV2PayloadContent::Dummy); 43 | //! for payload in p { 44 | //! match payload.content { 45 | //! IkeV2PayloadContent::SA(ref sa) => { /* .. */ }, 46 | //! _ => () 47 | //! } 48 | //! } 49 | //! }, 50 | //! e => { eprintln!("Parsing payload failed: {:?}", e); }, 51 | //! } 52 | //! }, 53 | //! _ => { eprintln!("Parsing header failed"); }, 54 | //! } 55 | //! } 56 | //! # } 57 | //! ``` 58 | 59 | #![deny(/*missing_docs,*/ 60 | unstable_features, 61 | unused_import_braces, unused_qualifications)] 62 | #![forbid(unsafe_code)] 63 | 64 | mod error; 65 | mod esp; 66 | mod ikev2; 67 | mod ikev2_debug; 68 | mod ikev2_notify; 69 | mod ikev2_parser; 70 | mod ikev2_transforms; 71 | pub use error::*; 72 | pub use esp::*; 73 | pub use ikev2::*; 74 | pub use ikev2_debug::*; 75 | pub use ikev2_notify::*; 76 | pub use ikev2_parser::*; 77 | pub use ikev2_transforms::*; 78 | 79 | // re-export modules 80 | pub use nom; 81 | -------------------------------------------------------------------------------- /tests/ikev2.rs: -------------------------------------------------------------------------------- 1 | extern crate ipsec_parser; 2 | extern crate nom; 3 | 4 | mod ikev2 { 5 | use ipsec_parser::*; 6 | 7 | #[rustfmt::skip] 8 | static IKEV2_MSG: &[u8] = &[ 9 | 0x00, 0x00, 0x00, 0x00, 0xbc, 0xa1, 0x93, 0x7a, 0x7c, 0x2f, 0xf5, 0xb9, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x21, 0x20, 0x22, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2c, 11 | 0x22, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x00, 0x24, 0x01, 0x01, 0x00, 0x03, 0x03, 0x00, 0x00, 0x0c, 12 | 0x01, 0x00, 0x00, 0x14, 0x80, 0x0e, 0x00, 0x80, 0x03, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x05, 13 | 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x24, 0x02, 0x01, 0x00, 0x03, 14 | 0x03, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x14, 0x80, 0x0e, 0x00, 0x80, 0x03, 0x00, 0x00, 0x08, 15 | 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x13, 0x02, 0x00, 0x00, 0x2c, 16 | 0x03, 0x01, 0x00, 0x04, 0x03, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x0d, 0x80, 0x0e, 0x00, 0x80, 17 | 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x05, 18 | 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x04, 0x01, 0x00, 0x04, 19 | 0x03, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, 0x0d, 0x80, 0x0e, 0x00, 0x80, 0x03, 0x00, 0x00, 0x08, 20 | 0x03, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 21 | 0x04, 0x00, 0x00, 0x13, 0x28, 0x00, 0x00, 0x48, 0x00, 0x1c, 0x00, 0x00, 0x4d, 0x23, 0xb8, 0xf2, 22 | 0x2c, 0x03, 0xae, 0x3a, 0xb4, 0x0a, 0xf9, 0xfa, 0x76, 0xc2, 0x3c, 0x7b, 0xa6, 0xc5, 0x69, 0x81, 23 | 0xe1, 0x3c, 0x32, 0xfb, 0xc3, 0x69, 0x54, 0xc8, 0x7a, 0x96, 0xcb, 0xfa, 0x34, 0x63, 0x7c, 0x6f, 24 | 0x31, 0xb6, 0x50, 0xce, 0x5d, 0x87, 0x17, 0x00, 0x5c, 0xe7, 0x93, 0xc2, 0xee, 0xaf, 0xf1, 0xc5, 25 | 0x66, 0x28, 0x08, 0x11, 0xd9, 0x42, 0xd1, 0xb8, 0xe8, 0x02, 0x68, 0xbb, 0x00, 0x00, 0x00, 0x24, 26 | 0x99, 0xe2, 0xa3, 0x73, 0xcc, 0x44, 0xad, 0x10, 0xfb, 0x78, 0xdd, 0xa2, 0x52, 0x06, 0xa4, 0xa9, 27 | 0xb7, 0x55, 0x8e, 0x0d, 0x82, 0x69, 0x79, 0x9c, 0x68, 0x00, 0x36, 0x2c, 0x46, 0xa0, 0xbf, 0x7b 28 | ]; 29 | 30 | #[test] 31 | fn test_ipsec_ike_sa() { 32 | let bytes = &IKEV2_MSG[4..32]; 33 | let ike_sa = &IKEV2_MSG[32..]; 34 | let expected = IkeV2Header { 35 | init_spi: 0xbca1937a7c2ff5b9, 36 | resp_spi: 0x0, 37 | next_payload: IkePayloadType::SecurityAssociation, 38 | maj_ver: 2, 39 | min_ver: 0, 40 | exch_type: IkeExchangeType::IKE_SA_INIT, 41 | flags: 0x8, 42 | msg_id: 0, 43 | length: 300, 44 | }; 45 | let (_, res) = parse_ikev2_header(bytes).expect("failed to parse header"); 46 | // println!("{:?}",res); 47 | assert_eq!(res, expected); 48 | 49 | let (_, hdr) = parse_ikev2_payload_generic(ike_sa).expect("failed to parse payload"); 50 | // println!("{:?}",res_sa); 51 | let (rem, sa) = parse_ikev2_payload_sa(hdr.payload, 0).unwrap(); 52 | assert_eq!(rem.len(), 0); 53 | match sa { 54 | IkeV2PayloadContent::SA(ref sa_list) => { 55 | assert_eq!(sa_list.len(), 4); 56 | // for sa in sa_list.iter() { 57 | // println!("sa: {:?}", sa); 58 | // } 59 | sa_list 60 | .iter() 61 | .zip(&[3, 3, 4, 4]) 62 | .for_each(|(a, b)| assert_eq!(a.num_transforms, *b)); 63 | sa_list 64 | .iter() 65 | .for_each(|a| assert_eq!(a.transforms.len(), a.num_transforms as usize)); 66 | } 67 | _ => panic!("wrong type"), 68 | } 69 | } 70 | 71 | static IKEV2_INIT_REQ: &[u8] = include_bytes!("../assets/ike-sa-init-req.bin"); 72 | 73 | #[test] 74 | fn test_ipsec_ike_message() { 75 | let bytes = IKEV2_INIT_REQ; 76 | let expected_header = IkeV2Header { 77 | init_spi: 0x54c184961bc3fd77, 78 | resp_spi: 0x0, 79 | next_payload: IkePayloadType::SecurityAssociation, 80 | maj_ver: 2, 81 | min_ver: 0, 82 | exch_type: IkeExchangeType::IKE_SA_INIT, 83 | flags: 0x8, 84 | msg_id: 0, 85 | length: 256, 86 | }; 87 | let (rem, (hdr, res_list)) = parse_ikev2_message(bytes).expect("failed to parse header"); 88 | let list = res_list.expect("failed to parse payload"); 89 | assert_eq!(rem.len(), 0); 90 | assert_eq!(hdr, expected_header); 91 | assert_eq!(list.len(), 4); 92 | for (a, b) in list.iter().zip(&[33, 34, 40, 0]) { 93 | assert_eq!(a.hdr.next_payload_type, IkePayloadType(*b)); 94 | } 95 | } 96 | } // mod ikev2 97 | --------------------------------------------------------------------------------