From c9d8d362d25ee285a1c1d82bb550399fd93247ed Mon Sep 17 00:00:00 2001 From: hdvanegasm Date: Fri, 1 Nov 2024 12:45:32 -0500 Subject: [PATCH] Implemented secure channels using TLS --- .gitignore | 1 + Cargo.lock | 362 ++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + README.md | 39 ++++- generate_certs.sh | 31 ++++ net_config.json | 10 -- net_config_p0.json | 15 ++ net_config_p1.json | 15 ++ net_config_p2.json | 15 ++ src/main.rs | 4 +- src/net/channel.rs | 285 +++++++++++++++++++---------------- src/net/mod.rs | 97 ++++++++++-- 12 files changed, 712 insertions(+), 164 deletions(-) create mode 100644 generate_certs.sh delete mode 100644 net_config.json create mode 100644 net_config_p0.json create mode 100644 net_config_p1.json create mode 100644 net_config_p2.json diff --git a/.gitignore b/.gitignore index ea8c4bf..7c374f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/certs diff --git a/Cargo.lock b/Cargo.lock index 2a4c486..a8857ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -57,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -66,6 +66,33 @@ version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +[[package]] +name = "aws-lc-rs" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "bincode" version = "1.3.3" @@ -75,18 +102,78 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.20" @@ -127,12 +214,33 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "env_filter" version = "0.1.2" @@ -156,6 +264,22 @@ dependencies = [ "log", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" version = "0.2.15" @@ -167,12 +291,27 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -185,18 +324,64 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.22" @@ -209,6 +394,40 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -218,6 +437,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -295,6 +524,73 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.18" @@ -343,17 +639,37 @@ dependencies = [ "env_logger", "log", "rand", + "rustls", "serde", "serde_json", "thiserror", + "webpki-roots", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.85" @@ -391,6 +707,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -403,6 +725,36 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -496,3 +848,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index e760d80..c79e5f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ clap = { version = "4.5.20", features = ["derive"] } env_logger = "0.11.5" log = "0.4.22" rand = "0.8.5" +rustls = "0.23.16" serde = { version = "1.0.213", features = ["derive"] } serde_json = "1.0.132" thiserror = "1.0.65" +webpki-roots = "0.26.6" diff --git a/README.md b/README.md index e9c3d85..df7e693 100644 --- a/README.md +++ b/README.md @@ -69,31 +69,54 @@ Also, they are indexed consecutively. The configuration of the network for the execution of the protocol is written in a JSON format. -The following file is an example of the configuration JSON for an execution of three parties: +The following file is an example of the configuration JSON for the party with ID 0 for an execution of three parties: ```json { "base_port": 5000, - "timeout": 1000, + "timeout": 5000, "sleep_time": 500, "peer_ips": [ "127.0.0.1", "127.0.0.1", "127.0.0.1" + ], + "server_cert": "./certs/server_cert_p0.crt", + "priv_key": "./certs/priv_key_p0.pem", + "trusted_certs": [ + "./certs/rootCA.crt" ] } ``` -The `base_port`, is the port that will be used as a base to compute the actual port in which the party will be listening to. -For a party with index `i`, the listening port is `base_port + i`. The `timeout` is the number of ***milliseconds*** that -a party will repeatedly try to connect to another party. If the timeout is reached, the application returns an error. -The `sleep_time` is the number of ***milliseconds*** that a party will wait before trying to connect again with another -party in case the connection is not successful. And finally, the `peer_ips` is the list of IPs for all the +The fields above are explained next: + +- The `base_port`, is the port that will be used as a base to compute the actual port in which the party will be listening to. +For a party with index `i`, the listening port is `base_port + i`. +- The `timeout` is the number of ***milliseconds*** +a party will repeatedly try to connect with another party. If the timeout is reached, the application returns an error. +- The `sleep_time` is the number of ***milliseconds*** that a party will wait before trying to connect again with another +party in case the connection is not successful. +- The `peer_ips` is the list of IPs for all the peers engaged in the protocol. In this case, the array is specified in such a way that the party with index `i` has IP `peer_ips[i]`. +- The `server_cert` is the certificate path for that node for secure communication. +- The `priv_key` is the file with the private key associated with the certificate in `server_cert`. This private key is used for secure communication. +- `trusted_certs` is a list of paths with trusted CA certificates. This is useful in executions where the certificates are self-signed. > [!WARNING] -> All parties must have the same JSON configuration file. +> Each party should have its configuration JSON file with the corresponding certificates and private keys. + +### Generating self-signed certificates for local testing + +The script `./generate_certs.sh` will help you to generate self-signed certificates to test the tool. To generate the certificates for +`N` parties, you should run the command + +```text +bash ./generate_certs.sh +``` + +Then the certificates will be generated in the `./certs/` folder. Remember to add `./certs/rootCA.crt` to the list of trusted certificates and generate the JSON with the private key and certificates accordingly to each party. > [!NOTE] > This repository came as a result of a learning project by @hdvanegasm. diff --git a/generate_certs.sh b/generate_certs.sh new file mode 100644 index 0000000..64e1c1f --- /dev/null +++ b/generate_certs.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# source: https://users.rust-lang.org/t/use-tokio-tungstenite-with-rustls-instead-of-native-tls-for-secure-websockets/90130 +mkdir -p certs +cd certs + +# Create a self-signed root CA +openssl req -x509 -sha256 -nodes -subj "/C=FI/CN=hdvanegasm" -days 1825 -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt + +# Create file localhost.ext with the following content: +cat <<'EOF' >localhost.ext + authorityKeyIdentifier=keyid,issuer + basicConstraints=CA:FALSE + subjectAltName = @alt_names + [alt_names] + DNS.1 = server + IP.1 = 127.0.0.1 +EOF + +n_certificates=$(expr $1 - 1) +for i in $(seq 0 $n_certificates); do + # Create unencrypted private key and a CSR (certificate signing request) + openssl req -newkey rsa:2048 -nodes -subj "/C=FI/CN=hdvanegasm" -keyout "priv_key_p$i.pem" -out "server_cert_p$i.csr" + + # Create self-signed certificate (`cert.pem`) with the private key and CSR + openssl x509 -signkey "priv_key_p$i.pem" -in "server_cert_p$i.csr" -req -days 365 -out "server_cert_p$i.crt" + + # Sign the CSR (`cert.pem`) with the root CA certificate and private key + # => this overwrites `cert.pem` because it gets signed + openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in "server_cert_p$i.csr" -out "server_cert_p$i.crt" -days 365 -CAcreateserial -extfile localhost.ext +done diff --git a/net_config.json b/net_config.json deleted file mode 100644 index 01b8625..0000000 --- a/net_config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "base_port": 5000, - "timeout": 1000, - "sleep_time": 500, - "peer_ips": [ - "127.0.0.1", - "127.0.0.1", - "127.0.0.1" - ] -} diff --git a/net_config_p0.json b/net_config_p0.json new file mode 100644 index 0000000..700952c --- /dev/null +++ b/net_config_p0.json @@ -0,0 +1,15 @@ +{ + "base_port": 5000, + "timeout": 5000, + "sleep_time": 500, + "peer_ips": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ], + "server_cert": "./certs/server_cert_p0.crt", + "priv_key": "./certs/priv_key_p0.pem", + "trusted_certs": [ + "./certs/rootCA.crt" + ] +} diff --git a/net_config_p1.json b/net_config_p1.json new file mode 100644 index 0000000..c5c0fc5 --- /dev/null +++ b/net_config_p1.json @@ -0,0 +1,15 @@ +{ + "base_port": 5000, + "timeout": 5000, + "sleep_time": 500, + "peer_ips": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ], + "server_cert": "./certs/server_cert_p1.crt", + "priv_key": "./certs/priv_key_p1.pem", + "trusted_certs": [ + "./certs/rootCA.crt" + ] +} diff --git a/net_config_p2.json b/net_config_p2.json new file mode 100644 index 0000000..8f8a0f1 --- /dev/null +++ b/net_config_p2.json @@ -0,0 +1,15 @@ +{ + "base_port": 5000, + "timeout": 5000, + "sleep_time": 500, + "peer_ips": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ], + "server_cert": "./certs/server_cert_p2.crt", + "priv_key": "./certs/priv_key_p2.pem", + "trusted_certs": [ + "./certs/rootCA.crt" + ] +} diff --git a/src/main.rs b/src/main.rs index 368043c..b06d60b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use clap::Parser; use math::mersenne61::Mersenne61; use mpc::{reconstruct_secret, run_multiply_protocol, share::ShamirShare}; use net::{Network, NetworkConfig, Packet}; -use std::{error::Error, fs::File, path::Path}; +use std::{error::Error, path::Path}; /// Implementation of a node to execute a Shamir secret-sharing protocol. #[derive(Parser, Debug)] @@ -28,7 +28,7 @@ struct Args { fn main() -> Result<(), Box> { let mut log_builder = env_logger::Builder::new(); - log_builder.filter_level(log::LevelFilter::Info).init(); + log_builder.filter_level(log::LevelFilter::Debug).init(); let args = Args::parse(); diff --git a/src/net/channel.rs b/src/net/channel.rs index 9551a69..96cade2 100644 --- a/src/net/channel.rs +++ b/src/net/channel.rs @@ -1,6 +1,13 @@ use crate::net::Packet; +use rustls::pki_types::ServerName; +use rustls::{ + ClientConfig, ClientConnection, ConnectionCommon, ServerConfig, ServerConnection, SideData, + StreamOwned, +}; use std::collections::VecDeque; -use std::io::{Read, Write}; +use std::io::{self, Read, Write}; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; use std::{ net::{SocketAddr, TcpListener, TcpStream}, time::{Duration, Instant}, @@ -10,12 +17,11 @@ use thiserror::Error; /// Possible errors that may appear in a channel. #[derive(Debug, Error)] pub enum ChannelError { + /// The party tried to connect to the other party but the timeout was reached. #[error("connection timeout")] Timeout, - #[error("the channel is not alive")] - NotAlive, - + /// Trying to read from a channel with no information. #[error("channel buffer is empty")] EmptyBuffer, } @@ -23,152 +29,156 @@ pub enum ChannelError { /// Defines a channel of the network. pub trait Channel { /// Closes a channel. - fn close(&mut self) -> anyhow::Result<()>; + fn shutdown(&mut self) -> anyhow::Result<()>; /// Send a packet using the current channel. fn send(&mut self, packet: &Packet) -> anyhow::Result; /// Receives a packet from the current channel. fn recv(&mut self) -> anyhow::Result; } -/// Representation of a TCP channel between two parties. -#[derive(Debug, Default)] -pub struct TcpChannel { - /// Stream for the channel. If the channel is not connected - /// then the stream will be `None`. - stream: Option, -} +impl Channel for StreamOwned +where + C: Sized + DerefMut + Deref>, + T: Sized + Read + Write, + S: SideData, +{ + fn shutdown(&mut self) -> anyhow::Result<()> { + self.conn.send_close_notify(); + log::info!("channel successfully closed"); + Ok(()) + } -impl TcpChannel { - /// Accepts a connection in the corresponding listener. - pub(crate) fn accept_connection(listener: &TcpListener) -> anyhow::Result<(TcpChannel, usize)> { - let (mut channel, socket) = listener.accept()?; - - // Once the client is connected, we receive his ID from the current established channel. - let mut id_buffer = [0; (usize::BITS / 8) as usize]; - channel.read_exact(&mut id_buffer)?; - let remote_id = usize::from_le_bytes(id_buffer); - log::info!( - "accepted connection request acting like a server from {:?} with ID {}", - socket, - remote_id, - ); - - Ok(( - Self { - stream: Some(channel), - }, - remote_id, - )) + fn send(&mut self, packet: &Packet) -> anyhow::Result { + // First, we need to send the size of the packet to be able to know the amout + // of bits that are being sent. + let packet_size = packet.size(); + let bytes_size_packet = bincode::serialize(&packet_size)?; + self.write_all(&bytes_size_packet)?; + + // Then, we send the actual packet. + self.write_all(packet.as_slice())?; + Ok(packet.size()) } - /// Connect to the remote address as a client using the corresponding timeout. The party - /// tries to connect to the "server" multiple times using a sleep time between calls. - /// If the "server" party does not answer within the timeout, then the function returns - /// an error. - pub(crate) fn connect_as_client( - local_id: usize, - remote_addr: SocketAddr, - timeout: Duration, - sleep_time: Duration, - ) -> anyhow::Result { - let start_time = Instant::now(); - - // Repeatedly tries to connect to the server during the timeout. - log::info!("trying to connect as a client to {:?}", remote_addr); - loop { - match TcpStream::connect(remote_addr) { - Ok(mut stream) => { - // Send the id of the party that is connecting to the - // server once the connection is successfull. - stream.write_all(&local_id.to_le_bytes())?; - - log::info!( - "connected successfully with {:?} using the local port {:?}", - remote_addr, - stream.local_addr()? - ); + fn recv(&mut self) -> anyhow::Result { + let mut buffer_packet_size = [0; (usize::BITS / 8) as usize]; + self.read_exact(&mut buffer_packet_size)?; + let packet_size: usize = bincode::deserialize(&buffer_packet_size)?; - return Ok(Self { - stream: Some(stream), - }); - } - Err(_) => { - let elapsed = start_time.elapsed(); - if elapsed > timeout { - // At this moment the enlapsed time passed the timeout. Hence we return an - // error. Tired of waiting for the "server" to be ready. - log::error!( - "timeout reached, server not listening from ID {local_id} to server {:?}", - remote_addr - ); - anyhow::bail!(ChannelError::Timeout) - } - // The connection was not successfull. Hence, we try to connect again with the - // "server" party. - std::thread::sleep(sleep_time) - } - } - } - } -} + // Then, we receive the buffer the amount bytes until the end is reached. + let mut payload_buffer = vec![0; packet_size]; + self.read_exact(&mut payload_buffer)?; -impl Channel for TcpChannel { - fn close(&mut self) -> anyhow::Result<()> { - if let Some(stream) = &self.stream { - stream.shutdown(std::net::Shutdown::Both)?; - } - self.stream = None; - log::info!("channel successfully closed"); - Ok(()) + Ok(Packet::new(payload_buffer)) } +} - fn send(&mut self, packet: &Packet) -> anyhow::Result { - match &mut self.stream { - Some(stream) => { - // First, we need to send the size of the packet to be able to know the amout - // of bits that are being sent. - let packet_size = packet.size(); - let bytes_size_packet = bincode::serialize(&packet_size)?; - stream.write_all(&bytes_size_packet)?; - - // Then, we send the actual packet. - stream.write_all(packet.as_slice())?; - log::info!( - "sent packet to peer {:?} with {} bytes", - stream.peer_addr()?, - packet.size() - ); - Ok(packet.size()) - } - None => { - log::error!("the channel is not connected yet"); - anyhow::bail!(ChannelError::NotAlive) +/// Accepts a connection in the corresponding listener. +pub(crate) fn accept_connection( + listener: &TcpListener, + server_conf: &ServerConfig, +) -> anyhow::Result<(ServerConnection, TcpStream, usize)> { + let (mut stream, socket) = listener.accept()?; + stream.set_nonblocking(false)?; + + let mut tls_conn = ServerConnection::new(Arc::new(server_conf.clone()))?; + let (read_bytes, write_bytes) = tls_conn.complete_io(&mut stream)?; + log::debug!("Created TLS connection: read {read_bytes} bytes, write {write_bytes} bytes"); + + // Once the client is connected, we receive his ID from the current established channel. + let mut id_buffer = [0; (usize::BITS / 8) as usize]; + loop { + if tls_conn.wants_read() { + tls_conn.read_tls(&mut stream)?; + tls_conn.process_new_packets()?; + + match tls_conn.reader().read_exact(&mut id_buffer) { + Ok(()) => break Ok(()), + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + continue; + } + Err(err) => break Err(err), } } - } + }?; - fn recv(&mut self) -> anyhow::Result { - match &mut self.stream { - Some(stream) => { - let mut buffer_packet_size = [0; (usize::BITS / 8) as usize]; - stream.read_exact(&mut buffer_packet_size)?; - let packet_size: usize = bincode::deserialize(&buffer_packet_size)?; + let remote_id = usize::from_le_bytes(id_buffer); + log::info!( + "accepted connection request acting like a server from {:?} with ID {}", + socket, + remote_id, + ); - // Then, we receive the buffer the amount bytes until the end is reached. - let mut payload_buffer = vec![0; packet_size]; - stream.read_exact(&mut payload_buffer)?; + Ok((tls_conn, stream, remote_id)) +} + +/// Connect to the remote address as a client using the corresponding timeout. The party +/// tries to connect to the "server" (the other node) multiple times using a sleep time between calls. +/// If the "server" party does not answer within the timeout, then the function returns +/// an error. +pub(crate) fn connect_as_client( + local_id: usize, + remote_addr: SocketAddr, + timeout: Duration, + sleep_time: Duration, + client_conf: &ClientConfig, +) -> anyhow::Result<(ClientConnection, TcpStream)> { + let start_time = Instant::now(); + + // Repeatedly tries to connect to the server during the timeout. + log::info!("trying to connect as a client to {:?}", remote_addr); + loop { + match TcpStream::connect(remote_addr) { + Ok(mut stream) => { + // We want the stream to actually block. + stream.set_nonblocking(false)?; + + // Create the client connection. + let mut client_conn = ClientConnection::new( + Arc::new(client_conf.clone()), + ServerName::from(remote_addr.ip()), + )?; + let (read_bytes, write_bytes) = client_conn.complete_io(&mut stream)?; + log::debug!( + "TLS connection with {:?}: write {write_bytes} bytes, read {read_bytes} bytes", + remote_addr + ); + + // Send the id of the party that is connecting to the + // server once the connection is successfull. + client_conn.writer().write_all(&local_id.to_le_bytes())?; + let bytes = loop { + if client_conn.wants_write() { + match client_conn.write_tls(&mut stream) { + Ok(bytes) => break Ok(bytes), + Err(err) => break Err(err), + } + } + }?; + log::debug!("sending ID to {:?}: {bytes} bytes", remote_addr); log::info!( - "received packet from peer {:?} with {} bytes", - stream.peer_addr()?, - packet_size, + "connected successfully with {:?} using the local port {:?}", + remote_addr, + stream.local_addr()? ); - Ok(Packet::new(payload_buffer)) + break Ok((client_conn, stream)); } - None => { - log::error!("the channel is not alive to receive information"); - anyhow::bail!(ChannelError::NotAlive) + Err(_) => { + let elapsed = start_time.elapsed(); + if elapsed > timeout { + // At this moment the enlapsed time passed the timeout. Hence we return an + // error. Tired of waiting for the "server" to be ready. + log::error!( + "timeout reached, server not listening from ID {local_id} to server {:?}", + remote_addr + ); + anyhow::bail!(ChannelError::Timeout) + } + // The connection was not successfull. Hence, we try to connect again with the + // "server" party. + std::thread::sleep(sleep_time) } } } @@ -182,7 +192,7 @@ pub struct LoopBackChannel { } impl Channel for LoopBackChannel { - fn close(&mut self) -> anyhow::Result<()> { + fn shutdown(&mut self) -> anyhow::Result<()> { self.buffer.clear(); log::info!("channel successfully closed"); Ok(()) @@ -201,3 +211,20 @@ impl Channel for LoopBackChannel { .ok_or(anyhow::Error::new(ChannelError::EmptyBuffer)) } } + +/// A dumy channel acting as a placeholder. +pub struct DummyChannel; + +impl Channel for DummyChannel { + fn shutdown(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn send(&mut self, _: &Packet) -> anyhow::Result { + Ok(0) + } + + fn recv(&mut self) -> anyhow::Result { + Ok(Packet::empty()) + } +} diff --git a/src/net/mod.rs b/src/net/mod.rs index 7111645..5cfc94a 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,7 +1,11 @@ pub mod channel; -use crate::net::channel::{Channel, TcpChannel}; -use channel::LoopBackChannel; +use crate::net::channel::Channel; +use channel::{DummyChannel, LoopBackChannel}; +use rustls::{ + pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer}, + ClientConfig, RootCertStore, ServerConfig, StreamOwned, +}; use serde_json::Value; use std::{ cmp::Ordering, @@ -19,6 +23,9 @@ use std::{ pub struct Packet(Vec); impl Packet { + pub fn empty() -> Self { + Self(Vec::new()) + } /// Creates a new packet. pub fn new(buffer: Vec) -> Self { Self(buffer) @@ -42,7 +49,7 @@ impl From<&[u8]> for Packet { } /// Configuration of the network -pub struct NetworkConfig { +pub struct NetworkConfig<'a> { /// Port that will be use as a base to define the port of each party. Party `i` will listen at /// port `base_port + i`. base_port: u16, @@ -52,19 +59,25 @@ pub struct NetworkConfig { sleep_time: Duration, /// IPs of each peer. pub peer_ips: Vec, + /// Root of trust certificates when acting as a client. + root_cert_store: RootCertStore, + /// Certificates to act like a server. + server_cert: Vec>, + /// Private key to act like a server. + priv_key: PrivateKeyDer<'a>, } -impl NetworkConfig { +impl<'a> NetworkConfig<'a> { /// Creates a configuration for the network from a configuration file. pub fn new(path_file: &Path) -> anyhow::Result { let json_content = fs::read_to_string(path_file)?; let json: Value = serde_json::from_str(&json_content)?; + // Deserialize the peer ips. let peers_ips_json = json["peer_ips"].as_array().ok_or(Error::new( ErrorKind::InvalidInput, "the array of peers is not correct", ))?; - let mut peer_ips = Vec::new(); for ip_value in peers_ips_json { let ip_str = ip_value.as_str().ok_or(Error::new( @@ -74,6 +87,40 @@ impl NetworkConfig { peer_ips.push(Ipv4Addr::from_str(ip_str)?); } + // Get private key. + let priv_key_pem = json["priv_key"].as_str().ok_or(Error::new( + ErrorKind::InvalidData, + "the private key has not the correct format", + ))?; + let priv_key = PrivateKeyDer::from_pem_file(priv_key_pem)?; + + // Get the server certificate. + let server_cert_file = json["server_cert"].as_str().ok_or(Error::new( + ErrorKind::InvalidData, + "the private key has not the correct format", + ))?; + let server_cert = CertificateDer::pem_file_iter(server_cert_file)? + .map(|cert| cert.unwrap()) + .collect(); + + // Get trusted certificates. + let trusted_certs_json = json["trusted_certs"].as_array().ok_or(Error::new( + ErrorKind::InvalidInput, + "the array of peers is not correct", + ))?; + let mut trusted_certs = Vec::new(); + for trusted_cert in trusted_certs_json { + let trusted_cert_path = trusted_cert.as_str().ok_or(Error::new( + ErrorKind::InvalidInput, + "the ip of peer is not correct", + ))?; + trusted_certs + .extend(CertificateDer::pem_file_iter(trusted_cert_path)?.map(|cert| cert.unwrap())) + } + let mut root_cert_store = RootCertStore::empty(); + let (certs_added, certs_ignored) = root_cert_store.add_parsable_certificates(trusted_certs); + log::info!("added {certs_added} certificates, ignored {certs_ignored} certificates to the root certificate store"); + Ok(Self { base_port: json["base_port"].as_u64().ok_or(Error::new( ErrorKind::InvalidInput, @@ -81,13 +128,16 @@ impl NetworkConfig { ))? as u16, timeout: Duration::from_millis(json["timeout"].as_u64().ok_or(Error::new( ErrorKind::InvalidInput, - "the timout is not correct", + "timeout is not correct", ))?), sleep_time: Duration::from_millis(json["sleep_time"].as_u64().ok_or(Error::new( ErrorKind::InvalidInput, "the timeout is not correct", ))?), peer_ips, + root_cert_store, + priv_key, + server_cert, }) } } @@ -100,9 +150,24 @@ pub struct Network { } impl Network { + fn configure_tls( + config: &NetworkConfig<'static>, + ) -> anyhow::Result<(ClientConfig, ServerConfig)> { + // Configure the client TLS + let client_conf = ClientConfig::builder() + .with_root_certificates(config.root_cert_store.clone()) + .with_no_client_auth(); + + let server_conf = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(config.server_cert.clone(), config.priv_key.clone_key())?; + + Ok((client_conf, server_conf)) + } + /// Creates a new network using the ID of the current party and the number of parties connected /// to the network. - pub fn create(id: usize, config: NetworkConfig) -> anyhow::Result { + pub fn create(id: usize, config: NetworkConfig<'static>) -> anyhow::Result { log::info!("creating network"); let n_parties = config.peer_ips.len(); let server_port = config.base_port + id as u16; @@ -111,10 +176,12 @@ impl Network { let server_listener = TcpListener::bind(server_address)?; log::info!("listening on {:?}", server_address); + let (client_conf, server_conf) = Self::configure_tls(&config)?; + let mut peers: Vec> = Vec::new(); for i in 0..n_parties { if i != id { - peers.push(Box::new(TcpChannel::default())); + peers.push(Box::new(DummyChannel)); } else { peers.push(Box::new(LoopBackChannel::default())); } @@ -127,18 +194,22 @@ impl Network { let remote_port = config.base_port + i as u16; let remote_address = SocketAddr::new(std::net::IpAddr::V4(config.peer_ips[i]), remote_port); - let channel = TcpChannel::connect_as_client( + let (client_conn, tcp_stream) = channel::connect_as_client( id, remote_address, config.timeout, config.sleep_time, + &client_conf, )?; - peers[i] = Box::new(channel); + let stream = StreamOwned::new(client_conn, tcp_stream); + peers[i] = Box::new(stream); } Ordering::Greater => { log::info!("acting as a server for peer ID {i}"); - let (channel, remote_id) = TcpChannel::accept_connection(&server_listener)?; - peers[remote_id] = Box::new(channel); + let (server_conn, tcp_stream, remote_id) = + channel::accept_connection(&server_listener, &server_conf)?; + let stream = StreamOwned::new(server_conn, tcp_stream); + peers[remote_id] = Box::new(stream); } Ordering::Equal => { log::info!("adding the loop-back channel"); @@ -185,7 +256,7 @@ impl Network { self.peer_channels .get_mut(i) .expect("channel index not found") - .close()?; + .shutdown()?; } Ok(()) }