From fd5c8702fe740b73e6cf3cba3160819928170a6b Mon Sep 17 00:00:00 2001 From: librelois <elois@ifee.fr> Date: Thu, 29 Aug 2019 19:06:26 +0200 Subject: [PATCH] [feat] tools: impl PKSTL (Public Key Secure Transport Layer) --- Cargo.lock | 111 ++++ Cargo.toml | 1 + lib/crypto/src/agreement.rs | 125 ---- lib/crypto/src/errors.rs | 15 +- lib/crypto/src/lib.rs | 2 - lib/tools/pkstl/Cargo.toml | 36 ++ lib/tools/pkstl/src/agreement.rs | 211 ++++++ lib/tools/pkstl/src/complete.rs | 268 ++++++++ lib/tools/pkstl/src/complete/message.rs | 39 ++ lib/tools/pkstl/src/complete/serde.rs | 63 ++ .../pkstl/src/complete/serde/deserializer.rs | 106 +++ .../pkstl/src/complete/serde/serializer.rs | 121 ++++ lib/tools/pkstl/src/complete/writer.rs | 102 +++ lib/tools/pkstl/src/config.rs | 78 +++ lib/tools/pkstl/src/constants.rs | 49 ++ lib/tools/pkstl/src/digest.rs | 20 + lib/tools/pkstl/src/encryption.rs | 158 +++++ .../src/encryption/chacha20_poly1305_aead.rs} | 91 ++- lib/tools/pkstl/src/errors.rs | 106 +++ lib/tools/pkstl/src/format.rs | 156 +++++ lib/tools/pkstl/src/lib.rs | 100 +++ lib/tools/pkstl/src/message.rs | 494 ++++++++++++++ lib/tools/pkstl/src/minimal.rs | 604 ++++++++++++++++++ lib/tools/pkstl/src/reader.rs | 373 +++++++++++ lib/tools/pkstl/src/seeds.rs | 151 +++++ lib/tools/pkstl/src/signature.rs | 30 + lib/tools/pkstl/src/status.rs | 127 ++++ lib/tools/pkstl/tests/complete_mode.rs | 196 ++++++ lib/tools/pkstl/tests/complete_serde_mode.rs | 236 +++++++ lib/tools/pkstl/tests/minimal_mode.rs | 472 ++++++++++++++ 30 files changed, 4451 insertions(+), 190 deletions(-) delete mode 100644 lib/crypto/src/agreement.rs create mode 100644 lib/tools/pkstl/Cargo.toml create mode 100644 lib/tools/pkstl/src/agreement.rs create mode 100644 lib/tools/pkstl/src/complete.rs create mode 100644 lib/tools/pkstl/src/complete/message.rs create mode 100644 lib/tools/pkstl/src/complete/serde.rs create mode 100644 lib/tools/pkstl/src/complete/serde/deserializer.rs create mode 100644 lib/tools/pkstl/src/complete/serde/serializer.rs create mode 100644 lib/tools/pkstl/src/complete/writer.rs create mode 100644 lib/tools/pkstl/src/config.rs create mode 100644 lib/tools/pkstl/src/constants.rs create mode 100644 lib/tools/pkstl/src/digest.rs create mode 100644 lib/tools/pkstl/src/encryption.rs rename lib/{crypto/src/encryption.rs => tools/pkstl/src/encryption/chacha20_poly1305_aead.rs} (53%) create mode 100644 lib/tools/pkstl/src/errors.rs create mode 100644 lib/tools/pkstl/src/format.rs create mode 100644 lib/tools/pkstl/src/lib.rs create mode 100644 lib/tools/pkstl/src/message.rs create mode 100644 lib/tools/pkstl/src/minimal.rs create mode 100644 lib/tools/pkstl/src/reader.rs create mode 100644 lib/tools/pkstl/src/seeds.rs create mode 100644 lib/tools/pkstl/src/signature.rs create mode 100644 lib/tools/pkstl/src/status.rs create mode 100644 lib/tools/pkstl/tests/complete_mode.rs create mode 100644 lib/tools/pkstl/tests/complete_serde_mode.rs create mode 100644 lib/tools/pkstl/tests/minimal_mode.rs diff --git a/Cargo.lock b/Cargo.lock index 8e35fc86..baee1280 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" version = "0.7.6" @@ -227,6 +232,14 @@ name = "constant_time_eq" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.2.0" @@ -326,6 +339,15 @@ dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ctor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "difference" version = "2.0.0" @@ -872,6 +894,17 @@ name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "flate2" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fnv" version = "1.0.6" @@ -917,6 +950,11 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "half" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "heck" version = "0.3.1" @@ -1092,6 +1130,23 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "miniz-sys" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mio" version = "0.6.19" @@ -1223,6 +1278,14 @@ dependencies = [ "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pbkdf2" version = "0.3.0" @@ -1293,6 +1356,23 @@ name = "pkg-config" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "pkstl" +version = "0.1.0" +dependencies = [ + "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chacha20-poly1305-aead 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.16.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_cbor 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pretty_assertions" version = "0.5.1" @@ -1302,6 +1382,17 @@ dependencies = [ "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pretty_assertions" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ctor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "prettytable-rs" version = "0.8.0" @@ -1683,6 +1774,16 @@ dependencies = [ "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde_cbor" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "half 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "serde_derive" version = "1.0.99" @@ -2172,6 +2273,7 @@ dependencies = [ ] [metadata] +"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" @@ -2201,6 +2303,7 @@ dependencies = [ "checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "05e44b8cf3e1a625844d1750e1f7820da46044ff6d28f4d43e455ba3e5bb2c13" "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" @@ -2211,6 +2314,7 @@ dependencies = [ "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" +"checksum ctor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5b6b2f4752cc29efbfd03474c532ce8f916f2d44ec5bb8c21f93bc76e5365528" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" @@ -2219,6 +2323,7 @@ dependencies = [ "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" @@ -2226,6 +2331,7 @@ dependencies = [ "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum half 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9353c2a89d550b58fa0061d8ed8d002a7d8cdf2494eb0e432859bd3a9e543836" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" @@ -2248,6 +2354,8 @@ dependencies = [ "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +"checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" +"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -2262,6 +2370,7 @@ dependencies = [ "checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15" "checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884" "checksum os_type 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7edc011af0ae98b7f88cf7e4a83b70a54a75d2b8cb013d6efd02e5956207e9eb" +"checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" "checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" "checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" @@ -2271,6 +2380,7 @@ dependencies = [ "checksum pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f249ea6de7c7b7aba92b4ff4376a994c6dbd98fd2166c89d5c4947397ecb574d" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6" +"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" "checksum prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" @@ -2314,6 +2424,7 @@ 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.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" +"checksum serde_cbor 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "318690c4f04ae6553665f3846c0614c9995bb1ea51a2f1c5c4b4ed338c248b49" "checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" "checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" diff --git a/Cargo.toml b/Cargo.toml index 6885a622..8bce67f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "lib/tests-tools/common-tests-tools", "lib/tools/common-tools", "lib/tools/json-pest-parser", + "lib/tools/pkstl", "lib/tools/rules-engine", ] diff --git a/lib/crypto/src/agreement.rs b/lib/crypto/src/agreement.rs deleted file mode 100644 index 6c6b8246..00000000 --- a/lib/crypto/src/agreement.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -//! Manage cryptographic agreement operations. - -use crate::errors::CryptoError; -use ring::{agreement, digest, pbkdf2, rand}; -use std::num::NonZeroU32; - -const SHARED_SECRET_LEN: usize = digest::SHA384_OUTPUT_LEN; -const ITERATIONS: u32 = 3; - -static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA384; - -#[derive(Clone)] -/// Ephemeral public key used once to generate shared secret -pub struct EphemeralPublicKey(agreement::PublicKey); - -impl AsRef<[u8]> for EphemeralPublicKey { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -/// Ephemeral key pair used once to generate shared secret -pub struct EphemeralKeyPair { - privkey: agreement::EphemeralPrivateKey, - pubkey: EphemeralPublicKey, -} - -impl EphemeralKeyPair { - /// Generate ephemeral key pair - pub fn generate() -> Result<Self, CryptoError> { - let rng = rand::SystemRandom::new(); - let privkey = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng) - .map_err(|_| CryptoError::FailToGenEphemerKeyPair)?; - let pubkey = EphemeralPublicKey( - privkey - .compute_public_key() - .map_err(|_| CryptoError::FailToGenEphemerPubKey)?, - ); - - Ok(EphemeralKeyPair { privkey, pubkey }) - } - /// Get ephemeral public key - pub fn public_key(&self) -> &EphemeralPublicKey { - &self.pubkey - } - /// Compute shared secret - pub fn compute_shared_secret( - self, - other_ephemeral_public_key: &EphemeralPublicKey, - server: bool, - ) -> Result<[u8; SHARED_SECRET_LEN], CryptoError> { - let salt = if server { - self.pubkey.as_ref() - } else { - other_ephemeral_public_key.as_ref() - }; - - agreement::agree_ephemeral( - self.privkey, - &agreement::UnparsedPublicKey::new( - &agreement::X25519, - other_ephemeral_public_key.as_ref(), - ), - CryptoError::FailToComputeAgreement, - |key_material| Ok(derive(key_material, salt)), - ) - } -} - -fn derive(seed: &[u8], salt: &[u8]) -> [u8; SHARED_SECRET_LEN] { - let mut store_credentials: [u8; SHARED_SECRET_LEN] = [0u8; SHARED_SECRET_LEN]; - pbkdf2::derive( - PBKDF2_ALG, - NonZeroU32::new(ITERATIONS).expect("ITERATIONS must be > 0"), - salt, - seed, - &mut store_credentials, - ); - store_credentials -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_exchange_dh() -> Result<(), CryptoError> { - let ephemeral_kp_server = EphemeralKeyPair::generate()?; - let ephemeral_kp_client = EphemeralKeyPair::generate()?; - - let ephemeral_pk_server = ephemeral_kp_server.public_key().clone(); - let ephemeral_pk_client = ephemeral_kp_client.public_key().clone(); - - let shared_secret_server = - ephemeral_kp_server.compute_shared_secret(&ephemeral_kp_client.public_key(), true)?; - - let shared_secret_client = - ephemeral_kp_client.compute_shared_secret(&ephemeral_pk_server, false)?; - - assert_eq!(shared_secret_server.to_vec(), shared_secret_client.to_vec()); - - println!("ephemeral_pk_server={:?}", ephemeral_pk_server.as_ref()); - println!("ephemeral_pk_client={:?}", ephemeral_pk_client.as_ref()); - println!("shared_secret_server={:?}", shared_secret_server.to_vec()); - println!("shared_secret_client={:?}", shared_secret_client.to_vec()); - //panic!(); - Ok(()) - } -} diff --git a/lib/crypto/src/errors.rs b/lib/crypto/src/errors.rs index 9d4b43db..f2aa44ad 100644 --- a/lib/crypto/src/errors.rs +++ b/lib/crypto/src/errors.rs @@ -15,17 +15,6 @@ //! Manage cryptographic errors. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] /// Cryptographic error -pub enum CryptoError { - /// Fail to compute agreement - FailToComputeAgreement, - /// Fail to decrypt datas - FailToDecryptDatas(chacha20_poly1305_aead::DecryptError), - /// Fail to encrypt datas - FailToEncryptDatas(std::io::Error), - /// Fail to generate ephemeral key pair - FailToGenEphemerKeyPair, - /// Fail to generate ephemeral public key - FailToGenEphemerPubKey, -} +pub enum CryptoError {} diff --git a/lib/crypto/src/lib.rs b/lib/crypto/src/lib.rs index 9ce42463..5020ebcc 100644 --- a/lib/crypto/src/lib.rs +++ b/lib/crypto/src/lib.rs @@ -36,9 +36,7 @@ extern crate serde_derive; #[macro_use] extern crate log; -pub mod agreement; pub mod bases; -pub mod encryption; pub mod errors; pub mod hashs; pub mod keys; diff --git a/lib/tools/pkstl/Cargo.toml b/lib/tools/pkstl/Cargo.toml new file mode 100644 index 00000000..e489802d --- /dev/null +++ b/lib/tools/pkstl/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pkstl" +version = "0.1.0" +authors = ["elois <elois@duniter.org>"] +description = "Public Key Secure Transport Layer." +repository = "https://git.duniter.org/nodes/rust/duniter-rs" +readme = "README.md" +keywords = ["security", "transport", "cryptography"] +license = "AGPL-3.0" +edition = "2018" + +[lib] +path = "src/lib.rs" + +[dependencies] +bincode = { version = "1.0.*", optional = true } +chacha20-poly1305-aead = "0.1.2" +clear_on_drop = "0.2.3" +failure = "0.1.5" +flate2 = { version = "1.0.11", optional = true } +ring = "0.16.5" +serde = { version = "1.0.*", features = ["derive"], optional = true } +serde_cbor = { version = "0.10.1", optional = true } +serde_json = { version = "1.0.40", optional = true } +log = "0.4.*" + +[dev-dependencies] +pretty_assertions = "0.6.1" + +[features] +default = ["zip-sign"] +zip-sign = ["flate2"] +ser = ["zip-sign", "serde"] +bin = ["ser", "bincode"] +cbor = ["ser", "serde_cbor"] +json = ["ser", "serde_json"] diff --git a/lib/tools/pkstl/src/agreement.rs b/lib/tools/pkstl/src/agreement.rs new file mode 100644 index 00000000..3bc6f370 --- /dev/null +++ b/lib/tools/pkstl/src/agreement.rs @@ -0,0 +1,211 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage cryptographic agreement operations. + +use crate::seeds::{Seed32, Seed48, Seed64}; +use crate::{Error, Result}; +use ring::{agreement, pbkdf2, rand}; +use std::num::NonZeroU32; + +const ITERATIONS: u32 = 3; + +#[derive(Clone, Copy, Debug)] +pub enum SharedSecretLen { + B32, + B48, + B64, +} + +impl SharedSecretLen { + fn algo(self) -> pbkdf2::Algorithm { + match self { + Self::B32 => pbkdf2::PBKDF2_HMAC_SHA256, + Self::B48 => pbkdf2::PBKDF2_HMAC_SHA384, + Self::B64 => pbkdf2::PBKDF2_HMAC_SHA512, + } + } +} + +pub enum SharedSecret { + B32(Seed32), + B48(Seed48), + B64(Seed64), +} + +impl AsMut<[u8]> for SharedSecret { + fn as_mut(&mut self) -> &mut [u8] { + match self { + Self::B32(seed) => seed.as_mut(), + Self::B48(seed) => seed.as_mut(), + Self::B64(seed) => seed.as_mut(), + } + } +} + +impl SharedSecret { + fn new(len: SharedSecretLen) -> Self { + match len { + SharedSecretLen::B32 => SharedSecret::B32(Seed32::default()), + SharedSecretLen::B48 => SharedSecret::B48(Seed48::default()), + SharedSecretLen::B64 => SharedSecret::B64(Seed64::default()), + } + } +} + +#[derive(Clone)] +/// Ephemeral public key used once to generate shared secret +pub struct EphemeralPublicKey(agreement::PublicKey); + +impl AsRef<[u8]> for EphemeralPublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +/// Ephemeral key pair used once to generate shared secret +pub struct EphemeralKeyPair { + privkey: agreement::EphemeralPrivateKey, + pubkey: EphemeralPublicKey, +} + +impl EphemeralKeyPair { + /// Generate ephemeral key pair + pub fn generate() -> Result<Self> { + let rng = rand::SystemRandom::new(); + let privkey = agreement::EphemeralPrivateKey::generate(&agreement::X25519, &rng) + .map_err(|_| Error::FailToGenEphemerKeyPair)?; + let pubkey = EphemeralPublicKey( + privkey + .compute_public_key() + .map_err(|_| Error::FailToGenEphemerPubKey)?, + ); + + Ok(EphemeralKeyPair { privkey, pubkey }) + } + /// Get ephemeral public key + pub fn public_key(&self) -> &EphemeralPublicKey { + &self.pubkey + } + /// Compute shared secret + pub fn compute_shared_secret( + self, + other_ephemeral_public_key: &[u8], + shared_secret_len: SharedSecretLen, + ) -> Result<SharedSecret> { + let salt = if self.pubkey.as_ref() > other_ephemeral_public_key { + self.pubkey.as_ref() + } else { + other_ephemeral_public_key + }; + + agreement::agree_ephemeral( + self.privkey, + &agreement::UnparsedPublicKey::new(&agreement::X25519, other_ephemeral_public_key), + Error::FailToComputeAgreement, + |key_material| Ok(derive(key_material, salt, shared_secret_len)), + ) + } +} + +fn derive(seed: &[u8], salt: &[u8], shared_secret_len: SharedSecretLen) -> SharedSecret { + let mut shared_secret = SharedSecret::new(shared_secret_len); + pbkdf2::derive( + shared_secret_len.algo(), + NonZeroU32::new(ITERATIONS).expect("ITERATIONS must be > 0"), + salt, + seed, + shared_secret.as_mut(), + ); + shared_secret +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_exchange_dh_shared_secret_48b() -> Result<()> { + let ephemeral_kp_server = EphemeralKeyPair::generate()?; + let ephemeral_kp_client = EphemeralKeyPair::generate()?; + + let ephemeral_pk_server = ephemeral_kp_server.public_key().clone(); + let ephemeral_pk_client = ephemeral_kp_client.public_key().clone(); + + // Sharer secret of 48 bytes + let mut shared_secret_server_48b = ephemeral_kp_server.compute_shared_secret( + ephemeral_kp_client.public_key().as_ref(), + SharedSecretLen::B48, + )?; + + let mut shared_secret_client_48b = ephemeral_kp_client + .compute_shared_secret(ephemeral_pk_server.as_ref(), SharedSecretLen::B48)?; + + assert_eq!( + shared_secret_server_48b.as_mut().to_vec(), + shared_secret_client_48b.as_mut().to_vec() + ); + + println!("ephemeral_pk_server={:?}", ephemeral_pk_server.as_ref()); + println!("ephemeral_pk_client={:?}", ephemeral_pk_client.as_ref()); + println!( + "shared_secret_server={:?}", + shared_secret_server_48b.as_mut() + ); + println!( + "shared_secret_client={:?}", + shared_secret_client_48b.as_mut() + ); + + Ok(()) + } + + #[test] + fn test_exchange_dh_shared_secret_64b() -> Result<()> { + let ephemeral_kp_server = EphemeralKeyPair::generate()?; + let ephemeral_kp_client = EphemeralKeyPair::generate()?; + + let ephemeral_pk_server = ephemeral_kp_server.public_key().clone(); + let ephemeral_pk_client = ephemeral_kp_client.public_key().clone(); + + // Sharer secret of 64 bytes + let mut shared_secret_server_64b = ephemeral_kp_server.compute_shared_secret( + ephemeral_kp_client.public_key().as_ref(), + SharedSecretLen::B64, + )?; + + let mut shared_secret_client_64b = ephemeral_kp_client + .compute_shared_secret(ephemeral_pk_server.as_ref(), SharedSecretLen::B64)?; + + assert_eq!( + shared_secret_server_64b.as_mut().to_vec(), + shared_secret_client_64b.as_mut().to_vec() + ); + + println!("ephemeral_pk_server={:?}", ephemeral_pk_server.as_ref()); + println!("ephemeral_pk_client={:?}", ephemeral_pk_client.as_ref()); + println!( + "shared_secret_server={:?}", + shared_secret_server_64b.as_mut() + ); + println!( + "shared_secret_client={:?}", + shared_secret_client_64b.as_mut() + ); + + Ok(()) + } +} diff --git a/lib/tools/pkstl/src/complete.rs b/lib/tools/pkstl/src/complete.rs new file mode 100644 index 00000000..dc04d7b5 --- /dev/null +++ b/lib/tools/pkstl/src/complete.rs @@ -0,0 +1,268 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage complete secure and decentralized transport layer. + +pub mod message; +#[cfg(feature = "ser")] +pub mod serde; +pub mod writer; + +#[cfg(feature = "ser")] +pub use self::serde::IncomingMessage; + +use crate::{Error, Message, MinimalSecureLayer, Result, SdtlConfig, Seed32}; +use flate2::write::{DeflateDecoder, DeflateEncoder}; +use message::IncomingBinaryMessage; +use ring::signature::Ed25519KeyPair; +use std::io::{BufWriter, Write}; + +#[cfg(feature = "ser")] +use ::serde::de::DeserializeOwned; +#[cfg(feature = "ser")] +use ::serde::Serialize; +#[cfg(feature = "ser")] +use std::fmt::Debug; + +/// Secure layer +pub struct SecureLayer { + config: SdtlConfig, + minimal_secure_layer: MinimalSecureLayer, + sig_key_pair: Ed25519KeyPair, +} + +impl SecureLayer { + /// Change configuration + #[inline] + pub fn change_config(&mut self, new_config: SdtlConfig) { + self.config = new_config; + self.minimal_secure_layer.change_config(new_config.minimal); + } + fn compress(&self, bin_message: &[u8]) -> Result<Vec<u8>> { + // Create buffer + let buffer = BufWriter::new(Vec::with_capacity(bin_message.len())); + + // Determine compression level + let compression_level = if bin_message.len() < self.config.compression_min_size { + flate2::Compression::none() + } else { + self.config.compression + }; + + // Create compressor + let mut deflate_encoder = DeflateEncoder::new(buffer, compression_level); + + // Write message in compressor buffer + deflate_encoder + .write_all(&bin_message[..]) + .map_err(Error::ZipError)?; + + // Finalize compression + let bin_msg_compressed: BufWriter<Vec<u8>> = + deflate_encoder.finish().map_err(Error::ZipError)?; + + // Flush buffer + let bin_msg_compressed = bin_msg_compressed + .into_inner() + .map_err(|_| Error::BufferFlushError)?; + + Ok(bin_msg_compressed) + } + /// Create secure layer + #[inline] + pub fn create( + config: SdtlConfig, + sig_key_pair_seed: Option<Seed32>, + expected_remote_sig_pubkey: Option<Vec<u8>>, + ) -> Result<Self> { + let seed = sig_key_pair_seed.unwrap_or_else(Seed32::random); + + let secure_layer = SecureLayer { + config, + minimal_secure_layer: MinimalSecureLayer::create( + config.minimal, + expected_remote_sig_pubkey, + )?, + sig_key_pair: Ed25519KeyPair::from_seed_unchecked(seed.as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)?, + }; + + Ok(secure_layer) + } + /// Read binary incoming datas + pub fn read_bin(&mut self, incoming_datas: &[u8]) -> Result<Option<IncomingBinaryMessage>> { + let message_opt = self.minimal_secure_layer.read(incoming_datas)?; + + if let Some(message) = message_opt { + let user_message = match message { + Message::Connect { + custom_datas, + sig_pubkey, + .. + } => IncomingBinaryMessage::Connect { + custom_datas: if let Some(custom_datas) = custom_datas { + Some(Self::uncompress(&custom_datas)?) + } else { + None + }, + peer_sig_public_key: sig_pubkey, + }, + Message::Ack { custom_datas } => IncomingBinaryMessage::Ack { + custom_datas: if let Some(custom_datas) = custom_datas { + Some(Self::uncompress(&custom_datas)?) + } else { + None + }, + }, + Message::Message { custom_datas } => IncomingBinaryMessage::Message { + datas: if let Some(custom_datas) = custom_datas { + Some(Self::uncompress(&custom_datas)?) + } else { + None + }, + }, + }; + Ok(Some(user_message)) + } else { + Ok(None) + } + } + /// Read incoming datas + #[cfg(feature = "ser")] + #[inline] + pub fn read<M>(&mut self, incoming_datas: &[u8]) -> Result<Option<IncomingMessage<M>>> + where + M: Debug + DeserializeOwned, + { + self::serde::deserializer::read::<M>(self, incoming_datas) + } + fn uncompress(bin_zip_msg: &[u8]) -> Result<Vec<u8>> { + let mut deflate_decoder = DeflateDecoder::new(Vec::with_capacity(bin_zip_msg.len() * 5)); + deflate_decoder + .write_all(&bin_zip_msg) + .map_err(Error::ZipError)?; + deflate_decoder.finish().map_err(Error::ZipError) + } + /// Write ack message with optional binary custom datas + pub fn write_ack_msg_bin<W>( + &mut self, + custom_datas: Option<&[u8]>, + writer: &mut BufWriter<W>, + ) -> Result<()> + where + W: Write, + { + // Serialize and compress custom datas + let custom_datas = if let Some(custom_datas) = custom_datas { + Some(self.compress(custom_datas)?) + } else { + None + }; + + writer::write_ack_msg::<W>(self, custom_datas, writer) + } + /// Write ack message with optional custom datas + #[cfg(feature = "ser")] + #[inline] + pub fn write_ack_msg<M, W>( + &mut self, + custom_datas: Option<&M>, + writer: &mut BufWriter<W>, + ) -> Result<()> + where + M: Serialize, + W: Write, + { + self::serde::serializer::write_ack_msg::<M, W>(self, custom_datas, writer) + } + /// Write connect message with optional binary custom datas + pub fn write_connect_msg_bin<W>( + &mut self, + custom_datas: Option<&[u8]>, + writer: &mut BufWriter<W>, + ) -> Result<()> + where + W: Write, + { + // Compress custom datas + let custom_datas = if let Some(custom_datas) = custom_datas { + Some(self.compress(custom_datas)?) + } else { + None + }; + + writer::write_connect_msg(self, custom_datas, writer) + } + /// Write connect message with optional custom datas + #[cfg(feature = "ser")] + #[inline] + pub fn write_connect_msg<M, W>( + &mut self, + custom_datas: Option<&M>, + writer: &mut BufWriter<W>, + ) -> Result<()> + where + M: Serialize, + W: Write, + { + self::serde::serializer::write_connect_msg::<M, W>(self, custom_datas, writer) + } + /*/// Split secure layer in writer and reader + pub fn split(self) -> Result<(SecureWriter, SecureReader)> { + unimplemented!() + }*/ + /// Write message on a writer + #[cfg(feature = "ser")] + #[inline] + pub fn write<M, W>(&mut self, message: &M, writer: &mut BufWriter<W>) -> Result<()> + where + M: Serialize, + W: Write, + { + self::serde::serializer::write_message::<M, W>(self, message, writer) + } + /// Write binary message on a writer + pub fn write_bin<W>(&mut self, binary_message: &[u8], writer: &mut BufWriter<W>) -> Result<()> + where + W: Write, + { + // Compress message + let bin_zip_msg = self.compress(&binary_message[..])?; + + writer::write_bin_message::<W>(self, &bin_zip_msg, writer) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[cfg(feature = "ser")] + use crate::MessageFormat; + use crate::SdtlMinimalConfig; + + #[test] + fn test_change_config() -> Result<()> { + let mut msl = SecureLayer::create(SdtlConfig::default(), None, None)?; + msl.change_config(SdtlConfig { + compression: flate2::Compression::fast(), + compression_min_size: 8_192, + #[cfg(feature = "ser")] + message_format: MessageFormat::RawBinary, + minimal: SdtlMinimalConfig::default(), + }); + Ok(()) + } +} diff --git a/lib/tools/pkstl/src/complete/message.rs b/lib/tools/pkstl/src/complete/message.rs new file mode 100644 index 00000000..c14295e8 --- /dev/null +++ b/lib/tools/pkstl/src/complete/message.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage complete Public Key Secure Transport Layer. +//! Sub-module define incoming messages format. + +/// Incoming binary Message +#[derive(Debug)] +pub enum IncomingBinaryMessage { + /// Connect message + Connect { + /// Your custom datas + custom_datas: Option<Vec<u8>>, + /// Peer public key of signature algorithm + peer_sig_public_key: Vec<u8>, + }, + /// Ack message + Ack { + /// Your custom datas + custom_datas: Option<Vec<u8>>, + }, + /// Message + Message { + /// Message datas (This is an option because it's possible to receive an empty message) + datas: Option<Vec<u8>>, + }, +} diff --git a/lib/tools/pkstl/src/complete/serde.rs b/lib/tools/pkstl/src/complete/serde.rs new file mode 100644 index 00000000..d2723fda --- /dev/null +++ b/lib/tools/pkstl/src/complete/serde.rs @@ -0,0 +1,63 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage complete secure and decentralized transport layer with serialization/deserialization. + +use serde::de::DeserializeOwned; +use std::fmt::Debug; + +pub mod deserializer; +pub mod serializer; + +const HEADER_FORMAT_LEN: usize = 4; + +/// Incoming Message +#[derive(Debug)] +pub enum IncomingMessage<M: Debug + DeserializeOwned> { + /// Connect message + Connect { + /// Your custom datas + custom_datas: Option<M>, + /// Peer public key of signature algorithm + peer_sig_public_key: Vec<u8>, + }, + /// Ack message + Ack { + /// Your custom datas + custom_datas: Option<M>, + }, + /// Message + Message { + /// Message datas (This is an option because it's possible to receive an empty message) + datas: Option<M>, + }, +} + +#[derive(Debug)] +pub enum SerdeError { + #[cfg(feature = "bin")] + /// Bincode error + BincodeError(String), + #[cfg(feature = "cbor")] + /// Cbor error + CborError(serde_cbor::error::Error), + #[cfg(feature = "json")] + /// Json error + JsonError(serde_json::Error), + /// For the "raw binary" format, use the functions suffixed by _bin + UseSuffixedBinFunctions, + /// Not copyable error for linter + _IoError(std::io::Error), +} diff --git a/lib/tools/pkstl/src/complete/serde/deserializer.rs b/lib/tools/pkstl/src/complete/serde/deserializer.rs new file mode 100644 index 00000000..520b3fda --- /dev/null +++ b/lib/tools/pkstl/src/complete/serde/deserializer.rs @@ -0,0 +1,106 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define PKSTL deserializer. + +use super::SerdeError; +use super::{IncomingMessage, HEADER_FORMAT_LEN}; +use crate::format::MessageFormat; +use crate::{Error, IncomingBinaryMessage, Result, SecureLayer}; +use serde::de::DeserializeOwned; +use std::convert::TryFrom; +use std::fmt::Debug; + +pub(crate) fn read<M>( + sl: &mut SecureLayer, + incoming_datas: &[u8], +) -> Result<Option<IncomingMessage<M>>> +where + M: Debug + DeserializeOwned, +{ + let bin_msg_opt = sl.read_bin(incoming_datas)?; + + if let Some(bin_msg) = bin_msg_opt { + let user_message = match bin_msg { + IncomingBinaryMessage::Connect { + custom_datas, + peer_sig_public_key, + } => IncomingMessage::Connect { + custom_datas: if let Some(custom_datas) = custom_datas { + Some(deserialize(&custom_datas)?) + } else { + None + }, + peer_sig_public_key, + }, + IncomingBinaryMessage::Ack { custom_datas } => IncomingMessage::Ack { + custom_datas: if let Some(custom_datas) = custom_datas { + Some(deserialize(&custom_datas)?) + } else { + None + }, + }, + IncomingBinaryMessage::Message { datas } => IncomingMessage::Message { + datas: if let Some(datas) = datas { + Some(deserialize(&datas)?) + } else { + None + }, + }, + }; + Ok(Some(user_message)) + } else { + Ok(None) + } +} + +#[inline] +fn deserialize<M: Debug + DeserializeOwned>(binary_message: &[u8]) -> Result<M> { + if binary_message.len() < HEADER_FORMAT_LEN { + return Err(Error::RecvInvalidMsg( + crate::errors::IncomingMsgErr::MessageTooShort, + )); + } + + // Read format + let message_format = MessageFormat::try_from(&binary_message[..HEADER_FORMAT_LEN])?; + + deserialize_inner(&binary_message[HEADER_FORMAT_LEN..], message_format) + .map_err(Error::SerdeError) +} + +pub fn deserialize_inner<M>( + binary_message: &[u8], + message_format: MessageFormat, +) -> std::result::Result<M, SerdeError> +where + M: Debug + DeserializeOwned, +{ + match message_format { + MessageFormat::RawBinary => Err(SerdeError::UseSuffixedBinFunctions), + #[cfg(feature = "bin")] + MessageFormat::Bincode => Ok(bincode::deserialize::<M>(binary_message) + .map_err(|e| SerdeError::BincodeError(format!("{}", e)))?), + #[cfg(feature = "cbor")] + MessageFormat::Cbor => { + Ok(serde_cbor::from_slice::<M>(binary_message).map_err(SerdeError::CborError)?) + } + #[cfg(feature = "json")] + MessageFormat::Utf8Json => { + Ok(serde_json::from_slice::<M>(binary_message).map_err(SerdeError::JsonError)?) + } + _ => unimplemented!(), + } +} diff --git a/lib/tools/pkstl/src/complete/serde/serializer.rs b/lib/tools/pkstl/src/complete/serde/serializer.rs new file mode 100644 index 00000000..5a89e846 --- /dev/null +++ b/lib/tools/pkstl/src/complete/serde/serializer.rs @@ -0,0 +1,121 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define PKSTL serializer. + +use super::SerdeError; +use crate::format::MessageFormat; +use crate::{Error, Result, SecureLayer}; +use serde::Serialize; +use std::io::{BufWriter, Write}; + +pub(crate) fn write_connect_msg<M, W>( + sl: &mut SecureLayer, + custom_datas: Option<&M>, + writer: &mut BufWriter<W>, +) -> Result<()> +where + M: Serialize, + W: Write, +{ + // Serialize and compress custom datas + let custom_datas = if let Some(custom_datas) = custom_datas { + let bin_msg = serialize(custom_datas, sl.config.message_format)?; + Some(sl.compress(&bin_msg[..])?) + } else { + None + }; + + // Write binary message on a writer + crate::complete::writer::write_connect_msg::<W>(sl, custom_datas, writer) +} + +pub(crate) fn write_ack_msg<M, W>( + sl: &mut SecureLayer, + custom_datas: Option<&M>, + writer: &mut BufWriter<W>, +) -> Result<()> +where + M: Serialize, + W: Write, +{ + // Serialize and compress custom datas + let custom_datas = if let Some(custom_datas) = custom_datas { + let bin_msg = serialize(custom_datas, sl.config.message_format)?; + Some(sl.compress(&bin_msg[..])?) + } else { + None + }; + + // Write binary message on a writer + crate::complete::writer::write_ack_msg::<W>(sl, custom_datas, writer) +} + +pub(crate) fn write_message<M, W>( + sl: &mut SecureLayer, + message: &M, + writer: &mut BufWriter<W>, +) -> Result<()> +where + M: Serialize, + W: Write, +{ + // Serialize message + let bin_msg = serialize(message, sl.config.message_format)?; + + // Compress message + let bin_zip_msg = sl.compress(&bin_msg[..])?; + + // Write binary message on a writer + crate::complete::writer::write_bin_message::<W>(sl, &bin_zip_msg, writer) +} + +pub fn serialize<M>(message: &M, message_format: MessageFormat) -> Result<Vec<u8>> +where + M: Serialize, +{ + let mut writer = BufWriter::new(Vec::with_capacity(1_024)); + writer + .write(message_format.as_ref()) + .map_err(Error::WriteError)?; + serialize_inner(message, message_format, &mut writer).map_err(Error::SerdeError)?; + writer.into_inner().map_err(|_| Error::BufferFlushError) +} + +pub fn serialize_inner<M, W>( + message: &M, + message_format: MessageFormat, + writer: &mut W, +) -> std::result::Result<(), SerdeError> +where + M: Serialize, + W: Write, +{ + match message_format { + MessageFormat::RawBinary => Err(SerdeError::UseSuffixedBinFunctions), + #[cfg(feature = "bin")] + MessageFormat::Bincode => Ok(bincode::serialize_into(writer, message) + .map_err(|e| SerdeError::BincodeError(format!("{}", e)))?), + #[cfg(feature = "cbor")] + MessageFormat::Cbor => { + Ok(serde_cbor::to_writer(writer, message).map_err(SerdeError::CborError)?) + } + #[cfg(feature = "json")] + MessageFormat::Utf8Json => { + Ok(serde_json::to_writer(writer, message).map_err(SerdeError::JsonError)?) + } + _ => unimplemented!(), + } +} diff --git a/lib/tools/pkstl/src/complete/writer.rs b/lib/tools/pkstl/src/complete/writer.rs new file mode 100644 index 00000000..34d8a9b4 --- /dev/null +++ b/lib/tools/pkstl/src/complete/writer.rs @@ -0,0 +1,102 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage complete Public Key Secure Transport Layer. +//! Sub-module define write operations. + +use super::SecureLayer; +use crate::{Error, Result}; +use ring::signature::KeyPair; +use std::io::{BufWriter, Write}; + +#[inline] +pub fn write_connect_msg<W>( + sl: &mut SecureLayer, + custom_datas: Option<Vec<u8>>, + writer: &mut BufWriter<W>, +) -> Result<()> +where + W: Write, +{ + // Create connect message + let bin_connect_msg = sl.minimal_secure_layer.create_connect_message( + sl.sig_key_pair.public_key().as_ref(), + match custom_datas { + Some(ref d) => Some(&d[..]), + None => None, + }, + )?; + + // Write connect message + writer + .write(&bin_connect_msg) + .map_err(|_| Error::BufferFlushError)?; + + // Sign message and write signature + sign_bin_msg_and_write_sig(sl, &bin_connect_msg, writer) +} + +#[inline] +pub fn write_ack_msg<W>( + sl: &mut SecureLayer, + custom_datas: Option<Vec<u8>>, + writer: &mut BufWriter<W>, +) -> Result<()> +where + W: Write, +{ + // Create ack message + let bin_connect_msg = sl + .minimal_secure_layer + .create_ack_message(match custom_datas { + Some(ref d) => Some(&d[..]), + None => None, + })?; + + // Write ack message + writer + .write(&bin_connect_msg) + .map_err(|_| Error::BufferFlushError)?; + + // Sign message and write signature + sign_bin_msg_and_write_sig(sl, &bin_connect_msg, writer) +} + +#[inline] +fn sign_bin_msg_and_write_sig<W>( + sl: &mut SecureLayer, + bin_msg: &[u8], + writer: &mut BufWriter<W>, +) -> Result<()> +where + W: Write, +{ + writer + .write(sl.sig_key_pair.sign(bin_msg).as_ref()) + .map(|_| ()) + .map_err(|_| Error::BufferFlushError) +} + +#[inline] +pub fn write_bin_message<W>( + sl: &mut SecureLayer, + message: &[u8], + writer: &mut BufWriter<W>, +) -> Result<()> +where + W: Write, +{ + sl.minimal_secure_layer.write_message(message, writer) +} diff --git a/lib/tools/pkstl/src/config.rs b/lib/tools/pkstl/src/config.rs new file mode 100644 index 00000000..c80949b0 --- /dev/null +++ b/lib/tools/pkstl/src/config.rs @@ -0,0 +1,78 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage PKSTL configuration. + +use crate::encryption::EncryptAlgo; + +#[cfg(feature = "zip-sign")] +const DEFAULT_COMPRESSION_MIN_SIZE: usize = 8_192; + +#[cfg(feature = "ser")] +use crate::format::MessageFormat; + +#[cfg(feature = "zip-sign")] +#[derive(Clone, Copy, Debug, PartialEq)] +/// PKSTL Configuration +pub struct SdtlConfig { + /// Compression level + pub compression: flate2::Compression, + /// Compression minimal size in bytes + pub compression_min_size: usize, + #[cfg(feature = "ser")] + /// Message format + pub message_format: MessageFormat, + /// PKSTL minimum Configuration + pub minimal: SdtlMinimalConfig, +} + +impl Default for SdtlConfig { + fn default() -> Self { + SdtlConfig { + compression: flate2::Compression::fast(), + compression_min_size: DEFAULT_COMPRESSION_MIN_SIZE, + #[cfg(feature = "ser")] + message_format: MessageFormat::default(), + minimal: SdtlMinimalConfig::default(), + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +/// PKSTL minimum Configuration +pub struct SdtlMinimalConfig { + /// Encryption algorithm + pub encrypt_algo: EncryptAlgo, +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_default_config() { + assert_eq!( + SdtlConfig { + compression: flate2::Compression::fast(), + compression_min_size: DEFAULT_COMPRESSION_MIN_SIZE, + #[cfg(feature = "ser")] + message_format: MessageFormat::default(), + minimal: SdtlMinimalConfig::default(), + }, + SdtlConfig::default() + ) + } +} diff --git a/lib/tools/pkstl/src/constants.rs b/lib/tools/pkstl/src/constants.rs new file mode 100644 index 00000000..be53fe19 --- /dev/null +++ b/lib/tools/pkstl/src/constants.rs @@ -0,0 +1,49 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Declare PKSTL constants. + +/// Sig algo length +pub const SIG_ALGO_LEN: usize = 4; + +/// Current version +pub(crate) const CURRENT_VERSION: [u8; 4] = [0, 0, 0, 1]; + +/// Challenge size +pub(crate) const CHALLENGE_SIZE: usize = 32; + +/// Hash size +pub(crate) const HASH_SIZE: usize = 32; + +/// Ephemeral public key size +pub(crate) const EPK_SIZE: usize = 32; + +/// Magic value (at the beginning of all messages) +pub(crate) const MAGIC_VALUE: [u8; 4] = [0xE2, 0xC2, 0xE2, 0xD2]; + +/// Message type length +pub(crate) const MSG_TYPE_LEN: usize = 2; + +/// User message type +pub(crate) const USER_MSG_TYPE: &[u8] = &[0, 0]; + +/// Connect message type +pub(crate) const CONNECT_MSG_TYPE: &[u8] = &[0, 1]; + +/// Ack message type +pub(crate) const ACK_MSG_TYPE: &[u8] = &[0, 2]; + +/// Sig pubkey begin +pub(crate) const SIG_PUBKEY_BEGIN: usize = MSG_TYPE_LEN + EPK_SIZE + SIG_ALGO_LEN; diff --git a/lib/tools/pkstl/src/digest.rs b/lib/tools/pkstl/src/digest.rs new file mode 100644 index 00000000..39b79550 --- /dev/null +++ b/lib/tools/pkstl/src/digest.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage cryptographic digest operations. + +pub(crate) fn sha256(datas: &[u8]) -> impl AsRef<[u8]> { + ring::digest::digest(&ring::digest::SHA256, datas) +} diff --git a/lib/tools/pkstl/src/encryption.rs b/lib/tools/pkstl/src/encryption.rs new file mode 100644 index 00000000..b5da36a7 --- /dev/null +++ b/lib/tools/pkstl/src/encryption.rs @@ -0,0 +1,158 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage cryptographic encryption operations. + +mod chacha20_poly1305_aead; + +use crate::agreement::{SharedSecret, SharedSecretLen}; +use crate::Result; +use std::io::{BufWriter, Read, Write}; + +/// Encryption algorithm +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum EncryptAlgo { + /// ChaCha20 stream cipher use the Poly1305 authenticator with Associated Data (AEAD) algorithm (see https://tools.ietf.org/html/rfc7539). + Chacha20Poly1305Aead, +} + +impl Default for EncryptAlgo { + fn default() -> Self { + Self::Chacha20Poly1305Aead + } +} + +impl EncryptAlgo { + pub(crate) fn shared_secret_len(self) -> SharedSecretLen { + match self { + Self::Chacha20Poly1305Aead => SharedSecretLen::B48, + } + } +} + +pub enum EncryptAlgoWithSecretKey { + Chacha20Poly1305Aead(chacha20_poly1305_aead::SecretKey), +} + +impl EncryptAlgoWithSecretKey { + pub fn build(encrypt_algo: EncryptAlgo, shared_secret: SharedSecret) -> Self { + match encrypt_algo { + EncryptAlgo::Chacha20Poly1305Aead => { + if let SharedSecret::B48(seed) = shared_secret { + Self::Chacha20Poly1305Aead(chacha20_poly1305_aead::SecretKey::new(&seed)) + } else { + panic!("dev error: EncryptAlgo::Chacha20Poly1305Aead must request shared secret of 48 bytes !") + } + } + } + } +} + +#[inline] +pub(crate) fn decrypt<W: Write>( + encrypted_datas: &[u8], + algo_with_secret_key: &EncryptAlgoWithSecretKey, + writer: &mut BufWriter<W>, +) -> Result<()> { + match algo_with_secret_key { + EncryptAlgoWithSecretKey::Chacha20Poly1305Aead(secret_key) => { + chacha20_poly1305_aead::decrypt(encrypted_datas, secret_key, writer) + } + } +} + +/// Encrypt datas +#[inline] +pub(crate) fn encrypt<R: Read, W: Write>( + reader: &mut R, + algo_with_secret_key: &EncryptAlgoWithSecretKey, + writer: &mut BufWriter<W>, +) -> Result<()> { + match algo_with_secret_key { + EncryptAlgoWithSecretKey::Chacha20Poly1305Aead(secret_key) => { + chacha20_poly1305_aead::encrypt(reader, secret_key, writer) + } + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use crate::seeds::{tests::random_seed_48, Seed32, Seed48}; + + pub fn gen_random_encrypt_algo_with_secret() -> EncryptAlgoWithSecretKey { + let random_shared_secret = SharedSecret::B48(random_seed_48()); + EncryptAlgoWithSecretKey::build(EncryptAlgo::Chacha20Poly1305Aead, random_shared_secret) + } + + #[test] + fn test_default() { + assert_eq!(EncryptAlgo::Chacha20Poly1305Aead, EncryptAlgo::default()); + } + + #[test] + #[should_panic( + expected = "dev error: EncryptAlgo::Chacha20Poly1305Aead must request shared secret of 48 bytes !" + )] + fn test_encryption_with_wrong_shared_secret_len() { + let shared_secret = SharedSecret::B32(Seed32::new([ + 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + ])); + EncryptAlgoWithSecretKey::build(EncryptAlgo::Chacha20Poly1305Aead, shared_secret); + } + + #[test] + fn test_encryption_ok() -> Result<()> { + let datas = b"My secret datas".to_vec(); + + let shared_secret = SharedSecret::B48(Seed48::new([ + 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, + ])); + let encrypt_algo_with_secret_key = + EncryptAlgoWithSecretKey::build(EncryptAlgo::Chacha20Poly1305Aead, shared_secret); + + let mut encrypted_datas = BufWriter::new(Vec::with_capacity(datas.len())); + + encrypt( + &mut &datas[..], + &encrypt_algo_with_secret_key, + &mut encrypted_datas, + )?; + let encrypted_datas = encrypted_datas + .into_inner() + .expect("fail to flush encrypt buffer"); + + let mut decrypted_datas = BufWriter::new(Vec::with_capacity(datas.len())); + decrypt( + &encrypted_datas, + &encrypt_algo_with_secret_key, + &mut decrypted_datas, + )?; + let decrypted_datas = decrypted_datas + .into_inner() + .expect("fail to flush decrypt buffer"); + + println!("encrypted_datas={:?}", encrypted_datas); + println!("decrypted_datas={:?}", decrypted_datas); + + assert_eq!(datas, decrypted_datas); + + Ok(()) + } +} diff --git a/lib/crypto/src/encryption.rs b/lib/tools/pkstl/src/encryption/chacha20_poly1305_aead.rs similarity index 53% rename from lib/crypto/src/encryption.rs rename to lib/tools/pkstl/src/encryption/chacha20_poly1305_aead.rs index 33a0eb74..9dd33c0c 100644 --- a/lib/crypto/src/encryption.rs +++ b/lib/tools/pkstl/src/encryption/chacha20_poly1305_aead.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// Copyright (C) 2019 Eloïs SANCHEZ. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as @@ -13,12 +13,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -//! Manage cryptographic encryption operations. +//! Manage cryptographic encryption operations with Chacha20Poly1305Aead algorithm. -use crate::errors::CryptoError; use crate::seeds::Seed48; +use crate::{Error, Result}; use clear_on_drop::clear::Clear; -use std::io::Read; +use std::io::{BufWriter, Read, Write}; const CHACHA20_TAG_SIZE: usize = 16; @@ -33,9 +33,9 @@ pub struct SecretKey { impl Drop for SecretKey { #[inline] fn drop(&mut self) { - self.key.clear(); - self.nonce.clear(); - self.aad.clear(); + <[u8; 32] as Clear>::clear(&mut self.key); + <[u8; 12] as Clear>::clear(&mut self.nonce); + <[u8; 4] as Clear>::clear(&mut self.aad); } } @@ -53,85 +53,76 @@ impl SecretKey { } /// Decrypt datas -pub fn decrypt(encrypted_datas: &[u8], secret_key: &SecretKey) -> Result<Vec<u8>, CryptoError> { +pub fn decrypt<W: Write>( + encrypted_datas: &[u8], + secret_key: &SecretKey, + writer: &mut BufWriter<W>, +) -> Result<()> { let payload_len = encrypted_datas.len() - CHACHA20_TAG_SIZE; - let mut decrypted_datas = Vec::with_capacity(payload_len); - chacha20_poly1305_aead::decrypt( &secret_key.key, &secret_key.nonce, &secret_key.aad, &encrypted_datas[0..payload_len], &encrypted_datas[payload_len..], - &mut decrypted_datas, + writer, ) - .map_err(CryptoError::FailToDecryptDatas)?; + .map_err(Error::FailToDecryptDatas)?; - Ok(decrypted_datas) + Ok(()) } /// Encrypt datas -pub fn encrypt(datas: &[u8], secret_key: &SecretKey) -> Result<Vec<u8>, CryptoError> { - let mut encrypted_datas = Vec::with_capacity(datas.len() + CHACHA20_TAG_SIZE); - - let tag = chacha20_poly1305_aead::encrypt( - &secret_key.key, - &secret_key.nonce, - &secret_key.aad, - datas, - &mut encrypted_datas, - ) - .map_err(CryptoError::FailToEncryptDatas)?; - - encrypted_datas.append(&mut tag.to_vec()); - - Ok(encrypted_datas) -} - -/// Encrypt datas from reader -pub fn encrypt_read<R: Read>( - datas_max_size: usize, +pub fn encrypt<R: Read, W: Write>( reader: &mut R, secret_key: &SecretKey, -) -> Result<Vec<u8>, CryptoError> { - let mut encrypted_datas = Vec::with_capacity(datas_max_size + CHACHA20_TAG_SIZE); - + writer: &mut BufWriter<W>, +) -> Result<()> { let tag = chacha20_poly1305_aead::encrypt_read( &secret_key.key, &secret_key.nonce, &secret_key.aad, reader, - &mut encrypted_datas, + writer, ) - .map_err(CryptoError::FailToEncryptDatas)?; + .map_err(Error::FailToEncryptDatas)?; - encrypted_datas.append(&mut tag.to_vec()); + writer + .write(&tag.to_vec()) + .map_err(Error::FailToEncryptDatas)?; - Ok(encrypted_datas) + Ok(()) } #[cfg(test)] mod tests { use super::*; + use crate::seeds::Seed48; #[test] - fn test_encryption() -> Result<(), CryptoError> { + fn test_encryption() -> Result<()> { let datas = b"My secret datas".to_vec(); - let secret_key = SecretKey::new(&Seed48::random()); + let secret_key = SecretKey::new(&Seed48::new([ + 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, + ])); - let encrypted_datas = encrypt(&datas, &secret_key)?; - let decrypted_datas = decrypt(&encrypted_datas, &secret_key)?; + let mut encrypted_datas = BufWriter::new(Vec::with_capacity(datas.len())); - println!("encrypted_datas={:?}", encrypted_datas); - println!("decrypted_datas={:?}", decrypted_datas); - - assert_eq!(datas, decrypted_datas); + encrypt(&mut &datas[..], &secret_key, &mut encrypted_datas)?; + let encrypted_datas = encrypted_datas + .into_inner() + .expect("fail to flush encrypt buffer"); - let encrypted_datas = encrypt_read(datas.len(), &mut &datas[..], &secret_key)?; - let decrypted_datas = decrypt(&encrypted_datas, &secret_key)?; + let mut decrypted_datas = BufWriter::new(Vec::with_capacity(datas.len())); + decrypt(&encrypted_datas, &secret_key, &mut decrypted_datas)?; + let decrypted_datas = decrypted_datas + .into_inner() + .expect("fail to flush decrypt buffer"); println!("encrypted_datas={:?}", encrypted_datas); println!("decrypted_datas={:?}", decrypted_datas); diff --git a/lib/tools/pkstl/src/errors.rs b/lib/tools/pkstl/src/errors.rs new file mode 100644 index 00000000..d7069ed0 --- /dev/null +++ b/lib/tools/pkstl/src/errors.rs @@ -0,0 +1,106 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage Secure and decentralized transport layer errors. + +/// PKSTL Error +#[derive(Debug)] +pub enum Error { + /// Error when flush writer buffer + BufferFlushError, + /// The connection had already failed earlier + ConnectionHadFail, + /// Connect msg already written + ConnectMsgAlreadyWritten, + /// Fail to compute agreement + FailToComputeAgreement, + /// Fail to decrypt datas + FailToDecryptDatas(chacha20_poly1305_aead::DecryptError), + /// Fail to encrypt datas + FailToEncryptDatas(std::io::Error), + /// Fail to generate ephemeral key pair + FailToGenEphemerKeyPair, + /// Fail to generate ephemeral public key + FailToGenEphemerPubKey, + /// Fail to generate signature key pair + FailtoGenSigKeyPair, + /// Forbidden to write the ACK message now + ForbidWriteAckMsgNow, + /// Message must be signed + MessageMustBeSigned, + /// The negotiation must have been successful + NegoMustHaveBeenSuccessful, + #[cfg(feature = "ser")] + /// Error in serialization/deserialization + SerdeError(crate::complete::serde::SerdeError), + /// Serialization error + SerializationError(std::io::Error), + /// Tru to generate connect message too late + TryToGenConnectMsgTooLate, + /// Trying to write a message when the negotiation is not successful + TryToWriteMsgWhenNegoNotSuccessful, + /// Receive invalid message + RecvInvalidMsg(IncomingMsgErr), + /// Unexpected remote signature public key + UnexpectedRemoteSigPubKey, + /// Error on writer + WriteError(std::io::Error), + /// Written length error + WrittenLenError { + /// Expected + expected: usize, + /// Found + found: usize, + }, + #[cfg(feature = "zip-sign")] + /// Compression or decompression error + ZipError(std::io::Error), +} + +impl From<IncomingMsgErr> for Error { + fn from(e: IncomingMsgErr) -> Self { + Self::RecvInvalidMsg(e) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// Incoming message error +pub enum IncomingMsgErr { + /// Invalid challenge + InvalidChallenge, + /// Invalid hash or signature + InvalidHashOrSig, + /// Invalid magic value + InvalidMagicValue, + /// Message too short + MessageTooShort, + /// Unexpected a cck message + UnexpectedAckMsg, + /// Unexpected connect message + UnexpectedConnectMsg, + /// Unexpected user message + UnexpectedMessage, + /// Unexpected encryption state + /// It may be that the message is in clear when we expect it to be encrypted. + UnexpectedEncryptionState, + /// Unknown message format + UnknownMessageFormat, + /// Unknown message type + UnknownMessageType, + /// Unsupported signature algorithm + UnsupportedSigAlgo, + /// Unsupported version + UnsupportedVersion, +} diff --git a/lib/tools/pkstl/src/format.rs b/lib/tools/pkstl/src/format.rs new file mode 100644 index 00000000..cb85a459 --- /dev/null +++ b/lib/tools/pkstl/src/format.rs @@ -0,0 +1,156 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage PKSTL messages format. + +use crate::errors::IncomingMsgErr; +use std::convert::TryFrom; + +const RAW_BINARY: &[u8] = &[0, 0, 0, 0]; +const UTF8_PLAIN_TEXT: &[u8] = &[0, 0, 0, 1]; + +#[cfg(feature = "bin")] +const BINCODE: &[u8] = &[0, 0, 0, 4]; + +#[cfg(feature = "cbor")] +const CBOR: &[u8] = &[0, 0, 0, 3]; + +#[cfg(feature = "json")] +const UTF8_JSON: &[u8] = &[0, 0, 0, 2]; + +/// Message format +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MessageFormat { + /// raw binary + RawBinary, + /// UTF-8 plain text + Utf8PlainText, + #[cfg(feature = "json")] + /// UTF-8 JSON + Utf8Json, + #[cfg(feature = "cbor")] + /// CBOR (Binary JSON) + Cbor, + #[cfg(feature = "bin")] + /// Bincode + Bincode, +} + +impl Default for MessageFormat { + fn default() -> Self { + Self::RawBinary + } +} + +impl TryFrom<&[u8]> for MessageFormat { + type Error = IncomingMsgErr; + + fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { + match bytes { + #[cfg(feature = "bin")] + BINCODE => Ok(Self::Bincode), + #[cfg(feature = "cbor")] + CBOR => Ok(Self::Cbor), + RAW_BINARY => Ok(Self::RawBinary), + #[cfg(feature = "json")] + UTF8_JSON => Ok(Self::Utf8Json), + UTF8_PLAIN_TEXT => Ok(Self::Utf8PlainText), + _ => Err(IncomingMsgErr::UnknownMessageFormat), + } + } +} + +impl AsRef<[u8]> for MessageFormat { + fn as_ref(&self) -> &[u8] { + match self { + #[cfg(feature = "bin")] + Self::Bincode => BINCODE, + #[cfg(feature = "cbor")] + Self::Cbor => CBOR, + Self::RawBinary => RAW_BINARY, + #[cfg(feature = "json")] + Self::Utf8Json => UTF8_JSON, + Self::Utf8PlainText => UTF8_PLAIN_TEXT, + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_default() { + assert_eq!(MessageFormat::RawBinary, MessageFormat::default()); + } + + #[test] + fn test_messafe_format_as_ref() { + // RawBinary + assert_eq!(RAW_BINARY, MessageFormat::RawBinary.as_ref()); + + // Utf8PlainText + assert_eq!(UTF8_PLAIN_TEXT, MessageFormat::Utf8PlainText.as_ref()); + + // Utf8Json + #[cfg(feature = "json")] + assert_eq!(UTF8_JSON, MessageFormat::Utf8Json.as_ref()); + + // Cbor + #[cfg(feature = "cbor")] + assert_eq!(CBOR, MessageFormat::Cbor.as_ref()); + + // Bincode + #[cfg(feature = "bin")] + assert_eq!(BINCODE, MessageFormat::Bincode.as_ref()); + } + + #[test] + fn test_message_format_try_from() -> Result<(), IncomingMsgErr> { + // RawBinary + assert_eq!( + MessageFormat::RawBinary, + MessageFormat::try_from(RAW_BINARY)? + ); + + // Utf8PlainText + assert_eq!( + MessageFormat::Utf8PlainText, + MessageFormat::try_from(UTF8_PLAIN_TEXT)? + ); + + // Utf8Json + #[cfg(feature = "json")] + assert_eq!(MessageFormat::Utf8Json, MessageFormat::try_from(UTF8_JSON)?); + + // Cbor + #[cfg(feature = "cbor")] + assert_eq!(MessageFormat::Cbor, MessageFormat::try_from(CBOR)?); + + // Bincode + #[cfg(feature = "bin")] + assert_eq!(MessageFormat::Bincode, MessageFormat::try_from(BINCODE)?); + + // UnknownMessageFormat + let bytes = vec![0, 0, 0, 5]; + assert_eq!( + Err(IncomingMsgErr::UnknownMessageFormat), + MessageFormat::try_from(&bytes[..]), + ); + + Ok(()) + } +} diff --git a/lib/tools/pkstl/src/lib.rs b/lib/tools/pkstl/src/lib.rs new file mode 100644 index 00000000..09e8bc5b --- /dev/null +++ b/lib/tools/pkstl/src/lib.rs @@ -0,0 +1,100 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Public Key Secure Transport Layer. + +#![deny( + clippy::option_unwrap_used, + clippy::result_unwrap_used, + missing_docs, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unsafe_code, + unstable_features, + unused_import_braces, + unused_qualifications +)] + +mod agreement; +#[cfg(feature = "zip-sign")] +mod complete; +mod config; +mod constants; +mod digest; +mod encryption; +mod errors; +#[cfg(feature = "ser")] +mod format; +mod message; +mod minimal; +mod reader; +mod seeds; +mod signature; +mod status; + +pub use agreement::EphemeralPublicKey; +pub use config::{SdtlConfig, SdtlMinimalConfig}; +pub use encryption::EncryptAlgo; +pub use errors::Error; +pub use message::{EncapsuledMessage, Message}; +pub use minimal::MinimalSecureLayer; +pub use seeds::Seed32; +pub use signature::{SIG_ALGO_ED25519, SIG_ALGO_ED25519_ARRAY}; + +#[cfg(feature = "ser")] +pub use complete::IncomingMessage; +#[cfg(feature = "ser")] +pub use format::MessageFormat; + +#[cfg(feature = "zip-sign")] +pub use complete::message::IncomingBinaryMessage; +#[cfg(feature = "zip-sign")] +pub use complete::SecureLayer; + +/// PKSTL Result +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum LocalNegoThread { + Created, + ConnectMsgSent, + ValidAckMsgReceived, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum RemoteNegoThread { + WaitConnectMsg, + ValidConnectMsgReceived, + AckMsgSent, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum MsgType { + Connect, + Ack, + UserMsg, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Action { + Create(MsgType), + Receive(MsgType), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum ActionSideEffects { + PushUserMsgIntoTmpStack, +} diff --git a/lib/tools/pkstl/src/message.rs b/lib/tools/pkstl/src/message.rs new file mode 100644 index 00000000..0ecf9f4d --- /dev/null +++ b/lib/tools/pkstl/src/message.rs @@ -0,0 +1,494 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage PKSTL messages. + +use crate::constants::*; +use crate::digest::sha256; +use crate::{Error, Result}; +use std::io::{BufWriter, Write}; + +const CONNECT_MSG_TYPE_HEADERS_SIZE: usize = 70; +const ACK_MSG_TYPE_HEADERS_SIZE: usize = 34; + +const HEADERS_AND_FOOTERS_MAX_SIZE: usize = 136; + +/// Message +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Message { + /// Connect message + Connect { + /// Signature algorithm + sig_algo: [u8; SIG_ALGO_LEN], + /// Signature public key + sig_pubkey: Vec<u8>, + /// Custom datas + custom_datas: Option<Vec<u8>>, + }, + /// Ack Message + Ack { + /// Custom datas + custom_datas: Option<Vec<u8>>, + }, + /// Message + Message { + /// Custom datas + custom_datas: Option<Vec<u8>>, + }, +} + +/// Message that referencing datas +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum MessageRef<'a> { + /// Connect message + Connect { + /// Signature algorithm + sig_algo: [u8; SIG_ALGO_LEN], + /// Signature public key + sig_pubkey: Vec<u8>, + /// Custom datas + custom_datas: Option<&'a [u8]>, + }, + /// Ack Message + Ack { + /// Custom datas + custom_datas: Option<&'a [u8]>, + }, + /// Message + Message { + /// Custom datas + custom_datas: Option<&'a [u8]>, + }, +} + +/// Encapsuled message +#[derive(Debug, PartialEq)] +pub struct EncapsuledMessage { + pub(crate) datas: Vec<u8>, +} + +impl AsRef<[u8]> for EncapsuledMessage { + fn as_ref(&self) -> &[u8] { + &self.datas[..] + } +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum MsgTypeHeaders { + Connect { + peer_ephemeral_pk: [u8; EPK_SIZE], + sig_algo: [u8; SIG_ALGO_LEN], + sig_pubkey: Vec<u8>, + }, + Ack { + challenge: [u8; CHALLENGE_SIZE], + }, + UserMsg, +} + +impl MsgTypeHeaders { + pub(crate) fn must_be_encrypted(&self) -> bool { + if let MsgTypeHeaders::UserMsg = self { + true + } else { + false + } + } +} + +struct InnerPreparedMsg<'a> { + bin_user_msg: Option<&'a [u8]>, + type_msg_headers: Vec<u8>, +} + +impl Message { + pub(crate) fn from_bytes(msg_bytes: Vec<u8>, msg_type_headers: MsgTypeHeaders) -> Result<Self> { + // Read custom datas + let custom_datas = if !msg_bytes.is_empty() { + Some(msg_bytes) + } else { + None + }; + + // Build message according to it's type + match msg_type_headers { + MsgTypeHeaders::UserMsg => Ok(Message::Message { custom_datas }), + MsgTypeHeaders::Connect { + sig_algo, + sig_pubkey, + .. + } => Ok(Message::Connect { + sig_algo, + sig_pubkey, + custom_datas, + }), + MsgTypeHeaders::Ack { .. } => Ok(Message::Ack { custom_datas }), + } + } +} + +impl<'a> MessageRef<'a> { + #[inline] + fn prepare_message( + &self, + self_epk: &[u8], + peer_epk: Option<&Vec<u8>>, + ) -> Result<InnerPreparedMsg<'a>> { + match self { + Self::Connect { + sig_algo, + sig_pubkey, + custom_datas, + } => { + // type message headers + let mut type_msg_headers = Vec::with_capacity(CONNECT_MSG_TYPE_HEADERS_SIZE); + type_msg_headers + .write(CONNECT_MSG_TYPE) + .map_err(Error::WriteError)?; + type_msg_headers + .write(self_epk) + .map_err(Error::WriteError)?; + type_msg_headers + .write(&sig_algo[..]) + .map_err(Error::WriteError)?; + type_msg_headers + .write(sig_pubkey) + .map_err(Error::WriteError)?; + + Ok(InnerPreparedMsg { + bin_user_msg: *custom_datas, + type_msg_headers, + }) + } + Self::Ack { custom_datas } => { + // type message headers + let mut type_msg_headers = Vec::with_capacity(ACK_MSG_TYPE_HEADERS_SIZE); + type_msg_headers + .write(ACK_MSG_TYPE) + .map_err(Error::WriteError)?; + // write challenge + if let Some(peer_epk) = peer_epk { + Self::write_challenge(peer_epk, &mut type_msg_headers)?; + } else { + panic!("Dev error: try to write ack message before known peer epk."); + } + + Ok(InnerPreparedMsg { + bin_user_msg: *custom_datas, + type_msg_headers, + }) + } + Self::Message { custom_datas } => Ok(InnerPreparedMsg { + bin_user_msg: *custom_datas, + type_msg_headers: USER_MSG_TYPE.to_vec(), + }), + } + } + /// Convert message to bytes + pub(crate) fn to_bytes( + &self, + self_epk: &[u8], + peer_epk: Option<&Vec<u8>>, + ) -> Result<EncapsuledMessage> { + let InnerPreparedMsg { + bin_user_msg, + type_msg_headers, + } = self.prepare_message(self_epk, peer_epk)?; + + let bin_user_msg_len = bin_user_msg.unwrap_or(&[]).len(); + + // Create temporary write buffer for datas that will then be signed or hashed + let mut bytes_will_signed_or_hashed = BufWriter::new(Vec::with_capacity( + bin_user_msg_len + HEADERS_AND_FOOTERS_MAX_SIZE, + )); + + // Write MAGIC_VALUE + bytes_will_signed_or_hashed + .write(&MAGIC_VALUE) + .map_err(Error::WriteError)?; + + // Write VERSION + bytes_will_signed_or_hashed + .write(&CURRENT_VERSION) + .map_err(Error::WriteError)?; + + // Write ENCAPSULED_MSG_SIZE + let encapsuled_msg_size = type_msg_headers.len() + bin_user_msg.unwrap_or(&[]).len(); + bytes_will_signed_or_hashed + .write(&(encapsuled_msg_size as u64).to_be_bytes()) + .map_err(Error::WriteError)?; + + // Write type message headers + bytes_will_signed_or_hashed + .write(&type_msg_headers) + .map_err(Error::WriteError)?; + + // Write user message + if let Some(bin_user_msg) = bin_user_msg { + bytes_will_signed_or_hashed + .write(bin_user_msg) + .map_err(Error::WriteError)?; + } + + // Flush bytes_will_signed buffer + let bytes_will_signed_or_hashed = bytes_will_signed_or_hashed + .into_inner() + .map_err(|_| Error::BufferFlushError)?; + + // Return the bytes will signed + Ok(EncapsuledMessage { + datas: bytes_will_signed_or_hashed, + }) + } + #[inline] + fn write_challenge<W: Write>(ephem_pk: &[u8], writer: &mut W) -> Result<()> { + writer + .write(sha256(ephem_pk).as_ref()) + .map_err(Error::WriteError)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_encapsuled_message() { + let encapsuled_msg = EncapsuledMessage { + datas: vec![1, 2, 3], + }; + + assert_eq!(&[1, 2, 3], encapsuled_msg.as_ref()); + } + + #[test] + fn test_connect_message_to_bytes() -> Result<()> { + let fake_epk = &[0u8; 32]; + + // Test connect message with custom datas + let message = MessageRef::Connect { + custom_datas: Some(&[5, 4, 4, 5]), + sig_algo: [0u8; SIG_ALGO_LEN], + sig_pubkey: vec![ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, + ], + }; + assert_eq!( + EncapsuledMessage { + datas: vec![ + 226, 194, 226, 210, // MAGIC_VALUE + 0, 0, 0, 1, // VERSION + 0, 0, 0, 0, 0, 0, 0, 74, // ENCAPSULED_MSG_LEN + 0, 1, // CONNECT_MSG_TYPE + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, // fake EPK (32 bytes) + 0, 0, 0, 0, // SIG_ALGO + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, // fake SIG_PK (32 bytes) + 5, 4, 4, 5 // custom datas + ], + }, + message.to_bytes(fake_epk, None)? + ); + + // Test connect message without custom datas + let message = MessageRef::Connect { + custom_datas: None, + sig_algo: [0u8; SIG_ALGO_LEN], + sig_pubkey: vec![ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, + ], + }; + + assert_eq!( + EncapsuledMessage { + datas: vec![ + 226, 194, 226, 210, // MAGIC_VALUE + 0, 0, 0, 1, // VERSION + 0, 0, 0, 0, 0, 0, 0, 70, // ENCAPSULED_MSG_LEN + 0, 1, // CONNECT_MSG_TYPE + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, // fake EPK (32 bytes) + 0, 0, 0, 0, // SIG_ALGO + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, // fake SIG_PK (32 bytes) + ], + }, + message.to_bytes(fake_epk, None,)? + ); + + Ok(()) + } + + #[test] + fn test_ack_message_to_bytes() -> Result<()> { + let fake_epk = &[0u8; 32]; + + // Test ack message with custom datas + let message = MessageRef::Ack { + custom_datas: Some(&[5, 4, 4, 5]), + }; + assert_eq!( + EncapsuledMessage { + datas: vec![ + 226, 194, 226, 210, // MAGIC_VALUE + 0, 0, 0, 1, // VERSION + 0, 0, 0, 0, 0, 0, 0, 38, // ENCAPSULED_MSG_LEN + 0, 2, // ACK_MSG_TYPE + 102, 104, 122, 173, 248, 98, 189, 119, 108, 143, 193, 139, 142, 159, 142, 32, + 8, 151, 20, 133, 110, 226, 51, 179, 144, 42, 89, 29, 13, 95, 41, + 37, // CHALLENGE (hash sha256) + 5, 4, 4, 5 // custom datas + ], + }, + message.to_bytes(fake_epk, Some(&fake_epk.to_vec()),)? + ); + + // Test ack message without custom datas + let message = MessageRef::Ack { custom_datas: None }; + assert_eq!( + EncapsuledMessage { + datas: vec![ + 226, 194, 226, 210, // MAGIC_VALUE + 0, 0, 0, 1, // VERSION + 0, 0, 0, 0, 0, 0, 0, 34, // ENCAPSULED_MSG_LEN + 0, 2, // ACK_MSG_TYPE + 102, 104, 122, 173, 248, 98, 189, 119, 108, 143, 193, 139, 142, 159, 142, 32, + 8, 151, 20, 133, 110, 226, 51, 179, 144, 42, 89, 29, 13, 95, 41, + 37, // CHALLENGE (hash sha256) + ], + }, + message.to_bytes(fake_epk, Some(&fake_epk.to_vec()),)? + ); + + Ok(()) + } + + #[test] + #[should_panic(expected = "Dev error: try to write ack message before known peer epk.")] + fn test_ack_message_to_bytes_before_recv_connect_msg() { + let fake_epk = &[0u8; 32]; + + // Test ack message without custom datas + let message = MessageRef::Ack { custom_datas: None }; + let _ = message.to_bytes(fake_epk, None); + } + + #[test] + fn test_user_message_to_bytes() -> Result<()> { + let fake_epk = &[0u8; 32]; + + // Test user message + let empty_user_message = MessageRef::Message { + custom_datas: Some(&[5, 4, 4, 5]), + }; + assert_eq!( + EncapsuledMessage { + datas: vec![ + 226, 194, 226, 210, // MAGIC_VALUE + 0, 0, 0, 1, // VERSION + 0, 0, 0, 0, 0, 0, 0, 6, // ENCAPSULED_MSG_LEN + 0, 0, // USER_MSG_TYPE + 5, 4, 4, 5 // custom datas + ], + }, + empty_user_message.to_bytes(fake_epk, None)? + ); + + // Test empty user message + let empty_user_message = MessageRef::Message { custom_datas: None }; + assert_eq!( + EncapsuledMessage { + datas: vec![ + 226, 194, 226, 210, // MAGIC_VALUE + 0, 0, 0, 1, // VERSION + 0, 0, 0, 0, 0, 0, 0, 2, // ENCAPSULED_MSG_LEN + 0, 0, // USER_MSG_TYPE + ], + }, + empty_user_message.to_bytes(fake_epk, None)? + ); + + Ok(()) + } + + #[test] + fn test_message_from_bytes() -> Result<()> { + // Define message + let mut msg_bytes = vec![ + 3, 3, 3, 3, // user message + ]; + + // User message + assert_eq!( + Message::Message { + custom_datas: Some(vec![3, 3, 3, 3]), + }, + Message::from_bytes(msg_bytes.clone(), MsgTypeHeaders::UserMsg)? + ); + + // Ack message + assert_eq!( + Message::Ack { + custom_datas: Some(vec![3, 3, 3, 3]), + }, + Message::from_bytes( + msg_bytes.clone(), + MsgTypeHeaders::Ack { + challenge: [0u8; CHALLENGE_SIZE] + } + )? + ); + + // Connect message + assert_eq!( + Message::Connect { + sig_pubkey: (0..31).collect(), + sig_algo: [0u8; SIG_ALGO_LEN], + custom_datas: Some(vec![3, 3, 3, 3]), + }, + Message::from_bytes( + msg_bytes.clone(), + MsgTypeHeaders::Connect { + peer_ephemeral_pk: [0u8; EPK_SIZE], + sig_algo: [0u8; SIG_ALGO_LEN], + sig_pubkey: (0..31).collect(), + } + )? + ); + + // Truncate user message content, msut be not panic + assert_eq!( + Message::Message { + custom_datas: Some(vec![3, 3]) + }, + Message::from_bytes(msg_bytes.drain(..2).collect(), MsgTypeHeaders::UserMsg)?, + ); + + // Empty message + let empty_msg_bytes = vec![]; + assert_eq!( + Message::Message { custom_datas: None }, + Message::from_bytes(empty_msg_bytes, MsgTypeHeaders::UserMsg)? + ); + + Ok(()) + } +} diff --git a/lib/tools/pkstl/src/minimal.rs b/lib/tools/pkstl/src/minimal.rs new file mode 100644 index 00000000..9ad02233 --- /dev/null +++ b/lib/tools/pkstl/src/minimal.rs @@ -0,0 +1,604 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Manage minimal secure and decentralized transport layer. + +use crate::agreement::{EphemeralKeyPair, EphemeralPublicKey}; +use crate::config::SdtlMinimalConfig; +use crate::constants::*; +use crate::digest::sha256; +use crate::encryption::{encrypt, EncryptAlgoWithSecretKey}; +use crate::errors::IncomingMsgErr; +use crate::message::{EncapsuledMessage, Message, MessageRef, MsgTypeHeaders}; +use crate::reader::{self, DecryptedIncomingDatas}; +use crate::signature::{self, SIG_ALGO_ED25519_ARRAY}; +use crate::status::SecureLayerStatus; +use crate::{Action, ActionSideEffects, Error, MsgType, Result}; +use std::io::{BufReader, BufWriter, Write}; + +/// Minimal secure layer +pub struct MinimalSecureLayer { + ack_msg_recv_too_early: Option<Vec<u8>>, + config: SdtlMinimalConfig, + pub(crate) encrypt_algo_with_secret: Option<EncryptAlgoWithSecretKey>, + ephemeral_kp: Option<EphemeralKeyPair>, + pub(crate) ephemeral_pubkey: EphemeralPublicKey, + peer_epk: Option<Vec<u8>>, + peer_sig_pubkey: Option<Vec<u8>>, + pub(crate) status: SecureLayerStatus, + tmp_stack_user_msgs: Vec<Vec<u8>>, +} + +impl MinimalSecureLayer { + /// Change configuration + pub fn change_config(&mut self, new_config: SdtlMinimalConfig) { + self.config = new_config; + } + /// Create minimal secure layer + pub fn create( + config: SdtlMinimalConfig, + expected_remote_sig_public_key: Option<Vec<u8>>, + ) -> Result<Self> { + let ephemeral_kp = EphemeralKeyPair::generate()?; + let ephemeral_pubkey = ephemeral_kp.public_key().clone(); + + let secure_layer = MinimalSecureLayer { + ack_msg_recv_too_early: None, + config, + encrypt_algo_with_secret: None, + ephemeral_pubkey, + ephemeral_kp: Some(ephemeral_kp), + peer_epk: None, + peer_sig_pubkey: expected_remote_sig_public_key, + status: SecureLayerStatus::init(), + tmp_stack_user_msgs: Vec::new(), + }; + + Ok(secure_layer) + } + pub(crate) fn compute_shared_secret(&mut self, peer_ephemeral_public_key: &[u8]) -> Result<()> { + let encrypt_algo = self.config.encrypt_algo; + let ephemeral_kp = self.ephemeral_kp.take(); + if let Some(ephemeral_kp) = ephemeral_kp { + let shared_secret = ephemeral_kp.compute_shared_secret( + peer_ephemeral_public_key, + encrypt_algo.shared_secret_len(), + )?; + + self.encrypt_algo_with_secret = + Some(EncryptAlgoWithSecretKey::build(encrypt_algo, shared_secret)); + + Ok(()) + } else if self.encrypt_algo_with_secret.is_some() { + // Shared secret already computed, do nothing + Ok(()) + } else { + unreachable!("dev error: fisrt call of compute_shared_secret() without ephemeral_kp !") + } + } + /// Drain temporary stack of remote messages + pub fn drain_tmp_stack_user_msgs(&mut self) -> Result<Vec<Message>> { + let bin_msgs: Vec<Vec<u8>> = self.tmp_stack_user_msgs.drain(..).collect(); + let mut msgs = Vec::with_capacity(bin_msgs.len()); + for bin_msg in bin_msgs { + if let Some(msg) = self.read_inner(&bin_msg, false)? { + msgs.push(msg); + } + } + Ok(msgs) + } + #[inline] + /// Encapsulate message + fn encapsulate_message(&mut self, message: &MessageRef) -> Result<EncapsuledMessage> { + message.to_bytes(&self.ephemeral_pubkey.as_ref(), self.peer_epk.as_ref()) + } + /// Take ACK message received too early + #[inline] + pub fn take_ack_msg_recv_too_early(&mut self) -> Result<Option<Message>> { + if let Some(bin_ack_msg) = self.ack_msg_recv_too_early.take() { + self.read(&bin_ack_msg) + } else { + Ok(None) + } + } + #[inline] + /// Read incoming datas + pub fn read(&mut self, incoming_datas: &[u8]) -> Result<Option<Message>> { + self.read_inner(incoming_datas, true) + } + fn read_inner( + &mut self, + incoming_datas: &[u8], + check_encrypt_state: bool, + ) -> Result<Option<Message>> { + // Decrypt incoming messsage and parse headers + let DecryptedIncomingDatas { + mut datas, + user_msg_begin, + user_msg_end, + msg_type_headers, + } = match reader::read( + self.encrypt_algo_with_secret.as_ref(), + incoming_datas, + check_encrypt_state, + ) { + Ok(decrypted_incoming_datas) => decrypted_incoming_datas, + Err(e) => { + self.status = SecureLayerStatus::Fail; + return Err(e); + } + }; + + //println!("DEBUG TMP: msg_type_headers={:#?}", msg_type_headers); + match msg_type_headers { + MsgTypeHeaders::Connect { + peer_ephemeral_pk, + ref sig_pubkey, + .. + } => { + // Verify (or get) peer sig pubkey + if let Some(ref peer_sig_pubkey) = self.peer_sig_pubkey { + if sig_pubkey != peer_sig_pubkey { + return Err(Error::UnexpectedRemoteSigPubKey); + } + } else { + self.peer_sig_pubkey = Some(sig_pubkey.to_vec()); + } + + // Verify sig + // The reader has already made sure that the signature algorithm is supported, + // as we only support the Ed25519 algorithm, we know that it is necessarily this one. + if !self.verify_sig(&datas, sig_pubkey, user_msg_end) { + return Err(IncomingMsgErr::InvalidHashOrSig.into()); + } + + // Update status + self.status + .apply_action(Action::Receive(MsgType::Connect))?; + + // Get peeer EPK and compute shared secret + self.peer_epk = Some(peer_ephemeral_pk.to_vec()); + self.compute_shared_secret(&peer_ephemeral_pk[..])?; + } + MsgTypeHeaders::Ack { challenge } => { + // Verify challenge + if challenge != sha256(self.ephemeral_pubkey.as_ref()).as_ref() { + return Err(IncomingMsgErr::InvalidChallenge.into()); + } + + let peer_sig_pubkey = if let Some(ref peer_sig_pubkey) = self.peer_sig_pubkey { + peer_sig_pubkey + } else if self.ack_msg_recv_too_early.is_none() { + self.ack_msg_recv_too_early = Some(incoming_datas.to_vec()); + return Ok(None); + } else { + self.status = SecureLayerStatus::Fail; + return Err(IncomingMsgErr::UnexpectedAckMsg.into()); + }; + + // Verify sig + // The reader has already made sure that the signature algorithm is supported, + // as we only support the Ed25519 algorithm, we know that it is necessarily this one. + if !self.verify_sig(&datas, peer_sig_pubkey, user_msg_end) { + return Err(IncomingMsgErr::InvalidHashOrSig.into()); + } + + // Update status + self.status.apply_action(Action::Receive(MsgType::Ack))?; + } + MsgTypeHeaders::UserMsg => { + // Verify status + if let Some(ActionSideEffects::PushUserMsgIntoTmpStack) = self + .status + .apply_action(Action::Receive(MsgType::UserMsg))? + { + self.tmp_stack_user_msgs.push(datas); + return Ok(None); + } + + // Verify hash + let datas_hashed = &datas[..user_msg_end]; + let hash = &datas[user_msg_end..]; + if hash != sha256(datas_hashed).as_ref() { + return Err(IncomingMsgErr::InvalidHashOrSig.into()); + } + } + } + + // Get message + let message = Message::from_bytes( + datas.drain(user_msg_begin..user_msg_end).collect(), + msg_type_headers, + )?; + + Ok(Some(message)) + } + /// Encrypt and write message on a writer + #[inline] + fn encrypt_and_write<W: Write>( + &mut self, + encapsuled_message: &EncapsuledMessage, + writer: &mut BufWriter<W>, + ) -> Result<()> { + let encrypt_algo_with_secret = + if let Some(ref encrypt_algo_with_secret) = self.encrypt_algo_with_secret { + encrypt_algo_with_secret + } else { + panic!("Dev error: try to get encrypt_algo_with_secret before it's computed !") + }; + + let mut datas_will_encrypted = BufWriter::new(Vec::with_capacity( + encapsuled_message.as_ref().len() + HASH_SIZE, + )); + + // Write encapsuled message + datas_will_encrypted + .write(encapsuled_message.as_ref()) + .map_err(Error::WriteError)?; + // Write encapsuled message hash + datas_will_encrypted + .write(sha256(encapsuled_message.as_ref()).as_ref()) + .map_err(Error::WriteError)?; + + // Flush datas_will_encrypted buffer + let datas_will_encrypted = datas_will_encrypted + .into_inner() + .map_err(|_| Error::BufferFlushError)?; + + // Encrypt + encrypt( + &mut BufReader::new(&datas_will_encrypted[..]), + encrypt_algo_with_secret, + writer, + )?; + + Ok(()) + } + #[inline] + /// Create connect message + pub fn create_connect_message( + &mut self, + public_key: &[u8], + custom_datas: Option<&[u8]>, + ) -> Result<Vec<u8>> { + // Update status + self.status.apply_action(Action::Create(MsgType::Connect))?; + + // Create message and update status + match self.encapsulate_message(&MessageRef::Connect { + sig_algo: SIG_ALGO_ED25519_ARRAY, + sig_pubkey: public_key.to_vec(), + custom_datas, + }) { + Ok(encapsuled_msg) => Ok(encapsuled_msg.datas), + Err(e) => { + self.status = SecureLayerStatus::Fail; + Err(e) + } + } + } + #[inline] + /// Create ack message + pub fn create_ack_message(&mut self, custom_datas: Option<&[u8]>) -> Result<Vec<u8>> { + // Update status + self.status.apply_action(Action::Create(MsgType::Ack))?; + + // Create message and update status + match self.encapsulate_message(&MessageRef::Ack { custom_datas }) { + Ok(encapsuled_msg) => Ok(encapsuled_msg.datas), + Err(e) => { + self.status = SecureLayerStatus::Fail; + Err(e) + } + } + } + #[inline] + /// Write message + pub fn write_message<W: Write>( + &mut self, + datas: &[u8], + writer: &mut BufWriter<W>, + ) -> Result<()> { + // Update status + self.status.apply_action(Action::Create(MsgType::UserMsg))?; + + match self.encapsuled_and_encrypt_and_write_message(datas, writer) { + Ok(()) => { + self.status = SecureLayerStatus::NegotiationSuccessful; + Ok(()) + } + Err(e) => { + self.status = SecureLayerStatus::Fail; + Err(e) + } + } + } + #[inline] + fn encapsuled_and_encrypt_and_write_message<W: Write>( + &mut self, + datas: &[u8], + writer: &mut BufWriter<W>, + ) -> Result<()> { + let encapsuled_msg = self.encapsulate_message(&MessageRef::Message { + custom_datas: Some(datas), + })?; + self.encrypt_and_write(&encapsuled_msg, writer) + } + #[inline] + fn verify_sig(&self, datas: &[u8], sig_pubkey: &[u8], user_msg_end: usize) -> bool { + let datas_signed = &datas[..user_msg_end]; + let sig = &datas[user_msg_end..]; + signature::verify_sig(sig_pubkey, datas_signed, sig) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::encryption::EncryptAlgo; + use crate::signature::SIG_ALGO_ED25519; + use crate::Seed32; + use ring::signature::{Ed25519KeyPair, KeyPair}; + + fn create_connect_msg_bytes(mut epk: Vec<u8>, sig_kp: &Ed25519KeyPair) -> Result<Vec<u8>> { + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut MAGIC_VALUE.to_vec()); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 74u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 1]); // CONNECT type + incoming_datas.append(&mut epk); // EPK + incoming_datas.append(&mut SIG_ALGO_ED25519.to_vec()); // SIG_ALGO + incoming_datas.append(&mut sig_kp.public_key().as_ref().to_vec()); // SIG_PK + incoming_datas.append(&mut vec![5, 4, 4, 5]); // User custom datas + let sig = sig_kp.sign(&incoming_datas); + incoming_datas.append(&mut sig.as_ref().to_vec()); // SIG + Ok(incoming_datas) + } + + fn create_ack_msg_bytes(remote_epk: Vec<u8>, sig_kp: &Ed25519KeyPair) -> Result<Vec<u8>> { + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut MAGIC_VALUE.to_vec()); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 34u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 2]); // ACK type + incoming_datas.append(&mut sha256(&remote_epk).as_ref().to_vec()); // Challenge + let sig = sig_kp.sign(&incoming_datas); + incoming_datas.append(&mut sig.as_ref().to_vec()); // SIG + Ok(incoming_datas) + } + + #[test] + fn test_change_config() -> Result<()> { + let mut msl = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + msl.change_config(SdtlMinimalConfig { + encrypt_algo: EncryptAlgo::Chacha20Poly1305Aead, + }); + Ok(()) + } + + #[test] + fn test_compute_shared_secret_twice() -> Result<()> { + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + let msl2 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + msl1.compute_shared_secret(msl2.ephemeral_pubkey.as_ref())?; + msl1.compute_shared_secret(msl2.ephemeral_pubkey.as_ref())?; + Ok(()) + } + + #[test] + fn test_status_update_to_fail() -> Result<()> { + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + let fake_encrypted_incoming_datas = &[0, 0, 0, 0]; + let result = msl1.read(fake_encrypted_incoming_datas); + + assert_eq!(SecureLayerStatus::Fail, msl1.status); + + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::UnexpectedMessage, e); + } else { + panic!("unexpected result") + } + Ok(()) + } + + #[test] + fn test_ack_msg_with_wrong_challenge() -> Result<()> { + // Create ack message + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut MAGIC_VALUE.to_vec()); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 34u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 2]); // ACK type + incoming_datas.append(&mut [0u8; 32].to_vec()); // fake challenge + incoming_datas.append(&mut [0u8; 32].to_vec()); // fake sig + + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Read ack msg + let result = msl1.read(&incoming_datas[..]); + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::InvalidChallenge, e); + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_write_user_msg_before_nego() -> Result<()> { + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Try to create ack message before connect message + let result = msl1.write_message(&[], &mut BufWriter::new(Vec::new())); + if let Err(Error::NegoMustHaveBeenSuccessful) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_create_ack_msg_before_connect() -> Result<()> { + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Try to create ack message before connect message + let result = msl1.create_ack_message(None); + if let Err(Error::ForbidWriteAckMsgNow) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_connect_msg_twice() -> Result<()> { + // Create sig keypair + let sig_kp = Ed25519KeyPair::from_seed_unchecked(Seed32::random().as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)?; + + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + let _ = msl1.create_connect_message(sig_kp.public_key().as_ref(), None)?; + + // Try to create connect message twice + let result = msl1.create_connect_message(sig_kp.public_key().as_ref(), None); + if let Err(Error::ConnectMsgAlreadyWritten) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_connect_msg_with_wrong_sig() -> Result<()> { + // Crate fake keys + let fake_ephem_pk = &[0u8; 32][..]; + let fake_sig_pk = [0u8; 32].to_vec(); + let _fake_signature_opt = Some(&[0u8; 32][..]); + + // Create connect msg bytes + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut MAGIC_VALUE.to_vec()); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 74u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 1]); // CONNECT type + incoming_datas.append(&mut fake_ephem_pk.to_vec()); // EPK + incoming_datas.append(&mut SIG_ALGO_ED25519.to_vec()); // SIG_ALGO + incoming_datas.append(&mut fake_sig_pk.clone()); // SIG_PK + incoming_datas.append(&mut vec![5, 4, 4, 5]); // User custom datas + incoming_datas.append(&mut [0u8; 32].to_vec()); // fake sig + + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Read connect msg + let result = msl1.read(&incoming_datas[..]); + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::InvalidHashOrSig, e); + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_recv_connect_msg_twice() -> Result<()> { + // Create sig keypair + let sig_kp = Ed25519KeyPair::from_seed_unchecked(Seed32::random().as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)?; + + // Create EKP + let ephemeral_kp = EphemeralKeyPair::generate()?; + + // Create connect msg bytes + let incoming_datas = + create_connect_msg_bytes(ephemeral_kp.public_key().as_ref().to_vec(), &sig_kp)?; + + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Read connect message + let _ = msl1.read(&incoming_datas[..])?; + + // Reread same connect message + let result = msl1.read(&incoming_datas[..]); + if let Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedConnectMsg)) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_recv_ack_msg_early_twice() -> Result<()> { + // Create sig keypair + let sig_kp = Ed25519KeyPair::from_seed_unchecked(Seed32::random().as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)?; + + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Create ack msg bytes + let incoming_datas = + create_ack_msg_bytes(msl1.ephemeral_pubkey.as_ref().to_vec(), &sig_kp)?; + + // Read ack message received too early + let _ = msl1.read(&incoming_datas[..]); + // Re-read ack message received too early + let result = msl1.read(&incoming_datas[..]); + if let Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedAckMsg)) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + + #[test] + fn test_recv_user_msg_before_nego() -> Result<()> { + // Create secure layer + let mut msl1 = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Create empty user msg fakely encryted + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut vec![0, 0, 0, 0]); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 2u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 0]); // USER_MSG_TYPE + incoming_datas.append(&mut sha256(&incoming_datas).as_ref().to_vec()); // Hash + + // Read user message received before_nego + let result = msl1.read(&incoming_datas[..]); + if let Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedMessage)) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } + } + +} diff --git a/lib/tools/pkstl/src/reader.rs b/lib/tools/pkstl/src/reader.rs new file mode 100644 index 00000000..62dae03c --- /dev/null +++ b/lib/tools/pkstl/src/reader.rs @@ -0,0 +1,373 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define PKSTL reader. + +use crate::constants::*; +use crate::encryption::{decrypt, EncryptAlgoWithSecretKey}; +use crate::errors::IncomingMsgErr; +use crate::message::MsgTypeHeaders; +use crate::signature::SIG_ALGO_ED25519; +use crate::{Error, Result}; +use std::io::{BufWriter, Write}; + +const MAGIC_VALUE_END: usize = 4; +const VERSION_END: usize = 8; +pub(crate) const ENCAPSULED_MSG_BEGIN: usize = 16; + +#[derive(Debug, PartialEq)] +pub(crate) struct DecryptedIncomingDatas { + pub(crate) datas: Vec<u8>, + pub(crate) user_msg_begin: usize, + pub(crate) user_msg_end: usize, + pub(crate) msg_type_headers: MsgTypeHeaders, +} + +/// Read incoming datas +pub(crate) fn read( + encrypt_algo_with_secret_opt: Option<&EncryptAlgoWithSecretKey>, + incoming_datas: &[u8], + check_encrypt_state: bool, +) -> std::result::Result<DecryptedIncomingDatas, Error> { + // Decrypt datas + let datas_encrypted; + let mut buffer = BufWriter::new(Vec::with_capacity(incoming_datas.len())); + if incoming_datas[..MAGIC_VALUE_END] == MAGIC_VALUE { + // Datas are not encrypted + datas_encrypted = false; + buffer.write(incoming_datas).map_err(Error::WriteError)?; + } else { + // Datas are encrypted + datas_encrypted = true; + if let Some(encrypt_algo_with_secret) = encrypt_algo_with_secret_opt { + decrypt(incoming_datas, encrypt_algo_with_secret, &mut buffer)?; + } else { + return Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedMessage)); + } + } + let decrypted_datas = buffer.into_inner().map_err(|_| Error::BufferFlushError)?; + + // Check magic value + if decrypted_datas[..MAGIC_VALUE_END] != MAGIC_VALUE { + return Err(IncomingMsgErr::InvalidMagicValue.into()); + } + + // Check version + if decrypted_datas[MAGIC_VALUE_END..VERSION_END] != CURRENT_VERSION { + return Err(IncomingMsgErr::UnsupportedVersion.into()); + } + + // Read ENCAPSULED_MSG_SIZE + let mut buffer_8_bytes: [u8; 8] = <[u8; 8]>::default(); + buffer_8_bytes.copy_from_slice(&decrypted_datas[VERSION_END..ENCAPSULED_MSG_BEGIN]); + let encapsuled_msg_size = u64::from_be_bytes(buffer_8_bytes) as usize; + let user_msg_end = ENCAPSULED_MSG_BEGIN + encapsuled_msg_size; + + // Read type headers + let (msg_type_headers, type_headers_len) = + read_type_headers(&decrypted_datas[ENCAPSULED_MSG_BEGIN..])?; + + if check_encrypt_state && datas_encrypted != msg_type_headers.must_be_encrypted() { + Err(Error::RecvInvalidMsg( + IncomingMsgErr::UnexpectedEncryptionState, + )) + } else { + Ok(DecryptedIncomingDatas { + datas: decrypted_datas, + user_msg_begin: ENCAPSULED_MSG_BEGIN + type_headers_len, + user_msg_end, + msg_type_headers, + }) + } +} + +fn read_type_headers(type_headers: &[u8]) -> Result<(MsgTypeHeaders, usize)> { + // Match message type + match &type_headers[..MSG_TYPE_LEN] { + USER_MSG_TYPE => Ok((MsgTypeHeaders::UserMsg, MSG_TYPE_LEN)), + CONNECT_MSG_TYPE => { + // Read PEER_EPHEMERAL_PUBKEY + let mut peer_ephemeral_pk = [0u8; EPK_SIZE]; + peer_ephemeral_pk.copy_from_slice(&type_headers[MSG_TYPE_LEN..MSG_TYPE_LEN + EPK_SIZE]); + // Read SIG_ALGO and SIG_PUBKEY + match &type_headers[(MSG_TYPE_LEN + EPK_SIZE)..(SIG_PUBKEY_BEGIN)] { + SIG_ALGO_ED25519 => { + let mut sig_algo = [0u8; SIG_ALGO_LEN]; + sig_algo.copy_from_slice( + &type_headers + [(MSG_TYPE_LEN + EPK_SIZE)..(MSG_TYPE_LEN + EPK_SIZE + SIG_ALGO_LEN)], + ); + Ok(( + MsgTypeHeaders::Connect { + peer_ephemeral_pk, + sig_algo, + sig_pubkey: type_headers[SIG_PUBKEY_BEGIN..(SIG_PUBKEY_BEGIN + 32)] + .to_vec(), + }, + SIG_PUBKEY_BEGIN + 32, + )) + } + _ => Err(IncomingMsgErr::UnsupportedSigAlgo.into()), + } + } + ACK_MSG_TYPE => { + let mut challenge = [0u8; CHALLENGE_SIZE]; + challenge + .copy_from_slice(&&type_headers[MSG_TYPE_LEN..(MSG_TYPE_LEN + CHALLENGE_SIZE)]); + Ok(( + MsgTypeHeaders::Ack { challenge }, + MSG_TYPE_LEN + CHALLENGE_SIZE, + )) + } + _ => Err(IncomingMsgErr::UnknownMessageType.into()), + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::digest::sha256; + use crate::encryption::{encrypt, tests::gen_random_encrypt_algo_with_secret}; + use crate::signature::{SIG_ALGO_ED25519, SIG_ALGO_ED25519_ARRAY}; + use pretty_assertions::assert_eq; + use std::io::BufReader; + + #[test] + fn test_unexpected_user_msg() { + let fake_encrypted_incoming_datas = &[0, 0, 0, 0]; + let result = read(None, fake_encrypted_incoming_datas, true); + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::UnexpectedMessage, e); + } else { + panic!("unexpected result") + } + } + + #[test] + fn test_msg_with_unsupported_version() { + let mut fake_incoming_datas = MAGIC_VALUE.to_vec(); + fake_incoming_datas.append(&mut vec![0, 0, 0, 2]); + + let result = read(None, &fake_incoming_datas, true); + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::UnsupportedVersion, e); + } else { + panic!("unexpected result") + } + } + + #[test] + fn test_unencrypted_usr_msg() { + let mut empty_user_msg = MAGIC_VALUE.to_vec(); + empty_user_msg.append(&mut CURRENT_VERSION.to_vec()); + empty_user_msg.append(&mut vec![0, 0, 0, 0, 0, 0, 0, 2]); // ENCAPSULED_MSG_SIZE + empty_user_msg.append(&mut USER_MSG_TYPE.to_vec()); + + let result = read(None, &empty_user_msg, true); + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::UnexpectedEncryptionState, e); + } else { + panic!("unexpected result") + } + } + + #[test] + fn test_user_msg_with_wrong_magiv_value() -> Result<()> { + let wrong_magic_value = vec![0, 0, 0, 0]; + let encrypt_algo_with_secret = gen_random_encrypt_algo_with_secret(); + let mut encrypted_datas = BufWriter::new(Vec::new()); + + encrypt( + &mut BufReader::new(&wrong_magic_value[..]), + &encrypt_algo_with_secret, + &mut encrypted_datas, + )?; + let encrypted_incoming_datas = encrypted_datas.into_inner().expect("buffer flush error"); + + let result = read( + Some(&encrypt_algo_with_secret), + &encrypted_incoming_datas[..], + true, + ); + if let Err(Error::RecvInvalidMsg(e)) = result { + assert_eq!(IncomingMsgErr::InvalidMagicValue, e); + } else { + panic!("unexpected result") + } + Ok(()) + } + + #[test] + fn test_read() -> Result<()> { + // Crate fake keys + let fake_ephem_pk = &[0u8; 32][..]; + let fake_sig_pk = [0u8; 32].to_vec(); + let _fake_signature_opt = Some(&[0u8; 32][..]); + + // Create fake challenge + let mut fake_challenge = [0u8; 32]; + fake_challenge.copy_from_slice(sha256(fake_ephem_pk).as_ref()); + + // Create encrypt_algo_with_secret + let encrypt_algo_with_secret = gen_random_encrypt_algo_with_secret(); + + ///////////////////// + // CONNECT MSG + ///////////////////// + + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut MAGIC_VALUE.to_vec()); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 74u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 1]); // CONNECT type + incoming_datas.append(&mut fake_ephem_pk.to_vec()); // EPK + incoming_datas.append(&mut SIG_ALGO_ED25519.to_vec()); // SIG_ALGO + incoming_datas.append(&mut fake_sig_pk.clone()); // SIG_PK + incoming_datas.append(&mut vec![5, 4, 4, 5]); // User custom datas + incoming_datas.append(&mut [0u8; 32].to_vec()); // fake sig + assert_eq!( + DecryptedIncomingDatas { + datas: incoming_datas.clone(), + user_msg_begin: 86, + user_msg_end: 90, + msg_type_headers: MsgTypeHeaders::Connect { + peer_ephemeral_pk: [0u8; EPK_SIZE], + sig_algo: SIG_ALGO_ED25519_ARRAY, + sig_pubkey: fake_sig_pk, + } + }, + read(Some(&encrypt_algo_with_secret), &incoming_datas[..], true)?, + ); + + ///////////////////// + // ACK MSG + ///////////////////// + + // Read incoming ack message without custom datas + let mut incoming_datas = Vec::with_capacity(100); + incoming_datas.append(&mut MAGIC_VALUE.to_vec()); + incoming_datas.append(&mut CURRENT_VERSION.to_vec()); + incoming_datas.append(&mut 34u64.to_be_bytes().to_vec()); // Encapsuled message length + incoming_datas.append(&mut vec![0, 2]); // ACK type + incoming_datas.append(&mut fake_challenge.to_vec()); // ACK challenge + incoming_datas.append(&mut [0u8; 32].to_vec()); // fake sig + assert_eq!( + DecryptedIncomingDatas { + datas: incoming_datas.clone(), + user_msg_begin: 50, + user_msg_end: 50, + msg_type_headers: MsgTypeHeaders::Ack { + challenge: fake_challenge, + } + }, + read(Some(&encrypt_algo_with_secret), &incoming_datas[..], true)?, + ); + + Ok(()) + } + + #[test] + fn test_read_user_type_headers() -> Result<()> { + let type_headers = vec![0, 0]; // USER_MSG_TYPE + + let expected = (MsgTypeHeaders::UserMsg, 2); + + assert_eq!(expected, read_type_headers(&type_headers[..])?); + + Ok(()) + } + + #[test] + fn test_read_unknown_type_headers() { + let type_headers = vec![1, 0]; // Unknown type + + let result = read_type_headers(&type_headers[..]); + + if let Err(Error::RecvInvalidMsg(e)) = read_type_headers(&type_headers[..]) { + assert_eq!(e, IncomingMsgErr::UnknownMessageType); + } else { + println!("{:?}", result); + panic!("Unexpected result"); + } + } + + #[test] + fn test_read_ack_type_headers() -> Result<()> { + let challenge = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0, 1, + ]; + let mut type_headers = vec![ + 0, 2, // ACK_MSG_TYPE + ]; + type_headers.append(&mut challenge.to_vec()); + + let expected = (MsgTypeHeaders::Ack { challenge }, 34); + + assert_eq!(expected, read_type_headers(&type_headers[..])?); + + Ok(()) + } + + #[test] + fn test_read_connect_type_headers() -> Result<()> { + let type_headers = vec![ + 0, 1, // CONNECT_MSG_TYPE + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0, 1, // EPK (32 bytes) + 0, 0, 0, 0, // SIG_ALGO_ED25519 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0, 1, // Sig pubkey (32 bytes) + ]; + + let expected = ( + MsgTypeHeaders::Connect { + peer_ephemeral_pk: [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, 1, + ], + sig_algo: [0, 0, 0, 0], + sig_pubkey: type_headers[38..].to_vec(), + }, + 70, + ); + + assert_eq!(expected, read_type_headers(&type_headers[..])?); + + Ok(()) + } + + #[test] + fn test_read_connect_type_headers_with_unsupported_sig_algo() { + let type_headers = vec![ + 0, 1, // CONNECT_MSG_TYPE + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0, 1, // EPK (32 bytes) + 0, 0, 0, 1, // Unsupported sig algo + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 0, 1, // Sig pubkey (32 bytes) + ]; + + let result = read_type_headers(&type_headers[..]); + + if let Err(Error::RecvInvalidMsg(e)) = read_type_headers(&type_headers[..]) { + assert_eq!(e, IncomingMsgErr::UnsupportedSigAlgo); + } else { + println!("{:?}", result); + panic!("Unexpected result"); + } + } +} diff --git a/lib/tools/pkstl/src/seeds.rs b/lib/tools/pkstl/src/seeds.rs new file mode 100644 index 00000000..f62ffed2 --- /dev/null +++ b/lib/tools/pkstl/src/seeds.rs @@ -0,0 +1,151 @@ +// Copyright (C) 2017-2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide wrappers around cryptographic seeds + +use clear_on_drop::clear::Clear; + +/// Store a 32 bytes seed. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Seed32([u8; 32]); + +impl AsRef<[u8]> for Seed32 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for Seed32 { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +impl Drop for Seed32 { + #[inline] + fn drop(&mut self) { + <[u8; 32] as Clear>::clear(&mut self.0); + } +} + +impl Seed32 { + #[inline] + /// Create new seed + pub fn new(seed_bytes: [u8; 32]) -> Seed32 { + Seed32(seed_bytes) + } + #[inline] + /// Generate random seed + pub fn random() -> Seed32 { + if let Ok(random_bytes) = ring::rand::generate::<[u8; 32]>(&ring::rand::SystemRandom::new()) + { + Seed32::new(random_bytes.expose()) + } else { + panic!("System error: fail to generate random seed !") + } + } +} + +/// Store a 48 bytes seed. +#[derive(Default)] +pub struct Seed48(InnerSeed48); + +struct InnerSeed48([u8; 48]); + +impl Default for InnerSeed48 { + fn default() -> Self { + InnerSeed48([0u8; 48]) + } +} + +impl AsRef<[u8]> for Seed48 { + fn as_ref(&self) -> &[u8] { + &(self.0).0 + } +} + +impl AsMut<[u8]> for Seed48 { + fn as_mut(&mut self) -> &mut [u8] { + &mut (self.0).0 + } +} + +impl Drop for Seed48 { + #[inline] + fn drop(&mut self) { + <InnerSeed48 as Clear>::clear(&mut self.0); + } +} + +#[cfg(test)] +impl Seed48 { + #[inline] + /// Create new seed + pub fn new(seed_bytes: [u8; 48]) -> Seed48 { + Seed48(InnerSeed48(seed_bytes)) + } +} + +/// Store a 64 bytes seed. +#[derive(Default)] +pub struct Seed64(InnerSeed64); + +struct InnerSeed64([u8; 64]); + +impl Default for InnerSeed64 { + fn default() -> Self { + InnerSeed64([0u8; 64]) + } +} + +impl AsMut<[u8]> for Seed64 { + fn as_mut(&mut self) -> &mut [u8] { + &mut (self.0).0 + } +} + +impl Drop for Seed64 { + #[inline] + fn drop(&mut self) { + <InnerSeed64 as Clear>::clear(&mut self.0); + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + + #[inline] + /// Generate random Seed48 + pub fn random_seed_48() -> Seed48 { + if let Ok(random_bytes) = ring::rand::generate::<[u8; 48]>(&ring::rand::SystemRandom::new()) + { + Seed48::new(random_bytes.expose()) + } else { + panic!("System error: fail to generate random seed !") + } + } + + #[test] + fn tests_seed32() { + assert_ne!(Seed32::random(), Seed32::random()); + + let mut seed = Seed32::new([3u8; 32]); + + assert_eq!(&[3u8; 32], seed.as_ref()); + assert_eq!(&mut [3u8; 32], seed.as_mut()); + } +} diff --git a/lib/tools/pkstl/src/signature.rs b/lib/tools/pkstl/src/signature.rs new file mode 100644 index 00000000..29641160 --- /dev/null +++ b/lib/tools/pkstl/src/signature.rs @@ -0,0 +1,30 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define PKSTL Signature. + +use ring::signature::UnparsedPublicKey; + +/// Signature algorithm Ed25519 +pub const SIG_ALGO_ED25519: &[u8] = &[0, 0, 0, 0]; + +/// Signature algorithm Ed25519 array +pub const SIG_ALGO_ED25519_ARRAY: [u8; 4] = [0, 0, 0, 0]; + +pub(crate) fn verify_sig(pubkey: &[u8], message: &[u8], sig: &[u8]) -> bool { + UnparsedPublicKey::new(&ring::signature::ED25519, pubkey) + .verify(message, sig) + .is_ok() +} diff --git a/lib/tools/pkstl/src/status.rs b/lib/tools/pkstl/src/status.rs new file mode 100644 index 00000000..200ac43e --- /dev/null +++ b/lib/tools/pkstl/src/status.rs @@ -0,0 +1,127 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Handle KPSTL status. + +use crate::errors::IncomingMsgErr; +use crate::{Action, ActionSideEffects, Error, LocalNegoThread, MsgType, RemoteNegoThread, Result}; + +/// Secure layer status +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum SecureLayerStatus { + /// An error has occurred or one peer message is wrong + Fail, + /// Negotiation in progress + OngoingNegotiation { + local: LocalNegoThread, + remote: RemoteNegoThread, + }, + /// Equivalent to "AckMsgWrittenAndPeerAckMsgOk" + NegotiationSuccessful, +} + +impl SecureLayerStatus { + pub(crate) fn init() -> Self { + SecureLayerStatus::OngoingNegotiation { + local: LocalNegoThread::Created, + remote: RemoteNegoThread::WaitConnectMsg, + } + } + pub(crate) fn apply_action(&mut self, action: Action) -> Result<Option<ActionSideEffects>> { + match self { + Self::Fail => Err(Error::ConnectionHadFail), + Self::OngoingNegotiation { local, remote } => match action { + Action::Create(msg_type) => match msg_type { + MsgType::Connect => { + if *local == LocalNegoThread::Created { + *local = LocalNegoThread::ConnectMsgSent; + Ok(None) + } else { + Err(Error::ConnectMsgAlreadyWritten) + } + } + MsgType::Ack => { + if *remote == RemoteNegoThread::ValidConnectMsgReceived { + if *local == LocalNegoThread::ValidAckMsgReceived { + *self = Self::NegotiationSuccessful; + } else { + *remote = RemoteNegoThread::AckMsgSent; + } + Ok(None) + } else { + Err(Error::ForbidWriteAckMsgNow) + } + } + MsgType::UserMsg => { + *self = Self::Fail; + Err(Error::NegoMustHaveBeenSuccessful) + } + }, + Action::Receive(msg_type) => match msg_type { + MsgType::Connect => { + if *remote == RemoteNegoThread::WaitConnectMsg { + *remote = RemoteNegoThread::ValidConnectMsgReceived; + Ok(None) + } else { + *self = Self::Fail; + Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedConnectMsg)) + } + } + MsgType::Ack => { + if *local == LocalNegoThread::ConnectMsgSent { + if *remote == RemoteNegoThread::AckMsgSent { + *self = Self::NegotiationSuccessful; + } else { + *local = LocalNegoThread::ValidAckMsgReceived; + } + Ok(None) + } else { + *self = Self::Fail; + Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedAckMsg)) + } + } + MsgType::UserMsg => { + if *remote == RemoteNegoThread::AckMsgSent + && *local == LocalNegoThread::ConnectMsgSent + { + Ok(Some(ActionSideEffects::PushUserMsgIntoTmpStack)) + } else { + *self = Self::Fail; + Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedMessage)) + } + } + }, + }, + Self::NegotiationSuccessful => match action { + Action::Create(msg_type) => match msg_type { + MsgType::Connect => Err(Error::ConnectMsgAlreadyWritten), + MsgType::Ack => Err(Error::ForbidWriteAckMsgNow), + MsgType::UserMsg => Ok(None), + }, + Action::Receive(msg_type) => match msg_type { + MsgType::Connect => { + *self = Self::Fail; + Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedConnectMsg)) + } + MsgType::Ack => { + *self = Self::Fail; + Err(Error::RecvInvalidMsg(IncomingMsgErr::UnexpectedAckMsg)) + } + MsgType::UserMsg => Ok(None), + }, + }, + } + } +} diff --git a/lib/tools/pkstl/tests/complete_mode.rs b/lib/tools/pkstl/tests/complete_mode.rs new file mode 100644 index 00000000..40b1b0b5 --- /dev/null +++ b/lib/tools/pkstl/tests/complete_mode.rs @@ -0,0 +1,196 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Test Public Key Secure Transport Layer in complete mode. + +#[cfg(feature = "zip-sign")] +mod tests { + use pkstl::*; + use ring::signature::{Ed25519KeyPair, KeyPair}; + use std::io::BufWriter; + + trait AsOptRef { + fn as_opt_ref(&self) -> Option<&[u8]>; + } + + impl AsOptRef for Option<Vec<u8>> { + fn as_opt_ref(&self) -> Option<&[u8]> { + match self { + Some(ref datas) => Some(&datas[..]), + None => None, + } + } + } + + fn server_infos() -> Result<(SecureLayer, Vec<u8>)> { + // Create server sig keypair seed + let seed = Seed32::random(); + + // Create server secure layer + let server_msl = SecureLayer::create(SdtlConfig::default(), Some(seed.clone()), None)?; + + // Get server sig pubkey + let server_sig_pubkey = Ed25519KeyPair::from_seed_unchecked(seed.as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)? + .public_key() + .as_ref() + .to_vec(); + + Ok((server_msl, server_sig_pubkey)) + } + + fn client_infos(expected_server_sig_pubkey: Option<Vec<u8>>) -> Result<SecureLayer> { + // Create client secure layer + let client_msl = + SecureLayer::create(SdtlConfig::default(), None, expected_server_sig_pubkey)?; + + Ok(client_msl) + } + + fn send_connect_msg( + sender_msl: &mut SecureLayer, + receiver_msl: &mut SecureLayer, + custom_datas: Option<Vec<u8>>, + ) -> Result<Vec<u8>> { + // Write connect message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write_connect_msg_bin(custom_datas.as_opt_ref(), &mut channel)?; + + // Receiver read connect message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + let msg_received = receiver_msl + .read_bin(&channel[..])? + .expect("Must be receive a message"); + if let IncomingBinaryMessage::Connect { + custom_datas: custom_datas_received, + peer_sig_public_key, + } = msg_received + { + assert_eq!(custom_datas, custom_datas_received); + Ok(peer_sig_public_key) + } else { + print!("Unexpected incoming message={:?}", msg_received); + panic!(); + } + } + + fn send_ack_msg( + sender_msl: &mut SecureLayer, + receiver_msl: &mut SecureLayer, + custom_datas: Option<Vec<u8>>, + ) -> Result<()> { + // Write ack message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write_ack_msg_bin(custom_datas.as_opt_ref(), &mut channel)?; + + // Receiver read ack message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + let msg_received = receiver_msl + .read_bin(&channel[..])? + .expect("Must be receive a message"); + if let IncomingBinaryMessage::Ack { + custom_datas: custom_datas_received, + } = msg_received + { + assert_eq!(custom_datas, custom_datas_received); + Ok(()) + } else { + print!("Unexpected incoming message={:?}", msg_received); + panic!(); + } + } + + fn send_user_msg( + sender_msl: &mut SecureLayer, + receiver_msl: &mut SecureLayer, + datas: Vec<u8>, + ) -> Result<()> { + // Write user message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write_bin(&datas[..], &mut channel)?; + + // Receiver read user message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + let msg_received = receiver_msl + .read_bin(&channel[..])? + .expect("Must be receive a message"); + if let IncomingBinaryMessage::Message { + datas: datas_received, + } = msg_received + { + assert_eq!(Some(datas), datas_received); + Ok(()) + } else { + print!("Unexpected incoming message={:?}", msg_received); + panic!(); + } + } + + #[test] + fn ordered_passing_case() -> Result<()> { + ////////////////////////// + // SERVER INFOS + ////////////////////////// + + let (mut server_msl, server_sig_pk) = server_infos()?; + + ////////////////////////// + // CLIENT INFOS + ////////////////////////// + + let mut client_msl = client_infos(Some(server_sig_pk.clone()))?; + + ////////////////////////// + // CLIENT CONNECT MSG + ////////////////////////// + + let _client_sig_pk_recv = + send_connect_msg(&mut client_msl, &mut server_msl, Some(vec![5, 4, 4, 5]))?; + + ////////////////////////// + // SERVER CONNECT MSG + ////////////////////////// + + let server_sig_pk_recv = + send_connect_msg(&mut server_msl, &mut client_msl, Some(vec![5, 3, 3, 5]))?; + assert_eq!(server_sig_pk, server_sig_pk_recv); + + ////////////////////////// + // SERVER ACK MSG + ////////////////////////// + + send_ack_msg(&mut server_msl, &mut client_msl, Some(vec![5, 9, 9, 5]))?; + + ////////////////////////// + // CLIENT ACK MSG + ////////////////////////// + + send_ack_msg(&mut client_msl, &mut server_msl, Some(vec![5, 0, 0, 5]))?; + + ////////////////////////// + // CLIENT USER MSG + ////////////////////////// + + send_user_msg(&mut client_msl, &mut server_msl, vec![5, 5, 5, 5])?; + + ////////////////////////// + // SERVER USER MSG + ////////////////////////// + + send_user_msg(&mut server_msl, &mut client_msl, vec![9, 9, 9, 9])?; + + Ok(()) + } +} diff --git a/lib/tools/pkstl/tests/complete_serde_mode.rs b/lib/tools/pkstl/tests/complete_serde_mode.rs new file mode 100644 index 00000000..973958a3 --- /dev/null +++ b/lib/tools/pkstl/tests/complete_serde_mode.rs @@ -0,0 +1,236 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Test Public Key Secure Transport Layer in complete mode with "ser" feature. + +#[cfg(feature = "ser")] +mod tests { + use pkstl::*; + use ring::signature::{Ed25519KeyPair, KeyPair}; + use serde::de::DeserializeOwned; + use serde::Serialize; + use std::fmt::Debug; + use std::io::BufWriter; + + fn server_infos(format: MessageFormat) -> Result<(SecureLayer, Vec<u8>)> { + // Create server sig keypair seed + let seed = Seed32::random(); + + // Create server secure layer + let mut conf = SdtlConfig::default(); + conf.message_format = format; + let server_msl = SecureLayer::create(conf, Some(seed.clone()), None)?; + + // Get server sig pubkey + let server_sig_pubkey = Ed25519KeyPair::from_seed_unchecked(seed.as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)? + .public_key() + .as_ref() + .to_vec(); + + Ok((server_msl, server_sig_pubkey)) + } + + fn client_infos( + expected_server_sig_pubkey: Option<Vec<u8>>, + format: MessageFormat, + ) -> Result<SecureLayer> { + // Create client secure layer + let mut conf = SdtlConfig::default(); + conf.message_format = format; + let client_msl = SecureLayer::create(conf, None, expected_server_sig_pubkey)?; + + Ok(client_msl) + } + + fn send_connect_msg<D: Debug + PartialEq + Serialize + DeserializeOwned>( + sender_msl: &mut SecureLayer, + receiver_msl: &mut SecureLayer, + custom_datas: Option<D>, + ) -> Result<Vec<u8>> { + // Write connect message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write_connect_msg(custom_datas.as_ref(), &mut channel)?; + + // Receiver read connect message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + let msg_received = receiver_msl + .read(&channel[..])? + .expect("Must be receive a message"); + if let IncomingMessage::Connect { + custom_datas: custom_datas_received, + peer_sig_public_key, + } = msg_received + { + assert_eq!(custom_datas, custom_datas_received); + Ok(peer_sig_public_key) + } else { + print!("Unexpected incoming message={:?}", msg_received); + panic!(); + } + } + + fn send_ack_msg<D: Debug + PartialEq + Serialize + DeserializeOwned>( + sender_msl: &mut SecureLayer, + receiver_msl: &mut SecureLayer, + custom_datas: Option<D>, + ) -> Result<()> { + // Write ack message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write_ack_msg(custom_datas.as_ref(), &mut channel)?; + + // Receiver read ack message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + let msg_received = receiver_msl + .read(&channel[..])? + .expect("Must be receive a message"); + if let IncomingMessage::Ack { + custom_datas: custom_datas_received, + } = msg_received + { + assert_eq!(custom_datas, custom_datas_received); + Ok(()) + } else { + print!("Unexpected incoming message={:?}", msg_received); + panic!(); + } + } + + fn send_user_msg<D: Debug + PartialEq + Serialize + DeserializeOwned>( + sender_msl: &mut SecureLayer, + receiver_msl: &mut SecureLayer, + datas: D, + ) -> Result<()> { + // Write user message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write(&datas, &mut channel)?; + + // Receiver read user message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + let msg_received = receiver_msl + .read(&channel[..])? + .expect("Must be receive a message"); + if let IncomingMessage::Message { + datas: datas_received, + } = msg_received + { + assert_eq!(Some(datas), datas_received); + Ok(()) + } else { + print!("Unexpected incoming message={:?}", msg_received); + panic!(); + } + } + + #[cfg(feature = "bin")] + #[test] + fn ordered_passing_case_bincode() -> Result<()> { + test_ordered_passing_case( + MessageFormat::Bincode, + Some("abc".to_owned()), + None, + Some("blablabla".to_owned()), + ) + } + + #[cfg(feature = "cbor")] + #[test] + fn ordered_passing_case_cbor() -> Result<()> { + test_ordered_passing_case( + MessageFormat::Cbor, + None, + Some("def".to_owned()), + Some("blablabla".to_owned()), + ) + } + + #[cfg(feature = "json")] + #[test] + fn ordered_passing_case_json() -> Result<()> { + test_ordered_passing_case( + MessageFormat::Utf8Json, + Some("abc".to_owned()), + Some("def".to_owned()), + None, + ) + } + + fn test_ordered_passing_case<D: Clone + Debug + PartialEq + Serialize + DeserializeOwned>( + message_format: MessageFormat, + connect_msg_custom_datas: Option<D>, + ack_msg_custom_datas: Option<D>, + user_msg_datas: Option<D>, + ) -> Result<()> { + ////////////////////////// + // SERVER INFOS + ////////////////////////// + + let (mut server_msl, server_sig_pk) = server_infos(message_format)?; + + ////////////////////////// + // CLIENT INFOS + ////////////////////////// + + let mut client_msl = client_infos(Some(server_sig_pk.clone()), message_format)?; + + ////////////////////////// + // CLIENT CONNECT MSG + ////////////////////////// + + let _client_sig_pk_recv = send_connect_msg( + &mut client_msl, + &mut server_msl, + connect_msg_custom_datas.clone(), + )?; + + ////////////////////////// + // SERVER CONNECT MSG + ////////////////////////// + + let server_sig_pk_recv = + send_connect_msg(&mut server_msl, &mut client_msl, connect_msg_custom_datas)?; + assert_eq!(server_sig_pk, server_sig_pk_recv); + + ////////////////////////// + // SERVER ACK MSG + ////////////////////////// + + send_ack_msg( + &mut server_msl, + &mut client_msl, + ack_msg_custom_datas.clone(), + )?; + + ////////////////////////// + // CLIENT ACK MSG + ////////////////////////// + + send_ack_msg(&mut client_msl, &mut server_msl, ack_msg_custom_datas)?; + + ////////////////////////// + // CLIENT USER MSG + ////////////////////////// + + send_user_msg(&mut client_msl, &mut server_msl, user_msg_datas.clone())?; + + ////////////////////////// + // SERVER USER MSG + ////////////////////////// + + send_user_msg(&mut server_msl, &mut client_msl, user_msg_datas)?; + + Ok(()) + } +} diff --git a/lib/tools/pkstl/tests/minimal_mode.rs b/lib/tools/pkstl/tests/minimal_mode.rs new file mode 100644 index 00000000..de5bd53d --- /dev/null +++ b/lib/tools/pkstl/tests/minimal_mode.rs @@ -0,0 +1,472 @@ +// Copyright (C) 2019 Eloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Test Public Key Secure Transport Layer in minimal mode. + +use pkstl::*; +use ring::signature::{Ed25519KeyPair, KeyPair}; +use std::io::{BufWriter, Write}; + +trait AsOptRef { + fn as_opt_ref(&self) -> Option<&[u8]>; +} + +impl AsOptRef for Option<Vec<u8>> { + fn as_opt_ref(&self) -> Option<&[u8]> { + match self { + Some(ref datas) => Some(&datas[..]), + None => None, + } + } +} + +fn client_infos(server_sig_kp: &[u8]) -> Result<(MinimalSecureLayer, Ed25519KeyPair)> { + // Create client sig keypair + let client_sig_kp = Ed25519KeyPair::from_seed_unchecked(Seed32::random().as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)?; + + // Create client secure layer + let client_msl = + MinimalSecureLayer::create(SdtlMinimalConfig::default(), Some(server_sig_kp.to_vec()))?; + + Ok((client_msl, client_sig_kp)) +} + +fn server_infos() -> Result<(MinimalSecureLayer, Ed25519KeyPair)> { + // Create server secure layer + let server_msl = MinimalSecureLayer::create(SdtlMinimalConfig::default(), None)?; + + // Create server sig keypair + let server_sig_kp = Ed25519KeyPair::from_seed_unchecked(Seed32::random().as_ref()) + .map_err(|_| Error::FailtoGenSigKeyPair)?; + + Ok((server_msl, server_sig_kp)) +} + +fn send_connect_msg_inner( + sender_msl: &mut MinimalSecureLayer, + sender_sig_kp: &Ed25519KeyPair, + receiver_msl: &mut MinimalSecureLayer, + custom_datas: Option<Vec<u8>>, +) -> Result<Option<Message>> { + // Create and sign connect message + let connect_msg = sender_msl.create_connect_message( + sender_sig_kp.public_key().as_ref(), + custom_datas.as_opt_ref(), + )?; + let sig = sender_sig_kp.sign(&connect_msg); + + // Write connect message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + channel + .write(&connect_msg) + .map_err(|_| Error::BufferFlushError)?; + channel + .write(sig.as_ref()) + .map_err(|_| Error::BufferFlushError)?; + + // Receiver read connect message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + receiver_msl.read(&channel[..]) +} + +#[inline] +fn send_connect_msg( + sender_msl: &mut MinimalSecureLayer, + sender_sig_kp: &Ed25519KeyPair, + receiver_msl: &mut MinimalSecureLayer, + custom_datas: Option<Vec<u8>>, +) -> Result<()> { + let connect_msg_received = send_connect_msg_inner( + sender_msl, + sender_sig_kp, + receiver_msl, + custom_datas.clone(), + )? + .expect("Must be receive a message"); + assert_eq!( + Message::Connect { + sig_algo: SIG_ALGO_ED25519_ARRAY, + sig_pubkey: sender_sig_kp.public_key().as_ref().to_vec(), + custom_datas, + }, + connect_msg_received, + ); + Ok(()) +} + +#[inline] +fn send_ack_msg( + sender_msl: &mut MinimalSecureLayer, + sender_sig_kp: &Ed25519KeyPair, + receiver_msl: &mut MinimalSecureLayer, + custom_datas: Option<Vec<u8>>, +) -> Result<()> { + let msg_received = send_ack_msg_inner( + sender_msl, + sender_sig_kp, + receiver_msl, + custom_datas.as_opt_ref(), + )? + .expect("Must be receive a message"); + assert_eq!(Message::Ack { custom_datas }, msg_received,); + Ok(()) +} + +fn send_ack_msg_inner<'a>( + sender_msl: &mut MinimalSecureLayer, + sender_sig_kp: &Ed25519KeyPair, + receiver_msl: &'a mut MinimalSecureLayer, + custom_datas: Option<&[u8]>, +) -> Result<Option<Message>> { + // Create and sign server ack message + let ack_msg = sender_msl.create_ack_message(custom_datas)?; + let sig = sender_sig_kp.sign(&ack_msg); + + // Write server ack message and it's sig in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + channel + .write(&ack_msg) + .map_err(|_| Error::BufferFlushError)?; + channel + .write(sig.as_ref()) + .map_err(|_| Error::BufferFlushError)?; + + // Client read server ack message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + receiver_msl.read(&channel[..]) +} + +#[inline] +fn send_user_msg( + sender_msl: &mut MinimalSecureLayer, + receiver_msl: &mut MinimalSecureLayer, + datas: Vec<u8>, +) -> Result<()> { + let msg_received = send_user_msg_inner(sender_msl, receiver_msl, datas.clone())? + .expect("Must be receive a message"); + assert_eq!( + Message::Message { + custom_datas: Some(datas), + }, + msg_received, + ); + Ok(()) +} + +fn send_user_msg_inner( + sender_msl: &mut MinimalSecureLayer, + receiver_msl: &mut MinimalSecureLayer, + datas: Vec<u8>, +) -> Result<Option<Message>> { + // Client write one message in channel + let mut channel = BufWriter::new(Vec::with_capacity(1_000)); + sender_msl.write_message(&datas, &mut channel)?; + + // Server read client message from channel + let channel = channel.into_inner().map_err(|_| Error::BufferFlushError)?; + receiver_msl.read(&channel[..]) +} + +#[test] +fn server_recv_ack_early() -> Result<()> { + ////////////////////////// + // SERVER INFOS + ////////////////////////// + + let (mut server_msl, server_sig_kp) = server_infos()?; + + ////////////////////////// + // CLIENT INFOS + ////////////////////////// + + let (mut client_msl, client_sig_kp) = client_infos(server_sig_kp.public_key().as_ref())?; + + ////////////////////////// + // SERVER CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut server_msl, + &server_sig_kp, + &mut client_msl, + Some(vec![5, 1, 1, 5]), + )?; + + ////////////////////////// + // CLIENT ACK MSG + ////////////////////////// + + assert_eq!( + None, + send_ack_msg_inner( + &mut client_msl, + &client_sig_kp, + &mut server_msl, + Some(&[7, 1, 1, 7]), + )? + ); + + ////////////////////////// + // CLIENT CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut client_msl, + &client_sig_kp, + &mut server_msl, + Some(vec![5, 4, 4, 5]), + )?; + + ////////////////////////// + // SERVER ACK MSG + ////////////////////////// + + send_ack_msg( + &mut server_msl, + &server_sig_kp, + &mut client_msl, + Some(vec![3, 1, 1, 3]), + )?; + + ///////////////////////////////////////// + // RECEIVE CLIENT USER MSG TOO EARLY + ///////////////////////////////////////// + + assert_eq!( + None, + send_user_msg_inner(&mut client_msl, &mut server_msl, vec![5, 2, 2, 5],)? + ); + + ////////////////////////////////////////// + // GET CLIENT ACK MSG RECEIVED TOO EARLY + ////////////////////////////////////////// + + assert_eq!( + Some(Message::Ack { + custom_datas: Some(vec![7, 1, 1, 7]), + }), + server_msl.take_ack_msg_recv_too_early()? + ); + + assert_eq!(None, server_msl.take_ack_msg_recv_too_early()?); + + ////////////////////////////////////////// + // GET CLIENT USER MSG RECEIVED TOO EARLY + ////////////////////////////////////////// + + assert_eq!( + vec![Message::Message { + custom_datas: Some(vec![5, 2, 2, 5]), + }], + server_msl.drain_tmp_stack_user_msgs()? + ); + + assert_eq!( + Vec::<Message>::with_capacity(0), + server_msl.drain_tmp_stack_user_msgs()? + ); + + Ok(()) +} + +#[test] +fn disordered_passing_case() -> Result<()> { + ////////////////////////// + // SERVER INFOS + ////////////////////////// + + let (mut server_msl, server_sig_kp) = server_infos()?; + + ////////////////////////// + // CLIENT INFOS + ////////////////////////// + + let (mut client_msl, client_sig_kp) = client_infos(server_sig_kp.public_key().as_ref())?; + + ////////////////////////// + // CLIENT CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut client_msl, + &client_sig_kp, + &mut server_msl, + Some(vec![5, 4, 4, 5]), + )?; + + ////////////////////////// + // SERVER ACK MSG + ////////////////////////// + + send_ack_msg( + &mut server_msl, + &server_sig_kp, + &mut client_msl, + Some(vec![5, 8, 8, 5]), + )?; + + ////////////////////////// + // SERVER CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut server_msl, + &server_sig_kp, + &mut client_msl, + Some(vec![5, 1, 1, 5]), + )?; + + ////////////////////////// + // CLIENT ACK MSG + ////////////////////////// + + send_ack_msg( + &mut client_msl, + &client_sig_kp, + &mut server_msl, + Some(vec![7, 4, 4, 7]), + )?; + + ////////////////////////// + // SERVER USER MSG + ////////////////////////// + + send_user_msg(&mut server_msl, &mut client_msl, vec![1, 5, 5, 1])?; + + Ok(()) +} + +#[test] +fn test_middle_man_detection() -> Result<()> { + ////////////////////////// + // SERVER INFOS + ////////////////////////// + + let (_server_msl, server_sig_kp) = server_infos()?; + + ////////////////////////// + // MIDDLE MAN INFOS + ////////////////////////// + + let (mut middle_msl, middle_sig_kp) = server_infos()?; + + ////////////////////////// + // CLIENT INFOS + ////////////////////////// + + let (mut client_msl, client_sig_kp) = client_infos(server_sig_kp.public_key().as_ref())?; + + ////////////////////////// + // CLIENT CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut client_msl, + &client_sig_kp, + &mut middle_msl, + Some(vec![7, 6, 5, 4]), + )?; + + ////////////////////////// + // MIDDLE MAN CONNECT MSG + ////////////////////////// + + let result = send_connect_msg( + &mut middle_msl, + &middle_sig_kp, + &mut client_msl, + Some(vec![7, 5, 6, 4]), + ); + if let Err(Error::UnexpectedRemoteSigPubKey) = result { + Ok(()) + } else { + println!("unexpected result={:?}", result); + panic!(); + } +} + +#[test] +fn ordered_passing_case() -> Result<()> { + ////////////////////////// + // SERVER INFOS + ////////////////////////// + + let (mut server_msl, server_sig_kp) = server_infos()?; + + ////////////////////////// + // CLIENT INFOS + ////////////////////////// + + let (mut client_msl, client_sig_kp) = client_infos(server_sig_kp.public_key().as_ref())?; + + ////////////////////////// + // CLIENT CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut client_msl, + &client_sig_kp, + &mut server_msl, + Some(vec![5, 4, 4, 5]), + )?; + + ////////////////////////// + // SERVER CONNECT MSG + ////////////////////////// + + send_connect_msg( + &mut server_msl, + &server_sig_kp, + &mut client_msl, + Some(vec![5, 6, 6, 5]), + )?; + + ////////////////////////// + // SERVER ACK MSG + ////////////////////////// + + send_ack_msg( + &mut server_msl, + &server_sig_kp, + &mut client_msl, + Some(vec![5, 8, 8, 5]), + )?; + + ////////////////////////// + // CLIENT ACK MSG + ////////////////////////// + + send_ack_msg( + &mut client_msl, + &client_sig_kp, + &mut server_msl, + Some(vec![5, 9, 9, 5]), + )?; + + ////////////////////////// + // CLIENT USER MSG + ////////////////////////// + + send_user_msg(&mut client_msl, &mut server_msl, vec![5, 7, 7, 5])?; + + ////////////////////////// + // SERVER USER MSG + ////////////////////////// + + send_user_msg(&mut server_msl, &mut client_msl, vec![7, 4, 4, 7])?; + + Ok(()) +} -- GitLab