├── .github ├── dependabot.yml └── workflows │ ├── rust.yml │ └── security-audit.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── client_hello_dhe.bin ├── esni.bin ├── tls_record_ch_fragmented_1.bin └── tls_record_ch_fragmented_2.bin ├── benches ├── bench_dh.rs └── bench_handshake.rs ├── build.rs ├── examples └── get-ciphersuite-info.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzzers │ ├── parse_dtls_plaintext.rs │ ├── parse_tls_extension.rs │ └── parse_tls_plaintext.rs ├── scripts ├── extract-iana-ciphers.py ├── extract-iana-ciphers2.py └── tls-ciphersuites.txt ├── src ├── certificate_transparency.rs ├── dtls.rs ├── lib.rs ├── tls_alert.rs ├── tls_ciphers.rs ├── tls_debug.rs ├── tls_dh.rs ├── tls_ec.rs ├── tls_extensions.rs ├── tls_handshake.rs ├── tls_message.rs ├── tls_record.rs ├── tls_records_parser.rs ├── tls_serialize.rs ├── tls_sign_hash.rs └── tls_states.rs └── tests ├── certificate_transparency.rs ├── tls_dh.rs ├── tls_extensions.rs ├── tls_handshake.rs └── tls_tls13.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: github-actions 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | merge_group: 7 | schedule: 8 | - cron: '0 18 * * *' 9 | 10 | jobs: 11 | check: 12 | name: Check 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | rust: 17 | - stable 18 | - 1.70.0 19 | - nightly 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: ${{ matrix.rust }} 25 | - run: RUSTFLAGS="-D warnings" cargo check --locked 26 | 27 | test_features: 28 | name: Test suite (with features) 29 | runs-on: ubuntu-latest 30 | strategy: 31 | matrix: 32 | features: 33 | - --features=default 34 | - --all-features 35 | - --features=serialize 36 | - --features=unstable 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Install stable toolchain 40 | uses: dtolnay/rust-toolchain@stable 41 | - run: cargo test --locked ${{ matrix.features }} 42 | 43 | no_std: 44 | name: no-std 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: dtolnay/rust-toolchain@stable 49 | - run: RUSTFLAGS="-D warnings" cargo check --locked --no-default-features 50 | 51 | fmt: 52 | name: Rustfmt 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: dtolnay/rust-toolchain@stable 57 | with: 58 | components: rustfmt 59 | - run: cargo fmt --all -- --check 60 | 61 | clippy: 62 | name: Clippy 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v4 66 | - uses: dtolnay/rust-toolchain@nightly 67 | with: 68 | components: clippy 69 | - run: cargo clippy --locked -- -D warnings 70 | 71 | doc: 72 | name: Build documentation 73 | runs-on: ubuntu-latest 74 | env: 75 | RUSTDOCFLAGS: --cfg docsrs 76 | steps: 77 | - uses: actions/checkout@v4 78 | - uses: dtolnay/rust-toolchain@nightly 79 | - run: cargo doc --workspace --no-deps --locked --all-features 80 | 81 | semver: 82 | name: Check semver compatibility 83 | runs-on: ubuntu-latest 84 | steps: 85 | - name: Checkout sources 86 | uses: actions/checkout@v4 87 | - name: Check semver 88 | uses: obi1kenobi/cargo-semver-checks-action@v2 89 | 90 | check-external-types: 91 | name: Validate external types appearing in public API 92 | runs-on: ubuntu-latest 93 | steps: 94 | - name: Checkout sources 95 | uses: actions/checkout@v4 96 | - name: Install rust toolchain 97 | uses: dtolnay/rust-toolchain@master 98 | with: 99 | toolchain: nightly-2024-05-01 100 | # ^ sync with https://github.com/awslabs/cargo-check-external-types/blob/main/rust-toolchain.toml 101 | - run: cargo install cargo-check-external-types 102 | - run: cargo check-external-types 103 | -------------------------------------------------------------------------------- /.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@v4 13 | - uses: rustsec/audit-check@v1 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | target 3 | /.idea 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.15" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.8" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.4" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "autocfg" 56 | version = "1.3.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 59 | 60 | [[package]] 61 | name = "clap" 62 | version = "4.5.17" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" 65 | dependencies = [ 66 | "clap_builder", 67 | "clap_derive", 68 | ] 69 | 70 | [[package]] 71 | name = "clap_builder" 72 | version = "4.5.17" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" 75 | dependencies = [ 76 | "anstream", 77 | "anstyle", 78 | "clap_lex", 79 | "strsim", 80 | ] 81 | 82 | [[package]] 83 | name = "clap_derive" 84 | version = "4.5.13" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" 87 | dependencies = [ 88 | "heck", 89 | "proc-macro2", 90 | "quote", 91 | "syn 2.0.77", 92 | ] 93 | 94 | [[package]] 95 | name = "clap_lex" 96 | version = "0.7.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 99 | 100 | [[package]] 101 | name = "colorchoice" 102 | version = "1.0.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 105 | 106 | [[package]] 107 | name = "cookie-factory" 108 | version = "0.3.3" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" 111 | dependencies = [ 112 | "futures", 113 | ] 114 | 115 | [[package]] 116 | name = "diff" 117 | version = "0.1.13" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 120 | 121 | [[package]] 122 | name = "equivalent" 123 | version = "1.0.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 126 | 127 | [[package]] 128 | name = "futures" 129 | version = "0.3.30" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 132 | dependencies = [ 133 | "futures-channel", 134 | "futures-core", 135 | "futures-executor", 136 | "futures-io", 137 | "futures-sink", 138 | "futures-task", 139 | "futures-util", 140 | ] 141 | 142 | [[package]] 143 | name = "futures-channel" 144 | version = "0.3.30" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 147 | dependencies = [ 148 | "futures-core", 149 | "futures-sink", 150 | ] 151 | 152 | [[package]] 153 | name = "futures-core" 154 | version = "0.3.30" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 157 | 158 | [[package]] 159 | name = "futures-executor" 160 | version = "0.3.30" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 163 | dependencies = [ 164 | "futures-core", 165 | "futures-task", 166 | "futures-util", 167 | ] 168 | 169 | [[package]] 170 | name = "futures-io" 171 | version = "0.3.30" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 174 | 175 | [[package]] 176 | name = "futures-macro" 177 | version = "0.3.30" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 180 | dependencies = [ 181 | "proc-macro2", 182 | "quote", 183 | "syn 2.0.77", 184 | ] 185 | 186 | [[package]] 187 | name = "futures-sink" 188 | version = "0.3.30" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 191 | 192 | [[package]] 193 | name = "futures-task" 194 | version = "0.3.30" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 197 | 198 | [[package]] 199 | name = "futures-util" 200 | version = "0.3.30" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 203 | dependencies = [ 204 | "futures-channel", 205 | "futures-core", 206 | "futures-io", 207 | "futures-macro", 208 | "futures-sink", 209 | "futures-task", 210 | "memchr", 211 | "pin-project-lite", 212 | "pin-utils", 213 | "slab", 214 | ] 215 | 216 | [[package]] 217 | name = "hashbrown" 218 | version = "0.14.5" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 221 | 222 | [[package]] 223 | name = "heck" 224 | version = "0.5.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 227 | 228 | [[package]] 229 | name = "hex-literal" 230 | version = "0.4.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" 233 | 234 | [[package]] 235 | name = "indexmap" 236 | version = "2.5.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" 239 | dependencies = [ 240 | "equivalent", 241 | "hashbrown", 242 | ] 243 | 244 | [[package]] 245 | name = "is_terminal_polyfill" 246 | version = "1.70.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 249 | 250 | [[package]] 251 | name = "memchr" 252 | version = "2.7.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 255 | 256 | [[package]] 257 | name = "minimal-lexical" 258 | version = "0.2.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 261 | 262 | [[package]] 263 | name = "nom" 264 | version = "7.1.3" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 267 | dependencies = [ 268 | "memchr", 269 | "minimal-lexical", 270 | ] 271 | 272 | [[package]] 273 | name = "nom-derive" 274 | version = "0.10.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "1ff943d68b88d0b87a6e0d58615e8fa07f9fd5a1319fa0a72efc1f62275c79a7" 277 | dependencies = [ 278 | "nom", 279 | "nom-derive-impl", 280 | "rustversion", 281 | ] 282 | 283 | [[package]] 284 | name = "nom-derive-impl" 285 | version = "0.10.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "cd0b9a93a84b0d3ec3e70e02d332dc33ac6dfac9cde63e17fcb77172dededa62" 288 | dependencies = [ 289 | "proc-macro2", 290 | "quote", 291 | "syn 1.0.109", 292 | ] 293 | 294 | [[package]] 295 | name = "num_enum" 296 | version = "0.7.3" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 299 | dependencies = [ 300 | "num_enum_derive", 301 | ] 302 | 303 | [[package]] 304 | name = "num_enum_derive" 305 | version = "0.7.3" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 308 | dependencies = [ 309 | "proc-macro-crate", 310 | "proc-macro2", 311 | "quote", 312 | "syn 2.0.77", 313 | ] 314 | 315 | [[package]] 316 | name = "phf" 317 | version = "0.11.2" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 320 | dependencies = [ 321 | "phf_shared", 322 | ] 323 | 324 | [[package]] 325 | name = "phf_codegen" 326 | version = "0.11.2" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 329 | dependencies = [ 330 | "phf_generator", 331 | "phf_shared", 332 | ] 333 | 334 | [[package]] 335 | name = "phf_generator" 336 | version = "0.11.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 339 | dependencies = [ 340 | "phf_shared", 341 | "rand", 342 | ] 343 | 344 | [[package]] 345 | name = "phf_shared" 346 | version = "0.11.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 349 | dependencies = [ 350 | "siphasher", 351 | ] 352 | 353 | [[package]] 354 | name = "pin-project-lite" 355 | version = "0.2.14" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 358 | 359 | [[package]] 360 | name = "pin-utils" 361 | version = "0.1.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 364 | 365 | [[package]] 366 | name = "pretty_assertions" 367 | version = "1.4.1" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" 370 | dependencies = [ 371 | "diff", 372 | "yansi", 373 | ] 374 | 375 | [[package]] 376 | name = "proc-macro-crate" 377 | version = "3.2.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 380 | dependencies = [ 381 | "toml_edit", 382 | ] 383 | 384 | [[package]] 385 | name = "proc-macro2" 386 | version = "1.0.86" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 389 | dependencies = [ 390 | "unicode-ident", 391 | ] 392 | 393 | [[package]] 394 | name = "quote" 395 | version = "1.0.37" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 398 | dependencies = [ 399 | "proc-macro2", 400 | ] 401 | 402 | [[package]] 403 | name = "rand" 404 | version = "0.8.5" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 407 | dependencies = [ 408 | "rand_core", 409 | ] 410 | 411 | [[package]] 412 | name = "rand_core" 413 | version = "0.6.4" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 416 | 417 | [[package]] 418 | name = "rusticata-macros" 419 | version = "4.1.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" 422 | dependencies = [ 423 | "nom", 424 | ] 425 | 426 | [[package]] 427 | name = "rustversion" 428 | version = "1.0.17" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 431 | 432 | [[package]] 433 | name = "siphasher" 434 | version = "0.3.11" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 437 | 438 | [[package]] 439 | name = "slab" 440 | version = "0.4.9" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 443 | dependencies = [ 444 | "autocfg", 445 | ] 446 | 447 | [[package]] 448 | name = "strsim" 449 | version = "0.11.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 452 | 453 | [[package]] 454 | name = "syn" 455 | version = "1.0.109" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 458 | dependencies = [ 459 | "proc-macro2", 460 | "quote", 461 | "unicode-ident", 462 | ] 463 | 464 | [[package]] 465 | name = "syn" 466 | version = "2.0.77" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 469 | dependencies = [ 470 | "proc-macro2", 471 | "quote", 472 | "unicode-ident", 473 | ] 474 | 475 | [[package]] 476 | name = "tls-parser" 477 | version = "0.12.2" 478 | dependencies = [ 479 | "clap", 480 | "cookie-factory", 481 | "hex-literal", 482 | "nom", 483 | "nom-derive", 484 | "num_enum", 485 | "phf", 486 | "phf_codegen", 487 | "pretty_assertions", 488 | "rusticata-macros", 489 | ] 490 | 491 | [[package]] 492 | name = "toml_datetime" 493 | version = "0.6.8" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 496 | 497 | [[package]] 498 | name = "toml_edit" 499 | version = "0.22.20" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" 502 | dependencies = [ 503 | "indexmap", 504 | "toml_datetime", 505 | "winnow", 506 | ] 507 | 508 | [[package]] 509 | name = "unicode-ident" 510 | version = "1.0.13" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 513 | 514 | [[package]] 515 | name = "utf8parse" 516 | version = "0.2.2" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 519 | 520 | [[package]] 521 | name = "windows-sys" 522 | version = "0.52.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 525 | dependencies = [ 526 | "windows-targets", 527 | ] 528 | 529 | [[package]] 530 | name = "windows-targets" 531 | version = "0.52.6" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 534 | dependencies = [ 535 | "windows_aarch64_gnullvm", 536 | "windows_aarch64_msvc", 537 | "windows_i686_gnu", 538 | "windows_i686_gnullvm", 539 | "windows_i686_msvc", 540 | "windows_x86_64_gnu", 541 | "windows_x86_64_gnullvm", 542 | "windows_x86_64_msvc", 543 | ] 544 | 545 | [[package]] 546 | name = "windows_aarch64_gnullvm" 547 | version = "0.52.6" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 550 | 551 | [[package]] 552 | name = "windows_aarch64_msvc" 553 | version = "0.52.6" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 556 | 557 | [[package]] 558 | name = "windows_i686_gnu" 559 | version = "0.52.6" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 562 | 563 | [[package]] 564 | name = "windows_i686_gnullvm" 565 | version = "0.52.6" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 568 | 569 | [[package]] 570 | name = "windows_i686_msvc" 571 | version = "0.52.6" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 574 | 575 | [[package]] 576 | name = "windows_x86_64_gnu" 577 | version = "0.52.6" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 580 | 581 | [[package]] 582 | name = "windows_x86_64_gnullvm" 583 | version = "0.52.6" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 586 | 587 | [[package]] 588 | name = "windows_x86_64_msvc" 589 | version = "0.52.6" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 592 | 593 | [[package]] 594 | name = "winnow" 595 | version = "0.6.18" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" 598 | dependencies = [ 599 | "memchr", 600 | ] 601 | 602 | [[package]] 603 | name = "yansi" 604 | version = "1.0.1" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 607 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Parser for the TLS protocol" 3 | license = "MIT/Apache-2.0" 4 | keywords = ["TLS","SSL","protocol","parser","nom"] 5 | homepage = "https://github.com/rusticata/tls-parser" 6 | repository = "https://github.com/rusticata/tls-parser.git" 7 | documentation = "https://docs.rs/tls-parser" 8 | name = "tls-parser" 9 | version = "0.12.2" 10 | authors = ["Pierre Chifflier "] 11 | categories = ["network-programming", "parser-implementations"] 12 | edition = "2018" 13 | rust-version = "1.63" 14 | 15 | readme = "README.md" 16 | build = "build.rs" 17 | 18 | include = [ 19 | "LICENSE-*", 20 | ".gitignore", 21 | "Cargo.toml", 22 | "benches/*.rs", 23 | "build.rs", 24 | "examples/*.rs", 25 | "src/*.rs", 26 | "tests/*.rs", 27 | "scripts/tls-ciphersuites.txt", 28 | "scripts/extract-iana-ciphers.py" 29 | ] 30 | 31 | [lib] 32 | name = "tls_parser" 33 | 34 | [features] 35 | default = ["std"] 36 | serialize = ["cookie-factory"] 37 | std = ["phf/std"] 38 | unstable = [] 39 | 40 | [dependencies] 41 | cookie-factory = { version="0.3", optional=true } 42 | num_enum = "0.7.2" 43 | nom = "7.0" 44 | nom-derive = "0.10" 45 | phf = { version="0.11", default-features=false } 46 | rusticata-macros = "4.0" 47 | 48 | [dev-dependencies] 49 | clap = { version="4.5", features = ["derive"]} 50 | hex-literal = "0.4" 51 | pretty_assertions = "1.4" 52 | 53 | [build-dependencies] 54 | phf_codegen = "0.11" 55 | 56 | [package.metadata.cargo_check_external_types] 57 | allowed_external_types = [ 58 | "nom", 59 | "nom::*", 60 | "nom_derive::*", 61 | "num_enum::*", 62 | "phf::map::Map", 63 | "rusticata_macros", 64 | ] 65 | -------------------------------------------------------------------------------- /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 | # tls-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 | [![Crates.io Version](https://img.shields.io/crates/v/tls-parser.svg)](https://crates.io/crates/tls-parser) 6 | [![GitHub CI](https://github.com/cpu/tls-parser/actions/workflows/rust.yml/badge.svg)](https://github.com/cpu/tls-parser/actions/workflows/rust.yml) 7 | [![Minimum rustc version](https://img.shields.io/badge/rustc-1.70.0+-lightgray.svg)](#rust-version-requirements) 8 | 9 | # TLS Parser 10 | 11 | A TLS parser, implemented with the [nom](https://github.com/Geal/nom) 12 | parser combinator framework. 13 | 14 | The goal of this parser is to implement TLS messages analysis, for example 15 | to use rules from a network IDS, for ex during the TLS handshake. 16 | 17 | It implements structures and parsing functions for records and messages, but 18 | need additional code to handle fragmentation, or to fully inspect messages. 19 | Parsing some TLS messages requires to know the previously selected parameters. 20 | See [the rusticata TLS parser](https://github.com/rusticata/rusticata/blob/master/src/tls.rs) 21 | for a full example. 22 | 23 | It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken 24 | to ensure security and safety of this crate, including design (recursion limit, defensive 25 | programming), tests, and fuzzing. It also aims to be panic-free. 26 | 27 | The code is available on [Github](https://github.com/rusticata/tls-parser) 28 | and is part of the [Rusticata](https://github.com/rusticata) project. 29 | 30 | ## Parsing records 31 | 32 | The main parsing functions are located in the [tls.rs](src/tls.rs) file. The entry functions are: 33 | - `parse_tls_plaintext`: parses a record as plaintext 34 | - `parse_tls_encrypted`: read an encrypted record. The parser has no crypto or decryption features, so the content 35 | will be left as opaque data. 36 | 37 | # Examples 38 | 39 | ```rust 40 | use tls_parser::parse_tls_plaintext; 41 | use tls_parser::nom::{Err, IResult}; 42 | 43 | let bytes : &[u8]= include_bytes!("../assets/client_hello_dhe.bin"); 44 | // [ 0x16, 0x03, 0x01 ... ]; 45 | let res = parse_tls_plaintext(&bytes); 46 | match res { 47 | Ok((rem,record)) => { 48 | // rem is the remaining data (not parsed) 49 | // record is an object of type TlsRecord 50 | }, 51 | Err(Err::Incomplete(needed)) => { 52 | eprintln!("Defragmentation required (TLS record)"); 53 | }, 54 | Err(e) => { eprintln!("parse_tls_record_with_header failed: {:?}",e); } 55 | } 56 | ``` 57 | 58 | Note that knowing if a record is plaintext or not is the responsibility of the caller. 59 | 60 | As reading TLS records may imply defragmenting records, some functions are 61 | provided to only read the record as opaque data (which ensures the record is 62 | complete and gives the record header) and then reading messages from data. 63 | 64 | Here is an example of two-steps parsing: 65 | 66 | ```rust 67 | 68 | // [ 0x16, 0x03, 0x01 ... ]; 69 | match parse_tls_raw_record(bytes) { 70 | Ok((rem, ref r)) => { 71 | match parse_tls_record_with_header(r.data, &r.hdr) { 72 | Ok((rem2,ref msg_list)) => { 73 | for msg in msg_list { 74 | // msg has type TlsMessage 75 | } 76 | } 77 | Err(Err::Incomplete(needed)) => { eprintln!("incomplete record") } 78 | Err(_) => { eprintln!("error while parsing record") } 79 | } 80 | } 81 | Err(Err::Incomplete(needed)) => { eprintln!("incomplete record header") } 82 | Err(_) => { eprintln!("error while parsing record header") } 83 | } 84 | ``` 85 | 86 | Some additional work is required if reading packets from the network, to support 87 | reassembly of TCP segments and reassembly of TLS records. 88 | 89 | For a complete example of a TLS parser supporting defragmentation and states, see the 90 | [rusticata/src/tls.rs](https://github.com/rusticata/rusticata/blob/master/src/tls.rs) file of 91 | the [rusticata](https://github.com/rusticata/rusticata) crate. 92 | 93 | ## State machine 94 | 95 | A TLS state machine is provided in [tls_states.rs](src/tls_states.rs). The state machine is separated from the 96 | parsing functions, and is almost independent. 97 | It is implemented as a table of transitions, mainly for the handshake phase. 98 | 99 | After reading a TLS message using the previous functions, the TLS state can be 100 | updated using the `tls_state_transition` function. If the transition succeeds, 101 | it returns `Ok(new_state)`, otherwise it returns `Err(error_state)`. 102 | 103 | ```rust 104 | 105 | struct ParseContext { 106 | state: TlsState, 107 | } 108 | 109 | match tls_state_transition(ctx.state, msg, to_server) { 110 | Ok(s) => { ctx.state = s; Ok(()) } 111 | Err(_) => { 112 | ctx.state = TlsState::Invalid; 113 | Err("Invalid state") 114 | } 115 | } 116 | ``` 117 | 118 | # Implementation notes 119 | 120 | When parsing messages, if a field is an integer corresponding to an enum of known values, 121 | it is not parsed as an enum type, but as an integer. While this complicates accesses, 122 | it allows to read invalid values and continue parsing (for an IDS, it's better to read 123 | values than to get a generic parse error). 124 | 125 | 126 | ## Changes 127 | 128 | ### 0.12.2 129 | 130 | - Reintroduce lifetime in `parse_content_and_signature`, compiler infers a wrong lifetime 131 | when elided. 132 | 133 | ### 0.12.1 134 | 135 | - Set MAX_RECORD_LEN to 2^14 + 256 for TLSCipherText (#72) 136 | - Change parse_content_and_signature definition to elide useless lifetimes 137 | 138 | ### 0.12.0 139 | 140 | - Set MSRV to 1.70 (required by num_enum) 141 | - Make functions to parse handshake messages public (#66) 142 | - Cargo: use phf without std if no_std was specified (#56) 143 | - Fix parsing and export of RFCs for SCSV 144 | - DTLS fragments as DTLSMessageHandshakeBody::Fragment 145 | - Update ciphersuites with old drafts + new AEGIS 146 | - Support SSLv3 ServerHello 147 | - Improve TlsCipherSuite: add PRF and methods to get parameters 148 | - Remove `rand_time` from `ClientHello`/`ServerHello` (this is deprecated since a long time, and makes 149 | getting the entire random value difficult) 150 | To access sub elements, use the `rand_time()` and `rand_bytes()` methods 151 | 152 | Thanks: Daniel McCarney, Lucas Kent, Andrew Finn, Adrien Guinet, Martin Algesten, Benoit Lemarchand 153 | 154 | ### 0.11.0 155 | 156 | - Upgrade to nom 7 157 | - Add `ClientHello` trait for common CH attributes 158 | - Get extensions in DTLS `ClientHello` 159 | - Add example/helper program to list/query ciphersuite information 160 | - Add pseudo-entries for `TLS_EMPTY_RENEGOTIATION_INFO_SCSV` and `TLS_FALLBACK_SCSV` (#16) 161 | - Update ciphersuites file 162 | - Added parsing for SignedCertificateTimestamp list 163 | - Re-export nom 164 | - Add support for `no_std` 165 | 166 | Thanks: @JackLiar, @xonatius 167 | 168 | ### 0.10.0 169 | 170 | - Upgrade to nom 6 171 | - Remove all macro-base parsers (use functions, and nom-derive when possible) 172 | - Add support for DTLS (Handshake) 173 | - Add functions to parse extensions expected in Client/Server Hello 174 | 175 | ### 0.9.4 176 | 177 | - In ServerHello, an empty SNI extension can be sent (RFC 6066) 178 | 179 | ### 0.9.3 180 | 181 | - Fix error in state machine (wrong Client Certificate direction) 182 | 183 | ### 0.9.2 184 | 185 | - Upgrade to phf 0.8 186 | - Upgrade cookie-factory to 0.3.0 187 | 188 | ### 0.9.1 189 | 190 | - Mark cookie-factory as optional (only used for serialization) 191 | 192 | ### 0.9.0 193 | 194 | - Upgrade to nom 5 195 | - Rustfmt 196 | 197 | ### 0.8.1 198 | 199 | - Set edition to 2018 200 | - Check heartbeat message length (subtraction could underflow) 201 | - Add more checks for record length (RFC compliance, not for parser safety) 202 | 203 | ### 0.8.0 204 | 205 | - Add support for record size limit extension 206 | - Add support for encrypted server name (eSNI) extension 207 | - State machine: use direction and support TLS 1.3 0-RTT 208 | - State machine: add new state to indicate connection is closed (after fatal alert) 209 | - Use TlsVersion type for SSL record version 210 | - Update doc, and use cargo sync-readme 211 | 212 | ### 0.7.1 213 | 214 | - Improve state machine, handle resumption failure, and non-fatal alerts 215 | - Improve handling of Signature/Hash algorithms, and display 216 | - Update ciphersuites to 2019-03-19 217 | 218 | ### 0.7.0 219 | 220 | - Convert most enums to newtypes 221 | - warning: this is a breaking change 222 | - Update dependencies and remove unused crates 223 | - Update ciphersuites to 2019-01-23 224 | 225 | ### 0.6.0 226 | 227 | - Upgrade to nom 4.0 228 | - warning: this is a breaking change 229 | - Fix wrong extension ID for padding and signed timestamp 230 | - Rewrite parse_cipher_suites and parse_compressions_algs to be faster 231 | - Update ciphersuites to 2018-08-13 232 | 233 | ## Standards 234 | Here is a non-exhaustive list of RFCs this parser is based on: 235 | - [RFC 2246](https://tools.ietf.org/html/rfc2246): The TLS Protocol Version 1.0 236 | - [RFC 4346](https://tools.ietf.org/html/rfc4346): The Transport Layer Security (TLS) Protocol Version 1.1 237 | - [RFC 4366](https://tools.ietf.org/html/rfc4366): Transport Layer Security (TLS) Extensions 238 | - [RFC 4492](https://tools.ietf.org/html/rfc4492): Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS) 239 | - [RFC 4507](https://tools.ietf.org/html/rfc4507): Transport Layer Security (TLS) Session 240 | Resumption without Server-Side State 241 | - [RFC 5077](https://tools.ietf.org/html/rfc5077): Transport Layer Security (TLS) Session 242 | Resumption without Server-Side State 243 | - [RFC 5246](https://tools.ietf.org/html/rfc5246): The Transport Layer Security (TLS) Protocol Version 1.2 244 | - [RFC 5430](https://tools.ietf.org/html/rfc5430): Suite B Profile for Transport Layer Security (TLS) 245 | - [RFC 5746](https://tools.ietf.org/html/rfc5746): Transport Layer Security (TLS) Renegotiation Indication Extension 246 | - [RFC 6066](https://tools.ietf.org/html/rfc6066): Transport Layer Security (TLS) Extensions: Extension Definitions 247 | - [RFC 6520](https://tools.ietf.org/html/rfc6520): Transport Layer Security (TLS) and 248 | Datagram Transport Layer Security (DTLS) Heartbeat Extension 249 | - [RFC 6961](https://tools.ietf.org/html/rfc6961): The Transport Layer Security (TLS) 250 | Multiple Certificate Status Request Extension 251 | - [RFC 7027](https://tools.ietf.org/html/rfc7027): Elliptic Curve Cryptography (ECC) Brainpool Curves 252 | for Transport Layer Security (TLS) 253 | - [RFC 7301](https://tools.ietf.org/html/rfc7301): Transport Layer Security (TLS) 254 | Application-Layer Protocol Negotiation Extension 255 | - [RFC 7366](https://tools.ietf.org/html/rfc7366): Encrypt-then-MAC for Transport Layer Security (TLS) and 256 | Datagram Transport Layer Security (DTLS) 257 | - [RFC 7627](https://tools.ietf.org/html/rfc7627): Transport Layer Security (TLS) Session Hash and 258 | Extended Master Secret Extension 259 | - [RFC 7919](https://tools.ietf.org/html/rfc7919): Negotiated Finite Field Diffie-Hellman Ephemeral Parameters 260 | for Transport Layer Security (TLS) 261 | - [RFC 8422](https://tools.ietf.org/html/rfc8422): Elliptic Curve Cryptography (ECC) Cipher Suites 262 | for Transport Layer Security (TLS) Versions 1.2 and Earlier 263 | - [RFC 8446](https://tools.ietf.org/html/rfc8446): The Transport Layer Security (TLS) Protocol Version 1.3 264 | - [draft-agl-tls-nextprotoneg-03](https://tools.ietf.org/html/draft-agl-tls-nextprotoneg-03): Transport Layer Security (TLS) Next Protocol Negotiation Extension 265 | 266 | ## FAQ and limitations 267 | 268 | ### Can the parser decrypt a TLS session if I provide the master secret ? 269 | 270 | No, it's not implemented 271 | 272 | ### Does the parser support TLS compression ? 273 | 274 | No. Note that most TLS implementations disabled it after the FREAK attack, so 275 | while detecting compression in `ServerHello` is possible in tls-parser, it 276 | should probably be interpreted as an alert. 277 | 278 | ### Where are located the TLS CipherSuites ? 279 | 280 | They are built when running `cargo build`. 281 | 282 | To ease updating the list from the 283 | [IANA TLS 284 | parameters](http://www.iana.org/assignments/tls-parameters/tls-parameters.xml), 285 | a script is provided ([scripts/extract-iana-ciphers.py](scripts/extract-iana-ciphers.py)). 286 | This script will download and pre-parse the list from IANA, and produce a file containing 287 | all ciphersuites names and parameters. 288 | 289 | During the build, [build.rs](build.rs) parses this file and produces a static, 290 | read-only hash table of all known ciphers and their properties. 291 | 292 | ## License 293 | 294 | Licensed under either of 295 | 296 | * Apache License, Version 2.0 297 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 298 | * MIT license 299 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 300 | 301 | at your option. 302 | 303 | ## Contribution 304 | 305 | Unless you explicitly state otherwise, any contribution intentionally submitted 306 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 307 | dual licensed as above, without any additional terms or conditions. 308 | -------------------------------------------------------------------------------- /assets/client_hello_dhe.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/tls-parser/b82bd696fc57e5d3a39bbb01f2d4c12ea4b70952/assets/client_hello_dhe.bin -------------------------------------------------------------------------------- /assets/esni.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/tls-parser/b82bd696fc57e5d3a39bbb01f2d4c12ea4b70952/assets/esni.bin -------------------------------------------------------------------------------- /assets/tls_record_ch_fragmented_1.bin: -------------------------------------------------------------------------------- 1 |  2 | / -------------------------------------------------------------------------------- /assets/tls_record_ch_fragmented_2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/tls-parser/b82bd696fc57e5d3a39bbb01f2d4c12ea4b70952/assets/tls_record_ch_fragmented_2.bin -------------------------------------------------------------------------------- /benches/bench_dh.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "unstable", test))] 2 | #![feature(test)] 3 | 4 | extern crate test; 5 | 6 | extern crate nom; 7 | extern crate tls_parser; 8 | 9 | mod tls_dh { 10 | use nom::sequence::pair; 11 | use test::Bencher; 12 | use tls_parser::*; 13 | 14 | #[rustfmt::skip] 15 | static ECDHE_PARAMS: &[u8] = &[ 16 | 0x03, 0x00, 0x19, 0x85, 0x04, 0x01, 0xd1, 0x50, 0x12, 0xf4, 0xc4, 0xcf, 0xd4, 0xc2, 0x1f, 0xe8, 17 | 0xf6, 0x85, 0xdc, 0xde, 0x0b, 0xeb, 0x3c, 0x0d, 0x0f, 0x97, 0x29, 0x36, 0x63, 0xc6, 0xc1, 0x3b, 18 | 0xfd, 0x38, 0xce, 0xde, 0x43, 0x7f, 0x7d, 0x57, 0x64, 0x54, 0x6f, 0x89, 0x3c, 0xe7, 0x5e, 0x28, 19 | 0x9e, 0x9d, 0x24, 0xca, 0x07, 0x63, 0xd5, 0x03, 0x30, 0x8b, 0xd8, 0x1a, 0xae, 0xb6, 0xa8, 0x5f, 20 | 0x10, 0x87, 0x81, 0x29, 0x1b, 0xef, 0xbd, 0x00, 0xeb, 0x29, 0x37, 0xb3, 0xc3, 0xda, 0x8e, 0xad, 21 | 0xf3, 0x9c, 0x10, 0xe3, 0x93, 0xeb, 0x0a, 0x53, 0x14, 0xea, 0x3c, 0x05, 0xb7, 0xc1, 0x6b, 0x79, 22 | 0xca, 0xfc, 0x9a, 0x5b, 0xc3, 0xaf, 0xf2, 0xdd, 0x9f, 0xdd, 0x07, 0xf5, 0x07, 0xef, 0xb4, 0x24, 23 | 0xac, 0xdb, 0xd2, 0x0d, 0x65, 0x37, 0x96, 0xa0, 0x15, 0xef, 0x7c, 0x6d, 0x66, 0x63, 0x0d, 0x41, 24 | 0x1d, 0xd7, 0x90, 0x05, 0x66, 0xcf, 0x79, 0x0c, 0x03, 0x02, 0x01, 0x01, 0x00, 0x7c, 0xa7, 0x5f, 25 | 0x73, 0x77, 0x2c, 0x92, 0x4c, 0xe4, 0xa7, 0x67, 0x86, 0x76, 0xf2, 0xa3, 0xf8, 0xd1, 0x9d, 0xca, 26 | 0x4f, 0x71, 0xd1, 0x67, 0xf4, 0xbe, 0x7e, 0xb3, 0x60, 0xc4, 0xf1, 0x6e, 0x90, 0x22, 0x97, 0xe9, 27 | 0xc2, 0x43, 0xc9, 0xfb, 0x46, 0x21, 0xd4, 0xe9, 0xed, 0xdc, 0x46, 0x5b, 0x3e, 0x4c, 0xfb, 0xf2, 28 | 0xeb, 0x3f, 0x09, 0x4e, 0x59, 0x5f, 0x6f, 0x60, 0x50, 0x8a, 0x80, 0x50, 0xa7, 0xc3, 0xb9, 0xf0, 29 | 0xd1, 0x80, 0xb0, 0x1b, 0x11, 0x53, 0xe4, 0xac, 0x45, 0xa8, 0x75, 0x59, 0x55, 0x1a, 0x20, 0xa5, 30 | 0xbb, 0x23, 0xb6, 0x1c, 0x39, 0xa8, 0x4e, 0x62, 0x57, 0xef, 0x4f, 0x11, 0xce, 0x64, 0x87, 0x9b, 31 | 0x5a, 0xb8, 0x06, 0xf1, 0x62, 0x63, 0x3d, 0x13, 0x46, 0x72, 0x79, 0x7e, 0x65, 0x5c, 0xb4, 0x0a, 32 | 0xe3, 0x63, 0x13, 0x05, 0xc9, 0xaa, 0xc3, 0x93, 0x9b, 0x69, 0x37, 0x04, 0xa6, 0x7b, 0x69, 0xa9, 33 | 0x72, 0x67, 0x32, 0x9d, 0xc9, 0x53, 0x05, 0xe5, 0x18, 0x00, 0x73, 0xcb, 0x40, 0xd8, 0x86, 0x81, 34 | 0x01, 0x78, 0x36, 0x8f, 0x62, 0x94, 0xb4, 0x88, 0x27, 0xdb, 0x8e, 0xe4, 0x76, 0x56, 0x1d, 0xac, 35 | 0x7d, 0x36, 0x4c, 0xb4, 0xad, 0x4c, 0xe0, 0x21, 0x1f, 0xd5, 0x2d, 0x30, 0xa0, 0x78, 0xba, 0x28, 36 | 0x0b, 0xb4, 0x6d, 0xf1, 0x95, 0x41, 0x11, 0xdb, 0x64, 0xaf, 0x11, 0xa2, 0x9b, 0x45, 0x07, 0x42, 37 | 0x95, 0xf1, 0xe4, 0x0a, 0x16, 0x0c, 0x7f, 0xa7, 0x96, 0xc1, 0x91, 0xf0, 0x7c, 0xf7, 0x67, 0xe6, 38 | 0x1c, 0xbd, 0x1d, 0xcb, 0xbc, 0x42, 0x2a, 0x47, 0x35, 0x28, 0x96, 0xc3, 0x08, 0x48, 0x7d, 0xe9, 39 | 0xf1, 0x42, 0x00, 0xee, 0xd5, 0x0e, 0xd4, 0x08, 0xd6, 0x34, 0x15, 0xd6, 0x7c, 0x4b, 0xc5, 0x23, 40 | 0xf4, 0x8c, 0xfa, 0x70, 0xd8, 0x60, 0x46, 0xd2, 0xa3, 0xba, 0x75, 0xa4, 0x8f 41 | ]; 42 | 43 | #[bench] 44 | fn bench_tls_ecdhe_params(b: &mut Bencher) { 45 | let bytes = ECDHE_PARAMS; 46 | b.iter(|| { 47 | let _ = pair(parse_ecdh_params, parse_digitally_signed)(bytes); 48 | }) 49 | } 50 | } // mod tls_dh 51 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate phf_codegen; 2 | 3 | use std::env; 4 | use std::fs::File; 5 | use std::io::BufRead; 6 | use std::io::{BufReader, BufWriter, Write}; 7 | use std::path::Path; 8 | 9 | fn titlecase_word(word: &str) -> String { 10 | word.chars() 11 | .enumerate() 12 | .map(|(i, c)| { 13 | if i == 0 { 14 | c.to_uppercase().collect::() 15 | } else { 16 | c.to_lowercase().collect::() 17 | } 18 | }) 19 | .collect() 20 | } 21 | 22 | fn main() { 23 | let path_txt = 24 | Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("scripts/tls-ciphersuites.txt"); 25 | let display = path_txt.display(); 26 | let file = match File::open(&path_txt) { 27 | // The `description` method of `io::Error` returns a string that 28 | // describes the error 29 | Err(why) => panic!("couldn't open {}: {}", display, why), 30 | Ok(file) => file, 31 | }; 32 | let f = BufReader::new(file); 33 | 34 | let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); 35 | let mut file = BufWriter::new(File::create(path).unwrap()); 36 | 37 | let mut map = phf_codegen::Map::new(); 38 | for line in f.lines() { 39 | let l = line.unwrap(); 40 | let mut v: Vec<&str> = l.split(':').collect(); 41 | 42 | if v[5].is_empty() { 43 | v[5] = "NULL" 44 | } 45 | 46 | let au = match v[3] { 47 | "SRP+DSS" => String::from("Srp_Dss"), 48 | "SRP+RSA" => String::from("Srp_Rsa"), 49 | _ => titlecase_word(v[3]).replace('+', "_"), 50 | }; 51 | 52 | let enc = match v[4] { 53 | "3DES" => String::from("TripleDes"), 54 | "CHACHA20_POLY1305" => String::from("Chacha20_Poly1305"), 55 | _ => titlecase_word(v[4]), 56 | }; 57 | 58 | let mac = String::from(match v[7] { 59 | "NULL" => "Null", 60 | "HMAC-MD5" => "HmacMd5", 61 | "HMAC-SHA1" => "HmacSha1", 62 | "HMAC-SHA256" => "HmacSha256", 63 | "HMAC-SHA384" => "HmacSha384", 64 | "HMAC-SHA512" => "HmacSha512", 65 | "AEAD" => "Aead", 66 | _ => panic!("Unknown mac {}", v[7]), 67 | }); 68 | 69 | let prf = titlecase_word(v[9]); 70 | 71 | let key = u16::from_str_radix(v[0], 16).unwrap(); 72 | let val = format!( 73 | r#"TlsCipherSuite{{ 74 | name:"{}", 75 | id:TlsCipherSuiteID(0x{}), 76 | kx:TlsCipherKx::{}, 77 | au:TlsCipherAu::{}, 78 | enc:TlsCipherEnc::{}, 79 | enc_mode:TlsCipherEncMode::{}, 80 | enc_size:{}, 81 | mac:TlsCipherMac::{}, 82 | mac_size:{}, 83 | prf: TlsPRF::{}, 84 | }}"#, 85 | v[1], 86 | v[0], 87 | titlecase_word(v[2]), // kx 88 | au, // au 89 | enc, // enc 90 | titlecase_word(v[5]), // enc_mode 91 | v[6], // enc_key_size 92 | mac, // mac 93 | v[8], // mac_size 94 | prf, // prf 95 | ) 96 | .clone(); 97 | 98 | map.entry(key, val.as_str()); 99 | } 100 | 101 | writeln!( 102 | &mut file, 103 | "#[allow(unused_qualifications)]\npub static CIPHERS: phf::Map = {};", 104 | map.build() 105 | ) 106 | .unwrap(); 107 | } 108 | -------------------------------------------------------------------------------- /examples/get-ciphersuite-info.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Helper program to list/query ciphersuite information 4 | * 5 | */ 6 | 7 | use clap::Parser; 8 | use std::num::ParseIntError; 9 | use tls_parser::TlsCipherSuite; 10 | 11 | #[derive(Parser)] 12 | struct CmdOptions { 13 | /// List all known ciphersuites 14 | #[arg(short = 'L', long)] 15 | list: bool, 16 | /// Display details (algorithms, mode, ...) 17 | #[arg(short, long)] 18 | long: bool, 19 | /// Use JSON for output 20 | #[arg(short = 'j', long)] 21 | to_json: bool, 22 | 23 | /// Ciphersuite IANA identifier (decimal or hexadecimal prefix by 0x) 24 | #[arg(short, long, value_name = "id")] 25 | id: Option, 26 | /// Ciphersuite IANA name 27 | #[arg(short, long, value_name = "name")] 28 | name: Option, 29 | } 30 | 31 | fn parse_u16(s: &str) -> Result { 32 | if s.starts_with("0x") { 33 | let s = s.trim_start_matches("0x"); 34 | u16::from_str_radix(s, 16) 35 | } else { 36 | s.parse::() 37 | } 38 | } 39 | 40 | fn print_ciphersuite(cs: &TlsCipherSuite, show_details: bool, to_json: bool) { 41 | if to_json { 42 | let mut entries = Vec::new(); 43 | entries.push(format!("\"id\":{}", cs.id)); 44 | entries.push(format!("\"hex_id\":\"0x{:x}\"", cs.id)); 45 | entries.push(format!("\"name\":\"{}\"", cs.name)); 46 | // 47 | if show_details { 48 | entries.push(format!("\"kx\":\"{:?}\"", cs.kx)); 49 | entries.push(format!("\"au\":\"{:?}\"", cs.au)); 50 | entries.push(format!("\"enc\":\"{:?}\"", cs.enc)); 51 | entries.push(format!("\"enc_mode\":\"{:?}\"", cs.enc_mode)); 52 | entries.push(format!("\"enc_size\":{}", cs.enc_size)); 53 | entries.push(format!("\"mac\":\"{:?}\"", cs.mac)); 54 | entries.push(format!("\"mac_size\":{}", cs.mac_size)); 55 | } 56 | let s = entries.join(","); 57 | println!("{{ {} }}", s); 58 | } else { 59 | let details = if show_details { 60 | format!( 61 | " kx={:?} au={:?} enc={:?} enc_mode={:?} enc_size={} mac={:?} mac_size={}", 62 | cs.kx, cs.au, cs.enc, cs.enc_mode, cs.enc_size, cs.mac, cs.mac_size 63 | ) 64 | } else { 65 | "".to_string() 66 | }; 67 | println!("{:04x} {}{}", cs.id, cs.name, details); 68 | } 69 | } 70 | 71 | fn find_by_id(id: u16, show_details: bool, to_json: bool) { 72 | let cipher = TlsCipherSuite::from_id(id); 73 | if let Some(cipher) = cipher { 74 | print_ciphersuite(cipher, show_details, to_json); 75 | } else { 76 | eprintln!("Unknown ciphersuite"); 77 | } 78 | } 79 | 80 | fn find_by_name(name: &str, show_details: bool, to_json: bool) { 81 | let cipher = TlsCipherSuite::from_name(name); 82 | if let Some(cipher) = cipher { 83 | print_ciphersuite(cipher, show_details, to_json); 84 | } else { 85 | eprintln!("Unknown ciphersuite"); 86 | } 87 | } 88 | 89 | fn main() { 90 | let options = CmdOptions::parse(); 91 | 92 | let show_details = options.long; 93 | 94 | if options.list { 95 | let mut id_list = tls_parser::CIPHERS.keys().collect::>(); 96 | id_list.sort(); 97 | for &id in &id_list { 98 | let cipher = TlsCipherSuite::from_id(*id).expect("could not get cipher"); 99 | print_ciphersuite(cipher, show_details, options.to_json); 100 | } 101 | return; 102 | } 103 | 104 | if let Some(str_id) = options.id { 105 | let id = parse_u16(&str_id).expect("Could not parse cipher ID"); 106 | find_by_id(id, show_details, options.to_json); 107 | } else if let Some(name) = options.name { 108 | find_by_name(&name, show_details, options.to_json); 109 | } else { 110 | eprintln!("Missing command"); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "tls-parser-fuzz" 4 | version = "0.0.1" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies.tls-parser] 12 | path = ".." 13 | [dependencies.libfuzzer-sys] 14 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [[bin]] 21 | name = "parse_tls_plaintext" 22 | path = "fuzzers/parse_tls_plaintext.rs" 23 | 24 | [[bin]] 25 | name = "parse_tls_extension" 26 | path = "fuzzers/parse_tls_extension.rs" 27 | 28 | [[bin]] 29 | name = "parse_dtls_plaintext" 30 | path = "fuzzers/parse_dtls_plaintext.rs" 31 | test = false 32 | doc = false 33 | -------------------------------------------------------------------------------- /fuzz/fuzzers/parse_dtls_plaintext.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | extern crate libfuzzer_sys; 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | // fuzzed code goes here 7 | let _ = tls_parser::parse_dtls_plaintext_record(data); 8 | }); 9 | -------------------------------------------------------------------------------- /fuzz/fuzzers/parse_tls_extension.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate tls_parser; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = tls_parser::parse_tls_extension(data); 7 | }); 8 | -------------------------------------------------------------------------------- /fuzz/fuzzers/parse_tls_plaintext.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate tls_parser; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = tls_parser::parse_tls_plaintext(data); 7 | }); 8 | -------------------------------------------------------------------------------- /scripts/extract-iana-ciphers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import urllib2 4 | from BeautifulSoup import BeautifulSoup, ResultSet 5 | file = urllib2.urlopen('https://www.iana.org/assignments/tls-parameters/tls-parameters.xml') 6 | data = file.read() 7 | with open('tls-parameters.xml', 'wb') as myFile: 8 | myFile.write(data) 9 | file.close() 10 | 11 | dom = BeautifulSoup(data) 12 | 13 | #ciphersuites=dom.findAll ("registry")[4] 14 | ciphersuites=dom.findAll (id="tls-parameters-4") 15 | if isinstance(ciphersuites,ResultSet): 16 | ciphersuites = ciphersuites.pop() 17 | 18 | for i in ciphersuites.findAll ("record"): 19 | value = "".join(i.value.contents) 20 | desc = "".join (i.description.contents) 21 | 22 | ignore_keywords = [ 23 | "Unassigned", 24 | "Reserved", 25 | ] 26 | f = filter(desc.startswith,ignore_keywords) 27 | 28 | if len(f) > 0: 29 | continue 30 | 31 | if desc == "TLS_EMPTY_RENEGOTIATION_INFO_SCSV": 32 | continue 33 | elif desc == "TLS_FALLBACK_SCSV": 34 | continue 35 | 36 | rfc = "NONE" 37 | if i.xref: 38 | rfc_tmp = filter (lambda (var,val) : var == "data", i.xref.attrs) 39 | if len (rfc_tmp) > 0: 40 | # rfc = rfc_tmp[0][1][3:7] 41 | rfc = rfc_tmp[0][1] 42 | 43 | real_value = "".join (map (lambda x : "%2.2x" % (int (x, 16)), value.split (","))) 44 | 45 | minver = 0x0300 46 | maxver = 0xffff 47 | 48 | if rfc == "rfc8446": 49 | kxau = ["TLS13"] 50 | encmac = desc[4:] # skip "TLS_" 51 | elif rfc == "draft-camwinget-tls-ts13-macciphersuites": 52 | kxau = ["TLS13"] 53 | encmac = "NULL_" + desc.split("_")[1] # forge string like NULL_SHA256 54 | else: 55 | (_kxau, encmac) = desc.split("_WITH_") 56 | kxau = _kxau.split ("_")[1:] 57 | export = 0 58 | if kxau[-1] == "EXPORT": 59 | export = 1 60 | maxver = 0x302 61 | kxau = kxau[:-1] 62 | if len (kxau) == 1: 63 | kx = kxau[0] 64 | au = kxau[0] 65 | elif kxau[0] == "SRP": 66 | kx = "_".join (kxau[0:1]) 67 | au = kx 68 | if len (kxau) > 2: 69 | au += "+" + "_".join (kxau[2:]) 70 | elif kxau[0] == "GOSTR341112": 71 | # unsupported suites from https://datatracker.ietf.org/doc/draft-smyshlyaev-tls12-gost-suites/ 72 | continue 73 | else: 74 | kx, au = kxau 75 | if au == "anon": 76 | au = "NULL" 77 | 78 | _encmac = encmac.split ("_") 79 | hashfun = _encmac [-1] 80 | _encstr = "_".join (_encmac [:-1]) 81 | _enc = _encmac [:-1] 82 | 83 | if _encstr == "DES40_CBC": 84 | enc = "DES" 85 | encmode = "CBC" 86 | encsize = 40 87 | elif len (_enc) == 3 and _enc[1] == "CBC" and _enc[2] == "40": 88 | enc = _enc[0] 89 | encmode = "CBC" 90 | encsize = 40 91 | elif _encstr == "DES_CBC": 92 | enc = "DES" 93 | encmode = "CBC" 94 | encsize = 56 95 | elif _encstr == "IDEA_CBC": 96 | enc = "IDEA" 97 | encmode = "CBC" 98 | encsize = 128 99 | elif _encstr == "3DES_EDE_CBC": 100 | enc = "3DES" 101 | encmode = "CBC" 102 | encsize = 168 103 | elif _encstr == "NULL": 104 | enc = "NULL" 105 | encmode = "" 106 | encsize = 0 107 | elif _encstr == "SEED_CBC": 108 | enc = "SEED" 109 | encmode = "CBC" 110 | encsize = 128 111 | elif _encstr == "CHACHA20_POLY1305": 112 | enc = "CHACHA20_POLY1305" 113 | encmode = "CBC" 114 | encsize = 256 115 | elif len (_enc) == 2: 116 | enc = _enc[0] 117 | encmode = "" 118 | encsize = int (_enc[1]) 119 | else: 120 | enc = _enc[0] 121 | encmode = _enc[2] 122 | encsize = int (_enc[1]) 123 | 124 | prf = "DEFAULT" 125 | prfsize = 0 126 | 127 | # fix crap from recent changes 128 | if hashfun == "8": 129 | hashfun = "_".join([encmode,hashfun]) 130 | encmode = "" 131 | 132 | if hashfun == "NULL": 133 | mac = "NULL" 134 | macsize = 0 135 | elif hashfun == "MD5": 136 | mac = "HMAC-MD5" 137 | macsize = 128 138 | elif hashfun == "SHA": 139 | mac = "HMAC-SHA1" 140 | macsize = 160 141 | elif hashfun == "SHA256": 142 | mac = "HMAC-SHA256" 143 | macsize = 256 144 | prf = "SHA256" 145 | prfsize = 256 146 | minver = 0x303 147 | elif hashfun == "SHA384": 148 | mac = "HMAC-SHA384" 149 | macsize = 384 150 | prf = "SHA384" 151 | prfsize = 384 152 | minver = 0x303 153 | elif hashfun == "CCM": 154 | #print encmode 155 | #mac = "CCM" 156 | #macsize = 0 157 | minver = 0x303 158 | encmode = "CCM" 159 | elif hashfun == "CCM_8": 160 | #print encmode 161 | #mac = "CCM_8" 162 | #macsize = 0 163 | minver = 0x303 164 | encmode = "CCM" 165 | else: 166 | print desc 167 | print encmac 168 | print hashfun 169 | raise "Unsupported." 170 | 171 | if encmode == "GCM" or encmode == "CCM": 172 | mac = "AEAD" 173 | macsize = encsize 174 | minver = 0x303 175 | if _encstr == "CHACHA20_POLY1305": 176 | mac = "AEAD" 177 | macsize = encsize 178 | minver = 0x303 179 | 180 | print "%s:%s:%s:%s:%s:%s:%d:%s:%d:%s:%d:%s:%d:%4.4x:%4.4x" % (real_value, desc, kx, au, enc, encmode, encsize, mac, macsize, prf, prfsize, rfc, export, minver, maxver) 181 | -------------------------------------------------------------------------------- /scripts/extract-iana-ciphers2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import csv 4 | import urllib.request 5 | import sys 6 | import re 7 | 8 | # Where to get the TLS parameters from. 9 | # See http://www.iana.org/assignments/tls-parameters/tls-parameters.xml. 10 | URL = "https://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv" 11 | 12 | def getCiphers(): 13 | req = urllib.request.urlopen(URL) 14 | data = req.read().decode('utf-8') 15 | # f = open("tls-parameters-4.csv", "r") 16 | # data = f.read() 17 | 18 | ciphers = [] 19 | reader = csv.DictReader(data.splitlines()) 20 | for row in reader: 21 | desc = row["Description"] 22 | rawval = row["Value"] 23 | rfcs = row["Reference"] 24 | 25 | # Just plain TLS values for now, to keep it simple. 26 | if "-" in rawval or not desc.startswith("TLS"): 27 | continue 28 | 29 | rv1, rv2 = rawval.split(",") 30 | rv1, rv2 = int(rv1, 16), int(rv2, 16) 31 | 32 | val = "%02x%02x" % (rv1, rv2) 33 | ciphers.append((val, desc, rfcs)) 34 | 35 | # Manually adding ciphers from https://datatracker.ietf.org/doc/html/draft-ietf-tls-56-bit-ciphersuites-01 36 | ciphers.append(("0062", "TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA", "draft-ietf-tls-56-bit-ciphersuites-01")) 37 | ciphers.append(("0064", "TLS_RSA_EXPORT1024_WITH_RC4_56_SHA", "draft-ietf-tls-56-bit-ciphersuites-01")) 38 | ciphers.append(("0063", "TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA", "draft-ietf-tls-56-bit-ciphersuites-01")) 39 | ciphers.append(("0065", "TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA", "draft-ietf-tls-56-bit-ciphersuites-01")) 40 | ciphers.append(("0066", "TLS_DHE_DSS_WITH_RC4_128_SHA", "draft-ietf-tls-56-bit-ciphersuites-01")) 41 | 42 | # Unsure which RFC these are coming from 43 | ciphers.append(("0060", "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5", "unknown")) 44 | ciphers.append(("0061", "TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5", "unknown")) 45 | 46 | return ciphers 47 | 48 | re_tls_with = re.compile('^TLS_(\w+)_WITH_(\w+)_(\w+)$') 49 | 50 | MAP_KX = { 51 | 'NULL': ['NULL', 'NULL'], 52 | 'DH_anon': ['DH', 'NULL'], 53 | 'DH_anon_EXPORT': ['DH', 'NULL'], 54 | 'DH_DSS': ['DH', 'DSS'], 55 | 'DH_DSS_EXPORT': ['DH', 'DSS'], 56 | 'DHE_DSS': ['DHE', 'DSS'], 57 | 'DHE_DSS_EXPORT': ['DHE', 'DSS'], 58 | 'DHE_DSS_EXPORT1024': ['DHE', 'DSS'], 59 | 'RSA': ['RSA', 'RSA'], 60 | 'RSA_EXPORT1024': ['RSA', 'RSA'], 61 | 'DH_RSA': ['DH', 'RSA'], 62 | 'DH_RSA_EXPORT': ['DH', 'RSA'], 63 | 'DHE_PSK': ['DHE', 'PSK'], 64 | 'DHE_RSA': ['DHE', 'RSA'], 65 | 'DHE_RSA_EXPORT': ['DHE', 'RSA'], 66 | 'ECCPWD': ['ECCPWD', 'ECCPWD'], 67 | 'ECDH_ECDSA': ['ECDH', 'ECDSA'], 68 | 'ECDH_anon': ['ECDH', 'NULL'], 69 | 'ECDH_RSA': ['ECDH', 'RSA'], 70 | 'ECDHE_ECDSA': ['ECDHE', 'ECDSA'], 71 | 'ECDHE_PSK': ['ECDHE', 'PSK'], 72 | 'ECDHE_RSA': ['ECDHE', 'RSA'], 73 | 'PSK': ['PSK', 'PSK'], 74 | 'PSK_DHE': ['DHE', 'PSK'], 75 | 'KRB5': ['KRB5', 'KRB5'], 76 | 'KRB5_EXPORT': ['KRB5', 'KRB5'], 77 | 'RSA_EXPORT': ['RSA', 'RSA'], 78 | 'RSA_PSK': ['RSA', 'PSK'], 79 | 'SRP_SHA': ['SRP', 'SRP'], 80 | 'SRP_SHA_DSS': ['SRP', 'SRP+DSS'], 81 | 'SRP_SHA_RSA': ['SRP', 'SRP+RSA'], 82 | 'TLS13': ['TLS13', 'TLS13'], 83 | } 84 | 85 | MAP_ENC = { 86 | 'NULL': ['NULL', '', 0], 87 | 'NULL_SHA256': ['NULL', '', 0], 88 | '3DES_EDE_CBC': ['3DES', 'CBC', 168], 89 | 'AEGIS_128L': ['AEGIS', 'NULL', 128], 90 | 'AEGIS_128X2': ['AEGIS', 'NULL', 128], 91 | 'AEGIS_256': ['AEGIS', 'NULL', 256], 92 | 'AEGIS_256X2': ['AEGIS', 'NULL', 256], 93 | 'AES_128_CBC': ['AES', 'CBC', 128], 94 | 'AES_256_CBC': ['AES', 'CBC', 256], 95 | 'AES_128_CCM': ['AES', 'CCM', 128], 96 | 'AES_128_CCM_8': ['AES', 'CCM', 128], 97 | 'AES_256_CCM': ['AES', 'CCM', 256], 98 | 'AES_256_CCM_8': ['AES', 'CCM', 256], 99 | 'AES_128_GCM': ['AES', 'GCM', 128], 100 | 'AES_256_GCM': ['AES', 'GCM', 256], 101 | 'ARIA_128_CBC': ['ARIA', 'CBC', 128], 102 | 'ARIA_256_CBC': ['ARIA', 'CBC', 256], 103 | 'ARIA_128_GCM': ['ARIA', 'GCM', 128], 104 | 'ARIA_256_GCM': ['ARIA', 'GCM', 256], 105 | 'CAMELLIA_128_CBC': ['CAMELLIA', 'CBC', 128], 106 | 'CAMELLIA_256_CBC': ['CAMELLIA', 'CBC', 256], 107 | 'CAMELLIA_128_GCM': ['CAMELLIA', 'GCM', 128], 108 | 'CAMELLIA_256_GCM': ['CAMELLIA', 'GCM', 256], 109 | 'CHACHA20_POLY1305': ['CHACHA20_POLY1305', '', 128], 110 | 'DES_CBC': ['DES', 'CBC', 56], 111 | 'DES_CBC_40': ['DES', 'CBC', 40], 112 | 'DES40_CBC': ['DES', 'CBC', 40], 113 | 'IDEA_CBC': ['IDEA', 'CBC', 128], 114 | 'RC2_CBC_40': ['RC2', 'CBC', 40], 115 | 'RC2_CBC_56': ['RC2', 'CBC', 56], 116 | 'RC4_40': ['RC4', '', 40], 117 | 'RC4_56': ['RC4', '', 56], 118 | 'RC4_128': ['RC4', '', 128], 119 | 'SEED_CBC': ['SEED', 'CBC', 128], 120 | 'SM4_CCM': ['SM4', 'CCM', 128], 121 | 'SM4_GCM': ['SM4', 'GCM', 128], 122 | } 123 | 124 | MAP_MAC = { 125 | 'NULL': ['NULL', 0, 'DEFAULT', 0], 126 | 'MD5': ['HMAC-MD5', 128, 'DEFAULT', 0], 127 | 'SHA': ['HMAC-SHA1', 160, 'DEFAULT', 0], 128 | 'SHA256': ['HMAC-SHA256', 256, 'SHA256', 256], 129 | 'SHA384': ['HMAC-SHA384', 384, 'SHA384', 384], 130 | 'SHA512': ['HMAC-SHA512', 512, 'SHA512', 512], 131 | 'SM3': ['SM3', 256, 'SM3', 256], 132 | } 133 | 134 | def extract_rfcs(rfcs): 135 | p = re.compile("\[|\]") 136 | m = p.split(rfcs) 137 | return [s.lower() for s in filter(lambda s: len(s) > 0, m)] 138 | 139 | def extract_ciphersuite_info(desc, rfcs): 140 | params = dict() 141 | if desc == "TLS_SHA256_SHA256": 142 | desc = "TLS_TLS13_WITH_NULL_SHA256" 143 | if desc == "TLS_SHA384_SHA384": 144 | desc = "TLS_TLS13_WITH_NULL_SHA384" 145 | if not "_WITH_" in desc: 146 | if desc.startswith("TLS_AES") or desc.startswith("TLS_CHACHA20") or desc.startswith("TLS_AEGIS"): 147 | # XXX special case: TLS 1.3: TLS_AES_128_GCM_SHA256 etc. 148 | desc = "TLS_TLS13_WITH_" + desc[4:] 149 | else: 150 | raise Exception("Unsupported ciphersuite %s" % desc) 151 | (_kxau, encmac) = desc.split("_WITH_") 152 | m = re_tls_with.match(desc) 153 | if m: 154 | orig_kx = params['kx'] = m.group(1) 155 | orig_au = params['au'] = m.group(1) 156 | orig_enc = params['enc'] = m.group(2) 157 | orig_mac = params['mac'] = m.group(3) 158 | # raise(Exception("Found {}".format(params))) 159 | else: 160 | raise Exception("Unsupported ciphersuite %s" % desc) 161 | # 162 | # normalize 163 | # 164 | if desc.endswith("CCM") or desc.endswith("CCM_8"): 165 | # special case: TLS_RSA_WITH_AES_128_CCM (RFC6655) 166 | orig_enc = orig_enc + "_" + orig_mac 167 | orig_mac = 'NULL' 168 | rfcs = extract_rfcs(rfcs) 169 | # 170 | # get parameters 171 | # 172 | (kx, au) = MAP_KX[orig_kx] 173 | enc_long = params['enc'] 174 | (enc, encmode, encsize) = MAP_ENC[orig_enc] 175 | (mac, macsize, prf, prfsize) = MAP_MAC[orig_mac] 176 | # 177 | # fixups 178 | # 179 | if encmode == "CCM" or encmode == "GCM": 180 | mac = "AEAD" 181 | macsize = encsize 182 | if enc == "CHACHA20_POLY1305": 183 | mac = "AEAD" 184 | macsize = encsize 185 | # XXX (not used yet) 186 | minver = 0x0300 187 | maxver = 0xffff 188 | # end 189 | params['kx'] = kx 190 | params['au'] = au 191 | params['enc'] = enc 192 | params['encmode'] = encmode 193 | params['encsize'] = encsize 194 | params['mac'] = mac 195 | params['macsize'] = macsize 196 | params['prf'] = prf 197 | params['prfsize'] = prfsize 198 | params['rfc'] = rfcs 199 | params['minver'] = minver 200 | params['maxver'] = maxver 201 | # print("Found {}".format(params)) 202 | return params 203 | 204 | ciphers = getCiphers() 205 | out = open(sys.argv[1], 'w') 206 | 207 | for value, desc, rfcs in ciphers: 208 | # filter special values 209 | full_desc = desc 210 | if desc == "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" or desc == "TLS_FALLBACK_SCSV": 211 | rfcs = extract_rfcs(rfcs) 212 | out.write("%s:%s:NULL:NULL:NULL::0:NULL:0:NULL:0:%s:0:0:0\n" % 213 | (value,desc,",".join(rfcs),) 214 | ) 215 | continue 216 | elif desc.startswith("TLS_SM4"): 217 | # special case: draft-yang-tls-tls13-sm-suites-03 218 | full_desc = "TLS_TLS13_WITH_" + desc[4:] 219 | elif desc.startswith("TLS_GOST"): 220 | # XXX ignore special case: TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC (draft-smyshlyaev-tls12-gost-suites) 221 | continue 222 | elif "draft-camwinget-tls-ts13-macciphersuites" in rfcs or "RFC-camwinget-tls-ts13-macciphersuites-12" in rfcs: 223 | # "TLS_SHA256_SHA256" and similar 224 | full_desc = "TLS_TLS13_WITH_NULL_" + desc[4:-7] 225 | # print("%s %s %s" % (value, desc, rfcs)) 226 | # split ciphersuite info 227 | cs_info = extract_ciphersuite_info(full_desc, rfcs) 228 | if cs_info is None: 229 | raise Exception("Unsupported ciphersuite %s" % desc) 230 | if cs_info['encsize'] == 40: 231 | export = 1 232 | else: 233 | export = 0 234 | out.write("%s:%s:%s:%s:%s:%s:%d:%s:%d:%s:%d:%s:%s:%4.4x:%4.4x\n" % 235 | (value,desc, 236 | cs_info['kx'], 237 | cs_info['au'], 238 | cs_info['enc'], 239 | cs_info['encmode'], 240 | cs_info['encsize'], 241 | cs_info['mac'], 242 | cs_info['macsize'], 243 | cs_info['prf'], 244 | cs_info['prfsize'], 245 | ','.join(cs_info['rfc']), 246 | export, 247 | cs_info['minver'], 248 | cs_info['maxver'], 249 | ) 250 | ) 251 | -------------------------------------------------------------------------------- /src/certificate_transparency.rs: -------------------------------------------------------------------------------- 1 | // Certificate Trasparency structures are defined in 2 | // [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962). 3 | use alloc::vec::Vec; 4 | use core::convert::TryInto; 5 | 6 | use nom::{ 7 | bytes::streaming::take, 8 | combinator::{complete, map_parser}, 9 | multi::{length_data, many0}, 10 | number::streaming::{be_u16, be_u64, be_u8}, 11 | IResult, 12 | }; 13 | use nom_derive::*; 14 | use rusticata_macros::newtype_enum; 15 | 16 | use crate::{parse_digitally_signed, DigitallySigned}; 17 | 18 | /// Certificate Transparency Version as defined in [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 19 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Nom)] 20 | pub struct CtVersion(pub u8); 21 | 22 | newtype_enum! { 23 | impl display CtVersion { 24 | V1 = 0, 25 | } 26 | } 27 | 28 | /// LogID as defined in [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 29 | #[derive(Clone, Debug, PartialEq)] 30 | pub struct CtLogID<'a> { 31 | pub key_id: &'a [u8; 32], 32 | } 33 | 34 | /// CtExtensions as defined in [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 35 | #[derive(Clone, Debug, PartialEq)] 36 | pub struct CtExtensions<'a>(pub &'a [u8]); 37 | 38 | /// Signed Certificate Timestamp as defined in [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 39 | #[derive(Clone, Debug, PartialEq)] 40 | pub struct SignedCertificateTimestamp<'a> { 41 | pub version: CtVersion, 42 | pub id: CtLogID<'a>, 43 | pub timestamp: u64, 44 | pub extensions: CtExtensions<'a>, 45 | pub signature: DigitallySigned<'a>, 46 | } 47 | 48 | pub(crate) fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID> { 49 | let (i, key_id) = take(32usize)(i)?; 50 | Ok(( 51 | i, 52 | CtLogID { 53 | key_id: key_id 54 | .try_into() 55 | .expect("take(32) is in sync with key_id size"), 56 | }, 57 | )) 58 | } 59 | 60 | pub(crate) fn parse_ct_extensions(i: &[u8]) -> IResult<&[u8], CtExtensions> { 61 | let (i, ext_len) = be_u16(i)?; 62 | let (i, ext_data) = take(ext_len as usize)(i)?; 63 | Ok((i, CtExtensions(ext_data))) 64 | } 65 | 66 | pub(crate) fn parse_ct_signed_certificate_timestamp_content( 67 | i: &[u8], 68 | ) -> IResult<&[u8], SignedCertificateTimestamp> { 69 | let (i, version) = be_u8(i)?; 70 | let (i, id) = parse_log_id(i)?; 71 | let (i, timestamp) = be_u64(i)?; 72 | let (i, extensions) = parse_ct_extensions(i)?; 73 | let (i, signature) = parse_digitally_signed(i)?; 74 | Ok(( 75 | i, 76 | SignedCertificateTimestamp { 77 | version: CtVersion(version), 78 | id, 79 | timestamp, 80 | extensions, 81 | signature, 82 | }, 83 | )) 84 | } 85 | 86 | /// Parses as single Signed Certificate Timestamp entry 87 | pub fn parse_ct_signed_certificate_timestamp( 88 | i: &[u8], 89 | ) -> IResult<&[u8], SignedCertificateTimestamp> { 90 | map_parser( 91 | length_data(be_u16), 92 | parse_ct_signed_certificate_timestamp_content, 93 | )(i) 94 | } 95 | 96 | /// Parses a list of Signed Certificate Timestamp entries 97 | pub fn parse_ct_signed_certificate_timestamp_list( 98 | i: &[u8], 99 | ) -> IResult<&[u8], Vec> { 100 | let (i, sct_len) = be_u16(i)?; 101 | let (i, sct_list) = map_parser( 102 | take(sct_len as usize), 103 | many0(complete(parse_ct_signed_certificate_timestamp)), 104 | )(i)?; 105 | Ok((i, sct_list)) 106 | } 107 | -------------------------------------------------------------------------------- /src/dtls.rs: -------------------------------------------------------------------------------- 1 | //! Datagram Transport Layer Security Version 1.2 (RFC 6347) 2 | 3 | use alloc::vec::Vec; 4 | use nom::bytes::streaming::take; 5 | use nom::combinator::{complete, cond, map, map_parser, opt, verify}; 6 | use nom::error::{make_error, ErrorKind}; 7 | use nom::multi::{length_data, many1}; 8 | use nom::number::streaming::{be_u16, be_u24, be_u64, be_u8}; 9 | use nom::{Err, IResult}; 10 | use nom_derive::Parse; 11 | 12 | use crate::tls_handshake::*; 13 | use crate::tls_message::*; 14 | use crate::tls_record::{TlsRecordType, MAX_RECORD_LEN}; 15 | use crate::TlsMessageAlert; 16 | 17 | /// DTLS Plaintext record header 18 | #[derive(Debug, PartialEq)] 19 | pub struct DTLSRecordHeader { 20 | pub content_type: TlsRecordType, 21 | pub version: TlsVersion, 22 | /// A counter value that is incremented on every cipher state change. 23 | pub epoch: u16, 24 | /// The sequence number for this record. 25 | pub sequence_number: u64, // really an u48 26 | pub length: u16, 27 | } 28 | 29 | /// DTLS Plaintext record 30 | /// 31 | /// Each DTLS record MUST fit within a single datagram. 32 | /// 33 | /// Multiple DTLS records may be placed in a single datagram. 34 | #[derive(Debug, PartialEq)] 35 | pub struct DTLSPlaintext<'a> { 36 | pub header: DTLSRecordHeader, 37 | pub messages: Vec>, 38 | } 39 | 40 | #[derive(Debug, PartialEq)] 41 | pub struct DTLSRawRecord<'a> { 42 | pub header: DTLSRecordHeader, 43 | pub fragment: &'a [u8], 44 | } 45 | 46 | #[derive(Debug, PartialEq)] 47 | pub struct DTLSClientHello<'a> { 48 | pub version: TlsVersion, 49 | pub random: &'a [u8], 50 | pub session_id: Option<&'a [u8]>, 51 | pub cookie: &'a [u8], 52 | /// A list of ciphers supported by client 53 | pub ciphers: Vec, 54 | /// A list of compression methods supported by client 55 | pub comp: Vec, 56 | pub ext: Option<&'a [u8]>, 57 | } 58 | 59 | impl<'a> ClientHello<'a> for DTLSClientHello<'a> { 60 | fn version(&self) -> TlsVersion { 61 | self.version 62 | } 63 | 64 | fn random(&self) -> &'a [u8] { 65 | self.random 66 | } 67 | 68 | fn session_id(&self) -> Option<&'a [u8]> { 69 | self.session_id 70 | } 71 | 72 | fn ciphers(&self) -> &Vec { 73 | &self.ciphers 74 | } 75 | 76 | fn comp(&self) -> &Vec { 77 | &self.comp 78 | } 79 | 80 | fn ext(&self) -> Option<&'a [u8]> { 81 | self.ext 82 | } 83 | } 84 | 85 | #[derive(Debug, PartialEq)] 86 | pub struct DTLSHelloVerifyRequest<'a> { 87 | pub server_version: TlsVersion, 88 | pub cookie: &'a [u8], 89 | } 90 | 91 | /// DTLS Generic handshake message 92 | #[derive(Debug, PartialEq)] 93 | pub struct DTLSMessageHandshake<'a> { 94 | pub msg_type: TlsHandshakeType, 95 | pub length: u32, 96 | pub message_seq: u16, 97 | pub fragment_offset: u32, 98 | pub fragment_length: u32, 99 | pub body: DTLSMessageHandshakeBody<'a>, 100 | } 101 | 102 | /// DTLS Generic handshake message 103 | #[derive(Debug, PartialEq)] 104 | pub enum DTLSMessageHandshakeBody<'a> { 105 | HelloRequest, 106 | ClientHello(DTLSClientHello<'a>), 107 | HelloVerifyRequest(DTLSHelloVerifyRequest<'a>), 108 | ServerHello(TlsServerHelloContents<'a>), 109 | NewSessionTicket(TlsNewSessionTicketContent<'a>), 110 | HelloRetryRequest(TlsHelloRetryRequestContents<'a>), 111 | Certificate(TlsCertificateContents<'a>), 112 | ServerKeyExchange(TlsServerKeyExchangeContents<'a>), 113 | CertificateRequest(TlsCertificateRequestContents<'a>), 114 | ServerDone(&'a [u8]), 115 | CertificateVerify(&'a [u8]), 116 | ClientKeyExchange(TlsClientKeyExchangeContents<'a>), 117 | Finished(&'a [u8]), 118 | CertificateStatus(TlsCertificateStatusContents<'a>), 119 | NextProtocol(TlsNextProtocolContent<'a>), 120 | Fragment(&'a [u8]), 121 | } 122 | 123 | /// DTLS plaintext message 124 | /// 125 | /// Plaintext records can only be found during the handshake. 126 | #[derive(Debug, PartialEq)] 127 | pub enum DTLSMessage<'a> { 128 | Handshake(DTLSMessageHandshake<'a>), 129 | ChangeCipherSpec, 130 | Alert(TlsMessageAlert), 131 | ApplicationData(TlsMessageApplicationData<'a>), 132 | Heartbeat(TlsMessageHeartbeat<'a>), 133 | } 134 | 135 | impl<'a> DTLSMessage<'a> { 136 | /// Tell if this DTLSMessage is a (handshake) fragment that needs combining with other 137 | /// fragments to be a complete message. 138 | pub fn is_fragment(&self) -> bool { 139 | match self { 140 | DTLSMessage::Handshake(h) => matches!(h.body, DTLSMessageHandshakeBody::Fragment(_)), 141 | _ => false, 142 | } 143 | } 144 | } 145 | 146 | // --------------------------- PARSERS --------------------------- 147 | 148 | /// DTLS record header 149 | // Section 4.1 of RFC6347 150 | pub fn parse_dtls_record_header(i: &[u8]) -> IResult<&[u8], DTLSRecordHeader> { 151 | let (i, content_type) = TlsRecordType::parse(i)?; 152 | let (i, version) = TlsVersion::parse(i)?; 153 | let (i, int0) = be_u64(i)?; 154 | let epoch = (int0 >> 48) as u16; 155 | let sequence_number = int0 & 0xffff_ffff_ffff; 156 | let (i, length) = be_u16(i)?; 157 | let record = DTLSRecordHeader { 158 | content_type, 159 | version, 160 | epoch, 161 | sequence_number, 162 | length, 163 | }; 164 | Ok((i, record)) 165 | } 166 | 167 | /// Treat the entire input as an opaque fragment. 168 | fn parse_dtls_fragment(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody> { 169 | Ok((&[], DTLSMessageHandshakeBody::Fragment(i))) 170 | } 171 | 172 | /// DTLS Client Hello 173 | // Section 4.2 of RFC6347 174 | fn parse_dtls_client_hello(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody> { 175 | let (i, version) = TlsVersion::parse(i)?; 176 | let (i, random) = take(32usize)(i)?; 177 | let (i, sidlen) = verify(be_u8, |&n| n <= 32)(i)?; 178 | let (i, session_id) = cond(sidlen > 0, take(sidlen as usize))(i)?; 179 | let (i, cookie) = length_data(be_u8)(i)?; 180 | let (i, ciphers_len) = be_u16(i)?; 181 | let (i, ciphers) = parse_cipher_suites(i, ciphers_len as usize)?; 182 | let (i, comp_len) = be_u8(i)?; 183 | let (i, comp) = parse_compressions_algs(i, comp_len as usize)?; 184 | let (i, ext) = opt(complete(length_data(be_u16)))(i)?; 185 | let content = DTLSClientHello { 186 | version, 187 | random, 188 | session_id, 189 | cookie, 190 | ciphers, 191 | comp, 192 | ext, 193 | }; 194 | Ok((i, DTLSMessageHandshakeBody::ClientHello(content))) 195 | } 196 | 197 | /// DTLS Client Hello 198 | // Section 4.2 of RFC6347 199 | fn parse_dtls_hello_verify_request(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody> { 200 | let (i, server_version) = TlsVersion::parse(i)?; 201 | let (i, cookie) = length_data(be_u8)(i)?; 202 | let content = DTLSHelloVerifyRequest { 203 | server_version, 204 | cookie, 205 | }; 206 | Ok((i, DTLSMessageHandshakeBody::HelloVerifyRequest(content))) 207 | } 208 | 209 | fn parse_dtls_handshake_msg_server_hello_tlsv12( 210 | i: &[u8], 211 | ) -> IResult<&[u8], DTLSMessageHandshakeBody> { 212 | map( 213 | parse_tls_server_hello_tlsv12::, 214 | DTLSMessageHandshakeBody::ServerHello, 215 | )(i) 216 | } 217 | 218 | fn parse_dtls_handshake_msg_serverdone( 219 | i: &[u8], 220 | len: usize, 221 | ) -> IResult<&[u8], DTLSMessageHandshakeBody> { 222 | map(take(len), DTLSMessageHandshakeBody::ServerDone)(i) 223 | } 224 | 225 | fn parse_dtls_handshake_msg_clientkeyexchange( 226 | i: &[u8], 227 | len: usize, 228 | ) -> IResult<&[u8], DTLSMessageHandshakeBody> { 229 | map( 230 | parse_tls_clientkeyexchange(len), 231 | DTLSMessageHandshakeBody::ClientKeyExchange, 232 | )(i) 233 | } 234 | 235 | fn parse_dtls_handshake_msg_certificate(i: &[u8]) -> IResult<&[u8], DTLSMessageHandshakeBody> { 236 | map(parse_tls_certificate, DTLSMessageHandshakeBody::Certificate)(i) 237 | } 238 | 239 | /// Parse a DTLS handshake message 240 | pub fn parse_dtls_message_handshake(i: &[u8]) -> IResult<&[u8], DTLSMessage> { 241 | let (i, msg_type) = map(be_u8, TlsHandshakeType)(i)?; 242 | let (i, length) = be_u24(i)?; 243 | let (i, message_seq) = be_u16(i)?; 244 | let (i, fragment_offset) = be_u24(i)?; 245 | let (i, fragment_length) = be_u24(i)?; 246 | // This packet contains fragment_length (which is less than length for fragmentation) 247 | let (i, raw_msg) = take(fragment_length)(i)?; 248 | 249 | // Handshake messages can be fragmented over multiple packets. When fragmented, the user 250 | // needs the fragment_offset, fragment_length and length to determine whether they received 251 | // all the fragments. The DTLS spec allows for overlapping and duplicated fragments. 252 | let is_fragment = fragment_offset > 0 || fragment_length < length; 253 | 254 | let (_, body) = match msg_type { 255 | _ if is_fragment => parse_dtls_fragment(raw_msg), 256 | TlsHandshakeType::ClientHello => parse_dtls_client_hello(raw_msg), 257 | TlsHandshakeType::HelloVerifyRequest => parse_dtls_hello_verify_request(raw_msg), 258 | TlsHandshakeType::ServerHello => parse_dtls_handshake_msg_server_hello_tlsv12(raw_msg), 259 | TlsHandshakeType::ServerDone => { 260 | parse_dtls_handshake_msg_serverdone(raw_msg, length as usize) 261 | } 262 | TlsHandshakeType::ClientKeyExchange => { 263 | parse_dtls_handshake_msg_clientkeyexchange(raw_msg, length as usize) 264 | } 265 | TlsHandshakeType::Certificate => parse_dtls_handshake_msg_certificate(raw_msg), 266 | _ => { 267 | // eprintln!("Unsupported message type {:?}", msg_type); 268 | Err(Err::Error(make_error(i, ErrorKind::Switch))) 269 | } 270 | }?; 271 | let msg = DTLSMessageHandshake { 272 | msg_type, 273 | length, 274 | message_seq, 275 | fragment_offset, 276 | fragment_length, 277 | body, 278 | }; 279 | Ok((i, DTLSMessage::Handshake(msg))) 280 | } 281 | 282 | /// Parse a DTLS changecipherspec message 283 | // XXX add extra verification hdr.len == 1 284 | pub fn parse_dtls_message_changecipherspec(i: &[u8]) -> IResult<&[u8], DTLSMessage> { 285 | let (i, _) = verify(be_u8, |&tag| tag == 0x01)(i)?; 286 | Ok((i, DTLSMessage::ChangeCipherSpec)) 287 | } 288 | 289 | /// Parse a DTLS alert message 290 | // XXX add extra verification hdr.len == 2 291 | pub fn parse_dtls_message_alert(i: &[u8]) -> IResult<&[u8], DTLSMessage> { 292 | let (i, alert) = TlsMessageAlert::parse(i)?; 293 | Ok((i, DTLSMessage::Alert(alert))) 294 | } 295 | 296 | pub fn parse_dtls_record_with_header<'i>( 297 | i: &'i [u8], 298 | hdr: &DTLSRecordHeader, 299 | ) -> IResult<&'i [u8], Vec>> { 300 | match hdr.content_type { 301 | TlsRecordType::ChangeCipherSpec => many1(complete(parse_dtls_message_changecipherspec))(i), 302 | TlsRecordType::Alert => many1(complete(parse_dtls_message_alert))(i), 303 | TlsRecordType::Handshake => many1(complete(parse_dtls_message_handshake))(i), 304 | // TlsRecordType::ApplicationData => many1(complete(parse_tls_message_applicationdata))(i), 305 | // TlsRecordType::Heartbeat => parse_tls_message_heartbeat(i, hdr.length), 306 | _ => { 307 | // eprintln!("Unsupported record type {:?}", hdr.content_type); 308 | Err(Err::Error(make_error(i, ErrorKind::Switch))) 309 | } 310 | } 311 | } 312 | 313 | /// Parse one DTLS plaintext record 314 | // Section 4.1 of RFC6347 315 | pub fn parse_dtls_plaintext_record(i: &[u8]) -> IResult<&[u8], DTLSPlaintext> { 316 | let (i, header) = parse_dtls_record_header(i)?; 317 | // As in TLS 1.2, the length should not exceed 2^14. 318 | if header.length > MAX_RECORD_LEN { 319 | return Err(Err::Error(make_error(i, ErrorKind::TooLarge))); 320 | } 321 | let (i, messages) = map_parser(take(header.length as usize), |i| { 322 | parse_dtls_record_with_header(i, &header) 323 | })(i)?; 324 | Ok((i, DTLSPlaintext { header, messages })) 325 | } 326 | 327 | /// Parse multiple DTLS plaintext record 328 | // Section 4.1 of RFC6347 329 | pub fn parse_dtls_plaintext_records(i: &[u8]) -> IResult<&[u8], Vec> { 330 | many1(complete(parse_dtls_plaintext_record))(i) 331 | } 332 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) 2 | //! [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 3 | //! [![Crates.io Version](https://img.shields.io/crates/v/tls-parser.svg)](https://crates.io/crates/tls-parser) 4 | //! [![GitHub CI](https://github.com/cpu/tls-parser/actions/workflows/rust.yml/badge.svg)](https://github.com/cpu/tls-parser/actions/workflows/rust.yml) 5 | //! [![Minimum rustc version](https://img.shields.io/badge/rustc-1.70.0+-lightgray.svg)](#rust-version-requirements) 6 | //! 7 | //! # TLS Parser 8 | //! 9 | //! A TLS parser, implemented with the [nom](https://github.com/Geal/nom) 10 | //! parser combinator framework. 11 | //! 12 | //! The goal of this parser is to implement TLS messages analysis, for example 13 | //! to use rules from a network IDS, for ex during the TLS handshake. 14 | //! 15 | //! It implements structures and parsing functions for records and messages, but 16 | //! need additional code to handle fragmentation, or to fully inspect messages. 17 | //! Parsing some TLS messages requires to know the previously selected parameters. 18 | //! See [the rusticata TLS parser](https://github.com/rusticata/rusticata/blob/master/src/tls.rs) 19 | //! for a full example. 20 | //! 21 | //! It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken 22 | //! to ensure security and safety of this crate, including design (recursion limit, defensive 23 | //! programming), tests, and fuzzing. It also aims to be panic-free. 24 | //! 25 | //! The code is available on [Github](https://github.com/rusticata/tls-parser) 26 | //! and is part of the [Rusticata](https://github.com/rusticata) project. 27 | //! 28 | //! ## Parsing records 29 | //! 30 | //! The main parsing functions are located in the [tls.rs](src/tls.rs) file. The entry functions are: 31 | //! - `parse_tls_plaintext`: parses a record as plaintext 32 | //! - `parse_tls_encrypted`: read an encrypted record. The parser has no crypto or decryption features, so the content 33 | //! will be left as opaque data. 34 | //! 35 | //! # Examples 36 | //! 37 | //! ```rust 38 | //! use tls_parser::parse_tls_plaintext; 39 | //! use tls_parser::nom::{Err, IResult}; 40 | //! 41 | //! let bytes : &[u8]= include_bytes!("../assets/client_hello_dhe.bin"); 42 | //! // [ 0x16, 0x03, 0x01 ... ]; 43 | //! let res = parse_tls_plaintext(&bytes); 44 | //! match res { 45 | //! Ok((rem,record)) => { 46 | //! // rem is the remaining data (not parsed) 47 | //! // record is an object of type TlsRecord 48 | //! }, 49 | //! Err(Err::Incomplete(needed)) => { 50 | //! eprintln!("Defragmentation required (TLS record)"); 51 | //! }, 52 | //! Err(e) => { eprintln!("parse_tls_record_with_header failed: {:?}",e); } 53 | //! } 54 | //! ``` 55 | //! 56 | //! Note that knowing if a record is plaintext or not is the responsibility of the caller. 57 | //! 58 | //! As reading TLS records may imply defragmenting records, some functions are 59 | //! provided to only read the record as opaque data (which ensures the record is 60 | //! complete and gives the record header) and then reading messages from data. 61 | //! 62 | //! Here is an example of two-steps parsing: 63 | //! 64 | //! ```rust 65 | //! # use tls_parser::{parse_tls_raw_record, parse_tls_record_with_header}; 66 | //! # use tls_parser::nom::{Err, IResult}; 67 | //! 68 | //! # let bytes : &[u8]= include_bytes!("../assets/client_hello_dhe.bin"); 69 | //! // [ 0x16, 0x03, 0x01 ... ]; 70 | //! match parse_tls_raw_record(bytes) { 71 | //! Ok((rem, ref r)) => { 72 | //! match parse_tls_record_with_header(r.data, &r.hdr) { 73 | //! Ok((rem2,ref msg_list)) => { 74 | //! for msg in msg_list { 75 | //! // msg has type TlsMessage 76 | //! } 77 | //! } 78 | //! Err(Err::Incomplete(needed)) => { eprintln!("incomplete record") } 79 | //! Err(_) => { eprintln!("error while parsing record") } 80 | //! } 81 | //! } 82 | //! Err(Err::Incomplete(needed)) => { eprintln!("incomplete record header") } 83 | //! Err(_) => { eprintln!("error while parsing record header") } 84 | //! } 85 | //! ``` 86 | //! 87 | //! Some additional work is required if reading packets from the network, to support 88 | //! reassembly of TCP segments and reassembly of TLS records. 89 | //! 90 | //! For a complete example of a TLS parser supporting defragmentation and states, see the 91 | //! [rusticata/src/tls.rs](https://github.com/rusticata/rusticata/blob/master/src/tls.rs) file of 92 | //! the [rusticata](https://github.com/rusticata/rusticata) crate. 93 | //! 94 | //! ## State machine 95 | //! 96 | //! A TLS state machine is provided in [tls_states.rs](src/tls_states.rs). The state machine is separated from the 97 | //! parsing functions, and is almost independent. 98 | //! It is implemented as a table of transitions, mainly for the handshake phase. 99 | //! 100 | //! After reading a TLS message using the previous functions, the TLS state can be 101 | //! updated using the `tls_state_transition` function. If the transition succeeds, 102 | //! it returns `Ok(new_state)`, otherwise it returns `Err(error_state)`. 103 | //! 104 | //! ```rust 105 | //! # use tls_parser::{tls_state_transition, TlsMessage, TlsState}; 106 | //! # use tls_parser::nom::{Err, IResult}; 107 | //! 108 | //! struct ParseContext { 109 | //! state: TlsState, 110 | //! } 111 | //! 112 | //! # fn update_state_machine(msg: &TlsMessage, ctx: &mut ParseContext, to_server:bool) -> Result<(),&'static str> { 113 | //! match tls_state_transition(ctx.state, msg, to_server) { 114 | //! Ok(s) => { ctx.state = s; Ok(()) } 115 | //! Err(_) => { 116 | //! ctx.state = TlsState::Invalid; 117 | //! Err("Invalid state") 118 | //! } 119 | //! } 120 | //! # } 121 | //! ``` 122 | //! 123 | //! # Implementation notes 124 | //! 125 | //! When parsing messages, if a field is an integer corresponding to an enum of known values, 126 | //! it is not parsed as an enum type, but as an integer. While this complicates accesses, 127 | //! it allows to read invalid values and continue parsing (for an IDS, it's better to read 128 | //! values than to get a generic parse error). 129 | 130 | #![deny(/*missing_docs,*/ 131 | unstable_features, 132 | /*unused_import_braces,*/ unused_qualifications)] 133 | #![forbid(unsafe_code)] 134 | #![allow(clippy::upper_case_acronyms)] 135 | #![no_std] 136 | 137 | #[cfg(any(test, feature = "std"))] 138 | #[macro_use] 139 | extern crate std; 140 | 141 | extern crate alloc; 142 | 143 | mod certificate_transparency; 144 | mod dtls; 145 | mod tls_alert; 146 | mod tls_ciphers; 147 | mod tls_debug; 148 | mod tls_dh; 149 | mod tls_ec; 150 | mod tls_extensions; 151 | mod tls_handshake; 152 | mod tls_message; 153 | mod tls_record; 154 | mod tls_records_parser; 155 | mod tls_sign_hash; 156 | mod tls_states; 157 | 158 | pub use certificate_transparency::*; 159 | pub use dtls::*; 160 | pub use tls_alert::*; 161 | pub use tls_ciphers::*; 162 | pub use tls_dh::*; 163 | pub use tls_ec::*; 164 | pub use tls_extensions::*; 165 | pub use tls_handshake::*; 166 | pub use tls_message::*; 167 | pub use tls_record::*; 168 | pub use tls_records_parser::*; 169 | pub use tls_sign_hash::*; 170 | pub use tls_states::*; 171 | 172 | #[cfg(all(feature = "serialize", not(feature = "std")))] 173 | compile_error!("features `serialize` cannot be enabled when using `no_std`"); 174 | 175 | #[cfg(all(feature = "serialize", feature = "std"))] 176 | mod tls_serialize; 177 | #[cfg(feature = "serialize")] 178 | pub use tls_serialize::*; 179 | 180 | pub use nom; 181 | pub use nom::{Err, IResult}; 182 | pub use rusticata_macros; 183 | -------------------------------------------------------------------------------- /src/tls_alert.rs: -------------------------------------------------------------------------------- 1 | use nom_derive::*; 2 | use rusticata_macros::newtype_enum; 3 | 4 | /// TLS alert severity 5 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Nom)] 6 | pub struct TlsAlertSeverity(pub u8); 7 | 8 | newtype_enum! { 9 | impl display TlsAlertSeverity { 10 | Warning = 0x01, 11 | Fatal = 0x02 12 | } 13 | } 14 | 15 | /// TLS alert description 16 | /// 17 | /// Alerts are defined in the [IANA TLS Alert 18 | /// Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6) 19 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Nom)] 20 | pub struct TlsAlertDescription(pub u8); 21 | 22 | newtype_enum! { 23 | impl display TlsAlertDescription { 24 | CloseNotify = 0x00, 25 | UnexpectedMessage = 0x0A, 26 | BadRecordMac = 0x14, 27 | DecryptionFailed = 0x15, 28 | RecordOverflow = 0x16, 29 | DecompressionFailure = 0x1E, 30 | HandshakeFailure = 0x28, 31 | NoCertificate = 0x29, 32 | BadCertificate = 0x2A, 33 | UnsupportedCertificate = 0x2B, 34 | CertificateRevoked = 0x2C, 35 | CertificateExpired = 0x2D, 36 | CertificateUnknown = 0x2E, 37 | IllegalParameter = 0x2F, 38 | UnknownCa = 0x30, 39 | AccessDenied = 0x31, 40 | DecodeError = 0x32, 41 | DecryptError = 0x33, 42 | ExportRestriction = 0x3C, 43 | ProtocolVersion = 0x46, 44 | InsufficientSecurity = 0x47, 45 | InternalError = 0x50, 46 | InappropriateFallback = 0x56, 47 | UserCancelled = 0x5A, 48 | NoRenegotiation = 0x64, 49 | MissingExtension = 0x6d, 50 | UnsupportedExtension = 0x6e, 51 | CertUnobtainable = 0x6f, 52 | UnrecognizedName = 0x70, 53 | BadCertStatusResponse = 0x71, 54 | BadCertHashValue = 0x72, 55 | UnknownPskIdentity = 0x73, 56 | CertificateRequired = 0x74, 57 | NoApplicationProtocol = 0x78 // [RFC7301] 58 | } 59 | } 60 | 61 | /// TLS alert message 62 | #[derive(Clone, PartialEq, Nom)] 63 | pub struct TlsMessageAlert { 64 | /// Should match a [TlsAlertSeverity](enum.TlsAlertSeverity.html) value 65 | pub severity: TlsAlertSeverity, 66 | /// Should match a [TlsAlertDescription](enum.TlsAlertDescription.html) value 67 | pub code: TlsAlertDescription, 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use crate::tls_alert::*; 73 | 74 | #[test] 75 | fn test_tlsalert_cast_severity() { 76 | let a = TlsAlertSeverity::Warning; 77 | 78 | let a_u8 = a.0; 79 | assert_eq!(a_u8, 0x01); 80 | 81 | let b = TlsAlertSeverity(a_u8); 82 | assert_eq!(b, TlsAlertSeverity::Warning); 83 | 84 | let s = format!("{}", b); 85 | assert_eq!(s, "Warning"); 86 | 87 | let s = format!("{}", TlsAlertSeverity(129)); 88 | assert_eq!(s, "TlsAlertSeverity(129 / 0x81)"); 89 | } 90 | 91 | #[test] 92 | fn test_tlsalert_cast_description() { 93 | let a = TlsAlertDescription::HandshakeFailure; 94 | 95 | let a_u8 = a.0; 96 | assert_eq!(a_u8, 0x28); 97 | 98 | let b = TlsAlertDescription(a_u8); 99 | assert_eq!(b, TlsAlertDescription::HandshakeFailure); 100 | } 101 | } // mod tests 102 | -------------------------------------------------------------------------------- /src/tls_ciphers.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! The [CIPHERS](static.CIPHERS.html) static hash map is built during the 3 | //! compilation of the crate, using `build.rs`. It parses a file derived from 4 | //! the [IANA TLS Cipher Suite 5 | //! Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4) 6 | //! to automatically extract parameters and add all known ciphersuites. 7 | 8 | #![allow(non_camel_case_types)] 9 | #![allow(clippy::unreadable_literal)] 10 | 11 | use core::convert::TryFrom; 12 | use num_enum::TryFromPrimitive; 13 | 14 | use crate::TlsCipherSuiteID; 15 | 16 | #[derive(Debug)] 17 | pub struct CipherSuiteNotFound(()); 18 | 19 | /// Key exchange methods 20 | #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] 21 | #[repr(u8)] 22 | pub enum TlsCipherKx { 23 | Null, 24 | Psk, 25 | Krb5, 26 | Srp, 27 | Rsa, 28 | Dh, 29 | Dhe, 30 | Ecdh, 31 | Ecdhe, 32 | Aecdh, 33 | Eccpwd, 34 | Tls13, 35 | } 36 | 37 | /// Authentication methods 38 | #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] 39 | #[repr(u8)] 40 | pub enum TlsCipherAu { 41 | Null, 42 | Psk, 43 | Krb5, 44 | Srp, 45 | Srp_Dss, 46 | Srp_Rsa, 47 | Dss, 48 | Rsa, 49 | Dhe, 50 | Ecdsa, 51 | Eccpwd, 52 | Tls13, 53 | } 54 | 55 | /// Encryption methods 56 | #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] 57 | #[repr(u8)] 58 | pub enum TlsCipherEnc { 59 | Null, 60 | Des, 61 | TripleDes, 62 | Rc2, 63 | Rc4, 64 | Aria, 65 | Idea, 66 | Seed, 67 | Aes, 68 | Camellia, 69 | Chacha20_Poly1305, 70 | Sm4, 71 | Aegis, 72 | } 73 | 74 | /// Encryption modes 75 | #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] 76 | #[repr(u8)] 77 | pub enum TlsCipherEncMode { 78 | Null, 79 | Cbc, 80 | Ccm, 81 | Gcm, 82 | } 83 | 84 | /// Message Authentication Code (MAC) methods 85 | #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] 86 | #[repr(u8)] 87 | pub enum TlsCipherMac { 88 | Null, 89 | HmacMd5, 90 | HmacSha1, 91 | HmacSha256, 92 | HmacSha384, 93 | HmacSha512, 94 | Aead, 95 | } 96 | 97 | /// Pseudo-Random Function (PRF) Function 98 | #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)] 99 | #[repr(u8)] 100 | pub enum TlsPRF { 101 | Default, 102 | Null, 103 | Md5AndSha1, 104 | Sha1, 105 | Sha256, 106 | Sha384, 107 | Sha512, 108 | Sm3, 109 | } 110 | 111 | /// TLS Ciphersuite 112 | /// 113 | /// A CipherSuite is a set of algorithm and parameters used to secure 114 | /// a network connection. 115 | #[derive(Clone, Debug, PartialEq, Eq)] 116 | pub struct TlsCipherSuite { 117 | /// The IANA name of this ciphersuite 118 | pub name: &'static str, 119 | /// The 16-bit identifier, provided by IANA, for this ciphersuite 120 | pub id: TlsCipherSuiteID, 121 | /// The Key Exchange method for this ciphersuite 122 | pub kx: TlsCipherKx, 123 | /// The Authentication method for this ciphersuite 124 | pub au: TlsCipherAu, 125 | /// Encryption cipher 126 | pub enc: TlsCipherEnc, 127 | /// Encryption mode 128 | pub enc_mode: TlsCipherEncMode, 129 | /// Key size of the encryption, in bits 130 | pub enc_size: u16, 131 | /// Message Authentication Code (MAC) algorithm 132 | pub mac: TlsCipherMac, 133 | /// Message Authentication Code (MAC) length 134 | pub mac_size: u16, 135 | /// Pseudo-Random Function, if specific 136 | pub prf: TlsPRF, 137 | } 138 | 139 | include!(concat!(env!("OUT_DIR"), "/codegen.rs")); 140 | 141 | impl TlsCipherSuite { 142 | /// Attempt to get reference on `TlsCipherSuite` identified by `id`. 143 | pub fn from_id(id: u16) -> Option<&'static TlsCipherSuite> { 144 | CIPHERS.get(&id) 145 | } 146 | 147 | /// Attempt to get reference on `TlsCipherSuite` identified by `name`. 148 | pub fn from_name(name: &str) -> Option<&'static TlsCipherSuite> { 149 | CIPHERS.values().find(|&v| v.name == name) 150 | } 151 | 152 | /// Get the key of this ciphersuite encryption algorithm, in bytes 153 | pub const fn enc_key_size(&self) -> usize { 154 | (self.enc_size / 8) as usize 155 | } 156 | 157 | /// Get the block size of this ciphersuite encryption algorithm, in bytes 158 | pub const fn enc_block_size(&self) -> usize { 159 | match self.enc { 160 | TlsCipherEnc::Null => 0, 161 | TlsCipherEnc::Des 162 | | TlsCipherEnc::Idea 163 | | TlsCipherEnc::Rc2 164 | | TlsCipherEnc::TripleDes => 8, 165 | TlsCipherEnc::Aes 166 | | TlsCipherEnc::Aria 167 | | TlsCipherEnc::Camellia 168 | | TlsCipherEnc::Seed 169 | | TlsCipherEnc::Sm4 => 16, 170 | // stream ciphers 171 | TlsCipherEnc::Chacha20_Poly1305 | TlsCipherEnc::Rc4 | TlsCipherEnc::Aegis => 0, 172 | } 173 | } 174 | 175 | /// Get the length of this ciphersuite MAC algorithm, in bytes 176 | pub const fn mac_length(&self) -> usize { 177 | match self.mac { 178 | TlsCipherMac::Null => 0, 179 | TlsCipherMac::Aead => 0, 180 | TlsCipherMac::HmacMd5 => 16, 181 | TlsCipherMac::HmacSha1 => 20, 182 | TlsCipherMac::HmacSha256 => 32, 183 | TlsCipherMac::HmacSha384 => 48, 184 | TlsCipherMac::HmacSha512 => 64, 185 | } 186 | } 187 | } 188 | 189 | impl TryFrom for &'static TlsCipherSuite { 190 | type Error = CipherSuiteNotFound; 191 | 192 | fn try_from(value: u16) -> Result { 193 | CIPHERS.get(&value).ok_or(CipherSuiteNotFound(())) 194 | } 195 | } 196 | 197 | impl TryFrom for &'static TlsCipherSuite { 198 | type Error = CipherSuiteNotFound; 199 | 200 | fn try_from(value: TlsCipherSuiteID) -> Result { 201 | CIPHERS.get(&value.0).ok_or(CipherSuiteNotFound(())) 202 | } 203 | } 204 | 205 | impl<'a> TryFrom<&'a str> for &'static TlsCipherSuite { 206 | type Error = CipherSuiteNotFound; 207 | 208 | fn try_from(value: &'a str) -> Result { 209 | CIPHERS 210 | .values() 211 | .find(|&v| v.name == value) 212 | .ok_or(CipherSuiteNotFound(())) 213 | } 214 | } 215 | 216 | #[cfg(test)] 217 | mod tests { 218 | use crate::tls_ciphers::{TlsCipherKx, TlsCipherSuite, CIPHERS}; 219 | use core::convert::TryFrom; 220 | 221 | #[test] 222 | fn test_cipher_count() { 223 | println!("loaded: {} cipher suites", CIPHERS.len()); 224 | assert!(!CIPHERS.is_empty()); 225 | } 226 | 227 | #[test] 228 | fn test_cipher_from_id() { 229 | let cipher = <&TlsCipherSuite>::try_from(0xc025).expect("could not get cipher"); 230 | println!("Found cipher: {:?}", cipher); 231 | } 232 | 233 | #[test] 234 | fn test_cipher_from_name() { 235 | let cipher = <&TlsCipherSuite>::try_from("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") 236 | .expect("could not get cipher"); 237 | println!("Found cipher: {:?}", cipher); 238 | } 239 | 240 | #[test] 241 | fn test_cipher_filter() { 242 | let ecdhe_ciphers_count = CIPHERS 243 | .values() 244 | .filter(|c| c.kx == TlsCipherKx::Ecdhe) 245 | .count(); 246 | assert!(ecdhe_ciphers_count > 20); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/tls_debug.rs: -------------------------------------------------------------------------------- 1 | use alloc::format; 2 | use alloc::vec::Vec; 3 | use core::fmt; 4 | use core::str::from_utf8; 5 | use rusticata_macros::debug::HexSlice; 6 | 7 | use crate::tls_alert::*; 8 | use crate::tls_dh::*; 9 | use crate::tls_ec::*; 10 | use crate::tls_extensions::*; 11 | use crate::tls_handshake::*; 12 | use crate::tls_record::*; 13 | use crate::tls_sign_hash::*; 14 | 15 | // ------------------------- tls.rs ------------------------------ 16 | impl<'a> fmt::Debug for TlsClientHelloContents<'a> { 17 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 18 | fmt.debug_struct("TlsClientHelloContents") 19 | .field("version", &self.version) 20 | .field("random", &HexSlice(self.random)) 21 | .field("session_id", &self.session_id.map(HexSlice)) 22 | .field("ciphers", &self.ciphers) 23 | .field("comp", &self.comp) 24 | .field("ext", &self.ext.map(HexSlice)) 25 | .finish() 26 | } 27 | } 28 | 29 | impl<'a> fmt::Debug for TlsServerHelloContents<'a> { 30 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 31 | fmt.debug_struct("TlsServerHelloContents") 32 | .field("version", &self.version) 33 | .field("random", &HexSlice(self.random)) 34 | .field("session_id", &self.session_id.map(HexSlice)) 35 | .field("cipher", &self.cipher) 36 | .field("compression", &self.compression) 37 | .field("ext", &self.ext.map(HexSlice)) 38 | .finish() 39 | } 40 | } 41 | 42 | impl<'a> fmt::Debug for TlsServerHelloV13Draft18Contents<'a> { 43 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 44 | fmt.debug_struct("TlsServerHelloV13Draft18Contents") 45 | .field("version", &self.version) 46 | .field("random", &HexSlice(self.random)) 47 | .field("cipher", &self.cipher) 48 | .field("ext", &self.ext.map(HexSlice)) 49 | .finish() 50 | } 51 | } 52 | 53 | impl<'a> fmt::Debug for TlsHelloRetryRequestContents<'a> { 54 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 55 | fmt.debug_struct("TlsHelloRetryRequestContents") 56 | .field("version", &self.version) 57 | .field("ext", &self.ext.map(HexSlice)) 58 | .finish() 59 | } 60 | } 61 | 62 | impl<'a> fmt::Debug for RawCertificate<'a> { 63 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 64 | fmt.debug_struct("RawCertificate") 65 | .field("data", &HexSlice(self.data)) 66 | .finish() 67 | } 68 | } 69 | 70 | impl<'a> fmt::Debug for TlsServerKeyExchangeContents<'a> { 71 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 72 | fmt.debug_struct("TlsServerKeyExchangeContents") 73 | .field("parameters", &HexSlice(self.parameters)) 74 | .finish() 75 | } 76 | } 77 | 78 | impl<'a> fmt::Debug for TlsClientKeyExchangeContents<'a> { 79 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 80 | match *self { 81 | TlsClientKeyExchangeContents::Dh(p) => fmt.write_fmt(format_args!("{:?}", HexSlice(p))), 82 | TlsClientKeyExchangeContents::Ecdh(ref p) => fmt.write_fmt(format_args!("{:?}", p)), 83 | TlsClientKeyExchangeContents::Unknown(p) => { 84 | fmt.write_fmt(format_args!("{:?}", HexSlice(p))) 85 | } 86 | } 87 | } 88 | } 89 | 90 | impl fmt::Debug for TlsRecordHeader { 91 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 92 | fmt.debug_struct("TlsRecordHeader") 93 | .field("type", &self.record_type) 94 | .field("version", &self.version) 95 | .field("len", &self.len) 96 | .finish() 97 | } 98 | } 99 | 100 | // ------------------------- tls_alert.rs ------------------------------ 101 | impl fmt::Debug for TlsMessageAlert { 102 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 103 | fmt.debug_struct("TlsMessageAlert") 104 | .field("severity", &self.severity) 105 | .field("code", &self.code) 106 | .finish() 107 | } 108 | } 109 | 110 | // ------------------------- tls_dh.rs ------------------------------ 111 | impl<'a> fmt::Debug for ServerDHParams<'a> { 112 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 113 | let gs = self.dh_g.len() * 8; 114 | fmt.debug_struct("ServerDHParams") 115 | .field("group size", &gs) 116 | .field("dh_p", &HexSlice(self.dh_p)) 117 | .field("dh_g", &HexSlice(self.dh_g)) 118 | .field("dh_ys", &HexSlice(self.dh_ys)) 119 | .finish() 120 | } 121 | } 122 | 123 | // ------------------------- tls_ec.rs ------------------------------ 124 | impl<'a> fmt::Debug for ECParametersContent<'a> { 125 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 126 | match *self { 127 | ECParametersContent::ExplicitPrime(ref p) => { 128 | fmt.write_fmt(format_args!("ExplicitPrime({:?})", p)) 129 | } 130 | // ECParametersContent::ExplicitChar2(ref p) => { 131 | // fmt.write_fmt(format_args!("ExplicitChar2({:?})", HexSlice(p))) 132 | // } 133 | ECParametersContent::NamedGroup(p) => write!(fmt, "{}", p), 134 | } 135 | } 136 | } 137 | 138 | impl<'a> fmt::Debug for ECParameters<'a> { 139 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 140 | fmt.debug_struct("ECParameters") 141 | .field("curve_type", &format!("{}", self.curve_type)) 142 | .field("params_content", &self.params_content) 143 | .finish() 144 | } 145 | } 146 | 147 | // ------------------------- tls_extensions.rs ------------------------------ 148 | impl<'a> fmt::Debug for TlsExtension<'a> { 149 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 150 | match *self { 151 | TlsExtension::SNI(ref v) => { 152 | let v: Vec<_> = v 153 | .iter() 154 | .map(|&(ty, n)| { 155 | let s = from_utf8(n).unwrap_or(""); 156 | format!("type={},name={}", ty, s) 157 | }) 158 | .collect(); 159 | write!(fmt, "TlsExtension::SNI({:?})", v) 160 | } 161 | TlsExtension::MaxFragmentLength(l) => { 162 | write!(fmt, "TlsExtension::MaxFragmentLength({})", l) 163 | } 164 | TlsExtension::StatusRequest(data) => { 165 | write!(fmt, "TlsExtension::StatusRequest({:?})", data) 166 | } 167 | TlsExtension::EllipticCurves(ref v) => { 168 | let v2: Vec<_> = v.iter().map(|&curve| format!("{}", curve)).collect(); 169 | write!(fmt, "TlsExtension::EllipticCurves({:?})", v2) 170 | } 171 | TlsExtension::EcPointFormats(v) => write!(fmt, "TlsExtension::EcPointFormats({:?})", v), 172 | TlsExtension::SignatureAlgorithms(ref v) => { 173 | let v2: Vec<_> = v 174 | .iter() 175 | .map(|&alg| { 176 | let s = format!("{}", SignatureScheme(alg)); 177 | if s.starts_with("SignatureScheme") { 178 | format!( 179 | "{}", 180 | SignatureAndHashAlgorithm { 181 | hash: HashAlgorithm((alg >> 8) as u8), 182 | sign: SignAlgorithm((alg & 0xff) as u8) 183 | } 184 | ) 185 | } else { 186 | s 187 | } 188 | }) 189 | .collect(); 190 | write!(fmt, "TlsExtension::SignatureAlgorithms({:?})", v2) 191 | } 192 | TlsExtension::SessionTicket(data) => { 193 | write!(fmt, "TlsExtension::SessionTicket(data={:?})", data) 194 | } 195 | TlsExtension::RecordSizeLimit(data) => { 196 | write!(fmt, "TlsExtension::RecordSizeLimit(data={})", data) 197 | } 198 | TlsExtension::KeyShareOld(data) => { 199 | write!(fmt, "TlsExtension::KeyShareOld(data={:?})", HexSlice(data)) 200 | } 201 | TlsExtension::KeyShare(data) => { 202 | write!(fmt, "TlsExtension::KeyShare(data={:?})", HexSlice(data)) 203 | } 204 | TlsExtension::PreSharedKey(data) => { 205 | write!(fmt, "TlsExtension::PreSharedKey(data={:?})", HexSlice(data)) 206 | } 207 | TlsExtension::EarlyData(o) => write!(fmt, "TlsExtension::EarlyData({:?})", o), 208 | TlsExtension::SupportedVersions(ref v) => { 209 | let v2: Vec<_> = v.iter().map(|c| format!("{}", c)).collect(); 210 | write!(fmt, "TlsExtension::SupportedVersions(v={:?})", v2) 211 | } 212 | TlsExtension::Cookie(data) => write!(fmt, "TlsExtension::Cookie(data={:?})", data), 213 | TlsExtension::PskExchangeModes(ref v) => { 214 | write!(fmt, "TlsExtension::PskExchangeModes({:?})", v) 215 | } 216 | TlsExtension::Heartbeat(mode) => write!(fmt, "TlsExtension::Heartbeat(mode={})", mode), 217 | TlsExtension::ALPN(ref v) => { 218 | let v: Vec<_> = v 219 | .iter() 220 | .map(|c| from_utf8(c).unwrap_or("")) 221 | .collect(); 222 | write!(fmt, "TlsExtension::ALPN({:?})", v) 223 | } 224 | TlsExtension::SignedCertificateTimestamp(data) => write!( 225 | fmt, 226 | "TlsExtension::SignedCertificateTimestamp(data={:?})", 227 | data 228 | ), 229 | TlsExtension::Padding(data) => write!(fmt, "TlsExtension::Padding(data={:?})", data), 230 | TlsExtension::EncryptThenMac => write!(fmt, "TlsExtension::EncryptThenMac"), 231 | TlsExtension::ExtendedMasterSecret => write!(fmt, "TlsExtension::ExtendedMasterSecret"), 232 | TlsExtension::OidFilters(ref v) => { 233 | let v: Vec<_> = v.iter().map(|c| format!("{:?}", c)).collect(); 234 | write!(fmt, "TlsExtension::OidFilters({:?})", v) 235 | } 236 | TlsExtension::PostHandshakeAuth => write!(fmt, "TlsExtension::PostHandshakeAuth"), 237 | TlsExtension::NextProtocolNegotiation => { 238 | write!(fmt, "TlsExtension::NextProtocolNegotiation") 239 | } 240 | TlsExtension::RenegotiationInfo(data) => { 241 | write!(fmt, "TlsExtension::RenegotiationInfo(data={:?})", data) 242 | } 243 | TlsExtension::EncryptedServerName { 244 | ciphersuite, group, .. 245 | } => write!( 246 | fmt, 247 | "TlsExtension::EncryptedServerName{{cipher: {:?}, group: {:?} ..}}", 248 | ciphersuite, group 249 | ), 250 | TlsExtension::Grease(t, data) => write!( 251 | fmt, 252 | "TlsExtension::Grease(0x{:x},data={:?})", 253 | t, 254 | HexSlice(data) 255 | ), 256 | TlsExtension::Unknown(t, data) => write!( 257 | fmt, 258 | "TlsExtension::Unknown(type=0x{:x},data={:?})", 259 | t.0, data 260 | ), 261 | } 262 | } 263 | } 264 | 265 | // ------------------------- tls_sign_hash.rs ------------------------------ 266 | impl fmt::Display for SignatureAndHashAlgorithm { 267 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 268 | write!(fmt, "HashSign({},{})", self.hash, self.sign) 269 | } 270 | } 271 | 272 | impl fmt::Debug for SignatureAndHashAlgorithm { 273 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 274 | write!( 275 | fmt, 276 | "SignatureAndHashAlgorithm({},{})", 277 | self.hash, self.sign 278 | ) 279 | } 280 | } 281 | 282 | impl<'a> fmt::Debug for DigitallySigned<'a> { 283 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 284 | fmt.debug_struct("DigitallySigned") 285 | .field("alg", &self.alg) 286 | .field("data", &HexSlice(self.data)) 287 | .finish() 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/tls_dh.rs: -------------------------------------------------------------------------------- 1 | use nom::multi::length_data; 2 | use nom::number::streaming::be_u16; 3 | use nom::IResult; 4 | use nom_derive::*; 5 | 6 | /// Diffie-Hellman parameters, defined in \[RFC5246\] section 7.4.3 7 | #[derive(PartialEq, NomBE)] 8 | pub struct ServerDHParams<'a> { 9 | /// The prime modulus used for the Diffie-Hellman operation. 10 | #[nom(Parse = "length_data(be_u16)")] 11 | pub dh_p: &'a [u8], 12 | /// The generator used for the Diffie-Hellman operation. 13 | #[nom(Parse = "length_data(be_u16)")] 14 | pub dh_g: &'a [u8], 15 | /// The server's Diffie-Hellman public value (g^X mod p). 16 | #[nom(Parse = "length_data(be_u16)")] 17 | pub dh_ys: &'a [u8], 18 | } 19 | 20 | #[inline] 21 | pub fn parse_dh_params(i: &[u8]) -> IResult<&[u8], ServerDHParams> { 22 | ServerDHParams::parse(i) 23 | } 24 | -------------------------------------------------------------------------------- /src/tls_ec.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use nom::error::{make_error, ErrorKind}; 3 | use nom::multi::length_data; 4 | use nom::number::streaming::be_u8; 5 | use nom::{Err, IResult}; 6 | use nom_derive::*; 7 | use rusticata_macros::newtype_enum; 8 | 9 | /// Named elliptic curves 10 | /// 11 | /// Named curves, as defined in [RFC4492](https://tools.ietf.org/html/rfc4492), [RFC7027](https://tools.ietf.org/html/rfc7027), [RFC7919](https://tools.ietf.org/html/rfc7919) and 12 | /// [IANA Supported Groups 13 | /// Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8) 14 | #[derive(Clone, Copy, PartialEq, Eq, NomBE)] 15 | pub struct NamedGroup(pub u16); 16 | 17 | newtype_enum! { 18 | impl debug NamedGroup { 19 | Sect163k1 = 1, 20 | Sect163r1 = 2, 21 | Sect163r2 = 3, 22 | Sect193r1 = 4, 23 | Sect193r2 = 5, 24 | Sect233k1 = 6, 25 | Sect233r1 = 7, 26 | Sect239k1 = 8, 27 | Sect283k1 = 9, 28 | Sect283r1 = 10, 29 | Sect409k1 = 11, 30 | Sect409r1 = 12, 31 | Sect571k1 = 13, 32 | Sect571r1 = 14, 33 | Secp160k1 = 15, 34 | Secp160r1 = 16, 35 | Secp160r2 = 17, 36 | Secp192k1 = 18, 37 | Secp192r1 = 19, 38 | Secp224k1 = 20, 39 | Secp224r1 = 21, 40 | Secp256k1 = 22, 41 | Secp256r1 = 23, 42 | Secp384r1 = 24, 43 | Secp521r1 = 25, 44 | BrainpoolP256r1 = 26, 45 | BrainpoolP384r1 = 27, 46 | BrainpoolP512r1 = 28, 47 | EcdhX25519 = 29, 48 | EcdhX448 = 30, 49 | BrainpoolP256r1tls13 = 31, 50 | BrainpoolP384r1tls13 = 32, 51 | BrainpoolP512r1tls13 = 33, 52 | Sm2 = 41, 53 | Ffdhe2048 = 0x100, 54 | Ffdhe3072 = 0x101, 55 | Ffdhe4096 = 0x102, 56 | Ffdhe6144 = 0x103, 57 | Ffdhe8192 = 0x104, 58 | ArbitraryExplicitPrimeCurves = 0xFF01, 59 | ArbitraryExplicitChar2Curves = 0xFF02, 60 | } 61 | } 62 | 63 | impl NamedGroup { 64 | /// Return key size of curve in bits, or None if unknown 65 | pub fn key_bits(self: NamedGroup) -> Option { 66 | match self { 67 | NamedGroup::Sect163k1 => Some(163), 68 | NamedGroup::Sect163r1 => Some(163), 69 | NamedGroup::Sect163r2 => Some(163), 70 | NamedGroup::Sect193r1 => Some(193), 71 | NamedGroup::Sect193r2 => Some(193), 72 | NamedGroup::Sect233k1 => Some(233), 73 | NamedGroup::Sect233r1 => Some(233), 74 | NamedGroup::Sect239k1 => Some(239), 75 | NamedGroup::Sect283k1 => Some(283), 76 | NamedGroup::Sect283r1 => Some(283), 77 | NamedGroup::Sect409k1 => Some(409), 78 | NamedGroup::Sect409r1 => Some(409), 79 | NamedGroup::Sect571k1 => Some(571), 80 | NamedGroup::Sect571r1 => Some(571), 81 | NamedGroup::Secp160k1 => Some(160), 82 | NamedGroup::Secp160r1 => Some(160), 83 | NamedGroup::Secp160r2 => Some(160), 84 | NamedGroup::Secp192k1 => Some(192), 85 | NamedGroup::Secp192r1 => Some(192), 86 | NamedGroup::Secp224k1 => Some(224), 87 | NamedGroup::Secp224r1 => Some(224), 88 | NamedGroup::Secp256k1 => Some(256), 89 | NamedGroup::Secp256r1 => Some(256), 90 | NamedGroup::Secp384r1 => Some(384), 91 | NamedGroup::Secp521r1 => Some(521), 92 | NamedGroup::BrainpoolP256r1 => Some(256), 93 | NamedGroup::BrainpoolP384r1 => Some(384), 94 | NamedGroup::BrainpoolP512r1 => Some(521), 95 | NamedGroup::EcdhX25519 => Some(253), 96 | _ => None, 97 | } 98 | } 99 | } 100 | 101 | /// Elliptic curve 102 | /// 103 | /// a and b specify the coefficients of the curve 104 | #[derive(Debug, PartialEq, NomBE)] 105 | pub struct ECCurve<'a> { 106 | #[nom(Parse = "length_data(be_u8)")] 107 | pub a: &'a [u8], 108 | #[nom(Parse = "length_data(be_u8)")] 109 | pub b: &'a [u8], 110 | } 111 | 112 | /// Elliptic curve types, as defined in the 113 | /// [IANA EC Curve Type Registry 114 | /// Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10) 115 | #[derive(Clone, Copy, PartialEq, Eq, NomBE)] 116 | pub struct ECCurveType(pub u8); 117 | 118 | newtype_enum! { 119 | impl display ECCurveType { 120 | ExplicitPrime = 1, 121 | ExplicitChar2 = 2, 122 | NamedGroup = 3, 123 | } 124 | } 125 | 126 | /// EC Point 127 | #[derive(Clone, Debug, PartialEq, NomBE)] 128 | pub struct ECPoint<'a> { 129 | #[nom(Parse = "length_data(be_u8)")] 130 | pub point: &'a [u8], 131 | } 132 | 133 | /// Elliptic curve parameters, conveyed verbosely as a prime field, as 134 | /// defined in [RFC4492](https://tools.ietf.org/html/rfc4492) section 5.4 135 | #[derive(Debug, PartialEq, NomBE)] 136 | pub struct ExplicitPrimeContent<'a> { 137 | #[nom(Parse = "length_data(be_u8)")] 138 | pub prime_p: &'a [u8], 139 | pub curve: ECCurve<'a>, 140 | pub base: ECPoint<'a>, 141 | #[nom(Parse = "length_data(be_u8)")] 142 | pub order: &'a [u8], 143 | #[nom(Parse = "length_data(be_u8)")] 144 | pub cofactor: &'a [u8], 145 | } 146 | 147 | /// Elliptic curve parameters content (depending on EC type) 148 | #[derive(PartialEq, Nom)] 149 | #[nom(Selector = "ECCurveType")] 150 | pub enum ECParametersContent<'a> { 151 | #[nom(Selector = "ECCurveType::ExplicitPrime")] 152 | ExplicitPrime(ExplicitPrimeContent<'a>), 153 | // TODO ExplicitChar2 is defined in [RFC4492] section 5.4 154 | // #[nom(Selector="ECCurveType::ExplicitChar2")] 155 | // ExplicitChar2(&'a [u8]), 156 | #[nom(Selector = "ECCurveType::NamedGroup")] 157 | NamedGroup(NamedGroup), 158 | } 159 | 160 | /// Elliptic curve parameters, 161 | /// defined in [RFC4492](https://tools.ietf.org/html/rfc4492) section 5.4 162 | #[derive(PartialEq, NomBE)] 163 | pub struct ECParameters<'a> { 164 | /// Should match a [ECCurveType](enum.ECCurveType.html) value 165 | pub curve_type: ECCurveType, 166 | #[nom(Parse = "{|i| ECParametersContent::parse(i, curve_type)}")] 167 | pub params_content: ECParametersContent<'a>, 168 | } 169 | 170 | /// ECDH parameters 171 | /// defined in [RFC4492](https://tools.ietf.org/html/rfc4492) section 5.4 172 | #[derive(Debug, PartialEq, NomBE)] 173 | pub struct ServerECDHParams<'a> { 174 | pub curve_params: ECParameters<'a>, 175 | pub public: ECPoint<'a>, 176 | } 177 | 178 | /// Parse the entire input as a list of named groups (curves) 179 | pub fn parse_named_groups(i: &[u8]) -> IResult<&[u8], Vec> { 180 | let len = i.len(); 181 | if len == 0 { 182 | return Ok((i, Vec::new())); 183 | } 184 | if len % 2 == 1 || len > i.len() { 185 | return Err(Err::Error(make_error(i, ErrorKind::LengthValue))); 186 | } 187 | let v = (i[..len]) 188 | .chunks(2) 189 | .map(|chunk| NamedGroup((chunk[0] as u16) << 8 | chunk[1] as u16)) 190 | .collect(); 191 | Ok((&i[len..], v)) 192 | } 193 | 194 | #[inline] 195 | pub fn parse_ec_parameters(i: &[u8]) -> IResult<&[u8], ECParameters> { 196 | ECParameters::parse(i) 197 | } 198 | 199 | #[inline] 200 | pub fn parse_ecdh_params(i: &[u8]) -> IResult<&[u8], ServerECDHParams> { 201 | ServerECDHParams::parse(i) 202 | } 203 | -------------------------------------------------------------------------------- /src/tls_message.rs: -------------------------------------------------------------------------------- 1 | use alloc::{vec, vec::Vec}; 2 | use nom::bytes::streaming::take; 3 | use nom::combinator::verify; 4 | use nom::error::{make_error, ErrorKind}; 5 | use nom::number::streaming::{be_u16, be_u8}; 6 | use nom::{Err, IResult}; 7 | use nom_derive::Parse; 8 | 9 | use crate::tls_alert::*; 10 | use crate::tls_handshake::*; 11 | 12 | /// TLS plaintext message 13 | /// 14 | /// Plaintext records can only be found during the handshake. 15 | #[derive(Clone, Debug, PartialEq)] 16 | pub enum TlsMessage<'a> { 17 | Handshake(TlsMessageHandshake<'a>), 18 | ChangeCipherSpec, 19 | Alert(TlsMessageAlert), 20 | ApplicationData(TlsMessageApplicationData<'a>), 21 | Heartbeat(TlsMessageHeartbeat<'a>), 22 | } 23 | 24 | /// TLS application data 25 | /// 26 | /// Since this message can only be sent after the handshake, data is 27 | /// stored as opaque. 28 | #[derive(Clone, Debug, PartialEq)] 29 | pub struct TlsMessageApplicationData<'a> { 30 | pub blob: &'a [u8], 31 | } 32 | 33 | /// TLS heartbeat message, as defined in [RFC6520](https://tools.ietf.org/html/rfc6520) 34 | /// 35 | /// Heartbeat messages should not be sent during handshake, but in practise 36 | /// they can (and this caused heartbleed). 37 | #[derive(Clone, Debug, PartialEq)] 38 | pub struct TlsMessageHeartbeat<'a> { 39 | pub heartbeat_type: TlsHeartbeatMessageType, 40 | pub payload_len: u16, 41 | pub payload: &'a [u8], 42 | } 43 | 44 | /// Parse a TLS changecipherspec message 45 | // XXX add extra verification hdr.len == 1 46 | pub fn parse_tls_message_changecipherspec(i: &[u8]) -> IResult<&[u8], TlsMessage> { 47 | let (i, _) = verify(be_u8, |&tag| tag == 0x01)(i)?; 48 | Ok((i, TlsMessage::ChangeCipherSpec)) 49 | } 50 | 51 | /// Parse a TLS alert message 52 | // XXX add extra verification hdr.len == 2 53 | pub fn parse_tls_message_alert(i: &[u8]) -> IResult<&[u8], TlsMessage> { 54 | let (i, alert) = TlsMessageAlert::parse(i)?; 55 | Ok((i, TlsMessage::Alert(alert))) 56 | } 57 | 58 | /// Parse a TLS applicationdata message 59 | /// 60 | /// Read the entire input as applicationdata 61 | pub fn parse_tls_message_applicationdata(i: &[u8]) -> IResult<&[u8], TlsMessage> { 62 | let msg = TlsMessage::ApplicationData(TlsMessageApplicationData { blob: i }); 63 | Ok((&[], msg)) 64 | } 65 | 66 | /// Parse a TLS heartbeat message 67 | pub fn parse_tls_message_heartbeat( 68 | i: &[u8], 69 | tls_plaintext_len: u16, 70 | ) -> IResult<&[u8], Vec> { 71 | let (i, heartbeat_type) = TlsHeartbeatMessageType::parse(i)?; 72 | let (i, payload_len) = be_u16(i)?; 73 | if tls_plaintext_len < 3 { 74 | return Err(Err::Error(make_error(i, ErrorKind::Verify))); 75 | } 76 | let (i, payload) = take(payload_len as usize)(i)?; 77 | let v = vec![TlsMessage::Heartbeat(TlsMessageHeartbeat { 78 | heartbeat_type, 79 | payload_len, 80 | payload, 81 | })]; 82 | Ok((i, v)) 83 | } 84 | -------------------------------------------------------------------------------- /src/tls_record.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use nom::bytes::streaming::take; 3 | use nom::combinator::{complete, map_parser}; 4 | use nom::error::{make_error, ErrorKind}; 5 | use nom::multi::many1; 6 | use nom::{Err, IResult}; 7 | use nom_derive::{NomBE, Parse}; 8 | use rusticata_macros::newtype_enum; 9 | 10 | use crate::tls_handshake::*; 11 | use crate::tls_message::*; 12 | use crate::TlsVersion; 13 | 14 | /// Max record size for TLSCipherText (RFC8446 5.2) 15 | pub const MAX_RECORD_LEN: u16 = (1 << 14) + 256; 16 | 17 | /// Content type, as defined in IANA TLS ContentType registry 18 | #[derive(Clone, Copy, PartialEq, Eq, NomBE)] 19 | pub struct TlsRecordType(pub u8); 20 | 21 | newtype_enum! { 22 | impl debug TlsRecordType { 23 | ChangeCipherSpec = 0x14, 24 | Alert = 0x15, 25 | Handshake = 0x16, 26 | ApplicationData = 0x17, 27 | Heartbeat = 0x18, 28 | } 29 | } 30 | 31 | impl From for u8 { 32 | fn from(v: TlsRecordType) -> u8 { 33 | v.0 34 | } 35 | } 36 | 37 | /// TLS record header 38 | #[derive(Clone, Copy, PartialEq, NomBE)] 39 | pub struct TlsRecordHeader { 40 | pub record_type: TlsRecordType, 41 | pub version: TlsVersion, 42 | pub len: u16, 43 | } 44 | 45 | /// TLS plaintext record 46 | /// 47 | /// A TLS record can contain multiple messages (sharing the same record type). 48 | /// Plaintext records can only be found during the handshake. 49 | #[derive(Clone, Debug, PartialEq)] 50 | pub struct TlsPlaintext<'a> { 51 | pub hdr: TlsRecordHeader, 52 | pub msg: Vec>, 53 | } 54 | 55 | /// TLS encrypted data 56 | /// 57 | /// This struct only contains an opaque pointer (data are encrypted). 58 | #[derive(Clone, Debug, PartialEq)] 59 | pub struct TlsEncryptedContent<'a> { 60 | pub blob: &'a [u8], 61 | } 62 | 63 | /// Encrypted TLS record (containing opaque data) 64 | #[derive(Clone, Debug, PartialEq)] 65 | pub struct TlsEncrypted<'a> { 66 | pub hdr: TlsRecordHeader, 67 | pub msg: TlsEncryptedContent<'a>, 68 | } 69 | 70 | /// Tls Record with raw (unparsed) data 71 | /// 72 | /// Use [`parse_tls_raw_record`] to parse content 73 | #[derive(Clone, Debug, PartialEq)] 74 | pub struct TlsRawRecord<'a> { 75 | pub hdr: TlsRecordHeader, 76 | pub data: &'a [u8], 77 | } 78 | 79 | /// Read TLS record header 80 | /// 81 | /// This function is used to get the record header. 82 | /// After calling this function, caller can read the expected number of bytes and use 83 | /// [`parse_tls_record_with_header`] to parse content. 84 | #[inline] 85 | pub fn parse_tls_record_header(i: &[u8]) -> IResult<&[u8], TlsRecordHeader> { 86 | TlsRecordHeader::parse(i) 87 | } 88 | 89 | /// Given data and a TLS record header, parse content. 90 | /// 91 | /// A record can contain multiple messages (with the same type). 92 | /// 93 | /// Note that message length is checked (not required for parser safety, but for 94 | /// strict protocol conformance). 95 | /// 96 | /// This function will fail on fragmented records. To support fragmented records, use 97 | /// [crate::TlsRecordsParser]]. 98 | #[rustfmt::skip] 99 | #[allow(clippy::trivially_copy_pass_by_ref)] // TlsRecordHeader is only 6 bytes, but we prefer not breaking current API 100 | pub fn parse_tls_record_with_header<'i>(i:&'i [u8], hdr:&TlsRecordHeader ) -> IResult<&'i [u8], Vec>> { 101 | match hdr.record_type { 102 | TlsRecordType::ChangeCipherSpec => many1(complete(parse_tls_message_changecipherspec))(i), 103 | TlsRecordType::Alert => many1(complete(parse_tls_message_alert))(i), 104 | TlsRecordType::Handshake => many1(complete(parse_tls_message_handshake))(i), 105 | TlsRecordType::ApplicationData => many1(complete(parse_tls_message_applicationdata))(i), 106 | TlsRecordType::Heartbeat => parse_tls_message_heartbeat(i, hdr.len), 107 | _ => Err(Err::Error(make_error(i, ErrorKind::Switch))) 108 | } 109 | } 110 | 111 | /// Parse one packet only, as plaintext 112 | /// A single record can contain multiple messages, they must share the same record type 113 | pub fn parse_tls_plaintext(i: &[u8]) -> IResult<&[u8], TlsPlaintext> { 114 | let (i, hdr) = parse_tls_record_header(i)?; 115 | if hdr.len > MAX_RECORD_LEN { 116 | return Err(Err::Error(make_error(i, ErrorKind::TooLarge))); 117 | } 118 | let (i, msg) = map_parser(take(hdr.len as usize), |i| { 119 | parse_tls_record_with_header(i, &hdr) 120 | })(i)?; 121 | Ok((i, TlsPlaintext { hdr, msg })) 122 | } 123 | 124 | /// Parse one packet only, as encrypted content 125 | pub fn parse_tls_encrypted(i: &[u8]) -> IResult<&[u8], TlsEncrypted> { 126 | let (i, hdr) = parse_tls_record_header(i)?; 127 | if hdr.len > MAX_RECORD_LEN { 128 | return Err(Err::Error(make_error(i, ErrorKind::TooLarge))); 129 | } 130 | let (i, blob) = take(hdr.len as usize)(i)?; 131 | let msg = TlsEncryptedContent { blob }; 132 | Ok((i, TlsEncrypted { hdr, msg })) 133 | } 134 | 135 | /// Read TLS record envelope, but do not decode data 136 | /// 137 | /// This function is used to get the record type, and to make sure the record is 138 | /// complete (not fragmented). 139 | /// After calling this function, use [`parse_tls_record_with_header`] or [crate::TlsRecordsParser] to parse content. 140 | pub fn parse_tls_raw_record(i: &[u8]) -> IResult<&[u8], TlsRawRecord> { 141 | let (i, hdr) = parse_tls_record_header(i)?; 142 | if hdr.len > MAX_RECORD_LEN { 143 | return Err(Err::Error(make_error(i, ErrorKind::TooLarge))); 144 | } 145 | let (i, data) = take(hdr.len as usize)(i)?; 146 | Ok((i, TlsRawRecord { hdr, data })) 147 | } 148 | 149 | /// Parse one packet only, as plaintext 150 | /// 151 | /// This function is deprecated. Use [`parse_tls_plaintext`] instead. 152 | /// 153 | /// This function will be removed from API, as the name is not correct: it is 154 | /// not possible to parse TLS packets without knowing the TLS state. 155 | #[deprecated(since = "0.5.0", note = "Use parse_tls_plaintext")] 156 | #[inline] 157 | pub fn tls_parser(i: &[u8]) -> IResult<&[u8], TlsPlaintext> { 158 | parse_tls_plaintext(i) 159 | } 160 | 161 | /// Parse one chunk of data, possibly containing multiple TLS plaintext records 162 | /// 163 | /// This function is deprecated. Use [`parse_tls_plaintext`] instead, checking if 164 | /// there are remaining bytes, and calling [`parse_tls_plaintext`] recursively. 165 | /// 166 | /// This function will be removed from API, as it should be replaced by a more 167 | /// useful one to handle fragmentation. 168 | pub fn tls_parser_many(i: &[u8]) -> IResult<&[u8], Vec> { 169 | many1(complete(parse_tls_plaintext))(i) 170 | } 171 | -------------------------------------------------------------------------------- /src/tls_records_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | parse_tls_record_with_header, TlsMessage, TlsRawRecord, TlsRecordHeader, TlsRecordType, 3 | }; 4 | use alloc::vec::Vec; 5 | use nom::{ 6 | error::{Error, ErrorKind}, 7 | Err, IResult, Needed, 8 | }; 9 | 10 | pub const MAX_RECORD_DATA: usize = 10 * 1024 * 1024; 11 | 12 | /// Helper tool to defragment and parse TLS records 13 | #[derive(Debug, Default)] 14 | pub struct TlsRecordsParser { 15 | record_defrag_buffer: Vec, 16 | current_record_type: Option, 17 | } 18 | 19 | impl TlsRecordsParser { 20 | /// Reset the parser state (deleting all previous records) 21 | pub fn reset(&mut self) { 22 | *self = Self::default(); 23 | } 24 | 25 | /// Returns `true` if defragmentation is in progress 26 | pub fn defrag_in_progress(&self) -> bool { 27 | self.current_record_type.is_some() 28 | } 29 | 30 | /// Attempt to parse all messages from a single record 31 | /// 32 | /// Record types `ChangeCipherSpec` and `Alert` cannot be fragmented. 33 | /// 34 | /// This function does not defragment data, but guarantees that no data is copied. 35 | /// 36 | /// Returns 37 | /// - the bytes remaining, and the list of messages. The remaining bytes should be empty. 38 | /// - `Incomplete` if a message is fragmented. Caller should use function [Self::parse_record] instead 39 | /// - `ErrorKind::NonEmpty` if defragmentation is already in progress 40 | pub fn parse_record_nocopy<'a>( 41 | &mut self, 42 | record: TlsRawRecord<'a>, 43 | ) -> IResult<&'a [u8], Vec>> { 44 | if self.defrag_in_progress() { 45 | return Err(Err::Failure(Error::new(&[], ErrorKind::NonEmpty))); 46 | } 47 | match parse_tls_record_with_header(record.data, &record.hdr) { 48 | Err(Err::Error(e)) | Err(Err::Failure(e)) if e.code == ErrorKind::Complete => { 49 | Err(Err::Incomplete(Needed::Unknown)) 50 | } 51 | other => other, 52 | } 53 | } 54 | 55 | /// Attempt to parse all messages from a record (iterative) 56 | /// 57 | /// Parse all messages from a record, keeping previous fragments (incrementally) if required. 58 | /// If current record is fragmented, copy record data and return `Incomplete`. 59 | /// 60 | /// Record types `ChangeCipherSpec` and `Alert` cannot be fragmented. 61 | /// 62 | /// Record types cannot be interleaved. If defragmentation has started for a record type, other record types 63 | /// will be rejected. 64 | /// 65 | /// Returns 66 | /// - the bytes remaining, and the list of messages. The remaining bytes should be empty. 67 | /// - `Incomplete` if a message is fragmented. Caller should get next record and call function again 68 | /// - `ErrorKind::TooLarge` if record contents exceeds [`MAX_RECORD_DATA`] 69 | /// - `ErrorKind::Tag` if the provided record does not have the same record type as the first record from the list 70 | pub fn parse_record<'p, 'a: 'p>( 71 | &'p mut self, 72 | record: TlsRawRecord<'a>, 73 | ) -> IResult<&'p [u8], Vec>> { 74 | if !self.defrag_in_progress() { 75 | // first fragment 76 | 77 | if record.hdr.record_type == TlsRecordType::Alert 78 | || record.hdr.record_type == TlsRecordType::ChangeCipherSpec 79 | { 80 | return self.parse_record_nocopy(record); 81 | } 82 | 83 | // before defragmenting, check that message is indeed fragmented 84 | match parse_tls_record_with_header(record.data, &record.hdr) { 85 | Ok(res) => return Ok(res), 86 | Err(Err::Incomplete(_)) => (), 87 | Err(Err::Error(e)) | Err(Err::Failure(e)) if e.code == ErrorKind::Complete => (), 88 | Err(e) => return Err(e), 89 | } 90 | 91 | // record is indeed fragmented: keep contents and return Incomplete 92 | self.current_record_type = Some(record.hdr.record_type); 93 | // replace previous buffer 94 | self.record_defrag_buffer.clear(); 95 | self.record_defrag_buffer.extend_from_slice(record.data); 96 | return Err(Err::Incomplete(Needed::Unknown)); 97 | } 98 | 99 | // record is not the first 100 | debug_assert!(!self.record_defrag_buffer.is_empty()); 101 | 102 | let record_type = record.hdr.record_type; 103 | if Some(record_type) != self.current_record_type { 104 | return Err(Err::Error(Error::new(&[], ErrorKind::Tag))); 105 | } 106 | 107 | if self 108 | .record_defrag_buffer 109 | .len() 110 | .saturating_add(record.data.len()) 111 | >= MAX_RECORD_DATA 112 | { 113 | return Err(Err::Error(Error::new(&[], ErrorKind::TooLarge))); 114 | } 115 | self.record_defrag_buffer.extend_from_slice(record.data); 116 | 117 | // create a pseudo-header with correct length 118 | let header = TlsRecordHeader { 119 | len: self.record_defrag_buffer.len() as u16, 120 | ..record.hdr 121 | }; 122 | 123 | match parse_tls_record_with_header(&self.record_defrag_buffer, &header) { 124 | // we have a complete message list. Remove the parsed records and return 125 | Ok(r) => { 126 | // set current_record_type to None, but keep buffer (remaining bytes) 127 | self.current_record_type = None; 128 | Ok(r) 129 | } 130 | Err(Err::Error(e)) | Err(Err::Failure(e)) if e.code == ErrorKind::Complete => { 131 | Err(Err::Incomplete(Needed::Unknown)) 132 | } 133 | // other errors 134 | other => other, 135 | } 136 | } 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use crate::{parse_tls_raw_record, TlsMessageHandshake, TlsVersion}; 142 | 143 | use super::*; 144 | 145 | static REC_CH: &[u8] = include_bytes!("../assets/client_hello_dhe.bin"); 146 | static REC_CH_FRAG_1: &[u8] = include_bytes!("../assets/tls_record_ch_fragmented_1.bin"); 147 | static REC_CH_FRAG_2: &[u8] = include_bytes!("../assets/tls_record_ch_fragmented_2.bin"); 148 | 149 | #[test] 150 | fn tls_records_parser_nocopy() { 151 | let (_, record) = parse_tls_raw_record(REC_CH).expect("could not parse client_hello"); 152 | let (_, record1) = parse_tls_raw_record(REC_CH_FRAG_1).expect("could not parse fragment 1"); 153 | 154 | // 155 | // check that _nocopy parser works 156 | let mut parser = TlsRecordsParser::default(); 157 | let parser_result_nocopy = parser.parse_record_nocopy(record); 158 | 159 | assert!(parser_result_nocopy.is_ok()); 160 | 161 | // 162 | // check that _nocopy parser fails with fragmented data 163 | let mut parser = TlsRecordsParser::default(); 164 | let parser_result_nocopy = parser.parse_record_nocopy(record1.clone()); 165 | assert!(matches!(parser_result_nocopy, Err(Err::Incomplete(_)))); 166 | } 167 | 168 | #[test] 169 | fn tls_records_parser_fragmented() { 170 | let (_, record) = parse_tls_raw_record(REC_CH).expect("could not parse client_hello"); 171 | let (_, record1) = parse_tls_raw_record(REC_CH_FRAG_1).expect("could not parse fragment 1"); 172 | let (_, record2) = parse_tls_raw_record(REC_CH_FRAG_2).expect("could not parse fragment 2"); 173 | 174 | // 175 | // check that parser works with complete data 176 | let mut parser = TlsRecordsParser::default(); 177 | let (rem, messages) = parser.parse_record(record).expect("parsing failed"); 178 | assert!(rem.is_empty()); 179 | assert_eq!(messages.len(), 1); 180 | assert!(!parser.defrag_in_progress()); 181 | 182 | // 183 | // check that parser works with fragmented data 184 | let mut parser = TlsRecordsParser::default(); 185 | let parser_result1 = parser.parse_record(record1); 186 | assert!(matches!(parser_result1, Err(Err::Incomplete(_)))); 187 | let (rem, messages) = parser 188 | .parse_record(record2) 189 | .expect("defragmentation failed"); 190 | assert!(rem.is_empty()); 191 | assert_eq!(messages.len(), 1); 192 | let ch = &messages[0]; 193 | assert!(matches!( 194 | ch, 195 | TlsMessage::Handshake(TlsMessageHandshake::ClientHello(_)) 196 | )); 197 | 198 | parser.reset(); 199 | 200 | // // does not compile (expected): remaining bytes borrow `parser` and cannot be used 201 | // // after `parser` has been modified (here, mutably borrowed) 202 | // assert!(!rem.is_empty()); 203 | } 204 | 205 | #[test] 206 | fn tls_records_parser_empty() { 207 | let record = TlsRawRecord { 208 | hdr: TlsRecordHeader { 209 | record_type: TlsRecordType::Handshake, 210 | version: TlsVersion::Tls12, 211 | len: 0, 212 | }, 213 | data: &[], 214 | }; 215 | let mut parser = TlsRecordsParser::default(); 216 | let parser_result = parser.parse_record(record); 217 | assert!(parser_result.is_err()); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/tls_serialize.rs: -------------------------------------------------------------------------------- 1 | use crate::tls_ec::{ECPoint, NamedGroup}; 2 | use crate::tls_extensions::{SNIType, TlsExtension, TlsExtensionType}; 3 | use crate::tls_handshake::*; 4 | use crate::tls_message::*; 5 | use crate::tls_record::*; 6 | use alloc::vec::Vec; 7 | use cookie_factory::bytes::{be_u16, be_u24, be_u8}; 8 | use cookie_factory::combinator::slice; 9 | use cookie_factory::multi::{all, many_ref}; 10 | use cookie_factory::sequence::tuple; 11 | use cookie_factory::*; 12 | use std::io::Write; 13 | 14 | pub use cookie_factory::GenError; 15 | pub use rusticata_macros::Serialize; 16 | 17 | fn gen_tls_ext_sni_hostname<'a, 'b: 'a, W: Write + 'a>( 18 | i: &(SNIType, &'b [u8]), 19 | ) -> impl SerializeFn + 'a { 20 | tuple((be_u8((i.0).0), be_u16(i.1.len() as u16), slice(i.1))) 21 | } 22 | 23 | fn length_be_u16(f: F) -> impl SerializeFn 24 | where 25 | W: Write, 26 | F: SerializeFn>, 27 | { 28 | move |out| { 29 | // use a temporary buffer 30 | let (buf, len) = gen(&f, Vec::new())?; 31 | tuple((be_u16(len as u16), slice(buf)))(out) 32 | } 33 | } 34 | 35 | fn length_be_u24(f: F) -> impl SerializeFn 36 | where 37 | W: Write, 38 | F: SerializeFn>, 39 | { 40 | move |out| { 41 | // use a temporary buffer 42 | let (buf, len) = gen(&f, Vec::new())?; 43 | tuple((be_u24(len as u32), slice(buf)))(out) 44 | } 45 | } 46 | 47 | fn tagged_extension(tag: u16, f: F) -> impl SerializeFn 48 | where 49 | W: Write, 50 | F: SerializeFn>, 51 | { 52 | move |out| tuple((be_u16(tag), length_be_u16(&f)))(out) 53 | } 54 | 55 | fn gen_tls_ext_sni<'a, W>(m: &'a [(SNIType, &[u8])]) -> impl SerializeFn + 'a 56 | where 57 | W: Write + 'a, 58 | { 59 | tagged_extension( 60 | u16::from(TlsExtensionType::ServerName), 61 | length_be_u16(many_ref(m, gen_tls_ext_sni_hostname)), 62 | ) 63 | } 64 | 65 | fn gen_tls_ext_max_fragment_length(l: u8) -> impl SerializeFn 66 | where 67 | W: Write, 68 | { 69 | tagged_extension(u16::from(TlsExtensionType::MaxFragmentLength), be_u8(l)) 70 | } 71 | 72 | fn gen_tls_named_group(g: NamedGroup) -> impl SerializeFn 73 | where 74 | W: Write, 75 | { 76 | be_u16(g.0) 77 | } 78 | 79 | fn gen_tls_ext_elliptic_curves<'a, W>(v: &'a [NamedGroup]) -> impl SerializeFn + 'a 80 | where 81 | W: Write + 'a, 82 | { 83 | tagged_extension( 84 | u16::from(TlsExtensionType::SupportedGroups), 85 | length_be_u16(all(v.iter().map(|&g| gen_tls_named_group(g)))), 86 | ) 87 | } 88 | 89 | /// Serialize a single TLS extension 90 | /// 91 | /// # Example 92 | /// 93 | /// ```rust 94 | /// use cookie_factory::{gen_simple, GenError}; 95 | /// use tls_parser::TlsExtension; 96 | /// use tls_parser::gen_tls_extensions; 97 | /// 98 | /// fn extensions_to_vec(ext: &[TlsExtension]) -> Result, GenError> { 99 | /// gen_simple(gen_tls_extensions(&ext), Vec::new()) 100 | /// } 101 | /// ``` 102 | /// 103 | /// # Note 104 | /// 105 | /// **Implementation is incomplete: 106 | /// only a few extensions are supported** (*Work in progress*) 107 | pub fn gen_tls_extension<'a, W>(m: &'a TlsExtension) -> impl SerializeFn + 'a 108 | where 109 | W: Write + 'a, 110 | { 111 | move |out| match m { 112 | TlsExtension::SNI(ref v) => gen_tls_ext_sni(v)(out), 113 | TlsExtension::MaxFragmentLength(l) => gen_tls_ext_max_fragment_length(*l)(out), 114 | 115 | TlsExtension::EllipticCurves(ref v) => gen_tls_ext_elliptic_curves(v)(out), 116 | _ => Err(GenError::NotYetImplemented), 117 | } 118 | } 119 | 120 | /// Serialize a list of TLS extensions 121 | pub fn gen_tls_extensions<'a, W>(m: &'a [TlsExtension]) -> impl SerializeFn + 'a 122 | where 123 | W: Write + 'a, 124 | { 125 | length_be_u16(many_ref(m, gen_tls_extension)) 126 | } 127 | 128 | fn gen_tls_sessionid<'a, W>(m: &'a Option<&[u8]>) -> impl SerializeFn + 'a 129 | where 130 | W: Write + 'a, 131 | { 132 | move |out| match m { 133 | None => be_u8(0)(out), 134 | Some(o) => be_u8(o.len() as u8)(out).and_then(slice(o)), 135 | } 136 | } 137 | 138 | fn maybe_extensions<'a, W>(m: &'a Option<&[u8]>) -> impl SerializeFn + 'a 139 | where 140 | W: Write + 'a, 141 | { 142 | move |out| match m { 143 | Some(o) => be_u16(o.len() as u16)(out).and_then(slice(o)), 144 | None => be_u16(0)(out), 145 | } 146 | } 147 | 148 | /// Serialize a ClientHello message 149 | pub fn gen_tls_clienthello<'a, W>(m: &'a TlsClientHelloContents) -> impl SerializeFn + 'a 150 | where 151 | W: Write + 'a, 152 | { 153 | tuple(( 154 | be_u8(u8::from(TlsHandshakeType::ClientHello)), 155 | length_be_u24(tuple(( 156 | be_u16(m.version.0), 157 | slice(m.random), // check that length is 32 158 | gen_tls_sessionid(&m.session_id), 159 | be_u16(m.ciphers.len() as u16 * 2), 160 | all(m.ciphers.iter().map(|cipher| be_u16(cipher.0))), 161 | be_u8(m.comp.len() as u8), 162 | all(m.comp.iter().map(|comp| be_u8(comp.0))), 163 | maybe_extensions(&m.ext), 164 | ))), 165 | )) 166 | } 167 | 168 | /// Serialize a ServerHello message 169 | pub fn gen_tls_serverhello<'a, W>(m: &'a TlsServerHelloContents) -> impl SerializeFn + 'a 170 | where 171 | W: Write + 'a, 172 | { 173 | tuple(( 174 | be_u8(u8::from(TlsHandshakeType::ServerHello)), 175 | length_be_u24(tuple(( 176 | be_u16(m.version.0), 177 | slice(m.random), // check that length is 32 178 | gen_tls_sessionid(&m.session_id), 179 | be_u16(m.cipher.0), 180 | be_u8(m.compression.0), 181 | maybe_extensions(&m.ext), 182 | ))), 183 | )) 184 | } 185 | 186 | /// Serialize a ServerHello (TLS 1.3 draft 18) message 187 | pub fn gen_tls_serverhellodraft18<'a, W>( 188 | m: &'a TlsServerHelloV13Draft18Contents, 189 | ) -> impl SerializeFn + 'a 190 | where 191 | W: Write + 'a, 192 | { 193 | tuple(( 194 | be_u8(u8::from(TlsHandshakeType::ServerHello)), 195 | length_be_u24(tuple(( 196 | be_u16(m.version.0), 197 | slice(m.random), // check that length is 32 198 | be_u16(m.cipher.0), 199 | maybe_extensions(&m.ext), 200 | ))), 201 | )) 202 | } 203 | 204 | /// Serialize a ClientKeyExchange message, from raw contents 205 | fn gen_tls_clientkeyexchange_unknown<'a, W>(m: &'a [u8]) -> impl SerializeFn + 'a 206 | where 207 | W: Write + 'a, 208 | { 209 | tuple(( 210 | be_u8(u8::from(TlsHandshakeType::ClientKeyExchange)), 211 | length_be_u24(slice(m)), 212 | )) 213 | } 214 | 215 | /// Serialize a ClientKeyExchange message, for DH parameters 216 | fn gen_tls_clientkeyexchange_dh<'a, W>(m: &'a [u8]) -> impl SerializeFn + 'a 217 | where 218 | W: Write + 'a, 219 | { 220 | tuple(( 221 | be_u8(u8::from(TlsHandshakeType::ClientKeyExchange)), 222 | length_be_u24(length_be_u16(slice(m))), 223 | )) 224 | } 225 | 226 | /// Serialize a ClientKeyExchange message, for ECDH parameters 227 | fn gen_tls_clientkeyexchange_ecdh<'a, W>(m: &'a ECPoint) -> impl SerializeFn + 'a 228 | where 229 | W: Write + 'a, 230 | { 231 | tuple(( 232 | be_u8(u8::from(TlsHandshakeType::ClientKeyExchange)), 233 | length_be_u24(tuple(( 234 | // for ECDH, length is only 1 byte 235 | be_u8(m.point.len() as u8), 236 | slice(m.point), 237 | ))), 238 | )) 239 | } 240 | 241 | /// Serialize a ClientKeyExchange message 242 | pub fn gen_tls_clientkeyexchange<'a, W>( 243 | m: &'a TlsClientKeyExchangeContents, 244 | ) -> impl SerializeFn + 'a 245 | where 246 | W: Write + 'a, 247 | { 248 | move |out| match m { 249 | TlsClientKeyExchangeContents::Unknown(b) => gen_tls_clientkeyexchange_unknown(b)(out), 250 | TlsClientKeyExchangeContents::Dh(b) => gen_tls_clientkeyexchange_dh(b)(out), 251 | TlsClientKeyExchangeContents::Ecdh(ref b) => gen_tls_clientkeyexchange_ecdh(b)(out), 252 | } 253 | } 254 | 255 | /// Serialize a HelloRequest message 256 | pub fn gen_tls_hellorequest() -> impl SerializeFn 257 | where 258 | W: Write, 259 | { 260 | tuple((be_u8(u8::from(TlsHandshakeType::HelloRequest)), be_u24(0))) 261 | } 262 | 263 | /// Serialize a Finished message 264 | pub fn gen_tls_finished<'a, W>(m: &'a [u8]) -> impl SerializeFn + 'a 265 | where 266 | W: Write + 'a, 267 | { 268 | tuple(( 269 | be_u8(u8::from(TlsHandshakeType::Finished)), 270 | length_be_u24(slice(m)), 271 | )) 272 | } 273 | 274 | /// Serialize a TLS handshake message 275 | fn gen_tls_messagehandshake<'a, W>(m: &'a TlsMessageHandshake<'a>) -> impl SerializeFn + 'a 276 | where 277 | W: Write + 'a, 278 | { 279 | move |out| match m { 280 | TlsMessageHandshake::HelloRequest => gen_tls_hellorequest()(out), 281 | TlsMessageHandshake::ClientHello(ref m) => gen_tls_clienthello(m)(out), 282 | TlsMessageHandshake::ServerHello(ref m) => gen_tls_serverhello(m)(out), 283 | TlsMessageHandshake::ServerHelloV13Draft18(ref m) => gen_tls_serverhellodraft18(m)(out), 284 | TlsMessageHandshake::ClientKeyExchange(ref m) => gen_tls_clientkeyexchange(m)(out), 285 | TlsMessageHandshake::Finished(m) => gen_tls_finished(m)(out), 286 | _ => Err(GenError::NotYetImplemented), 287 | } 288 | } 289 | 290 | impl<'a> Serialize> for TlsMessageHandshake<'a> { 291 | type Error = GenError; 292 | fn serialize(&self) -> Result, Self::Error> { 293 | gen_simple(gen_tls_messagehandshake(self), Vec::new()) 294 | } 295 | } 296 | 297 | /// Serialize a ChangeCipherSpec message 298 | pub fn gen_tls_changecipherspec() -> impl SerializeFn 299 | where 300 | W: Write, 301 | { 302 | be_u8(u8::from(TlsRecordType::ChangeCipherSpec)) 303 | } 304 | 305 | /// Serialize a TLS message 306 | /// 307 | /// # Example 308 | /// 309 | /// ```rust 310 | /// use cookie_factory::{gen_simple, GenError}; 311 | /// use tls_parser::TlsMessage; 312 | /// use tls_parser::gen_tls_message; 313 | /// 314 | /// fn tls_message_to_vec(msg: &TlsMessage) -> Result, GenError> { 315 | /// gen_simple(gen_tls_message(&msg), Vec::new()) 316 | /// } 317 | /// ``` 318 | pub fn gen_tls_message<'a, W>(m: &'a TlsMessage<'a>) -> impl SerializeFn + 'a 319 | where 320 | W: Write + 'a, 321 | { 322 | move |out| match m { 323 | TlsMessage::Handshake(ref m) => gen_tls_messagehandshake(m)(out), 324 | TlsMessage::ChangeCipherSpec => gen_tls_changecipherspec()(out), 325 | _ => Err(GenError::NotYetImplemented), 326 | } 327 | } 328 | 329 | impl<'a> Serialize> for TlsMessage<'a> { 330 | type Error = GenError; 331 | fn serialize(&self) -> Result, Self::Error> { 332 | gen_simple(gen_tls_message(self), Vec::new()) 333 | } 334 | } 335 | 336 | /// Serialize a TLS plaintext record 337 | /// 338 | /// # Example 339 | /// 340 | /// ```rust 341 | /// use cookie_factory::{gen_simple, GenError}; 342 | /// use tls_parser::TlsPlaintext; 343 | /// use tls_parser::gen_tls_plaintext; 344 | /// 345 | /// fn tls_message_to_vec(rec: &TlsPlaintext) -> Result, GenError> { 346 | /// gen_simple(gen_tls_plaintext(&rec), Vec::new()) 347 | /// } 348 | /// ``` 349 | pub fn gen_tls_plaintext<'a, W>(p: &'a TlsPlaintext) -> impl SerializeFn + 'a 350 | where 351 | W: Write + 'a, 352 | { 353 | tuple(( 354 | be_u8(p.hdr.record_type.0), 355 | be_u16(p.hdr.version.0), 356 | length_be_u16(all(p.msg.iter().map(gen_tls_message))), 357 | )) 358 | } 359 | 360 | impl<'a> Serialize> for TlsPlaintext<'a> { 361 | type Error = GenError; 362 | fn serialize(&self) -> Result, Self::Error> { 363 | gen_simple(gen_tls_plaintext(self), Vec::new()) 364 | } 365 | } 366 | 367 | #[cfg(test)] 368 | mod tests { 369 | use super::*; 370 | use crate::tls_extensions::parse_tls_extension; 371 | use hex_literal::hex; 372 | 373 | const CH_DHE: &[u8] = include_bytes!("../assets/client_hello_dhe.bin"); 374 | 375 | #[test] 376 | fn serialize_tagged_extension() { 377 | let expected = &hex!("12 34 00 02 00 01"); 378 | let res = 379 | gen_simple(tagged_extension(0x1234, be_u16(1)), Vec::new()).expect("serialize failed"); 380 | assert_eq!(&res, expected); 381 | } 382 | 383 | #[test] 384 | fn serialize_extension_sni() { 385 | let raw_sni = &hex!( 386 | " 387 | 00 00 00 14 00 12 00 00 0f 63 2e 64 69 73 71 75 388 | 73 63 64 6e 2e 63 6f 6d 389 | " 390 | ); 391 | let (_, ext) = parse_tls_extension(raw_sni).expect("could not parse sni extension"); 392 | if let TlsExtension::SNI(sni) = ext { 393 | let res = gen_simple(gen_tls_ext_sni(&sni), Vec::new()) 394 | .expect("could not serialize sni extension"); 395 | assert_eq!(&res, raw_sni); 396 | } else { 397 | panic!("parsed extension has wrong type"); 398 | } 399 | } 400 | 401 | #[test] 402 | fn serialize_tls_extensions() { 403 | let ext = vec![TlsExtension::SNI(vec![( 404 | SNIType::HostName, 405 | b"www.google.com", 406 | )])]; 407 | 408 | let res = gen_simple(gen_tls_extensions(&ext), Vec::new()) 409 | .expect("could not serialize extensions"); 410 | let v = [ 411 | 0x00, 0x17, // Extensions length (total) 412 | 0x00, 0x00, // SNI tag 413 | 0x00, 0x13, // SNI ext length 414 | 0x00, 0x11, // SNI list length 415 | // element 0: 416 | 0x00, // type 417 | 0x00, 0x0e, // length 418 | 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 419 | ]; 420 | assert_eq!(&res, &v); 421 | } 422 | 423 | #[test] 424 | fn serialize_plaintext() { 425 | let random: &[u8] = &[ 426 | 0x87, 0xd7, 0x9d, 0xb2, 0xff, 0x21, 0xeb, 0x04, 0xc8, 0xa5, 0x38, 0x39, 0x9a, 0xcf, 427 | 0xb7, 0xa3, 0x82, 0x1f, 0x82, 0x6c, 0x49, 0xbc, 0x8b, 0xb8, 0xa9, 0x03, 0x0a, 0x2d, 428 | 0xce, 0x38, 0x0b, 0xf4, 429 | ]; 430 | let ciphers = [ 431 | 0xc030, 0xc02c, 0xc028, 0xc024, 0xc014, 0xc00a, 0x00a5, 0x00a3, 0x00a1, 0x009f, 0x006b, 432 | 0x006a, 0x0069, 0x0068, 0x0039, 0x0038, 0x0037, 0x0036, 0x0088, 0x0087, 0x0086, 0x0085, 433 | 0xc032, 0xc02e, 0xc02a, 0xc026, 0xc00f, 0xc005, 0x009d, 0x003d, 0x0035, 0x0084, 0xc02f, 434 | 0xc02b, 0xc027, 0xc023, 0xc013, 0xc009, 0x00a4, 0x00a2, 0x00a0, 0x009e, 0x0067, 0x0040, 435 | 0x003f, 0x003e, 0x0033, 0x0032, 0x0031, 0x0030, 0x009a, 0x0099, 0x0098, 0x0097, 0x0045, 436 | 0x0044, 0x0043, 0x0042, 0xc031, 0xc02d, 0xc029, 0xc025, 0xc00e, 0xc004, 0x009c, 0x003c, 437 | 0x002f, 0x0096, 0x0041, 0xc011, 0xc007, 0xc00c, 0xc002, 0x0005, 0x0004, 0xc012, 0xc008, 438 | 0x0016, 0x0013, 0x0010, 0x000d, 0xc00d, 0xc003, 0x000a, 0x00ff, 439 | ]; 440 | let comp = vec![TlsCompressionID(0x00)]; 441 | 442 | let expected = TlsPlaintext { 443 | hdr: TlsRecordHeader { 444 | record_type: TlsRecordType::Handshake, 445 | version: TlsVersion::Tls10, 446 | len: 215, 447 | }, 448 | msg: vec![TlsMessage::Handshake(TlsMessageHandshake::ClientHello( 449 | TlsClientHelloContents { 450 | version: TlsVersion::Tls12, 451 | random, 452 | session_id: None, 453 | ciphers: ciphers.iter().map(|&x| TlsCipherSuiteID(x)).collect(), 454 | comp, 455 | ext: Some(&[]), 456 | }, 457 | ))], 458 | }; 459 | 460 | let res = expected 461 | .serialize() 462 | .expect("Could not serialize plaintext message"); 463 | let (_, res_reparse) = 464 | parse_tls_plaintext(&res).expect("Could not parse gen_tls_plaintext output"); 465 | assert_eq!(res_reparse, expected); 466 | } 467 | 468 | #[test] 469 | fn serialize_hellorequest() { 470 | let m = TlsMessageHandshake::HelloRequest; 471 | 472 | let res = m.serialize().expect("Could not serialize messages"); 473 | let v = [0, 0, 0, 0]; 474 | assert_eq!(&v[..], &res[..]); 475 | } 476 | 477 | #[test] 478 | fn serialize_tls_ext() { 479 | let ext = TlsExtension::SNI(vec![(SNIType::HostName, b"www.google.com")]); 480 | 481 | let res = 482 | gen_simple(gen_tls_extension(&ext), Vec::new()).expect("Could not serialize messages"); 483 | let v = [ 484 | 0x00, 0x00, // SNI tag 485 | 0x00, 0x13, // SNI ext length 486 | 0x00, 0x11, // SNI list length 487 | // element 0: 488 | 0x00, // type 489 | 0x00, 0x0e, // length 490 | 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 491 | ]; 492 | assert_eq!(&v[..], &res[..]); 493 | } 494 | 495 | #[test] 496 | fn serialize_clienthello() { 497 | let random: &[u8] = &[ 498 | 0xb2, 0x9d, 0xd7, 0x87, 0xff, 0x21, 0xeb, 0x04, 0xc8, 0xa5, 0x38, 0x39, 0x9a, 0xcf, 499 | 0xb7, 0xa3, 0x82, 0x1f, 0x82, 0x6c, 0x49, 0xbc, 0x8b, 0xb8, 0xa9, 0x03, 0x0a, 0x2d, 500 | 0xce, 0x38, 0x0b, 0xf4, 501 | ]; 502 | let ciphers = &[0xc030, 0xc02c]; 503 | let comp = vec![TlsCompressionID(0x00)]; 504 | 505 | let m = TlsMessageHandshake::ClientHello(TlsClientHelloContents { 506 | version: TlsVersion::Tls12, 507 | random, 508 | session_id: None, 509 | ciphers: ciphers.iter().map(|&x| TlsCipherSuiteID(x)).collect(), 510 | comp, 511 | ext: None, 512 | }); 513 | 514 | let res = m.serialize().expect("Could not serialize messages"); 515 | let v = [ 516 | 0x01, 0x00, 0x00, 0x2d, 0x03, 0x03, // type, length, version 517 | 0xb2, 0x9d, 0xd7, 0x87, // random time 518 | 0xff, 0x21, 0xeb, 0x04, 0xc8, 0xa5, 0x38, 0x39, // random data 519 | 0x9a, 0xcf, 0xb7, 0xa3, 0x82, 0x1f, 0x82, 0x6c, 0x49, 0xbc, 0x8b, 0xb8, 0xa9, 0x03, 520 | 0x0a, 0x2d, 0xce, 0x38, 0x0b, 0xf4, 0x00, // session ID 521 | 0x00, 0x04, 0xc0, 0x30, 0xc0, 0x2c, // ciphers 522 | 0x01, 0x00, // compression 523 | 0x00, 0x00, // extensions length 524 | ]; 525 | assert_eq!(&v[..], &res[..]); 526 | } 527 | 528 | #[test] 529 | fn serialize_serverhello() { 530 | let random: &[u8] = &[ 531 | 0xb2, 0x9d, 0xd7, 0x87, 0xff, 0x21, 0xeb, 0x04, 0xc8, 0xa5, 0x38, 0x39, 0x9a, 0xcf, 532 | 0xb7, 0xa3, 0x82, 0x1f, 0x82, 0x6c, 0x49, 0xbc, 0x8b, 0xb8, 0xa9, 0x03, 0x0a, 0x2d, 533 | 0xce, 0x38, 0x0b, 0xf4, 534 | ]; 535 | 536 | let m = TlsMessageHandshake::ServerHello(TlsServerHelloContents { 537 | version: TlsVersion::Tls12, 538 | random, 539 | session_id: None, 540 | cipher: TlsCipherSuiteID(0xc030), 541 | compression: TlsCompressionID(0), 542 | ext: None, 543 | }); 544 | 545 | let res = gen_simple(gen_tls_messagehandshake(&m), Vec::new()) 546 | .expect("Could not serialize message"); 547 | let v = [ 548 | 0x02, 0x00, 0x00, 0x28, 0x03, 0x03, // type, length, version 549 | 0xb2, 0x9d, 0xd7, 0x87, // random time 550 | 0xff, 0x21, 0xeb, 0x04, 0xc8, 0xa5, 0x38, 0x39, // random data 551 | 0x9a, 0xcf, 0xb7, 0xa3, 0x82, 0x1f, 0x82, 0x6c, 0x49, 0xbc, 0x8b, 0xb8, 0xa9, 0x03, 552 | 0x0a, 0x2d, 0xce, 0x38, 0x0b, 0xf4, 0x00, // session ID 553 | 0xc0, 0x30, // cipher 554 | 0x00, // compression 555 | 0x00, 0x00, // extensions length 556 | ]; 557 | assert_eq!(&v[..], &res[..]); 558 | } 559 | 560 | #[test] 561 | fn read_serialize_clienthello_dhe() { 562 | let (_, record) = parse_tls_plaintext(CH_DHE).expect("parsing failed"); 563 | let res = gen_simple(gen_tls_plaintext(&record), Vec::new()) 564 | .expect("Could not serialize message"); 565 | let (_, record2) = parse_tls_plaintext(&res).expect("re-parsing failed"); 566 | assert_eq!(record, record2); 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /src/tls_sign_hash.rs: -------------------------------------------------------------------------------- 1 | use nom::combinator::map; 2 | use nom::multi::length_data; 3 | use nom::number::streaming::be_u16; 4 | use nom::sequence::pair; 5 | use nom::IResult; 6 | use nom_derive::*; 7 | use rusticata_macros::newtype_enum; 8 | 9 | /// Hash algorithms, as defined in \[RFC5246\] 10 | #[derive(Clone, Debug, PartialEq, Eq, Nom)] 11 | pub struct HashAlgorithm(pub u8); 12 | 13 | newtype_enum! { 14 | impl display HashAlgorithm { 15 | None = 0, 16 | Md5 = 1, 17 | Sha1 = 2, 18 | Sha224 = 3, 19 | Sha256 = 4, 20 | Sha384 = 5, 21 | Sha512 = 6, 22 | Intrinsic = 8, // [RFC8422] 23 | } 24 | } 25 | 26 | /// Signature algorithms, as defined in \[RFC5246\] 27 | #[derive(Clone, Debug, PartialEq, Eq, Nom)] 28 | pub struct SignAlgorithm(pub u8); 29 | 30 | newtype_enum! { 31 | impl display SignAlgorithm { 32 | Anonymous = 0, 33 | Rsa = 1, 34 | Dsa = 2, 35 | Ecdsa = 3, 36 | Ed25519 = 7, // [RFC8422] 37 | Ed448 = 8, // [RFC8422] 38 | } 39 | } 40 | 41 | #[derive(Clone, PartialEq, Nom)] 42 | pub struct SignatureAndHashAlgorithm { 43 | pub hash: HashAlgorithm, 44 | pub sign: SignAlgorithm, 45 | } 46 | 47 | /// Signature algorithms, as defined in \[RFC8446\] 4.2.3 48 | #[derive(Debug, PartialEq, Eq, Nom)] 49 | pub struct SignatureScheme(pub u16); 50 | 51 | newtype_enum! { 52 | impl display SignatureScheme { 53 | /* RSASSA-PKCS1-v1_5 algorithms */ 54 | rsa_pkcs1_sha256 = 0x0401, 55 | rsa_pkcs1_sha384 = 0x0501, 56 | rsa_pkcs1_sha512 = 0x0601, 57 | 58 | /* ECDSA algorithms */ 59 | ecdsa_secp256r1_sha256 = 0x0403, 60 | ecdsa_secp384r1_sha384 = 0x0503, 61 | ecdsa_secp521r1_sha512 = 0x0603, 62 | 63 | /* ShangMi (SM) Cipher Suites */ 64 | sm2sig_sm3 = 0x0708, 65 | 66 | /* RSASSA-PSS algorithms with public key OID rsaEncryption */ 67 | rsa_pss_rsae_sha256 = 0x0804, 68 | rsa_pss_rsae_sha384 = 0x0805, 69 | rsa_pss_rsae_sha512 = 0x0806, 70 | 71 | /* EdDSA algorithms */ 72 | ed25519 = 0x0807, 73 | ed448 = 0x0808, 74 | 75 | /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ 76 | rsa_pss_pss_sha256 = 0x0809, 77 | rsa_pss_pss_sha384 = 0x080a, 78 | rsa_pss_pss_sha512 = 0x080b, 79 | 80 | /* Brainpool SignatureScheme Types for TLS 1.3 (RFC8734) */ 81 | ecdsa_brainpoolP256r1tls13_sha256 = 0x081a, 82 | ecdsa_brainpoolP384r1tls13_sha384 = 0x081b, 83 | ecdsa_brainpoolP512r1tls13_sha512 = 0x081c, 84 | 85 | /* Legacy algorithms */ 86 | rsa_pkcs1_sha1 = 0x0201, 87 | ecdsa_sha1 = 0x0203, 88 | } 89 | } 90 | 91 | impl SignatureScheme { 92 | pub fn is_reserved(&self) -> bool { 93 | self.0 >= 0xfe00 && self.0 < 0xff00 94 | } 95 | 96 | /// Get Hash algorithm (for tls <= 1.2) for legacy extension format 97 | pub fn hash_alg(&self) -> u8 { 98 | ((self.0 >> 8) & 0xff) as u8 99 | } 100 | 101 | /// Get Signature algorithm (for tls <= 1.2) for legacy extension format 102 | pub fn sign_alg(&self) -> u8 { 103 | (self.0 & 0xff) as u8 104 | } 105 | } 106 | 107 | /// DigitallySigned structure from \[RFC2246\] section 4.7 108 | /// has no algorithm definition. 109 | /// This should be deprecated in favor of 110 | /// DigitallySigned structure from \[RFC5246\] section 4.7 111 | #[derive(Clone, PartialEq)] 112 | pub struct DigitallySigned<'a> { 113 | pub alg: Option, 114 | // pub alg: Option, // SignatureScheme 115 | pub data: &'a [u8], 116 | } 117 | 118 | pub fn parse_digitally_signed_old(i: &[u8]) -> IResult<&[u8], DigitallySigned> { 119 | map(length_data(be_u16), |data| DigitallySigned { 120 | alg: None, 121 | data, 122 | })(i) 123 | } 124 | 125 | pub fn parse_digitally_signed(i: &[u8]) -> IResult<&[u8], DigitallySigned> { 126 | let (i, hash) = HashAlgorithm::parse(i)?; 127 | let (i, sign) = SignAlgorithm::parse(i)?; 128 | let (i, data) = length_data(be_u16)(i)?; 129 | let signed = DigitallySigned { 130 | alg: Some(SignatureAndHashAlgorithm { hash, sign }), 131 | data, 132 | }; 133 | Ok((i, signed)) 134 | } 135 | 136 | /// Parse DigitallySigned object, depending on the `ext` parameter which should 137 | /// be true if the TLS client has sent the `signature_algorithms` extension 138 | pub fn parse_content_and_signature<'a, F, T>( 139 | i: &'a [u8], 140 | fun: F, 141 | ext: bool, 142 | ) -> IResult<&'a [u8], (T, DigitallySigned<'a>)> 143 | where 144 | F: Fn(&'a [u8]) -> IResult<&'a [u8], T>, 145 | { 146 | if ext { 147 | pair(fun, parse_digitally_signed)(i) 148 | } else { 149 | pair(fun, parse_digitally_signed_old)(i) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/tls_states.rs: -------------------------------------------------------------------------------- 1 | use crate::tls_alert::TlsAlertSeverity; 2 | use crate::tls_handshake::*; 3 | use crate::tls_message::*; 4 | 5 | #[derive(Debug, PartialEq, Eq)] 6 | /// Error types for the state machine 7 | pub enum StateChangeError { 8 | InvalidTransition, 9 | ParseError, 10 | } 11 | 12 | /// TLS machine possible states 13 | #[derive(Copy, Clone, Debug, PartialEq)] 14 | pub enum TlsState { 15 | None, 16 | ClientHello, 17 | AskResumeSession, 18 | ResumeSession, 19 | ServerHello, 20 | Certificate, 21 | CertificateSt, 22 | ServerKeyExchange, 23 | ServerHelloDone, 24 | ClientKeyExchange, 25 | ClientChangeCipherSpec, 26 | 27 | CRCertRequest, 28 | CRHelloDone, 29 | CRCert, 30 | CRClientKeyExchange, 31 | CRCertVerify, 32 | 33 | NoCertSKE, 34 | NoCertHelloDone, 35 | NoCertCKE, 36 | 37 | PskHelloDone, 38 | PskCKE, 39 | 40 | SessionEncrypted, 41 | 42 | Alert, 43 | 44 | Finished, 45 | 46 | Invalid, 47 | } 48 | 49 | #[rustfmt::skip] 50 | fn tls_state_transition_handshake(state: TlsState, msg: &TlsMessageHandshake, to_server:bool) -> Result { 51 | match (state,msg,to_server) { 52 | (TlsState::None, TlsMessageHandshake::ClientHello(msg), true) => { 53 | match msg.session_id { 54 | Some(_) => Ok(TlsState::AskResumeSession), 55 | _ => Ok(TlsState::ClientHello) 56 | } 57 | }, 58 | // Server certificate 59 | (TlsState::ClientHello, &TlsMessageHandshake::ServerHello(_), false) => Ok(TlsState::ServerHello), 60 | (TlsState::ServerHello, &TlsMessageHandshake::Certificate(_), false) => Ok(TlsState::Certificate), 61 | // Server certificate, no client certificate requested 62 | (TlsState::Certificate, &TlsMessageHandshake::ServerKeyExchange(_), false) => Ok(TlsState::ServerKeyExchange), 63 | (TlsState::Certificate, &TlsMessageHandshake::CertificateStatus(_), false) => Ok(TlsState::CertificateSt), 64 | (TlsState::CertificateSt, &TlsMessageHandshake::ServerKeyExchange(_), false) => Ok(TlsState::ServerKeyExchange), 65 | (TlsState::ServerKeyExchange,&TlsMessageHandshake::ServerDone(_), false) => Ok(TlsState::ServerHelloDone), 66 | (TlsState::ServerHelloDone ,&TlsMessageHandshake::ClientKeyExchange(_), true) => Ok(TlsState::ClientKeyExchange), 67 | // Server certificate, client certificate requested 68 | (TlsState::Certificate, &TlsMessageHandshake::CertificateRequest(_), false)=> Ok(TlsState::CRCertRequest), 69 | (TlsState::ServerKeyExchange,&TlsMessageHandshake::CertificateRequest(_), false)=> Ok(TlsState::CRCertRequest), 70 | (TlsState::CRCertRequest, &TlsMessageHandshake::ServerDone(_), false) => Ok(TlsState::CRHelloDone), 71 | (TlsState::CRHelloDone, &TlsMessageHandshake::Certificate(_), true) => Ok(TlsState::CRCert), 72 | (TlsState::CRCert, &TlsMessageHandshake::ClientKeyExchange(_), true) => Ok(TlsState::CRClientKeyExchange), 73 | (TlsState::CRClientKeyExchange, &TlsMessageHandshake::CertificateVerify(_), _) => Ok(TlsState::CRCertVerify), 74 | // Server has no certificate (but accepts anonymous) 75 | (TlsState::ServerHello, &TlsMessageHandshake::ServerKeyExchange(_), false) => Ok(TlsState::NoCertSKE), 76 | (TlsState::NoCertSKE, &TlsMessageHandshake::ServerDone(_), false) => Ok(TlsState::NoCertHelloDone), 77 | (TlsState::NoCertHelloDone, &TlsMessageHandshake::ClientKeyExchange(_), true) => Ok(TlsState::NoCertCKE), 78 | // PSK 79 | (TlsState::Certificate, &TlsMessageHandshake::ServerDone(_), false) => Ok(TlsState::PskHelloDone), 80 | (TlsState::PskHelloDone, &TlsMessageHandshake::ClientKeyExchange(_), true) => Ok(TlsState::PskCKE), 81 | // Resuming session 82 | (TlsState::AskResumeSession, &TlsMessageHandshake::ServerHello(_), false) => Ok(TlsState::ResumeSession), 83 | // Resume session failed 84 | (TlsState::ResumeSession, &TlsMessageHandshake::Certificate(_), false) => Ok(TlsState::Certificate), 85 | // TLS 1.3 Draft 18 1-RTT 86 | // Re-use the ClientChangeCipherSpec state to indicate the next message will be encrypted 87 | (TlsState::ClientHello, &TlsMessageHandshake::ServerHelloV13Draft18(_), false) => Ok(TlsState::ClientChangeCipherSpec), 88 | // Hello requests must be accepted at any time (except start), but ignored [RFC5246] 7.4.1.1 89 | (TlsState::None, &TlsMessageHandshake::HelloRequest, _) => Err(StateChangeError::InvalidTransition), 90 | (s, &TlsMessageHandshake::HelloRequest, _) => Ok(s), 91 | // NewSessionTicket message (after CCS) 92 | (TlsState::ClientChangeCipherSpec, &TlsMessageHandshake::NewSessionTicket(_), false) => Ok(TlsState::ClientChangeCipherSpec), 93 | // All other transitions are considered invalid 94 | _ => Err(StateChangeError::InvalidTransition), 95 | } 96 | } 97 | 98 | /// Update the TLS state machine, doing one transition 99 | /// 100 | /// Given the previous state and the parsed message, return the new state or a state machine error. 101 | /// 102 | /// This state machine only implements the TLS handshake. 103 | /// 104 | /// Some transitions only check the new message type, while some others must match the content 105 | /// (for example, to check if the client asked to resume a session). 106 | /// 107 | /// If the previous state is `Invalid`, the state machine will not return an error, but keep the 108 | /// same `Invalid` state. This is used to raise error only once if the state machine keeps being 109 | /// updated by new messages. 110 | #[rustfmt::skip] 111 | pub fn tls_state_transition(state: TlsState, msg: &TlsMessage, to_server:bool) -> Result { 112 | match (state,msg,to_server) { 113 | (TlsState::Invalid,_,_) => Ok(TlsState::Invalid), 114 | (TlsState::SessionEncrypted,_, _) => Ok(TlsState::SessionEncrypted), 115 | (TlsState::Finished,_,_) => Ok(TlsState::Invalid), 116 | (_,TlsMessage::Handshake(m),_) => tls_state_transition_handshake(state,m,to_server), 117 | // Server certificate 118 | (TlsState::ClientKeyExchange, &TlsMessage::ChangeCipherSpec, _) => Ok(TlsState::ClientChangeCipherSpec), 119 | (TlsState::ClientChangeCipherSpec,&TlsMessage::ChangeCipherSpec, false) => Ok(TlsState::SessionEncrypted), 120 | // Server certificate, client certificate requested 121 | (TlsState::CRClientKeyExchange, &TlsMessage::ChangeCipherSpec, _) => Ok(TlsState::ClientChangeCipherSpec), 122 | (TlsState::CRCertVerify, &TlsMessage::ChangeCipherSpec, _) => Ok(TlsState::ClientChangeCipherSpec), 123 | // No server certificate 124 | (TlsState::NoCertCKE, &TlsMessage::ChangeCipherSpec, _) => Ok(TlsState::ClientChangeCipherSpec), 125 | // PSK 126 | (TlsState::PskCKE, &TlsMessage::ChangeCipherSpec, _) => Ok(TlsState::ClientChangeCipherSpec), 127 | // Resume session 128 | (TlsState::ResumeSession, &TlsMessage::ChangeCipherSpec, _) => Ok(TlsState::ClientChangeCipherSpec), 129 | // 0-rtt 130 | (TlsState::AskResumeSession, &TlsMessage::ChangeCipherSpec, true) => Ok(TlsState::AskResumeSession), 131 | // non-fatal alerts 132 | (s, TlsMessage::Alert(a), _) => { 133 | if a.severity == TlsAlertSeverity::Warning { Ok(s) } else { Ok(TlsState::Finished) } 134 | }, 135 | (_,_,_) => Err(StateChangeError::InvalidTransition), 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tests/certificate_transparency.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate pretty_assertions; 3 | 4 | extern crate nom; 5 | extern crate tls_parser; 6 | 7 | mod certificate_transparency { 8 | use tls_parser::*; 9 | 10 | static SCT_LIST: &[u8] = &[ 11 | 0x00, 0xf0, 0x00, 0x76, 0x00, 0xf6, 0x5c, 0x94, 0x2f, 0xd1, 0x77, 0x30, 0x22, 0x14, 0x54, 12 | 0x18, 0x08, 0x30, 0x94, 0x56, 0x8e, 0xe3, 0x4d, 0x13, 0x19, 0x33, 0xbf, 0xdf, 0x0c, 0x2f, 13 | 0x20, 0x0b, 0xcc, 0x4e, 0xf1, 0x64, 0xe3, 0x00, 0x00, 0x01, 0x72, 0x53, 0x4b, 0x97, 0xa5, 14 | 0x00, 0x00, 0x04, 0x03, 0x00, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xc6, 0x2d, 0xa9, 0x45, 15 | 0xd2, 0x81, 0xfd, 0xda, 0x9f, 0xf3, 0xf8, 0xa4, 0x18, 0xb4, 0x4d, 0x2f, 0x7c, 0x23, 0x60, 16 | 0xb5, 0x6a, 0xb9, 0x51, 0x88, 0x9c, 0x38, 0x1b, 0x36, 0xf8, 0xa9, 0xf2, 0x1d, 0x02, 0x20, 17 | 0x00, 0xe2, 0xfc, 0xde, 0xbc, 0x91, 0x08, 0x29, 0x46, 0x86, 0x08, 0x89, 0x5b, 0x62, 0xd4, 18 | 0x45, 0x3e, 0x91, 0xdd, 0x39, 0x76, 0xb7, 0xa6, 0xe4, 0xae, 0xd4, 0xd2, 0x38, 0x50, 0xe9, 19 | 0xc7, 0xd0, 0x00, 0x76, 0x00, 0x5c, 0xdc, 0x43, 0x92, 0xfe, 0xe6, 0xab, 0x45, 0x44, 0xb1, 20 | 0x5e, 0x9a, 0xd4, 0x56, 0xe6, 0x10, 0x37, 0xfb, 0xd5, 0xfa, 0x47, 0xdc, 0xa1, 0x73, 0x94, 21 | 0xb2, 0x5e, 0xe6, 0xf6, 0xc7, 0x0e, 0xca, 0x00, 0x00, 0x01, 0x72, 0x53, 0x4b, 0x97, 0xa0, 22 | 0x00, 0x00, 0x04, 0x03, 0x00, 0x47, 0x30, 0x45, 0x02, 0x20, 0x35, 0x6c, 0x91, 0x1f, 0xb3, 23 | 0x22, 0x79, 0xf2, 0x65, 0x95, 0x53, 0xcf, 0x3a, 0x36, 0xd7, 0xac, 0xde, 0xa1, 0xf4, 0xb1, 24 | 0xa9, 0x2e, 0xdd, 0x46, 0x0d, 0x96, 0xff, 0x1b, 0xda, 0x93, 0x4e, 0xe8, 0x02, 0x21, 0x00, 25 | 0xee, 0xed, 0x93, 0x37, 0xba, 0x12, 0xdb, 0x44, 0x67, 0x7e, 0x09, 0xa4, 0x5f, 0xd6, 0x66, 26 | 0x6c, 0x7b, 0x02, 0xb8, 0x63, 0x1f, 0xb6, 0xbf, 0x91, 0x53, 0x95, 0xb6, 0xf9, 0xd6, 0xfd, 27 | 0x45, 0x2d, 28 | ]; 29 | 30 | #[test] 31 | fn test_ct_parse_signed_certificate_timestamp_list() { 32 | let empty = &b""[..]; 33 | let res = parse_ct_signed_certificate_timestamp_list(SCT_LIST).unwrap(); 34 | let id1 = &[ 35 | 0xf6, 0x5c, 0x94, 0x2f, 0xd1, 0x77, 0x30, 0x22, 0x14, 0x54, 0x18, 0x08, 0x30, 0x94, 36 | 0x56, 0x8e, 0xe3, 0x4d, 0x13, 0x19, 0x33, 0xbf, 0xdf, 0x0c, 0x2f, 0x20, 0x0b, 0xcc, 37 | 0x4e, 0xf1, 0x64, 0xe3, 38 | ]; 39 | let id2 = &[ 40 | 0x5c, 0xdc, 0x43, 0x92, 0xfe, 0xe6, 0xab, 0x45, 0x44, 0xb1, 0x5e, 0x9a, 0xd4, 0x56, 41 | 0xe6, 0x10, 0x37, 0xfb, 0xd5, 0xfa, 0x47, 0xdc, 0xa1, 0x73, 0x94, 0xb2, 0x5e, 0xe6, 42 | 0xf6, 0xc7, 0x0e, 0xca, 43 | ]; 44 | let signature1 = DigitallySigned { 45 | alg: Some(SignatureAndHashAlgorithm { 46 | hash: HashAlgorithm::Sha256, 47 | sign: SignAlgorithm::Ecdsa, 48 | }), 49 | data: &[ 50 | 0x30, 0x45, 0x02, 0x21, 0x00, 0xc6, 0x2d, 0xa9, 0x45, 0xd2, 0x81, 0xfd, 0xda, 0x9f, 51 | 0xf3, 0xf8, 0xa4, 0x18, 0xb4, 0x4d, 0x2f, 0x7c, 0x23, 0x60, 0xb5, 0x6a, 0xb9, 0x51, 52 | 0x88, 0x9c, 0x38, 0x1b, 0x36, 0xf8, 0xa9, 0xf2, 0x1d, 0x02, 0x20, 0x00, 0xe2, 0xfc, 53 | 0xde, 0xbc, 0x91, 0x08, 0x29, 0x46, 0x86, 0x08, 0x89, 0x5b, 0x62, 0xd4, 0x45, 0x3e, 54 | 0x91, 0xdd, 0x39, 0x76, 0xb7, 0xa6, 0xe4, 0xae, 0xd4, 0xd2, 0x38, 0x50, 0xe9, 0xc7, 55 | 0xd0, 56 | ], 57 | }; 58 | let signature2 = DigitallySigned { 59 | alg: Some(SignatureAndHashAlgorithm { 60 | hash: HashAlgorithm::Sha256, 61 | sign: SignAlgorithm::Ecdsa, 62 | }), 63 | data: &[ 64 | 0x30, 0x45, 0x02, 0x20, 0x35, 0x6c, 0x91, 0x1f, 0xb3, 0x22, 0x79, 0xf2, 0x65, 0x95, 65 | 0x53, 0xcf, 0x3a, 0x36, 0xd7, 0xac, 0xde, 0xa1, 0xf4, 0xb1, 0xa9, 0x2e, 0xdd, 0x46, 66 | 0x0d, 0x96, 0xff, 0x1b, 0xda, 0x93, 0x4e, 0xe8, 0x02, 0x21, 0x00, 0xee, 0xed, 0x93, 67 | 0x37, 0xba, 0x12, 0xdb, 0x44, 0x67, 0x7e, 0x09, 0xa4, 0x5f, 0xd6, 0x66, 0x6c, 0x7b, 68 | 0x02, 0xb8, 0x63, 0x1f, 0xb6, 0xbf, 0x91, 0x53, 0x95, 0xb6, 0xf9, 0xd6, 0xfd, 0x45, 69 | 0x2d, 70 | ], 71 | }; 72 | assert_eq!( 73 | res, 74 | ( 75 | empty, 76 | vec![ 77 | SignedCertificateTimestamp { 78 | version: CtVersion::V1, 79 | id: CtLogID { key_id: id1 }, 80 | timestamp: 1590535362469, 81 | extensions: CtExtensions(empty), 82 | signature: signature1 83 | }, 84 | SignedCertificateTimestamp { 85 | version: CtVersion::V1, 86 | id: CtLogID { key_id: id2 }, 87 | timestamp: 1590535362464, 88 | extensions: CtExtensions(empty), 89 | signature: signature2 90 | } 91 | ] 92 | ) 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/tls_dh.rs: -------------------------------------------------------------------------------- 1 | mod tls_dh { 2 | use nom::sequence::pair; 3 | use tls_parser::*; 4 | 5 | #[rustfmt::skip] 6 | static ECDHE_PARAMS: &[u8] = &[ 7 | 0x03, 0x00, 0x19, 0x85, 0x04, 0x01, 0xd1, 0x50, 0x12, 0xf4, 0xc4, 0xcf, 0xd4, 0xc2, 0x1f, 0xe8, 8 | 0xf6, 0x85, 0xdc, 0xde, 0x0b, 0xeb, 0x3c, 0x0d, 0x0f, 0x97, 0x29, 0x36, 0x63, 0xc6, 0xc1, 0x3b, 9 | 0xfd, 0x38, 0xce, 0xde, 0x43, 0x7f, 0x7d, 0x57, 0x64, 0x54, 0x6f, 0x89, 0x3c, 0xe7, 0x5e, 0x28, 10 | 0x9e, 0x9d, 0x24, 0xca, 0x07, 0x63, 0xd5, 0x03, 0x30, 0x8b, 0xd8, 0x1a, 0xae, 0xb6, 0xa8, 0x5f, 11 | 0x10, 0x87, 0x81, 0x29, 0x1b, 0xef, 0xbd, 0x00, 0xeb, 0x29, 0x37, 0xb3, 0xc3, 0xda, 0x8e, 0xad, 12 | 0xf3, 0x9c, 0x10, 0xe3, 0x93, 0xeb, 0x0a, 0x53, 0x14, 0xea, 0x3c, 0x05, 0xb7, 0xc1, 0x6b, 0x79, 13 | 0xca, 0xfc, 0x9a, 0x5b, 0xc3, 0xaf, 0xf2, 0xdd, 0x9f, 0xdd, 0x07, 0xf5, 0x07, 0xef, 0xb4, 0x24, 14 | 0xac, 0xdb, 0xd2, 0x0d, 0x65, 0x37, 0x96, 0xa0, 0x15, 0xef, 0x7c, 0x6d, 0x66, 0x63, 0x0d, 0x41, 15 | 0x1d, 0xd7, 0x90, 0x05, 0x66, 0xcf, 0x79, 0x0c, 0x03, 0x02, 0x01, 0x01, 0x00, 0x7c, 0xa7, 0x5f, 16 | 0x73, 0x77, 0x2c, 0x92, 0x4c, 0xe4, 0xa7, 0x67, 0x86, 0x76, 0xf2, 0xa3, 0xf8, 0xd1, 0x9d, 0xca, 17 | 0x4f, 0x71, 0xd1, 0x67, 0xf4, 0xbe, 0x7e, 0xb3, 0x60, 0xc4, 0xf1, 0x6e, 0x90, 0x22, 0x97, 0xe9, 18 | 0xc2, 0x43, 0xc9, 0xfb, 0x46, 0x21, 0xd4, 0xe9, 0xed, 0xdc, 0x46, 0x5b, 0x3e, 0x4c, 0xfb, 0xf2, 19 | 0xeb, 0x3f, 0x09, 0x4e, 0x59, 0x5f, 0x6f, 0x60, 0x50, 0x8a, 0x80, 0x50, 0xa7, 0xc3, 0xb9, 0xf0, 20 | 0xd1, 0x80, 0xb0, 0x1b, 0x11, 0x53, 0xe4, 0xac, 0x45, 0xa8, 0x75, 0x59, 0x55, 0x1a, 0x20, 0xa5, 21 | 0xbb, 0x23, 0xb6, 0x1c, 0x39, 0xa8, 0x4e, 0x62, 0x57, 0xef, 0x4f, 0x11, 0xce, 0x64, 0x87, 0x9b, 22 | 0x5a, 0xb8, 0x06, 0xf1, 0x62, 0x63, 0x3d, 0x13, 0x46, 0x72, 0x79, 0x7e, 0x65, 0x5c, 0xb4, 0x0a, 23 | 0xe3, 0x63, 0x13, 0x05, 0xc9, 0xaa, 0xc3, 0x93, 0x9b, 0x69, 0x37, 0x04, 0xa6, 0x7b, 0x69, 0xa9, 24 | 0x72, 0x67, 0x32, 0x9d, 0xc9, 0x53, 0x05, 0xe5, 0x18, 0x00, 0x73, 0xcb, 0x40, 0xd8, 0x86, 0x81, 25 | 0x01, 0x78, 0x36, 0x8f, 0x62, 0x94, 0xb4, 0x88, 0x27, 0xdb, 0x8e, 0xe4, 0x76, 0x56, 0x1d, 0xac, 26 | 0x7d, 0x36, 0x4c, 0xb4, 0xad, 0x4c, 0xe0, 0x21, 0x1f, 0xd5, 0x2d, 0x30, 0xa0, 0x78, 0xba, 0x28, 27 | 0x0b, 0xb4, 0x6d, 0xf1, 0x95, 0x41, 0x11, 0xdb, 0x64, 0xaf, 0x11, 0xa2, 0x9b, 0x45, 0x07, 0x42, 28 | 0x95, 0xf1, 0xe4, 0x0a, 0x16, 0x0c, 0x7f, 0xa7, 0x96, 0xc1, 0x91, 0xf0, 0x7c, 0xf7, 0x67, 0xe6, 29 | 0x1c, 0xbd, 0x1d, 0xcb, 0xbc, 0x42, 0x2a, 0x47, 0x35, 0x28, 0x96, 0xc3, 0x08, 0x48, 0x7d, 0xe9, 30 | 0xf1, 0x42, 0x00, 0xee, 0xd5, 0x0e, 0xd4, 0x08, 0xd6, 0x34, 0x15, 0xd6, 0x7c, 0x4b, 0xc5, 0x23, 31 | 0xf4, 0x8c, 0xfa, 0x70, 0xd8, 0x60, 0x46, 0xd2, 0xa3, 0xba, 0x75, 0xa4, 0x8f 32 | ]; 33 | 34 | #[test] 35 | fn test_tls_ecdhe_params() { 36 | let empty = &b""[..]; 37 | let bytes = ECDHE_PARAMS; 38 | let point_data = &bytes[4..137]; 39 | let expected1 = ServerECDHParams { 40 | curve_params: ECParameters { 41 | curve_type: ECCurveType::NamedGroup, 42 | params_content: ECParametersContent::NamedGroup(NamedGroup::Secp521r1), 43 | }, 44 | public: ECPoint { point: point_data }, 45 | }; 46 | let expected2 = DigitallySigned { 47 | alg: Some(SignatureAndHashAlgorithm { 48 | hash: HashAlgorithm::Sha1, 49 | sign: SignAlgorithm::Rsa, 50 | }), 51 | data: &bytes[141..], 52 | }; 53 | let res = pair(parse_ecdh_params, parse_digitally_signed)(bytes); 54 | assert_eq!(res, Ok((empty, (expected1, expected2)))); 55 | } 56 | 57 | #[rustfmt::skip] 58 | static DHE_PARAMS: &[u8] = &[ 59 | 0x01, 0x00, 0xad, 0x10, 0x7e, 0x1e, 0x91, 0x23, 0xa9, 0xd0, 0xd6, 0x60, 0xfa, 0xa7, 0x95, 0x59, 60 | 0xc5, 0x1f, 0xa2, 0x0d, 0x64, 0xe5, 0x68, 0x3b, 0x9f, 0xd1, 0xb5, 0x4b, 0x15, 0x97, 0xb6, 0x1d, 61 | 0x0a, 0x75, 0xe6, 0xfa, 0x14, 0x1d, 0xf9, 0x5a, 0x56, 0xdb, 0xaf, 0x9a, 0x3c, 0x40, 0x7b, 0xa1, 62 | 0xdf, 0x15, 0xeb, 0x3d, 0x68, 0x8a, 0x30, 0x9c, 0x18, 0x0e, 0x1d, 0xe6, 0xb8, 0x5a, 0x12, 0x74, 63 | 0xa0, 0xa6, 0x6d, 0x3f, 0x81, 0x52, 0xad, 0x6a, 0xc2, 0x12, 0x90, 0x37, 0xc9, 0xed, 0xef, 0xda, 64 | 0x4d, 0xf8, 0xd9, 0x1e, 0x8f, 0xef, 0x55, 0xb7, 0x39, 0x4b, 0x7a, 0xd5, 0xb7, 0xd0, 0xb6, 0xc1, 65 | 0x22, 0x07, 0xc9, 0xf9, 0x8d, 0x11, 0xed, 0x34, 0xdb, 0xf6, 0xc6, 0xba, 0x0b, 0x2c, 0x8b, 0xbc, 66 | 0x27, 0xbe, 0x6a, 0x00, 0xe0, 0xa0, 0xb9, 0xc4, 0x97, 0x08, 0xb3, 0xbf, 0x8a, 0x31, 0x70, 0x91, 67 | 0x88, 0x36, 0x81, 0x28, 0x61, 0x30, 0xbc, 0x89, 0x85, 0xdb, 0x16, 0x02, 0xe7, 0x14, 0x41, 0x5d, 68 | 0x93, 0x30, 0x27, 0x82, 0x73, 0xc7, 0xde, 0x31, 0xef, 0xdc, 0x73, 0x10, 0xf7, 0x12, 0x1f, 0xd5, 69 | 0xa0, 0x74, 0x15, 0x98, 0x7d, 0x9a, 0xdc, 0x0a, 0x48, 0x6d, 0xcd, 0xf9, 0x3a, 0xcc, 0x44, 0x32, 70 | 0x83, 0x87, 0x31, 0x5d, 0x75, 0xe1, 0x98, 0xc6, 0x41, 0xa4, 0x80, 0xcd, 0x86, 0xa1, 0xb9, 0xe5, 71 | 0x87, 0xe8, 0xbe, 0x60, 0xe6, 0x9c, 0xc9, 0x28, 0xb2, 0xb9, 0xc5, 0x21, 0x72, 0xe4, 0x13, 0x04, 72 | 0x2e, 0x9b, 0x23, 0xf1, 0x0b, 0x0e, 0x16, 0xe7, 0x97, 0x63, 0xc9, 0xb5, 0x3d, 0xcf, 0x4b, 0xa8, 73 | 0x0a, 0x29, 0xe3, 0xfb, 0x73, 0xc1, 0x6b, 0x8e, 0x75, 0xb9, 0x7e, 0xf3, 0x63, 0xe2, 0xff, 0xa3, 74 | 0x1f, 0x71, 0xcf, 0x9d, 0xe5, 0x38, 0x4e, 0x71, 0xb8, 0x1c, 0x0a, 0xc4, 0xdf, 0xfe, 0x0c, 0x10, 75 | 0xe6, 0x4f, 0x01, 0x00, 0xac, 0x40, 0x32, 0xef, 0x4f, 0x2d, 0x9a, 0xe3, 0x9d, 0xf3, 0x0b, 0x5c, 76 | 0x8f, 0xfd, 0xac, 0x50, 0x6c, 0xde, 0xbe, 0x7b, 0x89, 0x99, 0x8c, 0xaf, 0x74, 0x86, 0x6a, 0x08, 77 | 0xcf, 0xe4, 0xff, 0xe3, 0xa6, 0x82, 0x4a, 0x4e, 0x10, 0xb9, 0xa6, 0xf0, 0xdd, 0x92, 0x1f, 0x01, 78 | 0xa7, 0x0c, 0x4a, 0xfa, 0xab, 0x73, 0x9d, 0x77, 0x00, 0xc2, 0x9f, 0x52, 0xc5, 0x7d, 0xb1, 0x7c, 79 | 0x62, 0x0a, 0x86, 0x52, 0xbe, 0x5e, 0x90, 0x01, 0xa8, 0xd6, 0x6a, 0xd7, 0xc1, 0x76, 0x69, 0x10, 80 | 0x19, 0x99, 0x02, 0x4a, 0xf4, 0xd0, 0x27, 0x27, 0x5a, 0xc1, 0x34, 0x8b, 0xb8, 0xa7, 0x62, 0xd0, 81 | 0x52, 0x1b, 0xc9, 0x8a, 0xe2, 0x47, 0x15, 0x04, 0x22, 0xea, 0x1e, 0xd4, 0x09, 0x93, 0x9d, 0x54, 82 | 0xda, 0x74, 0x60, 0xcd, 0xb5, 0xf6, 0xc6, 0xb2, 0x50, 0x71, 0x7c, 0xbe, 0xf1, 0x80, 0xeb, 0x34, 83 | 0x11, 0x8e, 0x98, 0xd1, 0x19, 0x52, 0x9a, 0x45, 0xd6, 0xf8, 0x34, 0x56, 0x6e, 0x30, 0x25, 0xe3, 84 | 0x16, 0xa3, 0x30, 0xef, 0xbb, 0x77, 0xa8, 0x6f, 0x0c, 0x1a, 0xb1, 0x5b, 0x05, 0x1a, 0xe3, 0xd4, 85 | 0x28, 0xc8, 0xf8, 0xac, 0xb7, 0x0a, 0x81, 0x37, 0x15, 0x0b, 0x8e, 0xeb, 0x10, 0xe1, 0x83, 0xed, 86 | 0xd1, 0x99, 0x63, 0xdd, 0xd9, 0xe2, 0x63, 0xe4, 0x77, 0x05, 0x89, 0xef, 0x6a, 0xa2, 0x1e, 0x7f, 87 | 0x5f, 0x2f, 0xf3, 0x81, 0xb5, 0x39, 0xcc, 0xe3, 0x40, 0x9d, 0x13, 0xcd, 0x56, 0x6a, 0xfb, 0xb4, 88 | 0x8d, 0x6c, 0x01, 0x91, 0x81, 0xe1, 0xbc, 0xfe, 0x94, 0xb3, 0x02, 0x69, 0xed, 0xfe, 0x72, 0xfe, 89 | 0x9b, 0x6a, 0xa4, 0xbd, 0x7b, 0x5a, 0x0f, 0x1c, 0x71, 0xcf, 0xff, 0x4c, 0x19, 0xc4, 0x18, 0xe1, 90 | 0xf6, 0xec, 0x01, 0x79, 0x81, 0xbc, 0x08, 0x7f, 0x2a, 0x70, 0x65, 0xb3, 0x84, 0xb8, 0x90, 0xd3, 91 | 0x19, 0x1f, 0x2b, 0xfa, 0x01, 0x00, 0x6f, 0x45, 0x37, 0xd8, 0xe5, 0x74, 0x44, 0xa4, 0xd0, 0xea, 92 | 0xa7, 0x37, 0x49, 0xb3, 0x06, 0xfa, 0xe6, 0x2e, 0x97, 0x18, 0x84, 0x13, 0x57, 0xd5, 0x5f, 0x87, 93 | 0xab, 0xc1, 0x4b, 0xc7, 0xac, 0x14, 0x52, 0x23, 0x1b, 0xb0, 0x64, 0xef, 0x0b, 0x52, 0x6d, 0x8f, 94 | 0xf3, 0x79, 0x1f, 0x91, 0xc0, 0x44, 0xd6, 0x07, 0xeb, 0x0a, 0xf2, 0x10, 0x96, 0xf1, 0x54, 0xa3, 95 | 0x11, 0x89, 0xb7, 0xdc, 0x87, 0xf1, 0x8f, 0x24, 0xb7, 0x08, 0xc5, 0xf4, 0x19, 0x67, 0x2c, 0x63, 96 | 0xde, 0xc3, 0x2d, 0x4e, 0xea, 0xbc, 0x4a, 0x55, 0x8a, 0x45, 0x7b, 0x57, 0xe2, 0x8a, 0xb1, 0x29, 97 | 0x0c, 0xcb, 0x41, 0xc3, 0xf0, 0x07, 0x85, 0x14, 0x35, 0x54, 0x35, 0x04, 0x69, 0xa4, 0x87, 0x9a, 98 | 0x97, 0x0f, 0x4f, 0xb8, 0x7c, 0x36, 0xa5, 0xd8, 0x89, 0xc8, 0x41, 0x62, 0xd8, 0x27, 0x30, 0xf5, 99 | 0x1b, 0x86, 0x46, 0xb6, 0x14, 0xa2, 0xea, 0x7c, 0xac, 0x62, 0xc1, 0xf8, 0x26, 0xf0, 0x63, 0x1e, 100 | 0x73, 0x74, 0xb2, 0xa0, 0x77, 0xd1, 0x2b, 0xeb, 0x79, 0x77, 0x65, 0xd9, 0x60, 0x50, 0x5d, 0x2b, 101 | 0x3b, 0x3c, 0xa2, 0xb5, 0x6a, 0x89, 0x2b, 0xb6, 0x92, 0xb0, 0x7e, 0x22, 0x03, 0xfe, 0xf5, 0x53, 102 | 0x90, 0x1f, 0x7d, 0xe9, 0x42, 0x82, 0xfa, 0x92, 0xb9, 0x7b, 0x15, 0xd4, 0xa6, 0x98, 0xe4, 0xf3, 103 | 0xee, 0xb6, 0xcc, 0xaa, 0xb2, 0xa6, 0xdf, 0x9a, 0xb9, 0x72, 0xcf, 0x5b, 0x76, 0x6b, 0xd5, 0x04, 104 | 0x98, 0x23, 0xa8, 0x29, 0xdf, 0x2a, 0xd9, 0xb7, 0x1e, 0xbc, 0xe3, 0x4a, 0x93, 0x08, 0x3a, 0xc4, 105 | 0x66, 0x1f, 0x2c, 0x94, 0x4d, 0x7a, 0xe6, 0xa9, 0x26, 0x40, 0x64, 0x2b, 0x31, 0xd7, 0xb5, 0x3b, 106 | 0x4e, 0xc5, 0x78, 0xe0, 0x90, 0x15, 0xaa, 0x95, 0x80, 0xc3, 0x22, 0x67, 0x5c, 0x21, 0x4c, 0xb7, 107 | 0xe3, 0x9f, 0xf2, 0xdb, 0x66, 0x97, 0x06, 0x01, 0x01, 0x00, 0x4e, 0x76, 0x32, 0x0e, 0xa2, 0xb2, 108 | 0x9f, 0x1c, 0xe0, 0x54, 0xff, 0x5c, 0xc7, 0xc4, 0x1b, 0xbc, 0x82, 0x8c, 0xfa, 0x05, 0xb7, 0xf3, 109 | 0x58, 0x0a, 0xa9, 0x12, 0x41, 0xb2, 0x4a, 0xfa, 0x3f, 0x0a, 0xc2, 0x30, 0xf3, 0xd7, 0x23, 0x28, 110 | 0xdf, 0x67, 0x51, 0x2f, 0x74, 0xef, 0x73, 0xe6, 0x9b, 0xf1, 0x2f, 0xe8, 0xda, 0x56, 0xba, 0x2a, 111 | 0x3f, 0xfe, 0x4c, 0xfb, 0x1c, 0xe5, 0xdd, 0x41, 0x1d, 0x5d, 0x20, 0xc1, 0x75, 0xc3, 0x62, 0x14, 112 | 0xa5, 0x32, 0x55, 0x2f, 0xfa, 0xf0, 0x8e, 0x9f, 0x95, 0x02, 0xfe, 0x15, 0x6f, 0x97, 0x18, 0xa1, 113 | 0x2f, 0xaf, 0xb0, 0x03, 0xcb, 0xac, 0x91, 0x36, 0x12, 0xd9, 0xea, 0x39, 0x9c, 0x40, 0xf5, 0xbd, 114 | 0x69, 0x9e, 0x2d, 0x12, 0xf7, 0x28, 0x18, 0x6e, 0x1b, 0x8d, 0x4c, 0x75, 0x40, 0x3e, 0xce, 0x5a, 115 | 0x8c, 0x24, 0x35, 0x34, 0x13, 0xa8, 0x7b, 0x12, 0x2f, 0x3f, 0x73, 0x48, 0x4f, 0x2a, 0xf0, 0x3b, 116 | 0xf5, 0xb0, 0x48, 0x39, 0x84, 0xd1, 0xc9, 0x78, 0x86, 0x78, 0xaf, 0x17, 0xf1, 0xc3, 0x08, 0x00, 117 | 0x1a, 0x16, 0x10, 0x85, 0xa3, 0x35, 0x58, 0xde, 0xae, 0xd3, 0x94, 0xb0, 0x66, 0x48, 0xbb, 0x4f, 118 | 0x58, 0x85, 0x55, 0x42, 0xee, 0xe3, 0x44, 0x97, 0x23, 0xb6, 0x70, 0xf6, 0xb1, 0x45, 0x1d, 0xe6, 119 | 0x0e, 0x63, 0x68, 0x37, 0x90, 0x42, 0xbe, 0x1c, 0xd3, 0x6d, 0x02, 0xc7, 0x47, 0x92, 0x5e, 0xd3, 120 | 0x62, 0xed, 0x3a, 0xa2, 0xdf, 0x98, 0xa6, 0x24, 0xb5, 0x17, 0x43, 0xb6, 0x6f, 0xcf, 0x70, 0x00, 121 | 0xb0, 0xf2, 0x18, 0xf6, 0xec, 0x7c, 0xed, 0x64, 0x36, 0x9b, 0x3a, 0xe2, 0x84, 0x74, 0xf8, 0xfa, 122 | 0x92, 0x40, 0xa0, 0xcd, 0x05, 0xc7, 0xd4, 0x80, 0x1d, 0x41, 0x03, 0xdb, 0x4d, 0x0e, 0xb7, 0x06, 123 | 0x7a, 0x52, 0xf7, 0x02, 0xa2, 0xb1, 0xbe, 0x76, 0xd7, 0x26 124 | ]; 125 | 126 | #[test] 127 | fn test_tls_dhe_params() { 128 | let empty = &b""[..]; 129 | let bytes = DHE_PARAMS; 130 | let expected1 = ServerDHParams { 131 | dh_p: &bytes[2..258], 132 | dh_g: &bytes[260..516], 133 | dh_ys: &bytes[518..774], 134 | }; 135 | let expected2 = DigitallySigned { 136 | alg: Some(SignatureAndHashAlgorithm { 137 | hash: HashAlgorithm::Sha512, 138 | sign: SignAlgorithm::Rsa, 139 | }), 140 | data: &bytes[778..], 141 | }; 142 | let res = pair(parse_dh_params, parse_digitally_signed)(bytes); 143 | assert_eq!(res, Ok((empty, (expected1, expected2)))); 144 | } 145 | } // mod tls_dh 146 | -------------------------------------------------------------------------------- /tests/tls_extensions.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate pretty_assertions; 3 | 4 | extern crate nom; 5 | extern crate tls_parser; 6 | 7 | mod tls_extensions { 8 | use tls_parser::*; 9 | 10 | #[rustfmt::skip] 11 | static CLIENT_EXTENSIONS1: &[u8] = &[ 12 | 0x00, 0x00, 0x00, 0x13, 0x00, 0x11, 0x00, 0x00, 0x0e, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6f, 0x6f, 13 | 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x0b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x02, 0x00, 14 | 0x0a, 0x00, 0x1c, 0x00, 0x1a, 0x00, 0x17, 0x00, 0x19, 0x00, 0x1c, 0x00, 0x1b, 0x00, 0x18, 0x00, 15 | 0x1a, 0x00, 0x16, 0x00, 0x0e, 0x00, 0x0d, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x09, 0x00, 0x0a, 0x00, 16 | 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 17 | 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 18 | 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x0f, 0x00, 0x01, 0x01 20 | ]; 21 | 22 | #[test] 23 | fn test_tls_extensions() { 24 | let empty = &b""[..]; 25 | let bytes = CLIENT_EXTENSIONS1; 26 | let ec_point_formats = &[0, 1, 2]; 27 | let ext1 = &[0, 0, 0, 0]; 28 | let ecc: Vec<_> = [23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10] 29 | .iter() 30 | .map(|&x| NamedGroup(x)) 31 | .collect(); 32 | let expected = Ok(( 33 | empty, 34 | vec![ 35 | TlsExtension::SNI(vec![(SNIType::HostName, b"www.google.com")]), 36 | TlsExtension::EcPointFormats(ec_point_formats), 37 | TlsExtension::EllipticCurves(ecc), 38 | TlsExtension::SessionTicket(empty), 39 | TlsExtension::SignatureAlgorithms(vec![ 40 | 0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402, 0x0403, 0x0301, 41 | 0x0302, 0x0303, 0x0201, 0x0202, 0x0203, 42 | ]), 43 | TlsExtension::StatusRequest(Some((CertificateStatusType::OCSP, ext1))), 44 | TlsExtension::Heartbeat(1), 45 | ], 46 | )); 47 | 48 | let res = parse_tls_extensions(bytes); 49 | 50 | assert_eq!(res, expected); 51 | } 52 | 53 | #[test] 54 | fn test_tls_extension_max_fragment_length() { 55 | let empty = &b""[..]; 56 | let bytes = &[0x00, 0x01, 0x00, 0x01, 0x04]; 57 | let expected = Ok((empty, TlsExtension::MaxFragmentLength(4))); 58 | 59 | let res = parse_tls_extension(bytes); 60 | 61 | assert_eq!(res, expected); 62 | } 63 | 64 | #[test] 65 | fn test_tls_extension_alpn() { 66 | let empty = &b""[..]; 67 | let bytes = &[ 68 | 0x00, 0x10, 0x00, 0x29, 0x00, 0x27, 0x05, 0x68, 0x32, 0x2d, 0x31, 0x36, 0x05, 0x68, 69 | 0x32, 0x2d, 0x31, 0x35, 0x05, 0x68, 0x32, 0x2d, 0x31, 0x34, 0x02, 0x68, 0x32, 0x08, 70 | 0x73, 0x70, 0x64, 0x79, 0x2f, 0x33, 0x2e, 0x31, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 71 | 0x31, 0x2e, 0x31, 72 | ]; 73 | let expected = Ok(( 74 | empty, 75 | TlsExtension::ALPN(vec![ 76 | b"h2-16", 77 | b"h2-15", 78 | b"h2-14", 79 | b"h2", 80 | b"spdy/3.1", 81 | b"http/1.1", 82 | ]), 83 | )); 84 | 85 | let res = parse_tls_extension(bytes); 86 | 87 | assert_eq!(res, expected); 88 | } 89 | 90 | #[test] 91 | fn test_tls_extension_encrypt_then_mac() { 92 | let empty = &b""[..]; 93 | let bytes = &[0x00, 0x16, 0x00, 0x00]; 94 | let expected = Ok((empty, TlsExtension::EncryptThenMac)); 95 | 96 | let res = parse_tls_extension(bytes); 97 | 98 | assert_eq!(res, expected); 99 | } 100 | 101 | #[test] 102 | fn test_tls_extension_extended_master_secret() { 103 | let empty = &b""[..]; 104 | let bytes = &[0x00, 0x17, 0x00, 0x00]; 105 | let expected = Ok((empty, TlsExtension::ExtendedMasterSecret)); 106 | 107 | let res = parse_tls_extension(bytes); 108 | 109 | assert_eq!(res, expected); 110 | } 111 | 112 | #[test] 113 | fn test_tls_extension_npn() { 114 | let empty = &b""[..]; 115 | let bytes = &[0x33, 0x74, 0x00, 0x00]; 116 | let expected = Ok((empty, TlsExtension::NextProtocolNegotiation)); 117 | 118 | let res = parse_tls_extension(bytes); 119 | 120 | assert_eq!(res, expected); 121 | } 122 | 123 | #[test] 124 | fn test_tls_extension_list() { 125 | let empty = &b""[..]; 126 | let bytes = &[0, 5, 0, 0, 0, 23, 0, 0, 255, 1, 0, 1, 0]; 127 | let expected = Ok(( 128 | empty, 129 | vec![ 130 | TlsExtension::StatusRequest(None), 131 | TlsExtension::ExtendedMasterSecret, 132 | TlsExtension::RenegotiationInfo(&[]), 133 | ], 134 | )); 135 | 136 | let res = parse_tls_extensions(bytes); 137 | println!("{:?}", res); 138 | 139 | assert_eq!(res, expected); 140 | } 141 | 142 | #[test] 143 | fn test_tls_extension_keyshare_helloretryrequest() { 144 | let empty = &b""[..]; 145 | let bytes = &[ 146 | 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0xa2, 0x4e, 0x84, 0xfa, 0x82, 0x63, 147 | 0xf8, 0xff, 0x20, 0x7a, 0x79, 0x82, 0xfd, 0x34, 0x12, 0xfc, 0xae, 0x8d, 0xd8, 0xe3, 148 | 0x1e, 0xf4, 0x5d, 0xe6, 0x61, 0x09, 0x3b, 0x7f, 0xa5, 0x81, 0x12, 0x63, 0x00, 0x2b, 149 | 0x00, 0x02, 0x7f, 0x17, 150 | ]; 151 | let expected = Ok(( 152 | empty, 153 | vec![ 154 | TlsExtension::KeyShare(&bytes[4..40]), 155 | TlsExtension::SupportedVersions(vec![TlsVersion(0x7f17)]), 156 | ], 157 | )); 158 | 159 | let res = parse_tls_extensions(bytes); 160 | assert_eq!(res, expected); 161 | } 162 | 163 | #[test] 164 | fn test_tls_extension_signed_certificate_timestamp() { 165 | let empty = &b""[..]; 166 | let bytes = &[0x00, 0x12, 0x00, 0x00]; 167 | let expected = Ok((empty, TlsExtension::SignedCertificateTimestamp(None))); 168 | 169 | let res = parse_tls_extension(bytes); 170 | 171 | assert_eq!(res, expected); 172 | } 173 | 174 | #[test] 175 | fn test_tls_extension_grease() { 176 | let empty = &b""[..]; 177 | let bytes = &[0x3a, 0x3a, 0x00, 0x01, 0x00]; 178 | let expected = TlsExtension::Grease(0x3a3a, &[0x00]); 179 | 180 | let res = parse_tls_extension(bytes); 181 | 182 | assert_eq!(res, Ok((empty, expected))); 183 | } 184 | 185 | const ESNI: &[u8] = include_bytes!("../assets/esni.bin"); 186 | 187 | #[test] 188 | fn test_tls_extension_esni() { 189 | let res = parse_tls_extension(ESNI).expect("Parsing eSNI failed"); 190 | match res.1 { 191 | TlsExtension::EncryptedServerName { 192 | ciphersuite, group, .. 193 | } => { 194 | assert_eq!(ciphersuite.0, 0x1301); 195 | assert_eq!(group.0, 0x1d); 196 | } 197 | _ => panic!("Wrong extension type (expected eSNI"), 198 | } 199 | } 200 | 201 | #[test] 202 | fn test_tls_extension_record_size_limit() { 203 | let empty = &b""[..]; 204 | let bytes = &[0x00, 0x1c, 0x00, 0x02, 0x40, 0x01]; 205 | let expected = TlsExtension::RecordSizeLimit(16385); 206 | let res = parse_tls_extension(bytes); 207 | assert_eq!(res, Ok((empty, expected))); 208 | } 209 | } // mod tls_extensions 210 | -------------------------------------------------------------------------------- /tests/tls_tls13.rs: -------------------------------------------------------------------------------- 1 | extern crate nom; 2 | extern crate tls_parser; 3 | 4 | mod tls_13 { 5 | use tls_parser::*; 6 | 7 | // Test vectors from https://tools.ietf.org/html/draft-thomson-tls-tls13-vectors-01 8 | #[rustfmt::skip] 9 | static TV_CLIENT_HELLO_1: &[u8] = &[ 10 | 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0xce, 0x05, 0xcf, 0xa3, 0xd9, 11 | 0x21, 0x70, 0xcb, 0xc2, 0x46, 0x5c, 0xdc, 0x3e, 0x3a, 0x2f, 0x57, 0x7f, 0x6e, 0xac, 0x80, 0x93, 12 | 0x61, 0x70, 0x8a, 0xb2, 0x44, 0xb0, 0x7d, 0x8f, 0xad, 0x86, 0x16, 0x00, 0x00, 0x3e, 0x13, 0x01, 13 | 0x13, 0x03, 0x13, 0x02, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x0a, 0xc0, 0x09, 14 | 0xc0, 0x13, 0xc0, 0x23, 0xc0, 0x27, 0xc0, 0x14, 0x00, 0x9e, 0xcc, 0xaa, 0x00, 0x33, 0x00, 0x32, 15 | 0x00, 0x67, 0x00, 0x39, 0x00, 0x38, 0x00, 0x6b, 0x00, 0x16, 0x00, 0x13, 0x00, 0x9c, 0x00, 0x2f, 16 | 0x00, 0x3c, 0x00, 0x35, 0x00, 0x3d, 0x00, 0x0a, 0x00, 0x05, 0x00, 0x04, 0x01, 0x00, 0x01, 0x95, 17 | 0x00, 0x15, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x0b, 0x00, 0x09, 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xff, 34 | 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x14, 0x00, 0x12, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 35 | 0x00, 0x19, 0x01, 0x00, 0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x04, 0x00, 0x0b, 0x00, 0x02, 36 | 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x28, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 37 | 0x2a, 0x98, 0x1d, 0xb6, 0xcd, 0xd0, 0x2a, 0x06, 0xc1, 0x76, 0x31, 0x02, 0xc9, 0xe7, 0x41, 0x36, 38 | 0x5a, 0xc4, 0xe6, 0xf7, 0x2b, 0x31, 0x76, 0xa6, 0xbd, 0x6a, 0x35, 0x23, 0xd3, 0xec, 0x0f, 0x4c, 39 | 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x12, 0x03, 0x03, 0x03, 0x02, 0x00, 0x0d, 0x00, 0x20, 0x00, 40 | 0x1e, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 41 | 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x01, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02, 0x02, 0x02, 0x00, 42 | 0x2d, 0x00, 0x02, 0x01, 0x01, 43 | ]; 44 | 45 | #[rustfmt::skip] 46 | static TV_SERVER_HELLO_1: &[u8] = &[ 47 | 0x16, 0x03, 0x01, 0x00, 0x52, 0x02, 0x00, 0x00, 0x4e, 0x7f, 0x12, 0x20, 0xb9, 0xc9, 0x20, 0x1c, 48 | 0xd1, 0x71, 0xa1, 0x5a, 0xbb, 0xa4, 0xe7, 0xed, 0xdc, 0xf3, 0xe8, 0x48, 0x8e, 0x71, 0x92, 0xff, 49 | 0xe0, 0x1e, 0xa5, 0xc1, 0x9f, 0x3d, 0x4b, 0x52, 0xff, 0xee, 0xbe, 0x13, 0x01, 0x00, 0x28, 0x00, 50 | 0x28, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9c, 0x1b, 0x0a, 0x74, 0x21, 0x91, 0x9a, 0x73, 0xcb, 51 | 0x57, 0xb3, 0xa0, 0xad, 0x9d, 0x68, 0x05, 0x86, 0x1a, 0x9c, 0x47, 0xe1, 0x1d, 0xf8, 0x63, 0x9d, 52 | 0x25, 0x32, 0x3b, 0x79, 0xce, 0x20, 0x1c, 53 | ]; 54 | 55 | #[test] 56 | fn test_tls13_ch() { 57 | let empty = &b""[..]; 58 | let bytes = TV_CLIENT_HELLO_1; 59 | let ciphers = &[ 60 | 0x1301, 0x1303, 0x1302, 0xc02b, 0xc02f, 0xcca9, 0xcca8, 0xc00a, 0xc009, 0xc013, 0xc023, 61 | 0xc027, 0xc014, 0x009e, 0xccaa, 0x0033, 0x0032, 0x0067, 0x0039, 0x0038, 0x006b, 0x0016, 62 | 0x0013, 0x009c, 0x002f, 0x003c, 0x0035, 0x003d, 0x000a, 0x0005, 0x0004, 63 | ]; 64 | let expected_ch = TlsPlaintext { 65 | hdr: TlsRecordHeader { 66 | record_type: TlsRecordType::Handshake, 67 | version: TlsVersion::Tls10, 68 | len: 512, 69 | }, 70 | msg: vec![TlsMessage::Handshake(TlsMessageHandshake::ClientHello( 71 | TlsClientHelloContents { 72 | version: TlsVersion::Tls12, 73 | random: &bytes[11..11 + 32], 74 | session_id: None, 75 | ciphers: ciphers.iter().map(|&x| TlsCipherSuiteID(x)).collect(), 76 | comp: vec![TlsCompressionID(0)], 77 | ext: Some(&bytes[112..]), 78 | }, 79 | ))], 80 | }; 81 | let ires = parse_tls_plaintext(bytes); 82 | assert_eq!(ires, Ok((empty, expected_ch))); 83 | } 84 | 85 | #[test] 86 | fn test_tls13_sh() { 87 | let empty = &b""[..]; 88 | let bytes = TV_SERVER_HELLO_1; 89 | let expected_sh = TlsPlaintext { 90 | hdr: TlsRecordHeader { 91 | record_type: TlsRecordType::Handshake, 92 | version: TlsVersion::Tls10, 93 | len: 82, 94 | }, 95 | msg: vec![TlsMessage::Handshake( 96 | TlsMessageHandshake::ServerHelloV13Draft18(TlsServerHelloV13Draft18Contents { 97 | version: TlsVersion::Tls13Draft18, 98 | random: &bytes[11..11 + 32], 99 | cipher: TlsCipherSuiteID(0x1301), 100 | ext: Some(&bytes[47..]), 101 | }), 102 | )], 103 | }; 104 | let expected_ext = vec![TlsExtension::KeyShareOld(&bytes[51..])]; 105 | let ires = parse_tls_plaintext(bytes); 106 | assert_eq!(ires, Ok((empty, expected_sh))); 107 | let res = ires.unwrap(); 108 | 109 | let msg = &res.1.msg[0]; 110 | let ext_raw = match *msg { 111 | TlsMessage::Handshake(TlsMessageHandshake::ServerHelloV13Draft18(ref sh)) => { 112 | sh.ext.unwrap() 113 | } 114 | _ => { 115 | panic!("Extensions parsing failed"); 116 | } 117 | }; 118 | let res_ext = parse_tls_extensions(ext_raw); 119 | assert_eq!(res_ext, Ok((empty, expected_ext))); 120 | } 121 | } // mod tls_13 122 | --------------------------------------------------------------------------------