diff --git a/Cargo.lock b/Cargo.lock index a93fbf8..cb26273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,24 @@ dependencies = [ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "c2-chacha" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crypto-simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-null 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "stream-cipher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cast" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cc" version = "1.0.35" @@ -308,6 +326,51 @@ dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "criterion" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "criterion-plot" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.7.1" @@ -317,6 +380,20 @@ dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-epoch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-epoch" version = "0.7.1" @@ -338,6 +415,14 @@ dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-utils" version = "0.6.5" @@ -347,6 +432,39 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crypto-simd" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "csv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv-core" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "curve25519-dalek" version = "1.1.3" @@ -497,6 +615,24 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hkdf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hmac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "humantime" version = "1.2.0" @@ -557,14 +693,17 @@ dependencies = [ "bloom-filter-rs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "c2-chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "config 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "cookie-factory 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "hkdf 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "i2p_ring 0.14.6 (git+https://github.com/str4d/ring.git?rev=7fe5a141201e4931ea2bbcd7eb3302fbf22867e9)", "i2p_snow 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -593,6 +732,7 @@ dependencies = [ "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "x25519-dalek 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "zip 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -604,6 +744,11 @@ dependencies = [ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -659,6 +804,9 @@ dependencies = [ name = "memchr" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "memoffset" @@ -886,6 +1034,22 @@ name = "podio" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ppv-lite86" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crypto-simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ppv-null" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crypto-simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pretty_assertions" version = "0.5.1" @@ -1045,6 +1209,36 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_xoshiro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -1137,6 +1331,19 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ryu" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "schannel" version = "0.1.15" @@ -1190,6 +1397,26 @@ name = "serde" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde_derive" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha1" version = "0.6.0" @@ -1270,11 +1497,24 @@ name = "static_slice" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "stream-cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "subtle" version = "2.0.0" @@ -1368,6 +1608,15 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tinytemplate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio" version = "0.1.18" @@ -1609,6 +1858,16 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "walkdir" +version = "2.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -1720,6 +1979,8 @@ dependencies = [ "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum c2-chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "72ee78b7bea3f0bbebde0217459bf4b878de7cd81606d9b70599cc2facf5d886" +"checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" "checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum chacha20-poly1305-aead 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77d2058ba29594f69c75e8a9018e0485e3914ca5084e3613cd64529042f5423b" @@ -1734,10 +1995,19 @@ dependencies = [ "checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0363053954f3e679645fc443321ca128b7b950a6fe288cf5f9335cc22ee58394" +"checksum criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76f9212ddf2f4a9eb2d401635190600656a1f88a932ef53d06e7fa4c7e02fb8e" +"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" +"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" +"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum crypto-simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28a0eee94b5af99ac4441823c99f59b1ef92a6a4b9723b4c6ad95e8cd4c994b2" +"checksum csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9044e25afb0924b5a5fc5511689b0918629e85d68ea591e5e87fbf1e85ea1b3b" +"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65" "checksum curve25519-dalek 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1f8a6fc0376eb52dc18af94915cc04dfdf8353746c0e8c550ae683a0815e5c1" "checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" @@ -1758,11 +2028,14 @@ dependencies = [ "checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum hkdf 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a89c4638cf4e02d9db29750659d2af13d9001b508716f77d4693ec8a1f8bda8" +"checksum hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f127a908633569f208325f86f71255d3363c79721d7f9fe31cd5569908819771" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum i2p_ring 0.14.6 (git+https://github.com/str4d/ring.git?rev=7fe5a141201e4931ea2bbcd7eb3302fbf22867e9)" = "" "checksum i2p_snow 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "744e750107c865af77dfe6489896d731e638b3c42f27e0d1276dc4e41ee46096" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" @@ -1796,6 +2069,8 @@ dependencies = [ "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" +"checksum ppv-lite86 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e173cf50e47caee6a3c831cc8fc0f3dc5a681eeb6194a77975e885fc40b602d2" +"checksum ppv-null 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "556fd3332bc060727be5959bf40d0ea8f3b657a5162328795eedcc50475f421e" "checksum pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" @@ -1813,6 +2088,9 @@ dependencies = [ "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929" +"checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" +"checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" @@ -1824,6 +2102,8 @@ dependencies = [ "checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum security-framework 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfab8dda0e7a327c696d893df9ffa19cadc4bd195797997f5223cf5831beaf05" @@ -1831,6 +2111,8 @@ dependencies = [ "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4" +"checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum signatory 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e282f6c031ca56210bb82a9aec3e5c1638345367cbab0208440e6a5a9493552f" @@ -1842,7 +2124,9 @@ dependencies = [ "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum static_slice 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "92a7e0c5e3dfb52e8fbe0e63a1b947bbb17b4036408b151353c4491374931362" +"checksum stream-cipher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8861bc80f649f5b4c9bd38b696ae9af74499d479dbfb327f0607de6b326a36bc" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926" "checksum subtle-encoding 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "83357cf892635c468b754d4bd3d99f904464d1d9d3eeb53fab92c5bc1ab82c04" "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" @@ -1853,6 +2137,7 @@ dependencies = [ "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7655088894274afb52b807bd3c87072daa1fedd155068b8705cabfd628956115" "checksum tokio 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "65641e515a437b308ab131a82ce3042ff9795bef5d6c5a9be4eb24195c417fd9" "checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" @@ -1878,6 +2163,7 @@ dependencies = [ "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index 9b1f74d..6554b96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ block-modes = "0.2" bloom-filter-rs = "0.1" byteorder = "1.2" bytes = "0.4" +c2-chacha = "0.1" chrono = "0.4" clap = { version = "2.32", optional = true } config = { version = "0.9", default-features = false, features = ["toml"] } @@ -26,6 +27,7 @@ data-encoding = "2.1" env_logger = { version = "0.6", optional = true } flate2 = "1.0" futures = "0.1" +hkdf = "0.7" i2p_ring = { git = "https://github.com/str4d/ring.git", rev = "7fe5a141201e4931ea2bbcd7eb3302fbf22867e9" } i2p_snow = "0.5.1" itertools = "0.8" @@ -52,9 +54,11 @@ tokio-threadpool = "0.1" tokio-timer = "0.2" tokio-tls = "0.2" untrusted = "0.6" +x25519-dalek = { version = "0.4", default-features = false, features = ["std", "u64_backend"] } zip = { version = "0.5", default-features = false, features = ["deflate"] } [dev-dependencies] +criterion = "0.2" pretty_assertions = "0.5" tempfile = "3" @@ -65,3 +69,7 @@ nightly = [] [[bin]] name = "ire" required-features = ["cli"] + +[[bench]] +name = "encls2_client_auth" +harness = false diff --git a/benches/encls2_client_auth.rs b/benches/encls2_client_auth.rs new file mode 100644 index 0000000..fa7dd40 --- /dev/null +++ b/benches/encls2_client_auth.rs @@ -0,0 +1,191 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use ire::{ + crypto::{SigningPrivateKey, SigningPublicKey}, + data::{ + dest::DestinationSecretKeys, + ls2::{ + enc::{ + auth::{ClientInfo, ClientSecretKey, PSKClientInfo, X25519ClientInfo}, + EncLS2Payload, EncryptedLS2, + }, + LeaseSet2, + }, + }, +}; +use rand::{thread_rng, Rng, RngCore}; +use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; + +fn fake_ls2(created: u32, expires: u16) -> LeaseSet2 { + let (dest, ls2_sigkey) = { + let dsk = DestinationSecretKeys::new(); + (dsk.dest, dsk.signing_private_key) + }; + + let mut ls2 = LeaseSet2::new(dest, created, expires, None); + ls2.sign(&ls2_sigkey).unwrap(); + ls2 +} + +fn server_x25519(c: &mut Criterion) { + let mut rng = thread_rng(); + let ls2 = fake_ls2(123_456_789, 2345); + + let payload = EncLS2Payload::LS2(ls2); + let credential = b"credential"; + + let blinded_privkey = SigningPrivateKey::new(); + let blinded_pubkey = SigningPublicKey::from_secret(&blinded_privkey).unwrap(); + + c.bench_function_over_inputs( + "EncLS2_Server_X25519", + move |b, &&count| { + let mut client_info = Vec::with_capacity(count); + for _ in 0..count { + let mut x25519_sk = [0u8; 32]; + rng.fill_bytes(&mut x25519_sk); + let x25519_pk = x25519(x25519_sk, X25519_BASEPOINT_BYTES); + client_info.push(X25519ClientInfo(x25519_pk)); + } + let client_info = ClientInfo::X25519(client_info); + + b.iter(|| { + EncryptedLS2::encrypt_payload( + &payload, + credential, + blinded_pubkey.clone(), + None, + &blinded_privkey, + Some(client_info.clone()), + ) + }) + }, + &[1, 10, 20, 50, 100], + ); +} + +fn client_x25519(c: &mut Criterion) { + let mut rng = thread_rng(); + let ls2 = fake_ls2(123_456_789, 2345); + + let payload = EncLS2Payload::LS2(ls2); + let credential = b"credential"; + + let blinded_privkey = SigningPrivateKey::new(); + let blinded_pubkey = SigningPublicKey::from_secret(&blinded_privkey).unwrap(); + + c.bench_function_over_inputs( + "EncLS2_Client_X25519", + move |b, &&count| { + let mut auth_keys = Vec::with_capacity(count); + let mut client_info = Vec::with_capacity(count); + for _ in 0..count { + let mut x25519_sk = [0u8; 32]; + rng.fill_bytes(&mut x25519_sk); + let x25519_pk = x25519(x25519_sk, X25519_BASEPOINT_BYTES); + + auth_keys.push(ClientSecretKey::X25519(x25519_sk)); + client_info.push(X25519ClientInfo(x25519_pk)); + } + let client_info = ClientInfo::X25519(client_info); + + let enc_ls2 = EncryptedLS2::encrypt_payload( + &payload, + credential, + blinded_pubkey.clone(), + None, + &blinded_privkey, + Some(client_info.clone()), + ) + .unwrap(); + + b.iter(|| enc_ls2.decrypt(credential, Some(&auth_keys.last().unwrap()))) + }, + &[1, 10, 20, 50, 100], + ); +} + +fn server_psk(c: &mut Criterion) { + let mut rng = thread_rng(); + let ls2 = fake_ls2(123_456_789, 2345); + + let payload = EncLS2Payload::LS2(ls2); + let credential = b"credential"; + + let blinded_privkey = SigningPrivateKey::new(); + let blinded_pubkey = SigningPublicKey::from_secret(&blinded_privkey).unwrap(); + + c.bench_function_over_inputs( + "EncLS2_Server_PSK", + move |b, &&count| { + let mut client_info = Vec::with_capacity(count); + for _ in 0..count { + let mut psk = [0; 32]; + rng.fill(&mut psk[..]); + client_info.push(PSKClientInfo(psk)); + } + let client_info = ClientInfo::PSK(client_info); + + b.iter(|| { + EncryptedLS2::encrypt_payload( + &payload, + credential, + blinded_pubkey.clone(), + None, + &blinded_privkey, + Some(client_info.clone()), + ) + }) + }, + &[1, 10, 20, 50, 100], + ); +} + +fn client_psk(c: &mut Criterion) { + let mut rng = thread_rng(); + let ls2 = fake_ls2(123_456_789, 2345); + + let payload = EncLS2Payload::LS2(ls2); + let credential = b"credential"; + + let blinded_privkey = SigningPrivateKey::new(); + let blinded_pubkey = SigningPublicKey::from_secret(&blinded_privkey).unwrap(); + + c.bench_function_over_inputs( + "EncLS2_Client_PSK", + move |b, &&count| { + let mut auth_keys = Vec::with_capacity(count); + let mut client_info = Vec::with_capacity(count); + for _ in 0..count { + let mut psk = [0; 32]; + rng.fill(&mut psk[..]); + let mut client_psk = [0; 32]; + client_psk.copy_from_slice(&psk[..]); + auth_keys.push(ClientSecretKey::PSK(client_psk)); + client_info.push(PSKClientInfo(psk)); + } + let client_info = ClientInfo::PSK(client_info); + + let enc_ls2 = EncryptedLS2::encrypt_payload( + &payload, + credential, + blinded_pubkey.clone(), + None, + &blinded_privkey, + Some(client_info.clone()), + ) + .unwrap(); + + b.iter(|| enc_ls2.decrypt(credential, Some(&auth_keys.last().unwrap()))) + }, + &[1, 10, 20, 50, 100], + ); +} + +criterion_group!( + benches, + server_x25519, + client_x25519, + server_psk, + client_psk +); +criterion_main!(benches); diff --git a/src/constants.rs b/src/constants.rs index 062c858..863c8e9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -86,3 +86,10 @@ pub const HIDDEN_CERT: u8 = 2; pub const SIGNED_CERT: u8 = 3; pub const MULTI_CERT: u8 = 4; pub const KEY_CERT: u8 = 5; + +// NetDB store types +pub const NETDB_STORE_RI: u8 = 0; +pub const NETDB_STORE_LS: u8 = 1; +pub const NETDB_STORE_LS2: u8 = 3; +pub const NETDB_STORE_ENC_LS2: u8 = 5; +pub const NETDB_STORE_META_LS2: u8 = 7; diff --git a/src/crypto/frame.rs b/src/crypto/frame.rs index 5a17944..0887edf 100644 --- a/src/crypto/frame.rs +++ b/src/crypto/frame.rs @@ -3,7 +3,7 @@ use nom::*; use crate::constants; use crate::crypto::{ - EncType, PrivateKey, PublicKey, SessionKey, SigType, Signature, SigningPrivateKey, + CryptoKey, EncType, PrivateKey, PublicKey, SessionKey, SigType, Signature, SigningPrivateKey, SigningPublicKey, }; @@ -55,6 +55,37 @@ pub fn gen_session_key<'a>( // Key material and signatures // +// CryptoKey + +named!( + pub crypto_key, + switch!(be_u16, + constants::ELGAMAL2048 => do_parse!( + tag!(b"\x01\x00") >> pubkey: public_key >> (CryptoKey::ElGamalAES(pubkey)) + ) | + crypto_type => do_parse!( + data: length_data!(be_u16) + >> (CryptoKey::Unsupported(crypto_type, data.to_vec())) + ) + ) +); + +pub fn gen_crypto_key<'a>( + input: (&'a mut [u8], usize), + crypto_key: &CryptoKey, +) -> Result<(&'a mut [u8], usize), GenError> { + match crypto_key { + CryptoKey::ElGamalAES(pubkey) => do_gen!( + input, + gen_be_u16!(constants::ELGAMAL2048) >> gen_be_u16!(256) >> gen_public_key(pubkey) + ), + CryptoKey::Unsupported(crypto_type, data) => do_gen!( + input, + gen_be_u16!(*crypto_type) >> gen_be_u16!(data.len()) >> gen_slice!(data) + ), + } +} + // PublicKey named!(pub public_key, do_parse!( diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index ca7d934..75b3914 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -28,7 +28,7 @@ pub(crate) mod frame; pub(crate) mod dh; mod dsa; -pub(crate) mod elgamal; +pub mod elgamal; pub(crate) mod math; pub(crate) const AES_BLOCK_SIZE: usize = 16; @@ -40,6 +40,7 @@ pub enum Error { InvalidKey, InvalidMessage, InvalidSignature, + KeyExpired, NoSignature, SigningFailed, TypeMismatch, @@ -53,6 +54,7 @@ impl fmt::Display for Error { Error::InvalidKey => "Invalid cryptographic key".fmt(f), Error::InvalidMessage => "Invalid message".fmt(f), Error::InvalidSignature => "Bad signature".fmt(f), + Error::KeyExpired => "Key expired".fmt(f), Error::NoSignature => "No signature".fmt(f), Error::SigningFailed => "Failed to create a signature".fmt(f), Error::TypeMismatch => "Signature type doesn't match key type".fmt(f), @@ -156,7 +158,7 @@ impl SigType { } } -/// Various encryption algorithms present on the network. +/// Field in a RouterInfo or Destination KeyCertificate. #[derive(Clone, Copy, Debug, PartialEq)] pub enum EncType { ElGamal2048, @@ -188,6 +190,27 @@ impl EncType { // Key material and signatures // +/// Key material for initiating various end-to-end encryption algorithms. +#[derive(Clone, Debug)] +pub enum CryptoKey { + ElGamalAES(PublicKey), + Unsupported(u16, Vec), +} + +pub enum CryptoSecretKey { + ElGamalAES(PrivateKey), +} + +impl CryptoSecretKey { + pub fn new_keypair() -> (Self, CryptoKey) { + let (privkey, pubkey) = elgamal::KeyPairGenerator::generate(); + ( + CryptoSecretKey::ElGamalAES(privkey), + CryptoKey::ElGamalAES(pubkey), + ) + } +} + /// The public component of an ElGamal encryption keypair. Represents only the /// exponent, not the primes (which are constants). pub struct PublicKey(pub [u8; 256]); diff --git a/src/data/dest.rs b/src/data/dest.rs index 2089f04..6bd11a1 100644 --- a/src/data/dest.rs +++ b/src/data/dest.rs @@ -11,7 +11,7 @@ pub(crate) mod frame; /// A Destination defines a particular endpoint to which messages can be /// directed for secure delivery. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Destination { pub(super) public_key: PublicKey, pub(super) padding: Option, diff --git a/src/data/dest/frame.rs b/src/data/dest/frame.rs index 43ce45c..6f9d39f 100644 --- a/src/data/dest/frame.rs +++ b/src/data/dest/frame.rs @@ -15,7 +15,7 @@ use crate::data::frame::{ #[cfg_attr(rustfmt, rustfmt_skip)] named!( - destination, + pub destination, do_parse!( public_key: public_key >> signing_data: take!(constants::KEYCERT_SIGKEY_BYTES) >> diff --git a/src/data/ls2.rs b/src/data/ls2.rs new file mode 100644 index 0000000..6c09722 --- /dev/null +++ b/src/data/ls2.rs @@ -0,0 +1,434 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use super::{Destination, Hash, Mapping, TunnelId}; +use crate::constants::{ + NETDB_STORE_ENC_LS2, NETDB_STORE_LS, NETDB_STORE_LS2, NETDB_STORE_META_LS2, +}; +use crate::crypto::{self, CryptoKey, Signature, SigningPrivateKey, SigningPublicKey}; +use crate::util::serialize; + +pub mod enc; + +#[allow(clippy::needless_pass_by_value)] +pub(crate) mod frame; + +/// A transient signing key that has been authorized by another signing key to +/// sign on its behalf. Used when a keyholder wants to keep their actual signing +/// key offline. +#[derive(Clone, Debug, PartialEq)] +pub struct TransientSigningPublicKey { + expires: u32, + pubkey: SigningPublicKey, + signature: Signature, +} + +impl TransientSigningPublicKey { + /// Create a new TransientSigningPublicKey (and its corresponding private key), + /// bound to the given parent SigningPrivateKey. + pub fn new(parent: &SigningPrivateKey, expires: u32) -> (Self, SigningPrivateKey) { + let privkey = SigningPrivateKey::new(); + let mut transient = TransientSigningPublicKey { + expires, + pubkey: SigningPublicKey::from_secret(&privkey).unwrap(), + signature: Signature::Unsupported(vec![]), + }; + + let transient_bytes = + serialize(|input| frame::gen_transient_key_sig_bytes(input, &transient)); + transient.signature = parent.sign(&transient_bytes).unwrap(); + + (transient, privkey) + } + + /// Verify the TransientSigningPublicKey against the given parent SigningPublicKey. + pub fn verify(&self, parent: &SigningPublicKey) -> Result<(), crypto::Error> { + let transient_bytes = serialize(|input| frame::gen_transient_key_sig_bytes(input, self)); + parent.verify(&transient_bytes, &self.signature) + } +} + +/// The standard header used for LeaseSet2 and MetaLeaseSet2. +#[derive(Clone, Debug)] +pub struct LeaseSet2Header { + pub dest: Destination, + created: u32, + expires: u16, + transient: Option, + published: bool, +} + +impl LeaseSet2Header { + // Helper to check that a SigningPrivateKey matches this header. + fn check_signing_private_key(&self, sk: &SigningPrivateKey) -> Result<(), crypto::Error> { + let vk = SigningPublicKey::from_secret(sk)?; + if &vk + == if let Some(transient) = &self.transient { + &transient.pubkey + } else { + &self.dest.signing_key + } + { + Ok(()) + } else { + Err(crypto::Error::InvalidKey) + } + } + + /// Helper for verifying a structure containing a LeaseSet2Header. + fn verify(&self, sig_bytes: &[u8], signature: &Signature) -> Result<(), crypto::Error> { + if let Some(transient) = &self.transient { + // Verify the transient pubkey + transient.verify(&self.dest.signing_key)?; + + // Transient key must have an expiry before the LeaseSet2 was created + if transient.expires >= self.created { + return Err(crypto::Error::KeyExpired); + } + + // Now verify the LeaseSet2 signature + transient.pubkey.verify(&sig_bytes, signature) + } else { + self.dest.signing_key.verify(&sig_bytes, signature) + } + } + + /// Returns true if the LeaseSet2Header has not expired. + fn is_current(&self) -> bool { + // Expiry time is the earliest of the transient key expiry time (if present) and + // the header expiry time. + let header_expires = u64::from(self.created) + u64::from(self.expires); + let expires = if let Some(transient) = &self.transient { + std::cmp::min(header_expires, u64::from(transient.expires)) + } else { + header_expires + }; + + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_else(|_| Duration::new(0, 0)) + .as_secs(); + + now < expires + } +} + +/// Defines the authorization for a particular tunnel to receive messages +/// targeting a Destination. Semantically identical to a Lease, except that +/// it has a 4-byte expiration (seconds since the epoch). +#[derive(Clone, Debug)] +pub struct Lease2 { + tunnel_gw: Hash, + tid: TunnelId, + end_date: u32, +} + +impl Lease2 { + pub fn new(tunnel_gw: Hash, tid: TunnelId, end_date: u32) -> Self { + Lease2 { + tunnel_gw, + tid, + end_date, + } + } +} + +/// A v2 LeaseSet. Contains a list of supported end-to-end cryptography types +/// (with corresponding key material). +#[derive(Clone, Debug)] +pub struct LeaseSet2 { + pub header: LeaseSet2Header, + properties: Mapping, + enc_keys: Vec, + leases: Vec, + signature: Option, +} + +impl LeaseSet2 { + /// Create a new LeaseSet2 for the given Destination. If transient is set, + /// the LeaseSet2 is created with offline keys. + pub fn new( + dest: Destination, + created: u32, + expires: u16, + transient: Option, + ) -> Self { + LeaseSet2 { + header: LeaseSet2Header { + dest, + created, + expires, + transient, + published: false, + }, + properties: Mapping::default(), + enc_keys: vec![], + leases: vec![], + signature: None, + } + } + + /// Add an encryption key to the LeaseSet2, advertising that peers may use the + /// corresponding CryptoType to communicate with this Destination. + pub fn add_key(&mut self, enc_key: CryptoKey) { + self.enc_keys.push(enc_key); + } + + /// Add a lease to the LeaseSet2. + pub fn add_lease(&mut self, lease: Lease2) { + self.leases.push(lease); + } + + /// Sign the LeaseSet2. The SigningPrivateKey must match the transient key if set, + /// otherwise it must match the Destination. + pub fn sign(&mut self, sk: &SigningPrivateKey) -> Result<(), crypto::Error> { + // Check that the SigningPrivateKey is correct + self.header.check_signing_private_key(sk)?; + + // Create the signature + let sig_bytes = serialize(|input| frame::gen_lease_set_2_sig_bytes(input, self)); + self.signature = Some(sk.sign(&sig_bytes)?); + Ok(()) + } + + /// Verify the LeaseSet2. + pub fn verify(&self) -> Result<(), crypto::Error> { + match self.signature.as_ref() { + Some(s) => { + let sig_bytes = serialize(|input| frame::gen_lease_set_2_sig_bytes(input, self)); + self.header.verify(&sig_bytes, s) + } + None => Err(crypto::Error::NoSignature), + } + } + + /// Returns true if the LeaseSet2 has not expired. + pub fn is_current(&self) -> bool { + self.header.is_current() + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MetaEntryType { + Unknown, + LeaseSet, + LeaseSet2, + EncryptedLS2, + MetaLeaseSet2, +} + +impl MetaEntryType { + fn from_type(entry_type: u8) -> Self { + match entry_type { + NETDB_STORE_LS => MetaEntryType::LeaseSet, + NETDB_STORE_LS2 => MetaEntryType::LeaseSet2, + NETDB_STORE_ENC_LS2 => MetaEntryType::EncryptedLS2, + NETDB_STORE_META_LS2 => MetaEntryType::MetaLeaseSet2, + _ => MetaEntryType::Unknown, + } + } + + fn to_type(self) -> u8 { + match self { + MetaEntryType::Unknown => 0, + MetaEntryType::LeaseSet => NETDB_STORE_LS, + MetaEntryType::LeaseSet2 => NETDB_STORE_LS2, + MetaEntryType::EncryptedLS2 => NETDB_STORE_ENC_LS2, + MetaEntryType::MetaLeaseSet2 => NETDB_STORE_META_LS2, + } + } +} + +/// A reference to another LeaseSet, LeaseSet2, or MetaLeaseSet2. +#[derive(Clone)] +pub struct MetaEntry { + hash: Hash, + entry_type: MetaEntryType, + cost: u8, + expires: u32, +} + +/// The root of a tree, the leaves of which are LeaseSet or LeaseSet2. +#[derive(Clone)] +pub struct MetaLeaseSet2 { + header: LeaseSet2Header, + properties: Mapping, + entries: Vec, + revocations: Vec, + signature: Option, +} + +impl MetaLeaseSet2 { + /// Create a new MetaLeaseSet2 for the given Destination. If transient is set, + /// the MetaLeaseSet2 is created with offline keys. + pub fn new( + dest: Destination, + created: u32, + expires: u16, + transient: Option, + ) -> Self { + MetaLeaseSet2 { + header: LeaseSet2Header { + dest, + created, + expires, + transient, + published: false, + }, + properties: Mapping::default(), + entries: vec![], + revocations: vec![], + signature: None, + } + } + + /// Add an entry to the MetaLeaseSet2. + pub fn add_entry(&mut self, entry: MetaEntry) { + self.entries.push(entry); + } + + /// Add a revocation to the MetaLeaseSet2. + pub fn add_revocation(&mut self, revocation: Hash) { + self.revocations.push(revocation); + } + + /// Sign the MetaLeaseSet2. The SigningPrivateKey must match the transient key if set, + /// otherwise it must match the Destination. + pub fn sign(&mut self, sk: &SigningPrivateKey) -> Result<(), crypto::Error> { + // Check that the SigningPrivateKey is correct + self.header.check_signing_private_key(sk)?; + + // Create the signature + let sig_bytes = serialize(|input| frame::gen_meta_ls2_sig_bytes(input, self)); + self.signature = Some(sk.sign(&sig_bytes)?); + Ok(()) + } + + /// Verify the MetaLeaseSet2. + pub fn verify(&self) -> Result<(), crypto::Error> { + match self.signature.as_ref() { + Some(s) => { + let sig_bytes = serialize(|input| frame::gen_meta_ls2_sig_bytes(input, self)); + self.header.verify(&sig_bytes, s) + } + None => Err(crypto::Error::NoSignature), + } + } + + /// Returns true if the MetaLeaseSet2 has not expired. + pub fn is_current(&self) -> bool { + self.header.is_current() + } +} + +#[cfg(test)] +mod tests { + use super::{LeaseSet2, MetaLeaseSet2, TransientSigningPublicKey}; + use crate::{ + crypto::{self, SigningPrivateKey}, + data::dest::DestinationSecretKeys, + }; + + #[test] + fn ls2_sign_verify() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let mut ls2 = LeaseSet2::new(dest, 0, 0, None); + ls2.sign(&dsk.signing_private_key).unwrap(); + assert_eq!(ls2.verify(), Ok(())); + } + + #[test] + fn ls2_sign_invalid_key() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let mut ls2 = LeaseSet2::new(dest, 0, 0, None); + assert_eq!( + ls2.sign(&SigningPrivateKey::new()), + Err(crypto::Error::InvalidKey) + ); + } + + #[test] + fn transient_ls2_sign_verify() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let (transient, t_sk) = TransientSigningPublicKey::new(&dsk.signing_private_key, 10); + let mut ls2 = LeaseSet2::new(dest, 20, 0, Some(transient)); + ls2.sign(&t_sk).unwrap(); + assert_eq!(ls2.verify(), Ok(())); + } + + #[test] + fn transient_ls2_sign_invalid_key() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let (transient, _) = TransientSigningPublicKey::new(&dsk.signing_private_key, 10); + let mut ls2 = LeaseSet2::new(dest, 20, 0, Some(transient)); + assert_eq!( + ls2.sign(&SigningPrivateKey::new()), + Err(crypto::Error::InvalidKey) + ); + } + + #[test] + fn transient_ls2_sign_expired() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let (transient, t_sk) = TransientSigningPublicKey::new(&dsk.signing_private_key, 10); + let mut ls2 = LeaseSet2::new(dest, 10, 0, Some(transient)); + ls2.sign(&t_sk).unwrap(); + assert_eq!(ls2.verify(), Err(crypto::Error::KeyExpired)); + } + + #[test] + fn meta_ls2_sign_verify() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let mut meta_ls2 = MetaLeaseSet2::new(dest, 0, 0, None); + meta_ls2.sign(&dsk.signing_private_key).unwrap(); + assert_eq!(meta_ls2.verify(), Ok(())); + } + + #[test] + fn meta_ls2_sign_invalid_key() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let mut meta_ls2 = MetaLeaseSet2::new(dest, 0, 0, None); + assert_eq!( + meta_ls2.sign(&SigningPrivateKey::new()), + Err(crypto::Error::InvalidKey) + ); + } + + #[test] + fn transient_meta_ls2_sign_verify() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let (transient, t_sk) = TransientSigningPublicKey::new(&dsk.signing_private_key, 10); + let mut meta_ls2 = MetaLeaseSet2::new(dest, 20, 0, Some(transient)); + meta_ls2.sign(&t_sk).unwrap(); + assert_eq!(meta_ls2.verify(), Ok(())); + } + + #[test] + fn transient_meta_ls2_sign_invalid_key() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let (transient, _) = TransientSigningPublicKey::new(&dsk.signing_private_key, 10); + let mut meta_ls2 = MetaLeaseSet2::new(dest, 20, 0, Some(transient)); + assert_eq!( + meta_ls2.sign(&SigningPrivateKey::new()), + Err(crypto::Error::InvalidKey) + ); + } + + #[test] + fn transient_meta_ls2_sign_expired() { + let dsk = DestinationSecretKeys::new(); + let dest = dsk.dest; + let (transient, t_sk) = TransientSigningPublicKey::new(&dsk.signing_private_key, 10); + let mut meta_ls2 = MetaLeaseSet2::new(dest, 10, 0, Some(transient)); + meta_ls2.sign(&t_sk).unwrap(); + assert_eq!(meta_ls2.verify(), Err(crypto::Error::KeyExpired)); + } +} diff --git a/src/data/ls2/enc.rs b/src/data/ls2/enc.rs new file mode 100644 index 0000000..428fcbb --- /dev/null +++ b/src/data/ls2/enc.rs @@ -0,0 +1,456 @@ +//! Encryption mechanism for lease information in the network database. +//! +//! Blinding is used to enforce that only clients with knowledge of the +//! Destination can decrypt the lease information. + +use byteorder::{BigEndian, WriteBytesExt}; +use c2_chacha::{ + stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek}, + Ietf as ChaCha20Ietf, +}; +use hkdf::Hkdf; +use rand::{rngs::OsRng, Rng}; +use sha2::{Digest, Sha256}; +use std::fmt; + +use super::{LeaseSet2, MetaLeaseSet2, TransientSigningPublicKey}; +use crate::crypto::{self, Signature, SigningPrivateKey, SigningPublicKey}; +use crate::util::serialize; + +pub mod auth; +pub(crate) mod frame; + +const SALT_LEN: usize = 16; +const S_KEY_LEN: usize = 32; +const S_IV_LEN: usize = 12; + +const LAYER_1_INFO: &[u8; 8] = b"ELS2_L1K"; +const LAYER_2_INFO: &[u8; 8] = b"ELS2_L2K"; + +/// Network database store errors +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + Crypto(crypto::Error), + InvalidClientAuth, + InvalidPayload, + NotAuthorised, +} + +impl From for Error { + fn from(e: crypto::Error) -> Self { + Error::Crypto(e) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Crypto(e) => e.fmt(f), + Error::InvalidClientAuth => "Client authentication layer is invalid".fmt(f), + Error::InvalidPayload => "Payload is invalid".fmt(f), + Error::NotAuthorised => "Not authorised to decrypt EncLS2".fmt(f), + } + } +} + +fn kdf( + secret_value: &[u8], + subcredential: &[u8], + created: u32, + salt: &[u8], + info: &[u8], + okm: &mut [u8], +) { + let mut ikm = Vec::new(); + ikm.extend_from_slice(secret_value); + ikm.extend_from_slice(subcredential); + (&mut ikm).write_u32::(created).unwrap(); + + Hkdf::::extract(Some(&salt), &ikm) + .expand(info, okm) + .unwrap(); +} + +fn stream_cipher(key: &[u8], iv: &[u8], input: &[u8], output: &mut [u8]) { + assert_eq!(input.len(), output.len()); + output.copy_from_slice(input); + let mut chacha20 = ChaCha20Ietf::new_var(key, iv).unwrap(); + // Skip the first block of output + chacha20.seek(64); + chacha20.apply_keystream(output); +} + +struct Encryptor; + +impl Encryptor { + fn encrypt( + auth_cookie: &[u8], + subcredential: &[u8], + created: u32, + info: &[u8], + plaintext: &[u8], + ) -> Vec { + assert!(!plaintext.is_empty()); + let mut ciphertext = Vec::new(); + ciphertext.resize(plaintext.len() + SALT_LEN, 0); + + // Generate a random salt, placing it at the start of the ciphertext + let mut rng = OsRng::new().unwrap(); + rng.fill(&mut ciphertext[0..SALT_LEN]); + + // Derive the encryption key and IV + let mut okm = [0; S_KEY_LEN + S_IV_LEN]; + kdf( + auth_cookie, + subcredential, + created, + &ciphertext[0..SALT_LEN], + info, + &mut okm, + ); + let key = &okm[0..S_KEY_LEN]; + let iv = &okm[S_KEY_LEN..S_KEY_LEN + S_IV_LEN]; + + // Encrypt the plaintext into the ciphertext buffer, following the salt + stream_cipher(key, iv, plaintext, &mut ciphertext[SALT_LEN..]); + ciphertext + } + + fn decrypt( + auth_cookie: &[u8], + subcredential: &[u8], + created: u32, + info: &[u8], + ciphertext: &[u8], + ) -> Vec { + // Slice the salt out of the ciphertext + assert!(ciphertext.len() > SALT_LEN); + let salt = &ciphertext[0..SALT_LEN]; + + // Derive the encryption key and IV + let mut okm = [0; S_KEY_LEN + S_IV_LEN]; + kdf(auth_cookie, subcredential, created, salt, info, &mut okm); + let key = &okm[0..S_KEY_LEN]; + let iv = &okm[S_KEY_LEN..S_KEY_LEN + S_IV_LEN]; + + // Decrypt the plaintext from the second half of the ciphertext + let mut plaintext = Vec::new(); + plaintext.resize(ciphertext.len() - SALT_LEN, 0); + stream_cipher(key, iv, &ciphertext[SALT_LEN..], &mut plaintext); + plaintext + } +} + +/// The inner payload of an encrypted LS2. +pub enum EncLS2Payload { + LS2(LeaseSet2), + MetaLS2(MetaLeaseSet2), +} + +impl EncLS2Payload { + fn decrypt( + auth_cookie: &[u8], + subcredential: &[u8], + created: u32, + ciphertext: &[u8], + ) -> Result { + let plaintext = Encryptor::decrypt( + auth_cookie, + subcredential, + created, + LAYER_2_INFO, + ciphertext, + ); + match frame::enc_ls2_payload(&plaintext) { + Ok((_, ret)) => Ok(ret), + Err(_) => Err(Error::InvalidPayload), + } + } + + fn encrypt(&self, auth_cookie: &[u8], subcredential: &[u8], created: u32) -> Vec { + let plaintext = serialize(|input| frame::gen_enc_ls2_payload(input, self)); + Encryptor::encrypt( + auth_cookie, + subcredential, + created, + LAYER_2_INFO, + &plaintext, + ) + } +} + +/// Client authentication layer inside an encrypted LS2. +#[derive(Debug, PartialEq)] +pub(crate) struct EncLS2ClientAuth { + auth_data: Option, + inner_ciphertext: Vec, +} + +impl EncLS2ClientAuth { + fn decrypt(subcredential: &[u8], created: u32, ciphertext: &[u8]) -> Result { + let plaintext = Encryptor::decrypt(&[], subcredential, created, LAYER_1_INFO, ciphertext); + match frame::enc_ls2_client_auth(&plaintext) { + Ok((_, ret)) => Ok(ret), + Err(_) => Err(Error::InvalidClientAuth), + } + } + + fn encrypt(&self, subcredential: &[u8], created: u32) -> Vec { + let plaintext = serialize(|input| frame::gen_enc_ls2_client_auth(input, self)); + Encryptor::encrypt(&[], subcredential, created, LAYER_1_INFO, &plaintext) + } +} + +/// Encrypted and blinded lease information. +pub struct EncryptedLS2 { + blinded_key: SigningPublicKey, + created: u32, + expires: u16, + transient: Option, + outer_ciphertext: Vec, + signature: Option, +} + +impl EncryptedLS2 { + pub fn encrypt_payload( + payload: &EncLS2Payload, + credential: &[u8], + blinded_key: SigningPublicKey, + transient: Option, + sig_key: &SigningPrivateKey, + client_info: Option, + ) -> Result { + // Signing key must correctly match either the blinded key or the transient key + if let Some(ref transient) = transient { + assert_eq!(SigningPublicKey::from_secret(&sig_key)?, transient.pubkey); + } else { + assert_eq!(SigningPublicKey::from_secret(&sig_key)?, blinded_key); + } + + // Outer timestamp and expiration must match payload + let (created, expires) = match payload { + EncLS2Payload::LS2(ls2) => (ls2.header.created, ls2.header.expires), + EncLS2Payload::MetaLS2(meta_ls2) => (meta_ls2.header.created, meta_ls2.header.expires), + }; + + // Compute subcredential + // TODO: Should `blinded_key.as_bytes()` include the type? + let subcredential = Sha256::default() + .chain(b"subcredential") + .chain(credential) + .chain(blinded_key.as_bytes()) + .result(); + + // Handle client authentication + let (auth_cookie, auth_data) = + auth::ClientAuthType::from_info(client_info, subcredential.as_slice(), created); + + // Encrypt layer 2 + let inner_ciphertext = payload.encrypt(&auth_cookie, subcredential.as_slice(), created); + + // Create layer 1 + let client_auth = EncLS2ClientAuth { + auth_data, + inner_ciphertext, + }; + + // Encrypt layer 1 + let outer_ciphertext = client_auth.encrypt(subcredential.as_slice(), created); + + // Create layer 0 + let mut enc_ls2 = EncryptedLS2 { + blinded_key, + created, + expires, + transient, + outer_ciphertext, + signature: None, + }; + let msg = serialize(|input| frame::gen_encrypted_ls2_signed_msg(input, &enc_ls2)); + enc_ls2.signature = Some(sig_key.sign(&msg)?); + + Ok(enc_ls2) + } + + pub fn decrypt( + &self, + credential: &[u8], + auth_key: Option<&auth::ClientSecretKey>, + ) -> Result { + // Always validate the signature first, to ensure ciphertext is unmodified. + if let Some(ref signature) = self.signature { + let msg = serialize(|input| frame::gen_encrypted_ls2_signed_msg(input, self)); + if let Some(ref transient) = self.transient { + transient.pubkey.verify(&msg, signature)?; + } else { + self.blinded_key.verify(&msg, signature)?; + } + } else { + return Err(Error::Crypto(crypto::Error::NoSignature)); + } + + // Compute subcredential + let subcredential = Sha256::default() + .chain(b"subcredential") + .chain(credential) + .chain(self.blinded_key.as_bytes()) + .result(); + + // Decrypt layer 1 + let client_auth = EncLS2ClientAuth::decrypt( + subcredential.as_slice(), + self.created, + &self.outer_ciphertext, + )?; + + // Handle client authentication + let auth_cookie = match (client_auth.auth_data, auth_key) { + (Some(auth_data), Some(key)) => { + auth_data.authenticate(key, subcredential.as_slice(), self.created)? + } + (Some(_), None) => return Err(Error::NotAuthorised), + (None, _) => vec![], + }; + + // Decrypt layer 2 + EncLS2Payload::decrypt( + &auth_cookie, + subcredential.as_slice(), + self.created, + &client_auth.inner_ciphertext, + ) + } +} + +#[cfg(test)] +mod tests { + use rand::{thread_rng, RngCore}; + use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; + + use super::{ + auth::{ClientInfo, ClientSecretKey, X25519ClientInfo}, + EncLS2Payload, EncryptedLS2, Encryptor, + }; + use crate::crypto::{SigningPrivateKey, SigningPublicKey}; + use crate::data::{dest::DestinationSecretKeys, ls2::LeaseSet2}; + + #[test] + fn encryptor_round_trip() { + let subcredential = b"subcredential"; + let auth_cookie = b"auth_cookie"; + let info = b"info"; + let plaintext = b"plaintext"; + + let ciphertext = Encryptor::encrypt(subcredential, auth_cookie, 0, info, plaintext); + + assert_eq!( + Encryptor::decrypt(subcredential, auth_cookie, 0, info, &ciphertext), + plaintext + ); + } + + fn fake_ls2(created: u32, expires: u16) -> LeaseSet2 { + let (dest, ls2_sigkey) = { + let dsk = DestinationSecretKeys::new(); + (dsk.dest, dsk.signing_private_key) + }; + + let mut ls2 = LeaseSet2::new(dest, created, expires, None); + ls2.sign(&ls2_sigkey).unwrap(); + ls2 + } + + #[test] + fn enc_ls2_round_trip() { + let ls2 = fake_ls2(123_456_789, 2345); + + let payload = EncLS2Payload::LS2(ls2); + let credential = b"credential"; + + let blinded_privkey = SigningPrivateKey::new(); + let blinded_pubkey = SigningPublicKey::from_secret(&blinded_privkey).unwrap(); + + // Encrypt the payload with client authorization disabled + let enc_ls2 = EncryptedLS2::encrypt_payload( + &payload, + credential, + blinded_pubkey, + None, + &blinded_privkey, + None, + ) + .unwrap(); + + // Can decrypt without any client key + match enc_ls2.decrypt(credential, None).unwrap() { + EncLS2Payload::LS2(decrypted_ls2) => { + assert_eq!(decrypted_ls2.header.created, 123_456_789); + assert_eq!(decrypted_ls2.header.expires, 2345); + assert!(decrypted_ls2.header.transient.is_none()); + assert!(!decrypted_ls2.header.published); + assert!(decrypted_ls2.enc_keys.is_empty()); + assert!(decrypted_ls2.leases.is_empty()); + assert!(decrypted_ls2.signature.is_some()); + } + _ => panic!(), + } + + // Decryption ignores a client key (e.g. if the Destination turns off client auth) + assert!(enc_ls2 + .decrypt(credential, Some(&ClientSecretKey::X25519([0; 32]))) + .is_ok()); + } + + #[test] + fn enc_ls2_client_auth_round_trip() { + let mut rng = thread_rng(); + let ls2 = fake_ls2(123_456_789, 2345); + + let payload = EncLS2Payload::LS2(ls2); + let credential = b"credential"; + + let blinded_privkey = SigningPrivateKey::new(); + let blinded_pubkey = SigningPublicKey::from_secret(&blinded_privkey).unwrap(); + + let mut x25519_sk = [0u8; 32]; + rng.fill_bytes(&mut x25519_sk[..]); + let x25519_pk = x25519(x25519_sk, X25519_BASEPOINT_BYTES); + + let auth_key = ClientSecretKey::X25519(x25519_sk); + let client_info = ClientInfo::X25519(vec![X25519ClientInfo(x25519_pk)]); + + // Encrypt the payload with a list of authorized clients + let enc_ls2 = EncryptedLS2::encrypt_payload( + &payload, + credential, + blinded_pubkey, + None, + &blinded_privkey, + Some(client_info), + ) + .unwrap(); + + // Cannot decrypt without a valid client key + assert!(enc_ls2.decrypt(credential, None).is_err()); + + // Cannot decrypt with an unauthorized client key + let mut x25519_sk2 = [0u8; 32]; + rng.fill_bytes(&mut x25519_sk2[..]); + let unauthorized = ClientSecretKey::X25519(x25519_sk2); + assert!(enc_ls2.decrypt(credential, Some(&unauthorized)).is_err()); + + // Can decrypt with the correct client key + match enc_ls2.decrypt(credential, Some(&auth_key)).unwrap() { + EncLS2Payload::LS2(decrypted_ls2) => { + assert_eq!(decrypted_ls2.header.created, 123_456_789); + assert_eq!(decrypted_ls2.header.expires, 2345); + assert!(decrypted_ls2.header.transient.is_none()); + assert!(!decrypted_ls2.header.published); + assert!(decrypted_ls2.enc_keys.is_empty()); + assert!(decrypted_ls2.leases.is_empty()); + assert!(decrypted_ls2.signature.is_some()); + } + _ => panic!(), + } + } +} diff --git a/src/data/ls2/enc/auth.rs b/src/data/ls2/enc/auth.rs new file mode 100644 index 0000000..740b706 --- /dev/null +++ b/src/data/ls2/enc/auth.rs @@ -0,0 +1,186 @@ +use rand::{rngs::OsRng, RngCore}; +use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; + +use super::{kdf, stream_cipher, Error, S_IV_LEN, S_KEY_LEN}; + +pub(super) const AUTH_ID_LEN: usize = 8; +pub(super) const AUTH_COOKIE_LEN: usize = 32; + +const X25519_AUTH_INFO: &[u8; 8] = b"ELS2_XCA"; + +#[derive(Clone)] +pub struct X25519ClientInfo(pub [u8; 32]); + +#[derive(Clone)] +pub struct PSKClientInfo(pub [u8; 32]); + +/// Client information +#[derive(Clone)] +pub enum ClientInfo { + X25519(Vec), + PSK(Vec), +} + +// Client's secret authentication key +pub enum ClientSecretKey { + X25519([u8; 32]), + PSK([u8; 32]), +} + +/// Per-client authentication data. +#[derive(Debug, PartialEq)] +pub(super) struct ClientAuthData { + pub(super) client_id: [u8; AUTH_ID_LEN], + pub(super) client_cookie: [u8; AUTH_COOKIE_LEN], +} + +/// Supported types of client authentication. +#[derive(Debug, PartialEq)] +pub(super) enum ClientAuthType { + X25519([u8; 32], Vec), + PSK([u8; 32], Vec), +} + +impl ClientAuthType { + pub(super) fn from_info( + client_info: Option, + subcredential: &[u8], + created: u32, + ) -> (Vec, Option) { + let mut rng = OsRng::new().unwrap(); + + macro_rules! base_auth { + ($clients:ident, $gen_auth_seed:expr, $gen_secret_value:expr, $salt:expr, $auth_type:ident) => {{ + let mut auth_cookie = vec![]; + auth_cookie.resize(AUTH_COOKIE_LEN, 0); + rng.fill_bytes(&mut auth_cookie[..]); + + let auth_seed = $gen_auth_seed; + + let auth_data = $clients + .into_iter() + .map(|client| { + let mut okm = [0; S_KEY_LEN + S_IV_LEN + AUTH_ID_LEN]; + kdf( + &$gen_secret_value(auth_seed, client.0), + subcredential, + created, + &$salt(auth_seed), + X25519_AUTH_INFO, + &mut okm, + ); + + let client_key = &okm[0..S_KEY_LEN]; + let client_iv = &okm[S_KEY_LEN..S_KEY_LEN + S_IV_LEN]; + + let mut client_id = [0; AUTH_ID_LEN]; + client_id.copy_from_slice( + &okm[S_KEY_LEN + S_IV_LEN..S_KEY_LEN + S_IV_LEN + AUTH_ID_LEN], + ); + + let mut client_cookie = [0; AUTH_COOKIE_LEN]; + stream_cipher(client_key, client_iv, &auth_cookie, &mut client_cookie); + + ClientAuthData { + client_id, + client_cookie, + } + }) + .collect(); + + ( + auth_cookie, + Some(ClientAuthType::$auth_type($salt(auth_seed), auth_data)), + ) + }}; + } + + match client_info { + Some(ClientInfo::X25519(clients)) => base_auth!( + clients, + { + let mut esk = [0u8; 32]; + rng.fill_bytes(&mut esk[..]); + let epk = x25519(esk, X25519_BASEPOINT_BYTES); + (esk, epk) + }, + |(esk, _), client_info| x25519(esk, client_info), + |(_, epk)| epk, + X25519 + ), + Some(ClientInfo::PSK(clients)) => base_auth!( + clients, + { + let mut auth_salt = [0; 32]; + rng.fill_bytes(&mut auth_salt[..]); + auth_salt + }, + |_, client_info| client_info, + |auth_salt| auth_salt, + PSK + ), + None => (vec![], None), + } + } + + /// Returns the authCookie if the client is authorized, or an error otherwise. + pub(super) fn authenticate( + &self, + key: &ClientSecretKey, + subcredential: &[u8], + created: u32, + ) -> Result, Error> { + macro_rules! base_auth { + ($secret_value:expr, $salt:expr, $auth_data:ident) => {{ + let mut okm = [0; S_KEY_LEN + S_IV_LEN + AUTH_ID_LEN]; + kdf( + $secret_value, + subcredential, + created, + $salt, + X25519_AUTH_INFO, + &mut okm, + ); + let client_key = &okm[0..S_KEY_LEN]; + let client_iv = &okm[S_KEY_LEN..S_KEY_LEN + S_IV_LEN]; + let client_id = &okm[S_KEY_LEN + S_IV_LEN..S_KEY_LEN + S_IV_LEN + AUTH_ID_LEN]; + + // Scan the list of clients to find ourselves + match $auth_data + .iter() + .filter_map(|data| { + if data.client_id == client_id { + // We are on the list! Get dat cookie :) + let mut auth_cookie = vec![]; + auth_cookie.resize(AUTH_COOKIE_LEN, 0); + stream_cipher( + client_key, + client_iv, + &data.client_cookie, + &mut auth_cookie, + ); + Some(auth_cookie) + } else { + None + } + }) + .next() + { + Some(auth_cookie) => Ok(auth_cookie), + None => Err(Error::NotAuthorised), + } + }}; + } + + match (self, key) { + (ClientAuthType::X25519(epk, auth_data), ClientSecretKey::X25519(sk)) => { + let shared_secret = x25519(*sk, *epk); + base_auth!(&shared_secret, &epk[..], auth_data) + } + (ClientAuthType::PSK(auth_salt, auth_data), ClientSecretKey::PSK(sk)) => { + base_auth!(&sk[..], &auth_salt[..], auth_data) + } + _ => Err(Error::NotAuthorised), + } + } +} diff --git a/src/data/ls2/enc/frame.rs b/src/data/ls2/enc/frame.rs new file mode 100644 index 0000000..a8acd3e --- /dev/null +++ b/src/data/ls2/enc/frame.rs @@ -0,0 +1,350 @@ +use cookie_factory::*; +use nom::*; + +use super::{ + auth::{ClientAuthData, ClientAuthType, AUTH_COOKIE_LEN, AUTH_ID_LEN}, + EncLS2ClientAuth, EncLS2Payload, EncryptedLS2, +}; +use crate::constants::{NETDB_STORE_ENC_LS2, NETDB_STORE_LS2, NETDB_STORE_META_LS2}; +use crate::crypto::frame::{ + gen_sig_type, gen_signature, gen_signing_key, sig_type, signature, signing_key, +}; +use crate::data::ls2::frame::{ + gen_lease_set_2, gen_meta_ls2, gen_transient_key, lease_set_2, meta_ls2, transient_key, +}; + +// Layer 2: payload + +named!( + pub(crate) enc_ls2_payload, + switch!(be_u8, + NETDB_STORE_LS2 => do_parse!(ls2: lease_set_2 >> (EncLS2Payload::LS2(ls2))) | + NETDB_STORE_META_LS2 => do_parse!(meta_ls2: meta_ls2 >> (EncLS2Payload::MetaLS2(meta_ls2))) + ) +); + +pub(crate) fn gen_enc_ls2_payload<'a>( + input: (&'a mut [u8], usize), + payload: &EncLS2Payload, +) -> Result<(&'a mut [u8], usize), GenError> { + match payload { + EncLS2Payload::LS2(ls2) => { + do_gen!(input, gen_be_u8!(NETDB_STORE_LS2) >> gen_lease_set_2(ls2)) + } + EncLS2Payload::MetaLS2(meta_ls2) => do_gen!( + input, + gen_be_u8!(NETDB_STORE_META_LS2) >> gen_meta_ls2(meta_ls2) + ), + } +} + +// Layer 1: client authentication + +named!( + client_auth_data, + do_parse!( + client_id: take!(AUTH_ID_LEN) + >> client_cookie: take!(AUTH_COOKIE_LEN) + >> ({ + let client_id = { + let mut tmp = [0; AUTH_ID_LEN]; + tmp.copy_from_slice(client_id); + tmp + }; + let client_cookie = { + let mut tmp = [0; AUTH_COOKIE_LEN]; + tmp.copy_from_slice(client_cookie); + tmp + }; + ClientAuthData { + client_id, + client_cookie, + } + }) + ) +); + +fn gen_client_auth_data<'a>( + input: (&'a mut [u8], usize), + auth_data: &ClientAuthData, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_slice!(auth_data.client_id) >> gen_slice!(auth_data.client_cookie) + ) +} + +struct ClientAuthFlags { + per_client: bool, + auth_type: u8, +} + +impl ClientAuthFlags { + fn none() -> Self { + ClientAuthFlags { + per_client: false, + auth_type: 0, + } + } + + fn with_type(auth_type: u8) -> Self { + ClientAuthFlags { + per_client: true, + auth_type, + } + } +} + +fn gen_client_auth_flags<'a>( + input: (&'a mut [u8], usize), + flags: &ClientAuthFlags, +) -> Result<(&'a mut [u8], usize), GenError> { + let mut x: u8 = 0; + if flags.per_client { + x |= 0b0001; + x |= (flags.auth_type << 1) & 0b1110; + } + gen_be_u8!(input, x) +} + +pub fn take_all<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { + let (res, i) = input.split_at(input.len()); + Ok((i, res)) +} + +named!( + pub(crate) enc_ls2_client_auth, + do_parse!( + flags: + bits!(do_parse!( + take_bits!(u8, 4) + >> auth_type: take_bits!(u8, 3) + >> per_client: take_bits!(u8, 1) + >> (ClientAuthFlags { + per_client: per_client > 0, + auth_type, + }) + )) + >> auth_data: + cond!( + flags.per_client, + switch!(value!(flags.auth_type), + 0 => do_parse!( + epk: take!(32) >> + entries: length_count!(be_u16, client_auth_data) >> + (ClientAuthType::X25519({ + let mut point = [0; 32]; + point.copy_from_slice(epk); + point + }, entries)) + ) | + 1 => do_parse!( + auth_salt: take!(32) >> + entries: length_count!(be_u16, client_auth_data) >> + (ClientAuthType::PSK({ + let mut salt = [0; 32]; + salt.copy_from_slice(auth_salt); + salt + }, entries)) + ) + ) + ) + >> inner_ciphertext: call!(take_all) + >> (EncLS2ClientAuth { + auth_data, + inner_ciphertext: inner_ciphertext.to_vec(), + }) + ) +); + +pub(crate) fn gen_enc_ls2_client_auth<'a>( + input: (&'a mut [u8], usize), + client_auth: &EncLS2ClientAuth, +) -> Result<(&'a mut [u8], usize), GenError> { + match client_auth.auth_data.as_ref() { + None => do_gen!( + input, + gen_client_auth_flags(&ClientAuthFlags::none()) + >> gen_slice!(client_auth.inner_ciphertext) + ), + Some(ClientAuthType::X25519(epk, auth_data)) => do_gen!( + input, + gen_client_auth_flags(&ClientAuthFlags::with_type(0)) + >> gen_slice!(epk.as_bytes()) + >> gen_be_u16!(auth_data.len()) + >> gen_many!(&auth_data, gen_client_auth_data) + >> gen_slice!(client_auth.inner_ciphertext) + ), + Some(ClientAuthType::PSK(auth_salt, auth_data)) => do_gen!( + input, + gen_client_auth_flags(&ClientAuthFlags::with_type(1)) + >> gen_slice!(&auth_salt) + >> gen_be_u16!(auth_data.len()) + >> gen_many!(&auth_data, gen_client_auth_data) + >> gen_slice!(client_auth.inner_ciphertext) + ), + } +} + +// Layer 0: plaintext information + +struct EncLS2Flags { + offline: bool, +} + +named!( + pub encrypted_ls2, + do_parse!( + blinded_sig_type: sig_type + >> blinded_key: call!(signing_key, blinded_sig_type) + >> created: be_u32 + >> expires: be_u16 + >> flags: + bits!(do_parse!( + take_bits!(u16, 15) + >> offline: take_bits!(u8, 1) + >> (EncLS2Flags { + offline: offline > 0, + }) + )) + >> transient: cond!(flags.offline, call!(transient_key, blinded_sig_type)) + >> outer_ciphertext: length_data!(be_u16) + >> signature: + call!( + signature, + if let Some(transient) = transient.as_ref() { + transient.pubkey.sig_type() + } else { + blinded_sig_type + } + ) + >> (EncryptedLS2 { + blinded_key, + created, + expires, + transient, + outer_ciphertext: outer_ciphertext.to_vec(), + signature: Some(signature), + }) + ) +); + +fn gen_encrypted_ls2_minus_sig<'a>( + input: (&'a mut [u8], usize), + enc_ls2: &EncryptedLS2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_sig_type(enc_ls2.blinded_key.sig_type()) + >> gen_signing_key(&enc_ls2.blinded_key) + >> gen_be_u32!(enc_ls2.created) + >> gen_be_u16!(enc_ls2.expires) + >> gen_be_u16!({ + let mut x: u16 = 0; + if enc_ls2.transient.is_some() { + x |= 0b01; + } + x + }) + >> gen_cond!( + enc_ls2.transient.is_some(), + do_gen!(gen_transient_key(enc_ls2.transient.as_ref().unwrap())) + ) + >> gen_be_u16!(enc_ls2.outer_ciphertext.len()) + >> gen_slice!(enc_ls2.outer_ciphertext) + ) +} + +pub fn gen_encrypted_ls2_signed_msg<'a>( + input: (&'a mut [u8], usize), + enc_ls2: &EncryptedLS2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_be_u8!(NETDB_STORE_ENC_LS2) >> gen_encrypted_ls2_minus_sig(enc_ls2) + ) +} + +pub fn gen_encrypted_ls2<'a>( + input: (&'a mut [u8], usize), + enc_ls2: &EncryptedLS2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_encrypted_ls2_minus_sig(enc_ls2) >> gen_signature(enc_ls2.signature.as_ref().unwrap()) + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! bake_and_eat { + ($oven:expr, $monster:expr, $value:expr, $expected:expr) => { + let mut res = vec![]; + res.resize($expected.len(), 0); + match $oven((&mut res, 0), &$value) { + Ok(_) => assert_eq!(&res, &$expected), + Err(e) => panic!("Unexpected error: {:?}", e), + } + match $monster(&res) { + Ok((_, m)) => assert_eq!(m, $value), + Err(e) => panic!("Unexpected error: {:?}", e), + } + }; + } + + #[test] + fn test_enc_ls2_client_auth() { + macro_rules! eval { + ($value:expr, $expected:expr) => { + bake_and_eat!( + gen_enc_ls2_client_auth, + enc_ls2_client_auth, + $value, + $expected + ) + }; + } + + eval!( + EncLS2ClientAuth { + auth_data: None, + inner_ciphertext: vec![], + }, + [0x00] + ); + + eval!( + EncLS2ClientAuth { + auth_data: None, + inner_ciphertext: vec![1, 2, 3, 4], + }, + [0x00, 0x01, 0x02, 0x03, 0x04] + ); + + eval!( + EncLS2ClientAuth { + auth_data: Some(ClientAuthType::X25519([0xff; 32], vec![])), + inner_ciphertext: vec![1, 2, 3, 4], + }, + &[ + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04 + ][..] + ); + + eval!( + EncLS2ClientAuth { + auth_data: Some(ClientAuthType::PSK([0xff; 32], vec![])), + inner_ciphertext: vec![1, 2, 3, 4], + }, + &[ + 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04 + ][..] + ); + } +} diff --git a/src/data/ls2/frame.rs b/src/data/ls2/frame.rs new file mode 100644 index 0000000..1d2a91e --- /dev/null +++ b/src/data/ls2/frame.rs @@ -0,0 +1,307 @@ +use cookie_factory::*; +use nom::*; + +use super::{ + Lease2, LeaseSet2, LeaseSet2Header, MetaEntry, MetaEntryType, MetaLeaseSet2, + TransientSigningPublicKey, +}; +use crate::constants::{NETDB_STORE_LS2, NETDB_STORE_META_LS2}; +use crate::crypto::{ + frame::{ + crypto_key, gen_crypto_key, gen_sig_type, gen_signature, gen_signing_key, sig_type, + signature, signing_key, + }, + SigType, +}; +use crate::data::{ + dest::frame::{destination, gen_destination}, + frame::{gen_hash, gen_mapping, gen_tunnel_id, hash, mapping, tunnel_id}, +}; + +// TransientSigningPublicKey + +named_args!( + pub transient_key(parent_sig_type: SigType), + do_parse!( + expires: be_u32 + >> sig_type: sig_type + >> pubkey: call!(signing_key, sig_type) + >> signature: call!(signature, parent_sig_type) + >> (TransientSigningPublicKey { + expires, + pubkey, + signature, + }) + ) +); + +pub fn gen_transient_key_sig_bytes<'a>( + input: (&'a mut [u8], usize), + transient: &TransientSigningPublicKey, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_be_u32!(transient.expires) + >> gen_sig_type(transient.pubkey.sig_type()) + >> gen_signing_key(&transient.pubkey) + ) +} + +pub fn gen_transient_key<'a>( + input: (&'a mut [u8], usize), + transient: &TransientSigningPublicKey, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_transient_key_sig_bytes(transient) >> gen_signature(&transient.signature) + ) +} + +// Lease2 + +named!( + lease2, + do_parse!( + tunnel_gw: hash + >> tid: tunnel_id + >> end_date: be_u32 + >> (Lease2 { + tunnel_gw, + tid, + end_date, + }) + ) +); + +fn gen_lease2<'a>( + input: (&'a mut [u8], usize), + lease: &Lease2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_hash(&lease.tunnel_gw) >> gen_tunnel_id(&lease.tid) >> gen_be_u32!(lease.end_date) + ) +} + +// LeaseSet2 standard header + +struct LS2Flags { + offline: bool, + unpublished: bool, +} + +named!( + lease_set_2_header, + do_parse!( + dest: destination + >> created: be_u32 + >> expires: be_u16 + >> flags: + bits!(do_parse!( + take_bits!(u16, 14) + >> unpublished: take_bits!(u8, 1) + >> offline: take_bits!(u8, 1) + >> (LS2Flags { + offline: offline > 0, + unpublished: unpublished > 0, + }) + )) + >> transient: + cond!( + flags.offline, + call!(transient_key, dest.signing_key.sig_type()) + ) + >> (LeaseSet2Header { + dest, + created, + expires, + transient, + published: !flags.unpublished, + }) + ) +); + +pub fn gen_lease_set_2_header<'a>( + input: (&'a mut [u8], usize), + header: &LeaseSet2Header, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_destination(&header.dest) + >> gen_be_u32!(header.created) + >> gen_be_u16!(header.expires) + >> gen_be_u16!({ + let mut x: u16 = 0; + if header.transient.is_some() { + x |= 0b01; + } + if !header.published { + x |= 0b10; + } + x + }) + >> gen_cond!( + header.transient.is_some(), + do_gen!(gen_transient_key(header.transient.as_ref().unwrap())) + ) + ) +} + +// LeaseSet2 + +named!( + pub lease_set_2, + do_parse!( + header: lease_set_2_header + >> properties: mapping + >> enc_keys: length_count!(be_u8, crypto_key) + >> leases: length_count!(be_u8, lease2) + >> signature: + call!( + signature, + if let Some(transient) = header.transient.as_ref() { + transient.pubkey.sig_type() + } else { + header.dest.signing_key.sig_type() + } + ) + >> (LeaseSet2 { + header, + properties, + enc_keys, + leases, + signature: Some(signature), + }) + ) +); + +pub fn gen_lease_set_2_minus_sig<'a>( + input: (&'a mut [u8], usize), + ls2: &LeaseSet2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_lease_set_2_header(&ls2.header) + >> gen_mapping(&ls2.properties) + >> gen_be_u8!(ls2.enc_keys.len() as u8) + >> gen_many!(&ls2.enc_keys, gen_crypto_key) + >> gen_be_u8!(ls2.leases.len() as u8) + >> gen_many!(&ls2.leases, gen_lease2) + ) +} + +pub fn gen_lease_set_2_sig_bytes<'a>( + input: (&'a mut [u8], usize), + ls2: &LeaseSet2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_be_u8!(NETDB_STORE_LS2) >> gen_lease_set_2_minus_sig(ls2) + ) +} + +pub fn gen_lease_set_2<'a>( + input: (&'a mut [u8], usize), + ls2: &LeaseSet2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_lease_set_2_minus_sig(ls2) >> gen_signature(ls2.signature.as_ref().unwrap()) + ) +} + +// MetaEntry + +named!( + meta_entry, + do_parse!( + hash: hash + >> be_u16 + >> entry_type: be_u8 + >> cost: be_u8 + >> expires: be_u32 + >> (MetaEntry { + hash, + entry_type: MetaEntryType::from_type(entry_type), + cost, + expires, + }) + ) +); + +pub fn gen_meta_entry<'a>( + input: (&'a mut [u8], usize), + entry: &MetaEntry, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_hash(&entry.hash) + >> gen_be_u16!(0) + >> gen_be_u8!(entry.entry_type.to_type()) + >> gen_be_u8!(entry.cost) + >> gen_be_u32!(entry.expires) + ) +} + +// MetaLeaseSet2 + +named!( + pub meta_ls2, + do_parse!( + header: lease_set_2_header + >> properties: mapping + >> entries: length_count!(be_u8, meta_entry) + >> revocations: length_count!(be_u8, hash) + >> signature: + call!( + signature, + if let Some(transient) = header.transient.as_ref() { + transient.pubkey.sig_type() + } else { + header.dest.signing_key.sig_type() + } + ) + >> (MetaLeaseSet2 { + header, + properties, + entries, + revocations, + signature: Some(signature), + }) + ) +); + +pub fn gen_meta_ls2_minus_sig<'a>( + input: (&'a mut [u8], usize), + meta_ls2: &MetaLeaseSet2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_lease_set_2_header(&meta_ls2.header) + >> gen_mapping(&meta_ls2.properties) + >> gen_be_u8!(meta_ls2.entries.len() as u8) + >> gen_many!(&meta_ls2.entries, gen_meta_entry) + >> gen_be_u8!(meta_ls2.revocations.len() as u8) + >> gen_many!(&meta_ls2.revocations, gen_hash) + ) +} + +pub fn gen_meta_ls2_sig_bytes<'a>( + input: (&'a mut [u8], usize), + meta_ls2: &MetaLeaseSet2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_be_u8!(NETDB_STORE_META_LS2) >> gen_meta_ls2_minus_sig(meta_ls2) + ) +} + +pub fn gen_meta_ls2<'a>( + input: (&'a mut [u8], usize), + meta_ls2: &MetaLeaseSet2, +) -> Result<(&'a mut [u8], usize), GenError> { + do_gen!( + input, + gen_meta_ls2_minus_sig(meta_ls2) >> gen_signature(meta_ls2.signature.as_ref().unwrap()) + ) +} diff --git a/src/data/mod.rs b/src/data/mod.rs index ca28afb..a47fec2 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -24,6 +24,7 @@ pub mod dest; #[allow(needless_pass_by_value)] pub(crate) mod frame; +pub mod ls2; pub use self::dest::{Destination, Lease, LeaseSet}; @@ -167,6 +168,12 @@ impl<'a> From<&'a str> for I2PString { #[derive(Clone, Debug, PartialEq)] pub struct Mapping(pub HashMap); +impl Default for Mapping { + fn default() -> Self { + Mapping(HashMap::new()) + } +} + /// A random number. pub struct SessionTag(pub [u8; 32]); diff --git a/src/i2np/frame.rs b/src/i2np/frame.rs index c7660d6..2e02ade 100644 --- a/src/i2np/frame.rs +++ b/src/i2np/frame.rs @@ -11,6 +11,9 @@ use sha2::{ use std::io::{Read, Write}; use super::*; +use crate::constants::{ + NETDB_STORE_ENC_LS2, NETDB_STORE_LS, NETDB_STORE_LS2, NETDB_STORE_META_LS2, NETDB_STORE_RI, +}; use crate::crypto::frame::{gen_session_key, session_key}; use crate::data::{ dest::frame::{gen_lease_set, lease_set}, @@ -19,6 +22,10 @@ use crate::data::{ gen_short_expiry, gen_tunnel_id, hash, i2p_date, router_info, session_tag, short_expiry, tunnel_id, }, + ls2::{ + enc::frame::{encrypted_ls2, gen_encrypted_ls2}, + frame::{gen_lease_set_2, gen_meta_ls2, lease_set_2, meta_ls2}, + }, }; // @@ -230,22 +237,25 @@ fn gen_reply_path<'a>( } } -#[cfg_attr(rustfmt, rustfmt_skip)] named!( database_store, do_parse!( - key: hash >> - ds_type: be_u8 >> - reply: reply_path >> - data: switch!(value!(ds_type), - 0 => do_parse!(ri: compressed_ri >> (DatabaseStoreData::RI(ri))) | - 1 => do_parse!(ls: lease_set >> (DatabaseStoreData::LS(ls))) - ) >> (MessagePayload::DatabaseStore(DatabaseStore { - key, - ds_type, - reply, - data, - })) + key: hash + >> ds_type: be_u8 + >> reply: reply_path + >> data: switch!(value!(ds_type), + NETDB_STORE_RI => do_parse!(ri: compressed_ri >> (DatabaseStoreData::RI(ri))) | + NETDB_STORE_LS => do_parse!(ls: lease_set >> (DatabaseStoreData::LS(ls))) | + NETDB_STORE_LS2 => do_parse!(ls2: lease_set_2 >> (DatabaseStoreData::LS2(ls2))) | + NETDB_STORE_ENC_LS2 => do_parse!(enc_ls2: encrypted_ls2 >> (DatabaseStoreData::EncLS2(enc_ls2))) | + NETDB_STORE_META_LS2 => do_parse!(meta_ls2: meta_ls2 >> (DatabaseStoreData::MetaLS2(meta_ls2))) + ) + >> (MessagePayload::DatabaseStore(DatabaseStore { + key, + ds_type, + reply, + data, + })) ) ); @@ -256,6 +266,9 @@ fn gen_database_store_data<'a>( match *data { DatabaseStoreData::RI(ref ri) => gen_compressed_ri(input, &ri), DatabaseStoreData::LS(ref ls) => gen_lease_set(input, &ls), + DatabaseStoreData::LS2(ref ls2) => gen_lease_set_2(input, &ls2), + DatabaseStoreData::EncLS2(ref enc_ls2) => gen_encrypted_ls2(input, &enc_ls2), + DatabaseStoreData::MetaLS2(ref meta_ls2) => gen_meta_ls2(input, &meta_ls2), } } diff --git a/src/i2np/mod.rs b/src/i2np/mod.rs index b15285a..9ce5669 100644 --- a/src/i2np/mod.rs +++ b/src/i2np/mod.rs @@ -13,8 +13,10 @@ use rand::{rngs::OsRng, thread_rng, Rng}; use std::fmt; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use crate::constants; use crate::crypto::{self, elgamal, SessionKey}; use crate::data::{ + ls2::{enc::EncryptedLS2, LeaseSet2, MetaLeaseSet2}, Certificate, Hash, I2PDate, LeaseSet, ReadError, RouterInfo, SessionTag, TunnelId, }; use crate::util::serialize; @@ -148,6 +150,9 @@ pub struct ReplyPath { pub enum DatabaseStoreData { RI(RouterInfo), LS(LeaseSet), + LS2(LeaseSet2), + EncLS2(EncryptedLS2), + MetaLS2(MetaLeaseSet2), } /// An unsolicited database store, or the response to a successful DatabaseLookup @@ -163,7 +168,7 @@ impl DatabaseStore { pub fn from_ri(ri: RouterInfo, reply: Option) -> Self { DatabaseStore { key: ri.router_id.hash(), - ds_type: 0, + ds_type: constants::NETDB_STORE_RI, reply, data: DatabaseStoreData::RI(ri), } @@ -172,11 +177,20 @@ impl DatabaseStore { pub fn from_ls(ls: LeaseSet, reply: Option) -> Self { DatabaseStore { key: ls.dest.hash(), - ds_type: 1, + ds_type: constants::NETDB_STORE_LS, reply, data: DatabaseStoreData::LS(ls), } } + + pub fn from_ls2(ls2: LeaseSet2, reply: Option) -> Self { + DatabaseStore { + key: ls2.header.dest.hash(), + ds_type: constants::NETDB_STORE_LS2, + reply, + data: DatabaseStoreData::LS2(ls2), + } + } } #[cfg_attr(tarpaulin, skip)] diff --git a/src/netdb/mod.rs b/src/netdb/mod.rs index f29a5ed..fd41f5f 100644 --- a/src/netdb/mod.rs +++ b/src/netdb/mod.rs @@ -231,6 +231,21 @@ impl Future for Engine { .store_lease_set(ds.key, ls) .expect("Failed to store LeaseSet"); } + DatabaseStoreData::LS2(_) => { + debug!("Received LeaseSet2 in msg {} from {}", msg.id, from); + } + DatabaseStoreData::EncLS2(_) => { + debug!( + "Received EncryptedLeaseSet2 in msg {} from {}", + msg.id, from + ); + } + DatabaseStoreData::MetaLS2(_) => { + debug!( + "Received MetaLeaseSet2 in msg {} from {}", + msg.id, from + ); + } }, MessagePayload::DatabaseSearchReply(dsr) => { if let Some(pending) = self