From 90272356bf0e5518247d7cff14e152b0bac395d4 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Fri, 18 Oct 2024 09:51:48 +0200 Subject: [PATCH] feat: add findex impl - client and server sides --- .github/workflows/main.yml | 4 +- .github/workflows/main_release.yml | 4 +- .pre-commit-config.yaml | 5 +- .rustfmt.toml | 58 + Cargo.lock | 733 +----------- Cargo.toml | 18 +- crate/cli/Cargo.toml | 10 +- crate/cli/src/actions/console.rs | 4 +- crate/cli/src/actions/findex/index.rs | 136 +++ crate/cli/src/actions/findex/mod.rs | 47 + crate/cli/src/actions/login.rs | 105 +- crate/cli/src/actions/logout.rs | 9 +- crate/cli/src/actions/markdown.rs | 5 +- crate/cli/src/actions/mod.rs | 2 +- crate/cli/src/actions/new_database.rs | 52 - crate/cli/src/actions/version.rs | 6 +- crate/cli/src/error/mod.rs | 35 +- crate/cli/src/error/result.rs | 2 +- crate/cli/src/lib.rs | 28 +- crate/cli/src/main.rs | 16 +- crate/cli/src/tests/auth_tests.rs | 14 +- crate/cli/src/tests/findex.rs | 58 + crate/cli/src/tests/mod.rs | 3 +- crate/cli/src/tests/new_database.rs | 2 +- crate/cli/src/tests/utils/cmd_logs.rs | 1 + crate/client/Cargo.toml | 7 +- crate/client/datasets/users.json | 1002 +++++++++++++++++ crate/client/src/certificate_verifier.rs | 3 +- crate/client/src/config.rs | 91 +- crate/client/src/error/mod.rs | 6 - crate/client/src/error/result.rs | 2 +- crate/client/src/file_utils.rs | 149 +-- crate/client/src/findex_rest_client.rs | 44 +- crate/client/src/lib.rs | 58 +- crate/client/src/result.rs | 9 +- crate/server/Cargo.toml | 17 +- .../src/config/command_line/clap_config.rs | 10 +- crate/server/src/config/command_line/db.rs | 137 +-- .../src/config/command_line/http_config.rs | 11 +- .../config/command_line/jwt_auth_config.rs | 27 +- crate/server/src/config/command_line/mod.rs | 4 +- .../src/config/command_line/workspace.rs | 120 -- crate/server/src/config/params/db_params.rs | 30 +- crate/server/src/config/params/http_params.rs | 14 +- .../server/src/config/params/server_params.rs | 22 +- crate/server/src/core/findex_server.rs | 44 - crate/server/src/core/implementation.rs | 80 +- crate/server/src/core/mod.rs | 3 +- crate/server/src/database/database_trait.rs | 36 +- crate/server/src/database/mod.rs | 18 +- crate/server/src/database/query.sql | 140 --- crate/server/src/database/redis/mod.rs | 264 +++-- crate/server/src/database/sqlite/mod.rs | 104 -- crate/server/src/error/mod.rs | 2 + crate/server/src/{ => error}/result.rs | 11 +- .../server/src/{error.rs => error/server.rs} | 48 +- crate/server/src/findex_server.rs | 72 +- crate/server/src/lib.rs | 4 +- crate/server/src/main.rs | 14 +- crate/server/src/middlewares/jwks.rs | 2 +- crate/server/src/middlewares/jwt.rs | 5 +- .../server/src/middlewares/jwt_token_auth.rs | 8 +- crate/server/src/middlewares/ssl_auth.rs | 23 +- crate/server/src/routes/error.rs | 46 + crate/server/src/routes/findex.rs | 159 +++ crate/server/src/routes/mod.rs | 67 +- crate/server/src/routes/version.rs | 26 + crate/server/src/secret.rs | 2 +- crate/server/src/tests/mod.rs | 18 +- crate/test_server/Cargo.toml | 1 - crate/test_server/src/lib.rs | 2 +- crate/test_server/src/test_server.rs | 49 +- deny.toml | 6 +- rust-toolchain | 1 + 74 files changed, 2396 insertions(+), 1979 deletions(-) create mode 100644 .rustfmt.toml create mode 100644 crate/cli/src/actions/findex/index.rs create mode 100644 crate/cli/src/actions/findex/mod.rs delete mode 100644 crate/cli/src/actions/new_database.rs create mode 100644 crate/cli/src/tests/findex.rs create mode 100644 crate/client/datasets/users.json delete mode 100644 crate/server/src/config/command_line/workspace.rs delete mode 100644 crate/server/src/core/findex_server.rs delete mode 100644 crate/server/src/database/query.sql delete mode 100644 crate/server/src/database/sqlite/mod.rs create mode 100644 crate/server/src/error/mod.rs rename crate/server/src/{ => error}/result.rs (89%) rename crate/server/src/{error.rs => error/server.rs} (89%) create mode 100644 crate/server/src/routes/error.rs create mode 100644 crate/server/src/routes/findex.rs create mode 100644 crate/server/src/routes/version.rs create mode 100644 rust-toolchain diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 69ca600..3797039 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,11 +15,11 @@ jobs: cargo-lint: uses: ./.github/workflows/clippy.yml with: - toolchain: nightly-2024-06-09 + toolchain: stable-2024-10-17 build_tests: uses: ./.github/workflows/build_all.yml secrets: inherit with: - toolchain: nightly-2024-06-09 + toolchain: stable-2024-10-17 debug_or_release: debug diff --git a/.github/workflows/main_release.yml b/.github/workflows/main_release.yml index 30a1c58..41818bf 100644 --- a/.github/workflows/main_release.yml +++ b/.github/workflows/main_release.yml @@ -22,11 +22,11 @@ jobs: cargo-lint: uses: ./.github/workflows/clippy.yml with: - toolchain: nightly-2024-06-09 + toolchain: stable-2024-10-17 build: uses: ./.github/workflows/build_all.yml secrets: inherit with: - toolchain: nightly-2024-06-09 + toolchain: stable-2024-10-17 debug_or_release: release diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9563295..b40c663 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -exclude: tests_data +exclude: crate/client/datasets/users.json repos: - repo: https://github.com/compilerla/conventional-pre-commit rev: v3.4.0 @@ -107,7 +107,7 @@ repos: args: [--skip-string-normalization] - repo: https://github.com/Cosmian/git-hooks.git - rev: v1.0.29 + rev: v1.0.30 hooks: - id: cargo-format # - id: dprint-toml-fix @@ -115,6 +115,7 @@ repos: # - id: cargo-update - id: cargo-machete - id: docker-compose-up + - id: cargo-build-kms - id: cargo-test - id: clippy-autofix-unreachable-pub - id: clippy-autofix-all-targets-all-features diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..9f10713 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,58 @@ +# Specifies which edition is used by the parser. +# Default value: "2015" +edition = "2021" + +# How imports should be grouped into use statements. Imports will be merged or split to the configured level of granularity. +# Default value: Preserve +imports_granularity = "Crate" + +# Format the metavariable matching patterns in macros. +# Default value: false +format_macro_matchers = true + +# Format string literals where necessary +# Default value: false +format_strings = true + +# Break comments to fit on the line +# Default value: false +# Possible values: true, false +wrap_comments = true + +# Convert /* */ comments to // comments where possible +# Default value: false +# Possible values: true, false +# normalize_comments = true + +# Reorder impl items. type and const are put first, then macros and methods. +# Default value: false +reorder_impl_items = true + +# Controls the strategy for how imports are grouped together. +# Default value: Preserve +group_imports = "StdExternalCrate" + +# Add trailing semicolon after break, continue and return +# Default value: true +trailing_semicolon = false + +# Enable unstable features on the unstable channel. +# Default value: false +unstable_features = true + +# Use field initialize shorthand if possible. +# Default value: false +use_field_init_shorthand = true + +# Which version of the formatting rules to use. Version::One is backwards-compatible with Rustfmt 1.0. Other versions are only backwards compatible within a major version number. +# Default value: "One" +version = "Two" + +# # Controls the edition of the Rust Style Guide to use for formatting (RFC 3338) +# # Default value: "2015" +# style_edition = "2021" + +# The following rust files listing have been made in october 2021. +# This listing allows us first to ignore all rust files formmatting. +# Then we can remove progressively from this list the files we want to format +ignore = [] diff --git a/Cargo.lock b/Cargo.lock index 721e7a7..04f347c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,33 +386,12 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] - [[package]] name = "asn1-rs" version = "0.6.2" @@ -509,12 +488,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.13.1" @@ -560,15 +533,6 @@ dependencies = [ "serde", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -578,15 +542,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "bstr" version = "1.10.0" @@ -631,15 +586,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" version = "1.1.30" @@ -655,30 +601,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" version = "0.4.38" @@ -729,7 +651,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -770,112 +691,22 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "cloudproof" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e4a805ae87855dc2676adb766401a0cb45d1e88eb7298ea6ffedc9dc8f58e7" -dependencies = [ - "cloudproof_aesgcm", - "cloudproof_anonymization", - "cloudproof_cover_crypt", - "cloudproof_ecies", - "cloudproof_findex 6.0.2", - "cloudproof_fpe", - "cosmian_crypto_core", -] - -[[package]] -name = "cloudproof_aesgcm" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f2755b9f0f157a2a8816cb88663af7741c72df12fba115ba1f98e6e7077b15" -dependencies = [ - "cosmian_crypto_core", -] - -[[package]] -name = "cloudproof_anonymization" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4c483a4f8ce9ebd178dfc8578b327d195ff7d44cbdab5bb171a8c8e8c7290f" -dependencies = [ - "argon2", - "base64 0.21.7", - "chrono", - "cosmian_crypto_core", - "hex", - "rand", - "rand_distr", - "regex", - "sha2", - "tiny-keccak", -] - -[[package]] -name = "cloudproof_cover_crypt" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b957408fa6185ed0a2140da5df66cd37326da478a11a6c1a9ac07650d58121" -dependencies = [ - "cosmian_cover_crypt", - "cosmian_crypto_core", - "serde_json", -] - -[[package]] -name = "cloudproof_ecies" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d90e7789327a566109776551d69980264647d3aab290116b8b90f3bae8c53a7" -dependencies = [ - "cosmian_crypto_core", -] - -[[package]] -name = "cloudproof_findex" -version = "5.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25dad7ff37557df4cb27a20d80727d75dfa2ddcbed14f02761e8bfb1f595741" -dependencies = [ - "async-trait", - "cosmian_crypto_core", - "cosmian_findex 5.0.3", - "redis", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "cloudproof_findex" version = "6.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648998ca94264039683dd60818940cb461790cd0f62b7823caaa814869d2c7f1" +source = "git+https://www.github.com/Cosmian/cloudproof_rust?branch=feat/add_basic_findex_rest_client#86831a602104b255dc6f0e16233d917ea43dbf5b" dependencies = [ "async-trait", + "base64 0.21.7", "cosmian_crypto_core", - "cosmian_findex 6.0.0", + "cosmian_findex", + "redis", + "reqwest", "serde", "tracing", "tracing-subscriber", ] -[[package]] -name = "cloudproof_fpe" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f1457fe34566c626b37275061dcc88dd71ebf06ebd3d7ee62c6b0b37f2c426" -dependencies = [ - "aes", - "cosmian_fpe", - "itertools", - "num-bigint", - "num-traits", -] - [[package]] name = "combine" version = "4.6.7" @@ -904,9 +735,6 @@ name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -dependencies = [ - "arbitrary", -] [[package]] name = "convert_case" @@ -948,20 +776,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cosmian_cover_crypt" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2162c569b1b72a9b18929bb6b6effdc59ef12101276559bbb7a01c3f08056b2e" -dependencies = [ - "cosmian_crypto_core", - "pqc_kyber", - "serde", - "serde_json", - "tiny-keccak", - "zeroize", -] - [[package]] name = "cosmian_crypto_core" version = "9.5.0" @@ -970,54 +784,18 @@ checksum = "1fec20750102fb599eea419a4167ff42f0b6770e6a187abdbbd4d45d01e576e1" dependencies = [ "aead", "aes-gcm", - "blake2", - "chacha20", - "chacha20poly1305", - "crypto_box", - "curve25519-dalek", - "digest", - "ed25519-dalek", - "elliptic-curve", "getrandom", "leb128", - "p192", - "p224", - "p256", - "p384", - "pkcs8", "rand_chacha", "rand_core", - "rsa", - "sha1", - "sha2", - "sha3", - "signature", "tiny-keccak", - "uuid", - "x509-cert", "zeroize", ] [[package]] name = "cosmian_findex" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cb51a6ded995165d6343c3b74d6f25191a02cbd6a2148a1403c0583a04e9e94" -dependencies = [ - "async-trait", - "base64 0.21.7", - "cosmian_crypto_core", - "never", - "rand", - "tiny-keccak", - "zeroize", -] - -[[package]] -name = "cosmian_findex" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac33fe1817637e96a87727995829d536c7d4554421d3ff2809cc39997bc1f91" +version = "6.0.1" +source = "git+https://www.github.com/Cosmian/findex?branch=fix/missing_some_structs_serialization#9c7ff1a4b98b21fb981157ca873dec767e0d8285" dependencies = [ "async-trait", "base64 0.21.7", @@ -1038,7 +816,7 @@ dependencies = [ "assert_cmd", "base64 0.21.7", "clap", - "cloudproof", + "cloudproof_findex", "const-oid", "cosmian_findex_client", "cosmian_logger", @@ -1066,10 +844,11 @@ name = "cosmian_findex_client" version = "0.1.0" dependencies = [ "base64 0.21.7", - "cloudproof", "der", + "faker_rand", "log", "pem", + "rand", "reqwest", "rustls", "serde", @@ -1091,29 +870,22 @@ dependencies = [ "actix-tls", "actix-web", "alcoholic_jwt", - "argon2", "async-trait", "base64 0.21.7", "chrono", "clap", - "cloudproof", - "cloudproof_findex 5.0.4", + "cloudproof_findex", "cosmian_logger", "dotenvy", "futures", - "hex", - "lazy_static", "num-bigint-dig", - "num_cpus", "openssl", - "rawsql", "redis", "reqwest", "serde", "serde_json", "sqlx", "thiserror", - "time", "tokio", "toml", "tracing", @@ -1122,21 +894,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cosmian_fpe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8146536a868bcda32bab8d40da8696668beae1e8075f773478cd5663e856baf1" -dependencies = [ - "cbc", - "cipher", - "libm", - "num-bigint", - "num-integer", - "num-traits", - "static_assertions", -] - [[package]] name = "cosmian_logger" version = "0.1.0" @@ -1226,18 +983,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -1249,36 +994,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto_box" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" -dependencies = [ - "aead", - "blake2", - "crypto_secretbox", - "curve25519-dalek", - "salsa20", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto_secretbox" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" -dependencies = [ - "aead", - "cipher", - "generic-array", - "poly1305", - "salsa20", - "subtle", - "zeroize", -] - [[package]] name = "ctr" version = "0.9.2" @@ -1288,33 +1003,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "data-encoding" version = "2.6.0" @@ -1327,7 +1015,6 @@ version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "arbitrary", "const-oid", "der_derive", "flagset", @@ -1369,17 +1056,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "derive_more" version = "0.99.18" @@ -1393,6 +1069,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + [[package]] name = "difflib" version = "0.4.0" @@ -1434,44 +1116,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "subtle", - "zeroize", -] - [[package]] name = "either" version = "1.13.0" @@ -1481,27 +1125,6 @@ dependencies = [ "serde", ] -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "encoding_rs" version = "0.8.34" @@ -1550,26 +1173,21 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - -[[package]] -name = "ff" -version = "0.13.0" +name = "faker_rand" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "300d2ddbf2245b5b5e723995e0961033121b4fc2be9045fb661af82bd739ffb6" dependencies = [ - "rand_core", - "subtle", + "deunicode", + "lazy_static", + "rand", ] [[package]] -name = "fiat-crypto" -version = "0.2.9" +name = "fastrand" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "flagset" @@ -1735,7 +1353,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1767,17 +1384,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - [[package]] name = "h2" version = "0.3.26" @@ -1919,9 +1525,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -2003,9 +1609,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" +checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" [[package]] name = "indexmap" @@ -2023,7 +1629,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "block-padding", "generic-array", ] @@ -2068,15 +1673,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -2100,9 +1696,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" @@ -2332,25 +1928,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "oauth2" version = "4.4.2" @@ -2409,9 +1986,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -2441,9 +2018,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -2457,54 +2034,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "p192" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0533bc6c238f2669aab8db75ae52879dc74e88d6bd3685bd4022a00fa85cd2" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sec1", -] - -[[package]] -name = "p224" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c06436d66652bc2f01ade021592c80a2aad401570a18aa18b82e440d2b9aa1" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - [[package]] name = "parking" version = "2.2.1" @@ -2534,33 +2063,12 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - [[package]] name = "pem" version = "3.0.4" @@ -2629,21 +2137,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkcs5" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" -dependencies = [ - "aes", - "cbc", - "der", - "pbkdf2", - "scrypt", - "sha2", - "spki", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -2651,8 +2144,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", - "pkcs5", - "rand_core", "spki", ] @@ -2662,17 +2153,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.2" @@ -2700,15 +2180,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "pqc_kyber" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b79004a05337e54e8ffc0ec7470e40fa26eca6fe182968ec2b803247f2283c" -dependencies = [ - "rand_core", -] - [[package]] name = "predicates" version = "3.1.2" @@ -2739,20 +2210,11 @@ dependencies = [ "termtree", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -2796,22 +2258,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "rawsql" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f70b36c8415361d34d90adf494f45bc18e568f909b5946d27c79559ddd812fa8" - [[package]] name = "redis" version = "0.23.3" @@ -2940,16 +2386,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "ring" version = "0.17.8" @@ -3059,15 +2495,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -3092,17 +2519,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "pbkdf2", - "salsa20", - "sha2", -] - [[package]] name = "sct" version = "0.7.1" @@ -3113,20 +2529,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -3178,11 +2580,10 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" dependencies = [ - "indexmap", "itoa", "memchr", "ryu", @@ -3248,16 +2649,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -3345,7 +2736,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "arbitrary", "base64ct", "der", ] @@ -3555,12 +2945,6 @@ dependencies = [ "url", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stringprep" version = "0.1.5" @@ -3655,7 +3039,6 @@ dependencies = [ "cosmian_findex_server", "cosmian_logger", "criterion", - "tempfile", "tokio", "tracing", "zeroize", @@ -3699,9 +3082,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -3758,27 +3139,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tls_codec" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" -dependencies = [ - "tls_codec_derive", - "zeroize", -] - -[[package]] -name = "tls_codec_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tokio" version = "1.40.0" @@ -4049,15 +3409,6 @@ dependencies = [ "serde", ] -[[package]] -name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" -dependencies = [ - "getrandom", -] - [[package]] name = "valuable" version = "0.1.0" @@ -4441,13 +3792,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ - "arbitrary", "const-oid", "der", - "sha1", - "signature", "spki", - "tls_codec", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a56729b..f41b70f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,11 @@ resolver = "2" [workspace.package] version = "0.1.0" edition = "2021" -rust-version = "1.71.0" -authors = ["Hatem M'naouer "] +rust-version = "1.82.0" +authors = [ + "Emmanuel Coste", + "Hatem M'naouer ", +] license = "BUSL-1.1" license-file = "LICENSE" repository = "https://github.com/Cosmian/cosmian_findex_server" @@ -37,21 +40,24 @@ incremental = false [profile.dev.package."*"] opt-level = 0 +# todo(manu): check if all deps are really required [workspace.dependencies] actix-rt = "2.10" actix-server = { version = "2.5", default-features = false } actix-web = { version = "4.9.0", default-features = false } +async-trait = "0.1.83" base64 = "0.21" chrono = "0.4" clap = { version = "4.5", default-features = false } -cloudproof = "3.0" +# cloudproof_findex = { path = "../cloudproof_rust/crates/findex" } +cloudproof_findex = { git = "https://www.github.com/Cosmian/cloudproof_rust", branch = "feat/add_basic_findex_rest_client" } der = { version = "0.7", default-features = false } env_logger = "0.11" hex = { version = "0.4", default-features = false } leb128 = "0.2.5" log = { version = "0.4", default-features = false } native-tls = "0.2" -num_cpus = "1.13" +num_cpus = "1.16" num-bigint-dig = { version = "0.8", default-features = false } openssl = { version = "0.10", default-features = false } pem = "3.0" @@ -64,11 +70,11 @@ sha3 = { version = "0.10", default-features = false } strum = { version = "0.25", default-features = false } thiserror = "1.0" time = "0.3" -tokio = { version = "1.39", default-features = false } +tokio = { version = "1.40", default-features = false } tracing-subscriber = { version = "0.3", default-features = false } tracing = "0.1" url = "2.5" -uuid = "1.10" +uuid = "1.11" x509-cert = { version = "0.2.5", default-features = false } x509-parser = "0.16" zeroize = { version = "1.8", default-features = false } diff --git a/crate/cli/Cargo.toml b/crate/cli/Cargo.toml index 19daa27..1f98828 100644 --- a/crate/cli/Cargo.toml +++ b/crate/cli/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true description = "CLI used to manage the Cosmian Findex." [[bin]] -name = "findex" +name = "cosmian_findex_cli" path = "src/main.rs" test = false @@ -32,7 +32,7 @@ clap = { workspace = true, features = [ "derive", "cargo", ] } -cloudproof = { workspace = true } +cloudproof_findex = { workspace = true, features = ["rest-interface"] } cosmian_findex_client = { path = "../client" } cosmian_logger = { path = "../logger" } der = { workspace = true, features = ["pem"] } @@ -52,9 +52,9 @@ actix-rt = { workspace = true } actix-server = { workspace = true } assert_cmd = "2.0" const-oid = { version = "0.9", features = ["db"] } -test_findex_server = { path = "../test_server" } openssl = { workspace = true } predicates = "3.1" -regex = { version = "1.10", default-features = false } -tempfile = "3.11" +regex = { version = "1.11", default-features = false } +tempfile = "3.13" +test_findex_server = { path = "../test_server" } x509-parser = { workspace = true, features = ["verify"] } diff --git a/crate/cli/src/actions/console.rs b/crate/cli/src/actions/console.rs index 1cdb03a..ec5b25e 100644 --- a/crate/cli/src/actions/console.rs +++ b/crate/cli/src/actions/console.rs @@ -15,7 +15,7 @@ impl Stdout { #[must_use] pub fn new(stdout: &str) -> Self { Self { - stdout: stdout.to_string(), + stdout: stdout.to_owned(), } } @@ -28,7 +28,7 @@ impl Stdout { pub fn write(&self) -> CliResult<()> { // Check if the output format should be JSON let json_format_from_env = std::env::var(KMS_CLI_FORMAT) - .unwrap_or_else(|_| CLI_DEFAULT_FORMAT.to_string()) + .unwrap_or_else(|_| CLI_DEFAULT_FORMAT.to_owned()) .to_lowercase() == CLI_JSON_FORMAT; diff --git a/crate/cli/src/actions/findex/index.rs b/crate/cli/src/actions/findex/index.rs new file mode 100644 index 0000000..bd725d6 --- /dev/null +++ b/crate/cli/src/actions/findex/index.rs @@ -0,0 +1,136 @@ +use std::collections::{HashMap, HashSet}; + +use clap::Parser; +use cloudproof_findex::{ + db_interfaces::DbInterfaceError, + reexport::{ + cosmian_crypto_core::FixedSizeCBytes, + cosmian_findex::{Data, IndexedValue, IndexedValueToKeywordsMap, Keyword, Label, UserKey}, + }, + Configuration, InstantiatedFindex, +}; +use cosmian_findex_client::FindexClient; +use serde::{Deserialize, Serialize}; +use tracing::trace; + +use super::FindexParameters; +use crate::{ + actions::console, + error::result::{CliResult, CliResultHelper}, +}; + +// to be deleted - start +// todo(manu): replace this by adding a CLI argument to provide the dataset file + +#[allow(non_snake_case)] +#[derive(Debug, Deserialize, Serialize)] +pub struct User { + pub(crate) firstName: String, + pub(crate) lastName: String, + pub(crate) phone: String, + pub(crate) email: String, + pub(crate) country: String, + pub(crate) region: String, + pub(crate) employeeNumber: String, + pub(crate) security: String, +} + +impl User { + #[must_use] + pub fn values(&self) -> Vec { + vec![ + self.firstName.clone(), + self.lastName.clone(), + self.phone.clone(), + self.email.clone(), + self.country.clone(), + self.region.clone(), + self.employeeNumber.clone(), + self.security.clone(), + ] + } +} + +/// Get the users from the dataset +/// # Errors +/// It returns an error if the dataset cannot be read or if the dataset cannot +/// be deserialized into a list of users +pub fn get_users() -> Result, DbInterfaceError> { + trace!("Current working directory: {:?}", std::env::current_dir()?); + let dataset = std::fs::read_to_string("../../crate/client/datasets/users.json")?; + serde_json::from_str::>(&dataset) + .map_err(|e| DbInterfaceError::Serialization(e.to_string())) +} +// to be deleted - end + +/// Index data with Findex +#[derive(Parser, Debug)] +#[clap(verbatim_doc_comment)] +pub struct IndexAction { + #[clap(flatten)] + pub findex_parameters: FindexParameters, +} + +impl IndexAction { + /// Process the server version action. + /// + /// # Arguments + /// + /// * `findex_rest_client` - The Findex server client instance used to + /// communicate with the Findex server server. + /// + /// # Errors + /// + /// Returns an error if the version query fails or if there is an issue + /// writing to the console. + #[allow(clippy::future_not_send)] // todo(manu): remove this + pub async fn process(&self, findex_rest_client: &FindexClient) -> CliResult<()> { + let config = Configuration::Rest( + findex_rest_client.client.clone(), + findex_rest_client.server_url.clone(), + findex_rest_client.server_url.clone(), + ); + let findex = InstantiatedFindex::new(config).await?; + + let key = hex::decode(self.findex_parameters.key.clone())?; + let user_key = UserKey::try_from_slice(&key)?; + let label = Label::from(self.findex_parameters.label.as_str()); + + // to be deleted - start + let users = get_users()?; + #[allow(clippy::cast_possible_wrap, clippy::as_conversions)] + let additions = users + .iter() + .enumerate() + .map(|(idx, user)| { + ( + IndexedValue::Data(Data::from((idx as i64).to_be_bytes().as_slice())), + user.values() + .iter() + .map(|word| Keyword::from(word.as_bytes())) + .collect::>(), + ) + }) + .collect::, HashSet)>>(); + let additions: HashMap, HashSet> = + additions.iter().cloned().collect(); + // to be deleted - end + + findex + .add( + &user_key, + &label, + IndexedValueToKeywordsMap::from(additions), + ) + .await?; + + let version = findex_rest_client + .version() + .await + .with_context(|| "Can't execute the version query on the findex server")?; + + console::Stdout::new(&version).write()?; + + Ok(()) + } +} diff --git a/crate/cli/src/actions/findex/mod.rs b/crate/cli/src/actions/findex/mod.rs new file mode 100644 index 0000000..167a399 --- /dev/null +++ b/crate/cli/src/actions/findex/mod.rs @@ -0,0 +1,47 @@ +use clap::{Parser, Subcommand}; +use cosmian_findex_client::FindexClient; +use index::IndexAction; + +use crate::error::result::CliResult; + +pub mod index; + +/// Index data with Findex +#[derive(Parser, Debug)] +#[clap(verbatim_doc_comment)] +// todo(manu): review global struct exposition +pub struct FindexParameters { + /// The user findex key used to index and search + #[clap(long, short = 'k')] + pub key: String, + /// The Findex label + #[clap(long, short = 'l')] + pub label: String, +} + +/// Index or Search with Findex +#[derive(Subcommand)] +pub enum FindexCommands { + Index(IndexAction), + // Search(SearchAction), // to be added +} + +impl FindexCommands { + /// Process the Findex commands action. + /// + /// # Arguments + /// + /// * `findex_rest_client` - The Findex client instance used to communicate + /// with the Findex server. + /// + /// # Errors + /// + /// Returns an error if the version query fails or if there is an issue + /// writing to the console. + #[allow(clippy::future_not_send)] // todo(manu): remove this + pub async fn process(&self, findex_rest_client: &FindexClient) -> CliResult<()> { + match self { + Self::Index(action) => action.process(findex_rest_client).await, + } + } +} diff --git a/crate/cli/src/actions/login.rs b/crate/cli/src/actions/login.rs index daa294d..dd4ba3d 100644 --- a/crate/cli/src/actions/login.rs +++ b/crate/cli/src/actions/login.rs @@ -30,17 +30,23 @@ use crate::{ error::{result::CliResult, CliError}, }; -/// Login to the Identity Provider of the Findex server using the `OAuth2` authorization code flow. +/// Login to the Identity Provider of the Findex server using the `OAuth2` +/// authorization code flow. /// -/// This command will open a browser window and ask you to login to the Identity Provider. -/// Once you have logged in, the access token will be saved in the findex configuration file. +/// This command will open a browser window and ask you to login to the Identity +/// Provider. Once you have logged in, the access token will be saved in the +/// findex configuration file. /// -/// The configuration file must contain an `oauth2_conf` object with the following fields: -/// - `client_id`: The client ID of your application. This is provided by the Identity Provider. -/// - `client_secret`: The client secret of your application. This is provided by the Identity Provider. +/// The configuration file must contain an `oauth2_conf` object with the +/// following fields: +/// - `client_id`: The client ID of your application. This is provided by the +/// Identity Provider. +/// - `client_secret`: The client secret of your application. This is provided +/// by the Identity Provider. /// - `authorize_url`: The authorization URL of the provider. For example, for Google it is `https://accounts.google.com/o/oauth2/v2/auth`. /// - `token_url`: The token URL of the provider. For example, for Google it is `https://oauth2.googleapis.com/token`. -/// - `scopes`: The scopes to request. For example, for Google it is `["openid", "email"]`. +/// - `scopes`: The scopes to request. For example, for Google it is `["openid", +/// "email"]`. /// /// The callback url must be authorized on the Identity Provider with value `http://localhost:17899/token`. #[derive(Parser, Debug)] @@ -49,10 +55,12 @@ pub struct LoginAction; impl LoginAction { /// This function processes the login action. - /// It loads the client configuration from the specified path, retrieves the `OAuth2` configuration, - /// initializes the login state, prompts the user to browse to the authorization URL, - /// finalizes the login process by receiving the authorization code and exchanging it for an access token, - /// updates the configuration with the access token, and saves the configuration to the specified path. + /// It loads the client configuration from the specified path, retrieves the + /// `OAuth2` configuration, initializes the login state, prompts the + /// user to browse to the authorization URL, finalizes the login process + /// by receiving the authorization code and exchanging it for an access + /// token, updates the configuration with the access token, and saves + /// the configuration to the specified path. /// /// # Arguments /// @@ -62,11 +70,15 @@ impl LoginAction { /// /// This function can return a `CliError` in the following cases: /// - /// * The `login` command requires an Identity Provider (`IdP`) that must be configured in the `oauth2_conf` object in the client configuration file. + /// * The `login` command requires an Identity Provider (`IdP`) that must be + /// configured in the `oauth2_conf` object in the client configuration + /// file. /// * The client configuration file cannot be loaded. - /// * The `OAuth2` configuration is missing or invalid in the client configuration file. + /// * The `OAuth2` configuration is missing or invalid in the client + /// configuration file. /// * The authorization URL cannot be parsed. - /// * The authorization code is not received or does not match the CSRF token. + /// * The authorization code is not received or does not match the CSRF + /// token. /// * The access token cannot be requested from the Identity Provider. /// * The token exchange request fails. /// * The token exchange response cannot be parsed. @@ -121,15 +133,16 @@ pub struct Oauth2LoginConfig { } /// This struct holds the state of the login process. -/// It is used to generate the authorization URL and to store the PKCE verifier and the CSRF token. +/// It is used to generate the authorization URL and to store the PKCE verifier +/// and the CSRF token. /// -/// The user should browse to the authorization URL and follow the instructions to authenticate. -/// The Url can be recovered by accessing to field `auth_url`. +/// The user should browse to the authorization URL and follow the instructions +/// to authenticate. The Url can be recovered by accessing to field `auth_url`. /// -/// The CSRF token is used to verify that the authorization code received on the redirect URL -/// matches the one generated by the client. -/// The PKCE verifier is used to verify that the authorization code received on the redirect URL -/// matches the one generated by the client. +/// The CSRF token is used to verify that the authorization code received on the +/// redirect URL matches the one generated by the client. +/// The PKCE verifier is used to verify that the authorization code received on +/// the redirect URL matches the one generated by the client. /// See [RFC 7636](https://tools.ietf.org/html/rfc7636) for more details. /// See [PKCE](https://oauth.net/2/pkce/) for more details. /// See [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) for more details. @@ -146,10 +159,12 @@ pub struct LoginState { /// This function initializes the login process. /// It returns a `LoginState` that holds the state of the login process. /// -/// The process should be completed by instructing the user to browse to the authorization URL and follow the instructions to authenticate +/// The process should be completed by instructing the user to browse to the +/// authorization URL and follow the instructions to authenticate /// and immediately after calling `finalize()` with the returned `LoginState`. /// -/// The Url can be recovered by accessing `auth_url` on the returned `LoginState` struct. +/// The Url can be recovered by accessing `auth_url` on the returned +/// `LoginState` struct. impl TryFrom for LoginState { type Error = CliError; @@ -210,15 +225,18 @@ impl LoginState { /// This function finalizes the login process. /// It returns the access token. /// - /// This function should be called immediately after the user has been instructed to browse to the authorization URL. - /// It starts a server on localhost:17899 and waits for the authorization code to be received - /// from the browser window. Once the code is received, the server is closed and the code is returned. + /// This function should be called immediately after the user has been + /// instructed to browse to the authorization URL. It starts a server on + /// localhost:17899 and waits for the authorization code to be received + /// from the browser window. Once the code is received, the server is closed + /// and the code is returned. /// /// # Errors /// /// This function can return a `CliError` in the following cases: /// - /// * The authorization code, state, or other parameters are not received from the redirect URL. + /// * The authorization code, state, or other parameters are not received + /// from the redirect URL. /// * The received state does not match the CSRF token. /// * The authorization code is not received on authentication. /// * The code received on authentication does not match the CSRF token. @@ -226,12 +244,14 @@ impl LoginState { /// * The token exchange request fails. /// * The token exchange response cannot be parsed. pub async fn finalize(&self) -> CliResult { - // recover the authorization code, state and other parameters from the redirect URL + // recover the authorization code, state and other parameters from the redirect + // URL let auth_parameters = Self::receive_authorization_parameters()?; - // Once the user has been redirected to the redirect URL, you'll have access to the - // authorization code. For security reasons, your code should verify that the `state` - // parameter returned by the server matches `csrf_state`. + // Once the user has been redirected to the redirect URL, you'll have access to + // the authorization code. For security reasons, your code should verify + // that the `state` parameter returned by the server matches + // `csrf_state`. let received_state = auth_parameters .get("state") .ok_or_else(|| CliError::Default("state not received on authentication".to_owned()))?; @@ -248,7 +268,8 @@ impl LoginState { // Now you can trade it for an access token. - // TODO: unfortunately, the following does not work because Google return the JWT token in the `id_token` field, not the access_token field + // TODO: unfortunately, the following does not work because Google return the + // JWT token in the `id_token` field, not the access_token field // let token_result = login_state // .client // .exchange_code(AuthorizationCode::new(authorization_code.to_string())) @@ -256,7 +277,8 @@ impl LoginState { // .set_pkce_verifier(login_state.pkce_verifier) // .request_async(async_http_client) // .await - // .map_err(|e| CliError::Default(format!("token exchange failed: {:?}", e)))?; + // .map_err(|e| CliError::Default(format!("token exchange failed: {:?}", + // e)))?; let token_result = request_token( &self.login_config, @@ -273,8 +295,11 @@ impl LoginState { }) } - /// This function starts the server on `localhost:17899` and waits for the authorization code to be received - /// from the browser window. Once the code is received, the server is closed and the authorization code is returned. + /// This function starts the server on `localhost:17899` and waits for the + /// authorization code to be received from the browser window. Once the + /// code is received, the server is closed and the authorization code is + /// returned. + #[allow(clippy::unwrap_used)] // todo(manu): remove this fn receive_authorization_parameters() -> CliResult> { let (auth_params_tx, auth_params_rx) = mpsc::channel::>(); // Spawn the server into a runtime @@ -326,17 +351,19 @@ pub struct OAuthResponse { /// This function requests the access token from the Identity Provider. /// -/// This function was rewritten because Google returns the JWT token in the `id_token` field, -/// not in the `access_token` field. +/// This function was rewritten because Google returns the JWT token in the +/// `id_token` field, not in the `access_token` field. /// /// For Google see: /// /// # Arguments /// -/// * `login_config` - The `Oauth2LoginConfig` containing the client configuration. +/// * `login_config` - The `Oauth2LoginConfig` containing the client +/// configuration. /// * `redirect_url` - The redirect URL used in the `OAuth2` flow. /// * `pkce_verifier` - The PKCE code verifier used in the `OAuth2` flow. -/// * `authorization_code` - The authorization code received from the Identity Provider. +/// * `authorization_code` - The authorization code received from the Identity +/// Provider. /// /// # Errors /// diff --git a/crate/cli/src/actions/logout.rs b/crate/cli/src/actions/logout.rs index 2b1a456..f92c35a 100644 --- a/crate/cli/src/actions/logout.rs +++ b/crate/cli/src/actions/logout.rs @@ -21,15 +21,18 @@ impl LogoutAction { /// /// # Errors /// - /// Returns an error if there is an issue loading or saving the configuration file. - /// + /// Returns an error if there is an issue loading or saving the + /// configuration file. #[allow(clippy::print_stdout)] pub fn process(&self, conf_path: &PathBuf) -> CliResult<()> { let mut conf = ClientConf::load(conf_path)?; conf.findex_access_token = None; conf.save(conf_path)?; - println!("\nThe access token was removed from the Findex server configuration file: {conf_path:?}"); + println!( + "\nThe access token was removed from the Findex server configuration file: \ + {conf_path:?}" + ); Ok(()) } diff --git a/crate/cli/src/actions/markdown.rs b/crate/cli/src/actions/markdown.rs index 2357d65..1c04524 100644 --- a/crate/cli/src/actions/markdown.rs +++ b/crate/cli/src/actions/markdown.rs @@ -17,7 +17,8 @@ impl MarkdownAction { /// /// # Errors /// - /// Returns an error if there is an issue creating or writing to the markdown file. + /// Returns an error if there is an issue creating or writing to the + /// markdown file. #[allow(clippy::print_stdout)] pub fn process(&self, cmd: &Command) -> CliResult<()> { let mut output = String::new(); @@ -144,7 +145,7 @@ fn write_subcommands<'a>( format!("{}.{}", parent_index, i + 1) }; let full_command = if parent_command.is_empty() { - sub_command.get_name().to_string() + sub_command.get_name().to_owned() } else { format!("{} {}", parent_command, sub_command.get_name()) }; diff --git a/crate/cli/src/actions/mod.rs b/crate/cli/src/actions/mod.rs index c435e64..2c49fe9 100644 --- a/crate/cli/src/actions/mod.rs +++ b/crate/cli/src/actions/mod.rs @@ -1,6 +1,6 @@ pub mod console; +pub mod findex; pub mod login; pub mod logout; pub mod markdown; -pub mod new_database; pub mod version; diff --git a/crate/cli/src/actions/new_database.rs b/crate/cli/src/actions/new_database.rs deleted file mode 100644 index 2151de4..0000000 --- a/crate/cli/src/actions/new_database.rs +++ /dev/null @@ -1,52 +0,0 @@ -use clap::Parser; -use cosmian_findex_client::FindexClient; - -use crate::error::result::{CliResult, CliResultHelper}; - -/// Initialize a new user encrypted database and return the secret (`SQLCipher` only). -/// -/// This secret is only displayed once and is not stored anywhere on the server. -/// The secret must be set in the `kms_database_secret` property -/// of the CLI `findex.json` configuration file to use the encrypted database. -/// -/// Passing the correct secret "auto-selects" the correct encrypted database: -/// multiple encrypted databases can be used concurrently on the same Findex server server. -/// -/// Note: this action creates a new database: it will not return the secret -/// of the last created database and will not overwrite it. -#[derive(Parser, Debug)] -#[clap(verbatim_doc_comment)] -pub struct NewDatabaseAction; - -impl NewDatabaseAction { - /// Process the `NewDatabaseAction` by querying the Findex server to get a new database. - /// - /// # Arguments - /// - /// * `findex_rest_client` - The Findex server client used to communicate with the Findex server server. - /// - /// # Errors - /// - /// Returns an error if the query execution on the Findex server server fails. - /// - #[allow(clippy::print_stdout)] - pub async fn process(&self, findex_rest_client: &FindexClient) -> CliResult<()> { - // Query the Findex server to get a new database - let token = findex_rest_client - .new_database() - .await - .with_context(|| "Can't execute the query on the findex server")?; - - println!( - "A new user encrypted database is configured. Use the following token (by adding it \ - to the 'kms_database_secret' entry of your KMS_CLI_CONF):\n\n{token}\n\n" - ); - - println!( - "Do not loose it: there is not other copy!\nIt is impossible to recover the database \ - without the token." - ); - - Ok(()) - } -} diff --git a/crate/cli/src/actions/version.rs b/crate/cli/src/actions/version.rs index 2ecf28b..dd5a381 100644 --- a/crate/cli/src/actions/version.rs +++ b/crate/cli/src/actions/version.rs @@ -14,11 +14,13 @@ impl ServerVersionAction { /// /// # Arguments /// - /// * `findex_rest_client` - The Findex server client instance used to communicate with the Findex server server. + /// * `findex_rest_client` - The Findex server client instance used to + /// communicate with the Findex server server. /// /// # Errors /// - /// Returns an error if the version query fails or if there is an issue writing to the console. + /// Returns an error if the version query fails or if there is an issue + /// writing to the console. pub async fn process(&self, findex_rest_client: &FindexClient) -> CliResult<()> { let version = findex_rest_client .version() diff --git a/crate/cli/src/error/mod.rs b/crate/cli/src/error/mod.rs index 0866e3f..ecda939 100644 --- a/crate/cli/src/error/mod.rs +++ b/crate/cli/src/error/mod.rs @@ -2,6 +2,10 @@ use std::{array::TryFromSliceError, num::TryFromIntError, str::Utf8Error}; #[cfg(test)] use assert_cmd::cargo::CargoError; +use cloudproof_findex::{ + db_interfaces::DbInterfaceError, + reexport::{cosmian_crypto_core::CryptoCoreError, cosmian_findex}, +}; use cosmian_findex_client::ClientError; use hex::FromHexError; use pem::PemError; @@ -75,18 +79,6 @@ impl From for CliError { } } -impl From for CliError { - fn from(e: cloudproof::reexport::crypto_core::reexport::pkcs8::Error) -> Self { - Self::Conversion(e.to_string()) - } -} - -impl From for CliError { - fn from(e: cloudproof::reexport::cover_crypt::Error) -> Self { - Self::InvalidRequest(e.to_string()) - } -} - impl From for CliError { fn from(e: TryFromSliceError) -> Self { Self::Conversion(e.to_string()) @@ -166,6 +158,24 @@ impl From for CliError { } } +impl From for CliError { + fn from(e: CryptoCoreError) -> Self { + Self::Cryptographic(e.to_string()) + } +} + +impl From> for CliError { + fn from(e: cosmian_findex::Error) -> Self { + Self::Cryptographic(e.to_string()) + } +} + +impl From for CliError { + fn from(e: DbInterfaceError) -> Self { + Self::Cryptographic(e.to_string()) + } +} + /// Return early with an error if a condition is not satisfied. /// /// This macro is equivalent to `if !$cond { return Err(From::from($err)); }`. @@ -217,6 +227,7 @@ macro_rules! cli_bail { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use crate::error::result::CliResult; diff --git a/crate/cli/src/error/result.rs b/crate/cli/src/error/result.rs index 01bf489..82779f9 100644 --- a/crate/cli/src/error/result.rs +++ b/crate/cli/src/error/result.rs @@ -43,7 +43,7 @@ where impl CliResultHelper for Option { fn context(self, context: &str) -> CliResult { - self.ok_or_else(|| CliError::Default(context.to_string())) + self.ok_or_else(|| CliError::Default(context.to_owned())) } fn with_context(self, op: O) -> CliResult diff --git a/crate/cli/src/lib.rs b/crate/cli/src/lib.rs index bd2f7b4..11336a1 100644 --- a/crate/cli/src/lib.rs +++ b/crate/cli/src/lib.rs @@ -15,10 +15,34 @@ clippy::cargo, clippy::nursery, + // // restriction lints + // clippy::map_err_ignore, + // clippy::print_stdout, + // clippy::redundant_clone, + // clippy::todo + // restriction lints - clippy::map_err_ignore, + clippy::unwrap_used, + clippy::get_unwrap, + clippy::expect_used, + clippy::indexing_slicing, + clippy::unwrap_in_result, + clippy::assertions_on_result_states, + clippy::panic, + clippy::panic_in_result_fn, + clippy::renamed_function_params, + clippy::verbose_file_reads, + clippy::str_to_string, + clippy::string_to_string, + clippy::unreachable, + clippy::as_conversions, clippy::print_stdout, - clippy::redundant_clone + clippy::empty_structs_with_brackets, + clippy::unseparated_literal_suffix, + clippy::map_err_ignore, + clippy::redundant_clone, + clippy::todo + )] #![allow( clippy::module_name_repetitions, diff --git a/crate/cli/src/main.rs b/crate/cli/src/main.rs index 9d4459a..108d533 100644 --- a/crate/cli/src/main.rs +++ b/crate/cli/src/main.rs @@ -3,8 +3,8 @@ use std::{path::PathBuf, process}; use clap::{CommandFactory, Parser, Subcommand}; use cosmian_findex_cli::{ actions::{ - login::LoginAction, logout::LogoutAction, markdown::MarkdownAction, - new_database::NewDatabaseAction, version::ServerVersionAction, + findex::FindexCommands, login::LoginAction, logout::LogoutAction, markdown::MarkdownAction, + version::ServerVersionAction, }, error::result::CliResult, }; @@ -30,21 +30,23 @@ struct Cli { /// Allow to connect using a self-signed cert or untrusted cert chain /// - /// `accept_invalid_certs` is useful if the CLI needs to connect to an HTTPS Findex server - /// running an invalid or insecure SSL certificate + /// `accept_invalid_certs` is useful if the CLI needs to connect to an HTTPS + /// Findex server running an invalid or insecure SSL certificate #[arg(long)] pub(crate) accept_invalid_certs: Option, } #[derive(Subcommand)] enum CliCommands { - NewDatabase(NewDatabaseAction), + #[command(subcommand)] + Findex(FindexCommands), ServerVersion(ServerVersionAction), Login(LoginAction), Logout(LogoutAction), /// Action to auto-generate doc in Markdown format - /// Run `cargo run --bin findex -- markdown documentation/docs/cli/main_commands.md` + /// Run `cargo run --bin findex -- markdown + /// documentation/docs/cli/main_commands.md` #[clap(hide = true)] Markdown(MarkdownAction), } @@ -80,7 +82,7 @@ async fn main_() -> CliResult<()> { conf.initialize_findex_client(opts.url.as_deref(), opts.accept_invalid_certs)?; match command { - CliCommands::NewDatabase(action) => action.process(&findex_rest_client).await?, + CliCommands::Findex(action) => action.process(&findex_rest_client).await?, CliCommands::ServerVersion(action) => action.process(&findex_rest_client).await?, _ => { tracing::error!("unexpected command"); diff --git a/crate/cli/src/tests/auth_tests.rs b/crate/cli/src/tests/auth_tests.rs index 7d859c7..c095364 100644 --- a/crate/cli/src/tests/auth_tests.rs +++ b/crate/cli/src/tests/auth_tests.rs @@ -7,7 +7,7 @@ use cosmian_findex_client::FINDEX_CLI_CONF_ENV; use cosmian_logger::log_utils::log_init; use tempfile::TempDir; use test_findex_server::{ - start_test_server_with_options, AuthenticationOptions, DBConfig, TestsContext, + start_test_server_with_options, AuthenticationOptions, DBConfig, DatabaseType, TestsContext, }; use tracing::{info, trace}; @@ -24,8 +24,7 @@ pub(crate) async fn test_all_authentications() -> CliResult<()> { info!("Testing server with no auth"); let ctx = start_test_server_with_options( DBConfig { - database_type: Some("sqlite".to_owned()), - sqlite_path: PathBuf::from("./sqlite-data-auth-tests"), + database_type: Some(DatabaseType::Redis), clear_database: true, ..DBConfig::default() }, @@ -40,8 +39,7 @@ pub(crate) async fn test_all_authentications() -> CliResult<()> { ctx.stop_server().await?; let default_db_config = DBConfig { - database_type: Some("sqlite".to_owned()), - sqlite_path: PathBuf::from("./sqlite-data-auth-tests"), + database_type: Some(DatabaseType::Redis), clear_database: false, ..DBConfig::default() }; @@ -102,9 +100,9 @@ pub(crate) async fn test_all_authentications() -> CliResult<()> { .await?; ctx.stop_server().await?; - // On recent versions of macOS, the root Certificate for the client is searched on the keychains - // and not found, since it is a local self-signed certificate. - // This is likely a bug in reqwest + // On recent versions of macOS, the root Certificate for the client is searched + // on the keychains and not found, since it is a local self-signed + // certificate. This is likely a bug in reqwest #[cfg(not(target_os = "macos"))] { // tls client cert auth diff --git a/crate/cli/src/tests/findex.rs b/crate/cli/src/tests/findex.rs new file mode 100644 index 0000000..b39c29b --- /dev/null +++ b/crate/cli/src/tests/findex.rs @@ -0,0 +1,58 @@ +use std::process::Command; + +use assert_cmd::prelude::*; +use cosmian_findex_client::FINDEX_CLI_CONF_ENV; +use cosmian_logger::log_utils::log_init; +use test_findex_server::start_default_test_findex_server; +use tracing::debug; + +use crate::{ + actions::findex::{index::IndexAction, FindexParameters}, + error::{result::CliResult, CliError}, + tests::{utils::recover_cmd_logs, PROG_NAME}, +}; + +fn findex(cli_conf_path: &str, action: IndexAction) -> CliResult { + let mut args = vec!["index".to_owned()]; + + args.push("--key".to_owned()); + args.push(action.findex_parameters.key.clone()); + + args.push("--label".to_owned()); + args.push(action.findex_parameters.label); + + let mut cmd = Command::cargo_bin(PROG_NAME)?; + cmd.env(FINDEX_CLI_CONF_ENV, cli_conf_path); + + cmd.arg("findex").args(args); + debug!("cmd: {:?}", cmd); + let output = recover_cmd_logs(&mut cmd); + if output.status.success() { + let findex_output = std::str::from_utf8(&output.stdout)?; + return Ok(findex_output.to_owned()); + } + Err(CliError::Default( + std::str::from_utf8(&output.stderr)?.to_owned(), + )) +} + +#[tokio::test] +#[allow(clippy::needless_return)] +pub(crate) async fn test_findex() -> CliResult<()> { + log_init(None); + let ctx = start_default_test_findex_server().await; + + let cmd = findex( + &ctx.owner_client_conf_path, + IndexAction { + findex_parameters: FindexParameters { + key: "11223344556677889900AABBCCDDEEFF".to_owned(), + label: "My Findex label".to_owned(), + }, + }, + )?; + + debug!("cmd: {}", cmd); + + Ok(()) +} diff --git a/crate/cli/src/tests/mod.rs b/crate/cli/src/tests/mod.rs index 4a18d73..40ec40c 100644 --- a/crate/cli/src/tests/mod.rs +++ b/crate/cli/src/tests/mod.rs @@ -1,5 +1,6 @@ mod auth_tests; +mod findex; mod new_database; mod utils; -const PROG_NAME: &str = "findex"; +const PROG_NAME: &str = "cosmian_findex_cli"; diff --git a/crate/cli/src/tests/new_database.rs b/crate/cli/src/tests/new_database.rs index 9808c2f..7852d90 100644 --- a/crate/cli/src/tests/new_database.rs +++ b/crate/cli/src/tests/new_database.rs @@ -36,7 +36,7 @@ pub(crate) async fn test_new_database() -> CliResult<()> { } #[tokio::test] -#[allow(clippy::needless_return)] +#[allow(clippy::needless_return, clippy::panic_in_result_fn)] pub(crate) async fn test_conf_does_not_exist() -> CliResult<()> { log_init(option_env!("RUST_LOG")); let ctx = start_default_test_findex_server().await; diff --git a/crate/cli/src/tests/utils/cmd_logs.rs b/crate/cli/src/tests/utils/cmd_logs.rs index 6dff72e..87e46f8 100644 --- a/crate/cli/src/tests/utils/cmd_logs.rs +++ b/crate/cli/src/tests/utils/cmd_logs.rs @@ -4,6 +4,7 @@ use std::{ }; /// Recover output logs from a command call `cmd` and re-inject it into stdio +#[allow(clippy::unwrap_used)] pub(crate) fn recover_cmd_logs(cmd: &mut Command) -> Output { let output = cmd .stdout(Stdio::piped()) diff --git a/crate/client/Cargo.toml b/crate/client/Cargo.toml index b1ef914..2a8f529 100644 --- a/crate/client/Cargo.toml +++ b/crate/client/Cargo.toml @@ -16,7 +16,6 @@ doctest = false [dependencies] base64 = { workspace = true } -cloudproof = { workspace = true } der = { workspace = true } log = { workspace = true } pem = { workspace = true } @@ -28,4 +27,8 @@ thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true } webpki-roots = "0.22" -x509-cert = { workspace = true } +x509-cert = { workspace = true, features = ["pem"] } + +[dev-dependencies] +faker_rand = "0.1" +rand = "0.8" diff --git a/crate/client/datasets/users.json b/crate/client/datasets/users.json new file mode 100644 index 0000000..6de389e --- /dev/null +++ b/crate/client/datasets/users.json @@ -0,0 +1,1002 @@ +[ + { + "firstName": "Felix", + "lastName": "Shepherd", + "phone": "06 52 23 63 25", + "email": "orci@icloud.org", + "country": "Germany", + "region": "Upper Austria", + "employeeNumber": "SPN82TTO0PP", + "security": "confidential" + }, + { + "firstName": "Emerson", + "lastName": "Wilkins", + "phone": "01 01 31 41 37", + "email": "enim.diam@icloud.edu", + "country": "Spain", + "region": "Antofagasta", + "employeeNumber": "BYE60HQT6XG", + "security": "confidential" + }, + { + "firstName": "Ocean", + "lastName": "Meyers", + "phone": "07 45 55 66 55", + "email": "ultrices.vivamus@aol.net", + "country": "Spain", + "region": "Podlaskie", + "employeeNumber": "SXK82FCR9EP", + "security": "confidential" + }, + { + "firstName": "Kiara", + "lastName": "Harper", + "phone": "07 17 88 69 58", + "email": "vitae@outlook.com", + "country": "Germany", + "region": "Rajasthan", + "employeeNumber": "CWN36QTX2BN", + "security": "secret" + }, + { + "firstName": "Joelle", + "lastName": "Becker", + "phone": "01 11 46 84 14", + "email": "felis.adipiscing@hotmail.org", + "country": "France", + "region": "İzmir", + "employeeNumber": "AFR04EPJ1YM", + "security": "secret" + }, + { + "firstName": "Stacy", + "lastName": "Reyes", + "phone": "03 53 66 40 67", + "email": "risus.a@yahoo.ca", + "country": "France", + "region": "Nord-Pas-de-Calais", + "employeeNumber": "ZVW02EAM3ZC", + "security": "secret" + }, + { + "firstName": "Donna", + "lastName": "Velazquez", + "phone": "01 69 11 40 51", + "email": "mus.donec@aol.couk", + "country": "Germany", + "region": "Tuyên Quang", + "employeeNumber": "DOP17EIM7ST", + "security": "top_secret" + }, + { + "firstName": "Wylie", + "lastName": "Snider", + "phone": "07 36 72 54 66", + "email": "fringilla.mi@google.com", + "country": "France", + "region": "Kansas", + "employeeNumber": "RYS34KBD5VW", + "security": "top_secret" + }, + { + "firstName": "Brielle", + "lastName": "Finley", + "phone": "02 75 95 77 31", + "email": "egestas.aliquam@hotmail.edu", + "country": "France", + "region": "Ulyanovsk Oblast", + "employeeNumber": "MFU36KUO6UD", + "security": "top_secret" + }, + { + "firstName": "Bryar", + "lastName": "Christian", + "phone": "02 44 54 20 55", + "email": "ullamcorper.eu.euismod@google.ca", + "country": "Germany", + "region": "Hatay", + "employeeNumber": "TRK72WOV9VH", + "security": "confidential" + }, + { + "firstName": "Diana", + "lastName": "Wilson", + "phone": "03 35 75 32 28", + "email": "nascetur.ridiculus.mus@outlook.net", + "country": "Germany", + "region": "Ulster", + "employeeNumber": "UPS23SOZ6QN", + "security": "confidential" + }, + { + "firstName": "Paul", + "lastName": "Ford", + "phone": "05 13 27 74 63", + "email": "pede.suspendisse@icloud.com", + "country": "Germany", + "region": "Rio Grande do Sul", + "employeeNumber": "CWT54TJX4RT", + "security": "confidential" + }, + { + "firstName": "Felicia", + "lastName": "Massey", + "phone": "06 72 81 43 63", + "email": "lacus.varius.et@yahoo.ca", + "country": "Germany", + "region": "Brecknockshire", + "employeeNumber": "BAC58KIS7DY", + "security": "secret" + }, + { + "firstName": "Barclay", + "lastName": "Allison", + "phone": "08 71 12 69 37", + "email": "in.cursus@aol.com", + "country": "Germany", + "region": "Caquetá", + "employeeNumber": "KLL08RGK2JW", + "security": "secret" + }, + { + "firstName": "Skyler", + "lastName": "Richmond", + "phone": "Figueroa", + "email": "elit@google.net", + "country": "France", + "region": "Chiapas", + "employeeNumber": "ITO71LVO4PD", + "security": "secret" + }, + { + "firstName": "Justin", + "lastName": "Cross", + "phone": "07 56 26 00 16", + "email": "neque.vitae@yahoo.edu", + "country": "Germany", + "region": "Friuli-Venezia Giulia", + "employeeNumber": "HHH01MIH6SZ", + "security": "top_secret" + }, + { + "firstName": "Miranda", + "lastName": "Cotton", + "phone": "06 73 42 44 47", + "email": "eget.magna@google.ca", + "country": "Spain", + "region": "Møre og Romsdal", + "employeeNumber": "DFW37PPI8TY", + "security": "top_secret" + }, + { + "firstName": "Figueroa", + "lastName": "Kane", + "phone": "02 44 08 45 32", + "email": "aenean.eget@protonmail.ca", + "country": "France", + "region": "Kansas", + "employeeNumber": "HFG82IKJ2OC", + "security": "top_secret" + }, + { + "firstName": "Lesley", + "lastName": "Sullivan", + "phone": "02 24 15 21 81", + "email": "orci.ut@protonmail.couk", + "country": "Spain", + "region": "Lubelskie", + "employeeNumber": "WOA67IVR6CM", + "security": "confidential" + }, + { + "firstName": "Clio", + "lastName": "Figueroa", + "phone": "06 87 82 58 97", + "email": "tellus@yahoo.org", + "country": "France", + "region": "Munster", + "employeeNumber": "ASS31LNB5CV", + "security": "confidential" + }, + { + "firstName": "Forrest", + "lastName": "Parsons", + "phone": "06 81 51 26 17", + "email": "iaculis.quis@yahoo.com", + "country": "France", + "region": "Hải Phòng", + "employeeNumber": "MJS53TBZ8UL", + "security": "confidential" + }, + { + "firstName": "Maxwell", + "lastName": "Park", + "phone": "01 37 79 08 31", + "email": "ut@yahoo.com", + "country": "Germany", + "region": "Guanacaste", + "employeeNumber": "PHQ43BNF8MI", + "security": "secret" + }, + { + "firstName": "Kalia", + "lastName": "Hayden", + "phone": "02 24 48 01 44", + "email": "non.egestas.a@aol.ca", + "country": "Spain", + "region": "Gävleborgs län", + "employeeNumber": "QUW73NPX4UG", + "security": "secret" + }, + { + "firstName": "Russell", + "lastName": "Willis", + "phone": "07 42 02 43 15", + "email": "sit.amet@icloud.edu", + "country": "France", + "region": "Bihar", + "employeeNumber": "QOU03UHS4LQ", + "security": "secret" + }, + { + "firstName": "Judah", + "lastName": "Chang", + "phone": "02 15 66 88 81", + "email": "tempor.arcu@icloud.ca", + "country": "Spain", + "region": "Assam", + "employeeNumber": "BJJ93AIN8LC", + "security": "top_secret" + }, + { + "firstName": "Chaim", + "lastName": "Richards", + "phone": "08 97 39 30 70", + "email": "nunc.sed@protonmail.com", + "country": "Germany", + "region": "Xīběi", + "employeeNumber": "JTS20MCR7GX", + "security": "top_secret" + }, + { + "firstName": "Zachary", + "lastName": "Porter", + "phone": "02 02 52 43 30", + "email": "suscipit.nonummy.fusce@hotmail.couk", + "country": "Germany", + "region": "Kaduna", + "employeeNumber": "NLN76SBS2EI", + "security": "top_secret" + }, + { + "firstName": "Cade", + "lastName": "Gould", + "phone": "09 82 18 22 16", + "email": "neque.vitae@google.org", + "country": "Germany", + "region": "Luik", + "employeeNumber": "RSW01YCJ6HJ", + "security": "confidential" + }, + { + "firstName": "Hiram", + "lastName": "Gates", + "phone": "02 46 85 81 87", + "email": "tristique.senectus@outlook.net", + "country": "Spain", + "region": "Niger", + "employeeNumber": "EGG84NJY5TH", + "security": "confidential" + }, + { + "firstName": "Deirdre", + "lastName": "Tate", + "phone": "02 99 26 61 08", + "email": "laoreet.ipsum@hotmail.com", + "country": "Spain", + "region": "Anambra", + "employeeNumber": "SLO36EYL1LQ", + "security": "confidential" + }, + { + "firstName": "Len", + "lastName": "Carlson", + "phone": "05 69 55 17 78", + "email": "non@aol.ca", + "country": "Germany", + "region": "Western Visayas", + "employeeNumber": "DHS57TIH5JX", + "security": "secret" + }, + { + "firstName": "Griffin", + "lastName": "Porter", + "phone": "03 44 78 02 98", + "email": "volutpat.nulla@protonmail.org", + "country": "Germany", + "region": "Rheinland-Pfalz", + "employeeNumber": "JTQ18TFU5XL", + "security": "secret" + }, + { + "firstName": "Caleb", + "lastName": "Sellers", + "phone": "08 43 33 76 76", + "email": "ipsum.dolor@aol.net", + "country": "Spain", + "region": "Lanarkshire", + "employeeNumber": "DJE23GBD4HV", + "security": "secret" + }, + { + "firstName": "Wang", + "lastName": "Chan", + "phone": "07 13 38 17 82", + "email": "venenatis.vel@outlook.net", + "country": "Spain", + "region": "Tripura", + "employeeNumber": "VYY77VOW0QR", + "security": "top_secret" + }, + { + "firstName": "Kalia", + "lastName": "Douglas", + "phone": "03 56 82 77 04", + "email": "mus.proin@hotmail.net", + "country": "France", + "region": "Tripura", + "employeeNumber": "AHM27UPN3HD", + "security": "top_secret" + }, + { + "firstName": "Ivy", + "lastName": "Wong", + "phone": "04 95 11 83 54", + "email": "sit.amet.lorem@google.org", + "country": "Germany", + "region": "Kahramanmaraş", + "employeeNumber": "YCE84QZN1AU", + "security": "top_secret" + }, + { + "firstName": "Brendan", + "lastName": "Rivers", + "phone": "02 24 31 11 26", + "email": "nonummy.ut@aol.couk", + "country": "Germany", + "region": "Free State", + "employeeNumber": "RVY06JHG6DN", + "security": "confidential" + }, + { + "firstName": "Jane", + "lastName": "Mckay", + "phone": "01 48 61 56 13", + "email": "vestibulum@protonmail.couk", + "country": "France", + "region": "Newfoundland and Labrador", + "employeeNumber": "CEL23TSP8QV", + "security": "confidential" + }, + { + "firstName": "Leroy", + "lastName": "Cole", + "phone": "06 78 56 75 66", + "email": "integer.eu@aol.edu", + "country": "Germany", + "region": "Bengkulu", + "employeeNumber": "SCI84CBR1UF", + "security": "confidential" + }, + { + "firstName": "Axel", + "lastName": "Buckley", + "phone": "09 61 37 41 27", + "email": "dignissim.maecenas@aol.com", + "country": "Germany", + "region": "Luik", + "employeeNumber": "JWL04CDN7BL", + "security": "secret" + }, + { + "firstName": "Martin", + "lastName": "Stuart", + "phone": "09 80 48 88 65", + "email": "feugiat.non@icloud.com", + "country": "Germany", + "region": "Queensland", + "employeeNumber": "LVT07UPM5YB", + "security": "secret" + }, + { + "firstName": "Jerry", + "lastName": "Gonzales", + "phone": "07 24 80 13 06", + "email": "convallis.convallis@aol.org", + "country": "Spain", + "region": "São Paulo", + "employeeNumber": "RBU57EWQ8MI", + "security": "secret" + }, + { + "firstName": "Pandora", + "lastName": "Robinson", + "phone": "05 92 75 54 58", + "email": "libero.mauris@yahoo.couk", + "country": "Spain", + "region": "Meghalaya", + "employeeNumber": "GFM62DCY6SQ", + "security": "top_secret" + }, + { + "firstName": "Xander", + "lastName": "Douglas", + "phone": "08 22 77 36 03", + "email": "arcu.sed@protonmail.couk", + "country": "France", + "region": "Felix", + "employeeNumber": "DIY45MVM4TV", + "security": "top_secret" + }, + { + "firstName": "Tyler", + "lastName": "Webb", + "phone": "07 71 71 93 44", + "email": "pede.praesent@outlook.edu", + "country": "Germany", + "region": "Northern Mindanao", + "employeeNumber": "XWN45SZY1HB", + "security": "top_secret" + }, + { + "firstName": "Martena", + "lastName": "Lynn", + "phone": "06 88 58 74 37", + "email": "magnis.dis@outlook.com", + "country": "Germany", + "region": "Zhōngnán", + "employeeNumber": "MYO35XFT4CC", + "security": "confidential" + }, + { + "firstName": "Clinton", + "lastName": "Bradshaw", + "phone": "08 42 86 17 33", + "email": "venenatis.lacus.etiam@google.net", + "country": "France", + "region": "North West", + "employeeNumber": "MIK59YOF7GO", + "security": "confidential" + }, + { + "firstName": "Giacomo", + "lastName": "House", + "phone": "08 84 84 60 44", + "email": "risus@hotmail.org", + "country": "Germany", + "region": "FATA", + "employeeNumber": "OCQ75JAR6BE", + "security": "confidential" + }, + { + "firstName": "Molly", + "lastName": "Whitehead", + "phone": "06 61 67 75 61", + "email": "interdum.nunc.sollicitudin@yahoo.couk", + "country": "France", + "region": "South Island", + "employeeNumber": "NIS84SJD6FR", + "security": "secret" + }, + { + "firstName": "Luke", + "lastName": "Reed", + "phone": "08 73 28 17 78", + "email": "molestie@protonmail.org", + "country": "Spain", + "region": "Central Region", + "employeeNumber": "KAY46LAI4JN", + "security": "secret" + }, + { + "firstName": "Mason", + "lastName": "Snider", + "phone": "02 57 65 38 41", + "email": "nulla.semper@protonmail.ca", + "country": "Spain", + "region": "Ilocos Region", + "employeeNumber": "YOC20OKT9UN", + "security": "secret" + }, + { + "firstName": "Dieter", + "lastName": "Bright", + "phone": "04 74 61 75 34", + "email": "in.molestie@hotmail.ca", + "country": "Spain", + "region": "Cajamarca", + "employeeNumber": "IJI23SQF8YO", + "security": "top_secret" + }, + { + "firstName": "Tashya", + "lastName": "Vazquez", + "phone": "07 98 70 76 64", + "email": "ultricies@yahoo.edu", + "country": "Spain", + "region": "Zhytomyr oblast", + "employeeNumber": "DBU82FRI2YJ", + "security": "top_secret" + }, + { + "firstName": "Jordan", + "lastName": "Wilder", + "phone": "06 50 84 72 43", + "email": "eget.lacus@aol.ca", + "country": "Spain", + "region": "Central Java", + "employeeNumber": "DGG17XWQ6UM", + "security": "top_secret" + }, + { + "firstName": "Dominique", + "lastName": "Mcfarland", + "phone": "07 17 21 15 05", + "email": "ac@outlook.ca", + "country": "Germany", + "region": "Kursk Oblast", + "employeeNumber": "JIP69EHR6KY", + "security": "confidential" + }, + { + "firstName": "Hyatt", + "lastName": "Marks", + "phone": "03 47 59 28 56", + "email": "at.sem.molestie@yahoo.com", + "country": "France", + "region": "Picardie", + "employeeNumber": "YVU78WCO3LK", + "security": "confidential" + }, + { + "firstName": "Kasper", + "lastName": "Brennan", + "phone": "04 57 71 59 02", + "email": "bibendum@google.ca", + "country": "France", + "region": "Rogaland", + "employeeNumber": "THP01CAA6LJ", + "security": "confidential" + }, + { + "firstName": "Summer", + "lastName": "Crane", + "phone": "05 35 71 18 22", + "email": "ipsum@aol.org", + "country": "Germany", + "region": "North Chungcheong", + "employeeNumber": "UJY85LZO1VG", + "security": "secret" + }, + { + "firstName": "Xenos", + "lastName": "Whitfield", + "phone": "08 30 96 35 54", + "email": "a.feugiat@outlook.couk", + "country": "Spain", + "region": "Kherson oblast", + "employeeNumber": "NVG99IMP6JD", + "security": "secret" + }, + { + "firstName": "Dale", + "lastName": "Lane", + "phone": "06 57 86 21 77", + "email": "lacus@aol.com", + "country": "Spain", + "region": "California", + "employeeNumber": "WOG25MBO3PC", + "security": "secret" + }, + { + "firstName": "Baker", + "lastName": "Knowles", + "phone": "02 59 68 76 19", + "email": "sem.ut@protonmail.edu", + "country": "France", + "region": "Heredia", + "employeeNumber": "JAI31QCC1CS", + "security": "top_secret" + }, + { + "firstName": "Elaine", + "lastName": "Chase", + "phone": "08 96 37 63 57", + "email": "tellus.sem.mollis@icloud.ca", + "country": "France", + "region": "Dalarnas län", + "employeeNumber": "TCF97SQG4US", + "security": "top_secret" + }, + { + "firstName": "Sophia", + "lastName": "Salinas", + "phone": "04 88 58 20 33", + "email": "arcu.sed@outlook.couk", + "country": "Spain", + "region": "East Java", + "employeeNumber": "QPD64DEE4MN", + "security": "top_secret" + }, + { + "firstName": "Ramona", + "lastName": "Sampson", + "phone": "07 66 19 62 82", + "email": "nullam.suscipit@yahoo.ca", + "country": "France", + "region": "Principado de Asturias", + "employeeNumber": "XDX32WIN7KD", + "security": "confidential" + }, + { + "firstName": "Iris", + "lastName": "Berg", + "phone": "01 00 34 62 27", + "email": "ante@aol.ca", + "country": "Spain", + "region": "Atacama", + "employeeNumber": "JOJ64MTG8YN", + "security": "confidential" + }, + { + "firstName": "Calvin", + "lastName": "Joyner", + "phone": "06 84 59 78 28", + "email": "ipsum@google.couk", + "country": "France", + "region": "Lagos", + "employeeNumber": "DPF55GCD6AS", + "security": "confidential" + }, + { + "firstName": "Martina", + "lastName": "Hickman", + "phone": "02 21 24 02 39", + "email": "erat.volutpat@protonmail.com", + "country": "Spain", + "region": "Catalunya", + "employeeNumber": "YRL83GET3VE", + "security": "secret" + }, + { + "firstName": "Xantha", + "lastName": "Montgomery", + "phone": "03 05 74 29 97", + "email": "risus.odio@protonmail.com", + "country": "Germany", + "region": "Merionethshire", + "employeeNumber": "UCQ10RTQ2SI", + "security": "secret" + }, + { + "firstName": "Amy", + "lastName": "Wright", + "phone": "05 65 15 40 63", + "email": "nunc.lectus@aol.com", + "country": "France", + "region": "Penza Oblast", + "employeeNumber": "IPT58WWO5LX", + "security": "secret" + }, + { + "firstName": "Alma", + "lastName": "Schroeder", + "phone": "04 23 46 16 33", + "email": "aliquet.magna.a@protonmail.couk", + "country": "Germany", + "region": "East Kalimantan", + "employeeNumber": "EMH30WSQ3MS", + "security": "top_secret" + }, + { + "firstName": "Marvin", + "lastName": "Bowen", + "phone": "07 42 38 86 27", + "email": "malesuada@aol.couk", + "country": "France", + "region": "Tasmania", + "employeeNumber": "YUQ42QOG7XD", + "security": "top_secret" + }, + { + "firstName": "Ila", + "lastName": "Drake", + "phone": "02 47 45 63 07", + "email": "posuere.enim@google.edu", + "country": "France", + "region": "Gävleborgs län", + "employeeNumber": "RJT61MYB8MY", + "security": "top_secret" + }, + { + "firstName": "Noble", + "lastName": "Cunningham", + "phone": "05 19 20 79 58", + "email": "mollis.non@yahoo.couk", + "country": "Spain", + "region": "Brussels Hoofdstedelijk Gewest", + "employeeNumber": "FCF77JFT8EW", + "security": "confidential" + }, + { + "firstName": "Lilah", + "lastName": "Stewart", + "phone": "05 56 71 95 15", + "email": "tincidunt.orci@google.ca", + "country": "Germany", + "region": "South Australia", + "employeeNumber": "HZI85ZFQ4SH", + "security": "confidential" + }, + { + "firstName": "Gavin", + "lastName": "Bailey", + "phone": "08 25 25 31 93", + "email": "dui.in.sodales@protonmail.net", + "country": "Spain", + "region": "Diyarbakır", + "employeeNumber": "NVH67DKP6FV", + "security": "confidential" + }, + { + "firstName": "Janna", + "lastName": "Hurst", + "phone": "04 26 15 12 98", + "email": "consectetuer.cursus.et@google.couk", + "country": "Spain", + "region": "Ceuta", + "employeeNumber": "EOR61GBW9OL", + "security": "secret" + }, + { + "firstName": "Kylie", + "lastName": "Mullen", + "phone": "02 46 54 90 13", + "email": "lobortis.quam@google.edu", + "country": "Germany", + "region": "Waals-Brabant", + "employeeNumber": "TBS73YUW3PQ", + "security": "secret" + }, + { + "firstName": "MacKensie", + "lastName": "Atkinson", + "phone": "08 03 38 16 24", + "email": "integer.eu@google.org", + "country": "France", + "region": "Sachsen", + "employeeNumber": "NGO64EMM1XG", + "security": "secret" + }, + { + "firstName": "Jack", + "lastName": "Armstrong", + "phone": "07 22 43 06 14", + "email": "varius.nam@protonmail.edu", + "country": "Spain", + "region": "Andaman and Nicobar Islands", + "employeeNumber": "EEI44MCQ4MF", + "security": "top_secret" + }, + { + "firstName": "Karly", + "lastName": "Maxwell", + "phone": "03 93 73 18 84", + "email": "pharetra.nam@icloud.edu", + "country": "Spain", + "region": "Henegouwen", + "employeeNumber": "ABY52MFR3GP", + "security": "top_secret" + }, + { + "firstName": "Lucius", + "lastName": "Baxter", + "phone": "06 37 54 06 88", + "email": "eu.metus@icloud.org", + "country": "France", + "region": "North Region", + "employeeNumber": "UCI44NTO1YV", + "security": "top_secret" + }, + { + "firstName": "Palmer", + "lastName": "Mccall", + "phone": "01 95 80 85 96", + "email": "a.auctor@google.edu", + "country": "Germany", + "region": "Michigan", + "employeeNumber": "QCB22NGM7VN", + "security": "confidential" + }, + { + "firstName": "Reagan", + "lastName": "Lynch", + "phone": "04 15 43 21 21", + "email": "donec.nibh@google.ca", + "country": "France", + "region": "Gävleborgs län", + "employeeNumber": "BUD99RIH9KB", + "security": "confidential" + }, + { + "firstName": "Kibo", + "lastName": "Mcintosh", + "phone": "08 88 35 57 43", + "email": "aliquam@protonmail.net", + "country": "France", + "region": "Małopolskie", + "employeeNumber": "VZR07UEQ2PU", + "security": "confidential" + }, + { + "firstName": "Peter", + "lastName": "Edwards", + "phone": "01 56 71 49 15", + "email": "cras@protonmail.edu", + "country": "Spain", + "region": "Chernivtsi oblast", + "employeeNumber": "FIY93USG7LF", + "security": "secret" + }, + { + "firstName": "Kato", + "lastName": "Parsons", + "phone": "02 15 37 96 48", + "email": "lectus@google.com", + "country": "France", + "region": "Western Australia", + "employeeNumber": "BXB16IOM1OH", + "security": "secret" + }, + { + "firstName": "Suki", + "lastName": "Newman", + "phone": "07 25 82 47 51", + "email": "sed.id@protonmail.ca", + "country": "France", + "region": "Bình Dương", + "employeeNumber": "EFC57JHW4MT", + "security": "secret" + }, + { + "firstName": "Sean", + "lastName": "Tucker", + "phone": "02 55 88 99 01", + "email": "quis.arcu.vel@icloud.ca", + "country": "Germany", + "region": "Kahramanmaraş", + "employeeNumber": "DPF11YBI4FX", + "security": "top_secret" + }, + { + "firstName": "Eve", + "lastName": "Collier", + "phone": "02 38 25 54 78", + "email": "diam.duis.mi@icloud.org", + "country": "Germany", + "region": "Corse", + "employeeNumber": "EXV52JNI9ZG", + "security": "top_secret" + }, + { + "firstName": "Martena", + "lastName": "Grimes", + "phone": "08 78 28 44 11", + "email": "cras.eu@yahoo.edu", + "country": "Germany", + "region": "South Island", + "employeeNumber": "NBX34YIH1OQ", + "security": "top_secret" + }, + { + "firstName": "Eagan", + "lastName": "Foster", + "phone": "05 27 63 48 34", + "email": "donec.sollicitudin@yahoo.couk", + "country": "Germany", + "region": "Munster", + "employeeNumber": "DVD87LBG6UL", + "security": "confidential" + }, + { + "firstName": "Eleanor", + "lastName": "Snow", + "phone": "05 95 41 55 09", + "email": "et.magnis@hotmail.couk", + "country": "Spain", + "region": "Antofagasta", + "employeeNumber": "JDG52IJE1FN", + "security": "confidential" + }, + { + "firstName": "Shaine", + "lastName": "Quinn", + "phone": "06 13 89 12 11", + "email": "sit.amet.lorem@protonmail.edu", + "country": "Germany", + "region": "West Region", + "employeeNumber": "XNW76UKP0DP", + "security": "confidential" + }, + { + "firstName": "Wylie", + "lastName": "Gay", + "phone": "08 99 13 97 15", + "email": "adipiscing.lobortis.risus@hotmail.ca", + "country": "Germany", + "region": "Antofagasta", + "employeeNumber": "OMR67KMM3QR", + "security": "secret" + }, + { + "firstName": "Xenos", + "lastName": "Olson", + "phone": "08 26 78 84 71", + "email": "pede.sagittis@aol.org", + "country": "Spain", + "region": "Paraíba", + "employeeNumber": "EQN94VSX2IJ", + "security": "secret" + }, + { + "firstName": "Beck", + "lastName": "Gray", + "phone": "08 72 82 86 44", + "email": "felis.ullamcorper@google.couk", + "country": "Germany", + "region": "Noord Holland", + "employeeNumber": "VCD88TYG7IX", + "security": "secret" + }, + { + "firstName": "Zeus", + "lastName": "Cherry", + "phone": "03 37 88 63 83", + "email": "dolor.sit@hotmail.edu", + "country": "France", + "region": "Munster", + "employeeNumber": "BTZ36YSA2XP", + "security": "top_secret" + }, + { + "firstName": "Aaron", + "lastName": "Stanton", + "phone": "09 21 34 93 29", + "email": "magna.sed@yahoo.org", + "country": "Germany", + "region": "Mexico City", + "employeeNumber": "UDB51OGB2XO", + "security": "top_secret" + }, + { + "firstName": "Uriah", + "lastName": "Foley", + "phone": "09 39 55 67 05", + "email": "mi.fringilla.mi@yahoo.edu", + "country": "Germany", + "region": "North Gyeongsang", + "employeeNumber": "JXW87GWD3IF", + "security": "top_secret" + }, + { + "firstName": "Ima", + "lastName": "Ewing", + "phone": "02 91 58 54 73", + "email": "vitae@yahoo.com", + "country": "Germany", + "region": "National Capital Region", + "employeeNumber": "JCO37AVA1LH", + "security": "confidential" + } +] diff --git a/crate/client/src/certificate_verifier.rs b/crate/client/src/certificate_verifier.rs index ad01e9e..1f5c67a 100644 --- a/crate/client/src/certificate_verifier.rs +++ b/crate/client/src/certificate_verifier.rs @@ -5,7 +5,8 @@ use rustls::{ Certificate, Error as RustTLSError, ServerName, }; -/// A TLS verifier adding the ability to match the leaf certificate with a trusted one. +/// A TLS verifier adding the ability to match the leaf certificate with a +/// trusted one. pub(crate) struct LeafCertificateVerifier { // The certificate we expect to see in the TLS connection expected_cert: Certificate, diff --git a/crate/client/src/config.rs b/crate/client/src/config.rs index dd14315..421fa01 100644 --- a/crate/client/src/config.rs +++ b/crate/client/src/config.rs @@ -5,6 +5,7 @@ use std::{ path::PathBuf, }; +// todo(manu): finder.json configuration tests are incorrect (delete secret_database) use der::{DecodePem, Encode}; #[cfg(target_os = "linux")] use log::info; @@ -15,17 +16,21 @@ use x509_cert::Certificate as X509Certificate; #[cfg(target_os = "linux")] use crate::client_bail; use crate::{ - error::{result::RestClientResultHelper, ClientError}, + error::{ + result::{ClientResult, RestClientResultHelper}, + ClientError, + }, FindexClient, }; /// Returns the path to the current user's home folder. /// -/// On Linux and macOS, the home folder is typically located at `/home/` -/// or `/Users/`, respectively. On Windows, the home folder is typically -/// located at `C:\Users\`. However, the location of the home folder can -/// be changed by the user or by system administrators, so it's important to check -/// for the existence of the appropriate environment variables. +/// On Linux and macOS, the home folder is typically located at +/// `/home/` or `/Users/`, respectively. On Windows, the +/// home folder is typically located at `C:\Users\`. However, the +/// location of the home folder can be changed by the user or by system +/// administrators, so it's important to check for the existence of the +/// appropriate environment variables. /// /// Returns `None` if the home folder cannot be determined. fn get_home_folder() -> Option { @@ -42,7 +47,8 @@ fn get_home_folder() -> Option { { return Some(PathBuf::from(hdrive).join(hpath)); } - // If none of the above environment variables exist, the home folder cannot be determined + // If none of the above environment variables exist, the home folder cannot be + // determined None } @@ -54,7 +60,8 @@ fn get_default_conf_path() -> Result { .map(|home| home.join(".cosmian/findex_client.json")) } -/// used for serialization +/// Required for `serde` serialization +#[allow(clippy::trivially_copy_pass_by_ref)] const fn not(b: &bool) -> bool { !*b } @@ -134,7 +141,8 @@ impl Default for ClientConf { } } -/// This method is used to configure the Findex CLI by reading a JSON configuration file. +/// This method is used to configure the Findex CLI by reading a JSON +/// configuration file. /// /// The method looks for a JSON configuration file with the following structure: /// @@ -148,17 +156,22 @@ impl Default for ClientConf { /// "ssl_client_pkcs12_password": "password" /// } /// ``` -/// The path to the configuration file is specified through the `FINDEX_CLI_CONF` environment variable. -/// If the environment variable is not set, a default path is used. -/// If the configuration file does not exist at the path, a new file is created with default values. +/// The path to the configuration file is specified through the +/// `FINDEX_CLI_CONF` environment variable. If the environment variable is not +/// set, a default path is used. If the configuration file does not exist at the +/// path, a new file is created with default values. /// -/// This function returns a Findex client configured according to the settings specified in the configuration file. +/// This function returns a Findex client configured according to the settings +/// specified in the configuration file. pub const FINDEX_CLI_CONF_ENV: &str = "FINDEX_CLI_CONF"; #[cfg(target_os = "linux")] pub(crate) const FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH: &str = "/etc/cosmian/findex.json"; impl ClientConf { - pub fn location(conf: Option) -> Result { + /// Returns the path to the configuration file. + /// # Errors + /// Returns an error if the configuration file does not exist. + pub fn location(conf: Option) -> ClientResult { // Obtain the configuration file path from: // - the `--conf` arg // - the environment variable corresponding to `FINDEX_CLI_CONF_ENV` @@ -174,8 +187,8 @@ impl ClientConf { // Error if the specified file does not exist if !conf_path.exists() { return Err(ClientError::NotSupported(format!( - "Configuration file {conf_path:?} specified in {FINDEX_CLI_CONF_ENV} environment \ - variable does not exist" + "Configuration file {conf_path:?} specified in {FINDEX_CLI_CONF_ENV} \ + environment variable does not exist" ))); } return Ok(conf_path); @@ -193,17 +206,19 @@ impl ClientConf { let default_system_path = PathBuf::from(FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH); if default_system_path.exists() { info!( - "No active user, using configuration at {FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH}" + "No active user, using configuration at \ + {FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH}" ); return Ok(default_system_path); } client_bail!( - "no configuration found at {FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH}, and no current \ - user, bailing out" + "no configuration found at {FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH}, and no \ + current user, bailing out" ); } Ok(user_conf) => { - // the user home exists, if there is no conf file, check /etc/cosmian/findex.json + // the user home exists, if there is no conf file, check + // /etc/cosmian/findex.json if !user_conf.exists() { let default_system_path = PathBuf::from(FINDEX_CLI_CONF_DEFAULT_SYSTEM_PATH); if default_system_path.exists() { @@ -223,7 +238,11 @@ impl ClientConf { } } - pub fn save(&self, conf_path: &PathBuf) -> Result<(), ClientError> { + /// Save the configuration to a file. + /// # Errors + /// Returns an error if the configuration cannot be serialized or written to + /// the file. + pub fn save(&self, conf_path: &PathBuf) -> ClientResult<()> { fs::write( conf_path, serde_json::to_string_pretty(&self) @@ -236,8 +255,14 @@ impl ClientConf { Ok(()) } - pub fn load(conf_path: &PathBuf) -> Result { - // Deserialize the configuration from the file, or create a default configuration if none exists + /// Load the configuration from a file. + /// If the file does not exist, create it with default values. + /// # Errors + /// Returns an error if the configuration cannot be deserialized or read + /// from the file. + pub fn load(conf_path: &PathBuf) -> ClientResult { + // Deserialize the configuration from the file, or create a default + // configuration if none exists let conf = if conf_path.exists() { // Configuration file exists, read and deserialize it let file = File::open(conf_path) @@ -245,7 +270,8 @@ impl ClientConf { serde_json::from_reader(BufReader::new(file)) .with_context(|| format!("Error while parsing configuration file {conf_path:?}"))? } else { - // Configuration file doesn't exist, create it with default values and serialize it + // Configuration file doesn't exist, create it with default values and serialize + // it let parent = conf_path .parent() .with_context(|| format!("Unable to get parent directory of {conf_path:?}"))?; @@ -263,8 +289,10 @@ impl ClientConf { /// Initialize a Findex REST client. /// - /// Parameters `findex_server_url` and `accept_invalid_certs` from the command line - /// will override the ones from the configuration file. + /// Parameters `findex_server_url` and `accept_invalid_certs` from the + /// command line will override the ones from the configuration file. + /// # Errors + /// Returns an error if the Findex REST client cannot be instantiated. pub fn initialize_findex_client( &self, findex_server_url: Option<&str>, @@ -297,6 +325,7 @@ impl ClientConf { } } +#[allow(unsafe_code, clippy::unwrap_used)] #[cfg(test)] mod tests { use std::{env, fs, path::PathBuf}; @@ -310,22 +339,22 @@ mod tests { env::set_var(FINDEX_CLI_CONF_ENV, "test_data/configs/findex.json"); } let conf_path = ClientConf::location(None).unwrap(); - assert!(ClientConf::load(&conf_path).is_ok()); + ClientConf::load(&conf_path).unwrap(); // another valid conf unsafe { env::set_var(FINDEX_CLI_CONF_ENV, "test_data/configs/findex_partial.json"); } let conf_path = ClientConf::location(None).unwrap(); - assert!(ClientConf::load(&conf_path).is_ok()); + ClientConf::load(&conf_path).unwrap(); // Default conf file unsafe { env::remove_var(FINDEX_CLI_CONF_ENV); } - let _ = fs::remove_file(get_default_conf_path().unwrap()); + fs::remove_file(get_default_conf_path().unwrap()).unwrap(); let conf_path = ClientConf::location(None).unwrap(); - assert!(ClientConf::load(&conf_path).is_ok()); + ClientConf::load(&conf_path).unwrap(); assert!(get_default_conf_path().unwrap().exists()); // invalid conf @@ -342,6 +371,6 @@ mod tests { } let conf_path = ClientConf::location(Some(PathBuf::from("test_data/configs/findex.json"))).unwrap(); - assert!(ClientConf::load(&conf_path).is_ok()); + ClientConf::load(&conf_path).unwrap(); } } diff --git a/crate/client/src/error/mod.rs b/crate/client/src/error/mod.rs index 652b74b..4b53777 100644 --- a/crate/client/src/error/mod.rs +++ b/crate/client/src/error/mod.rs @@ -64,12 +64,6 @@ impl From for ClientError { } } -impl From for ClientError { - fn from(e: cloudproof::reexport::crypto_core::CryptoCoreError) -> Self { - Self::UnexpectedError(e.to_string()) - } -} - /// Construct a server error from a string. #[macro_export] macro_rules! client_error { diff --git a/crate/client/src/error/result.rs b/crate/client/src/error/result.rs index ed31370..ea3a844 100644 --- a/crate/client/src/error/result.rs +++ b/crate/client/src/error/result.rs @@ -32,7 +32,7 @@ where impl RestClientResultHelper for Option { fn context(self, context: &str) -> ClientResult { - self.ok_or_else(|| ClientError::Default(context.to_string())) + self.ok_or_else(|| ClientError::Default(context.to_owned())) } fn with_context(self, op: O) -> ClientResult diff --git a/crate/client/src/file_utils.rs b/crate/client/src/file_utils.rs index a69bf84..3611c55 100644 --- a/crate/client/src/file_utils.rs +++ b/crate/client/src/file_utils.rs @@ -4,13 +4,17 @@ use std::{ path::{Path, PathBuf}, }; -use cloudproof::reexport::crypto_core::bytes_ser_de::{Deserializer, Serializer}; use serde::{de::DeserializeOwned, Serialize}; -use crate::{error::ClientError, ClientResultHelper}; +use crate::{ + error::{result::ClientResult, ClientError}, + ClientResultHelper, +}; /// Read all bytes from a file -pub fn read_bytes_from_file(file: &impl AsRef) -> Result, ClientError> { +/// # Errors +/// It returns an error if the file cannot be opened or read +pub fn read_bytes_from_file(file: &impl AsRef) -> ClientResult> { let mut buffer = Vec::new(); File::open(file) .with_context(|| format!("could not open the file {}", file.as_ref().display()))? @@ -21,6 +25,8 @@ pub fn read_bytes_from_file(file: &impl AsRef) -> Result, ClientEr } /// Read an object T from a JSON file +/// # Errors +/// It returns an error if the file cannot be opened or read pub fn read_from_json_file(file: &impl AsRef) -> Result where T: DeserializeOwned, @@ -31,6 +37,8 @@ where } /// Write all bytes to a file +/// # Errors +/// It returns an error if the file cannot be written pub fn write_bytes_to_file(bytes: &[u8], file: &impl AsRef) -> Result<(), ClientError> { fs::write(file, bytes).with_context(|| { format!( @@ -42,6 +50,8 @@ pub fn write_bytes_to_file(bytes: &[u8], file: &impl AsRef) -> Result<(), } /// Write a JSON object to a file +/// # Errors +/// It returns an error if the file cannot be written pub fn write_json_object_to_file( json_object: &T, file: &impl AsRef, @@ -58,6 +68,8 @@ where /// /// If no `output_file` is provided, then /// it reuses the `input_file` name with the extension `plain`. +/// # Errors +/// It returns an error if the file cannot be written pub fn write_single_decrypted_data( plaintext: &[u8], input_file: &Path, @@ -79,6 +91,8 @@ pub fn write_single_decrypted_data( /// /// If no `output_file` is provided, then /// it reuses the `input_file` name with the extension `enc`. +/// # Errors +/// It returns an error if the file cannot be written pub fn write_single_encrypted_data( encrypted_data: &[u8], input_file: &Path, @@ -96,132 +110,3 @@ pub fn write_single_encrypted_data( tracing::info!("The encrypted file is available at {output_file:?}"); Ok(()) } - -/// Read all bytes from multiple files and serialize them -/// into a unique vector using LEB128 serialization (bulk mode) -pub fn read_bytes_from_files_to_bulk(input_files: &[PathBuf]) -> Result, ClientError> { - let mut ser = Serializer::new(); - - // number of files to decrypt - let nb_input_files = u64::try_from(input_files.len()).map_err(|_| { - ClientError::Conversion(format!( - "number of input files is too big for architecture: {} bytes", - input_files.len() - )) - })?; - ser.write_leb128_u64(nb_input_files)?; - - input_files.iter().try_for_each(|input_file| { - let content = read_bytes_from_file(input_file)?; - ser.write_vec(&content)?; - Ok::<_, ClientError>(()) - })?; - - Ok(ser.finalize().to_vec()) -} - -/// Write bulk decrypted data -/// -/// Bulk data is compound of multiple chunks of data. -/// Sizes are written using LEB-128 serialization. -/// -/// Each chunk of plaintext data is written to its own file. -pub fn write_bulk_decrypted_data( - plaintext: &[u8], - input_files: &[PathBuf], - output_file: Option<&PathBuf>, -) -> Result<(), ClientError> { - let mut de = Deserializer::new(plaintext); - - // number of decrypted chunks - let nb_chunks = { - let len = de.read_leb128_u64()?; - usize::try_from(len).map_err(|_| { - ClientError::Conversion(format!( - "size of vector is too big for architecture: {len} bytes", - )) - })? - }; - - (0..nb_chunks).try_for_each(|idx| { - // get chunk of data from slice - let chunk_data = de.read_vec_as_ref()?; - - // Write plaintext data to its own file - // Reuse input file names if there are multiple inputs (and ignore `output_file`) - let input_file = &input_files[idx]; - let output_file = match output_file { - Some(output_file) if nb_chunks > 1 => { - let file_name = input_file.file_name().ok_or_else(|| { - ClientError::Conversion(format!( - "cannot get file name from input file {input_file:?}", - )) - })?; - output_file.join(PathBuf::from(file_name).with_extension("plain")) - } - _ => output_file.map_or_else( - || input_file.with_extension("plain"), - std::clone::Clone::clone, - ), - }; - - write_bytes_to_file(chunk_data, &output_file)?; - - tracing::info!("The decrypted file is available at {output_file:?}"); - Ok(()) - }) -} - -/// Write bulk encrypted data -/// -/// Bulk data is compound of multiple chunks of data. -/// Sizes are written using LEB-128 serialization. -/// -/// Each chunk of data: -/// - is compound of encrypted header + encrypted data -/// - is written to its own file. -pub fn write_bulk_encrypted_data( - plaintext: &[u8], - input_files: &[PathBuf], - output_file: Option<&PathBuf>, -) -> Result<(), ClientError> { - let mut de = Deserializer::new(plaintext); - - // number of encrypted chunks - let nb_chunks = { - let len = de.read_leb128_u64()?; - usize::try_from(len).map_err(|_| { - ClientError::Conversion(format!( - "size of vector is too big for architecture: {len} bytes", - )) - })? - }; - - (0..nb_chunks).try_for_each(|idx| { - // get chunk of data from slice - let chunk_data = de.read_vec_as_ref()?; - - // Write encrypted data to its own file - // Reuse input file names if there are multiple inputs (and ignore `output_file`) - let input_file = &input_files[idx]; - let output_file = match output_file { - Some(output_file) if nb_chunks > 1 => { - let file_name = input_file.file_name().ok_or_else(|| { - ClientError::Conversion(format!( - "cannot get file name from input file {input_file:?}", - )) - })?; - output_file.join(PathBuf::from(file_name).with_extension("enc")) - } - _ => output_file.map_or_else( - || input_file.with_extension("enc"), - std::clone::Clone::clone, - ), - }; - - write_bytes_to_file(chunk_data, &output_file)?; - - tracing::info!("The encrypted file is available at {output_file:?}"); - Ok(()) - }) -} diff --git a/crate/client/src/findex_rest_client.rs b/crate/client/src/findex_rest_client.rs index da88398..a23c843 100644 --- a/crate/client/src/findex_rest_client.rs +++ b/crate/client/src/findex_rest_client.rs @@ -21,11 +21,13 @@ use crate::{ #[derive(Clone)] pub struct FindexClient { pub server_url: String, - client: Client, + pub client: Client, } impl FindexClient { /// Instantiate a new Findex REST Client + /// # Errors + /// It returns an error if the client cannot be instantiated #[allow(clippy::too_many_arguments)] #[allow(dead_code)] pub fn instantiate( @@ -39,7 +41,7 @@ impl FindexClient { ) -> Result { let server_url = server_url .strip_suffix('/') - .map_or_else(|| server_url.to_string(), std::string::ToString::to_string); + .map_or_else(|| server_url.to_owned(), std::string::ToString::to_string); let mut headers = HeaderMap::new(); if let Some(bearer_token) = bearer_token { @@ -57,17 +59,16 @@ impl FindexClient { // We deal with 4 scenarios: // 1. HTTP: no TLS - // 2. HTTPS: - // a) self-signed: we want to remove the verifications - // b) signed in a tee context: we want to verify the /quote and then only accept the allowed certificate - // -> For efficiency purpose, this verification is made outside this call (async with the queries) - // Only the verified certificate is used here - // c) signed in a non-tee context: we want classic TLS verification based on the root ca - let builder = if let Some(certificate) = allowed_tee_tls_cert { - build_tls_client_tee(certificate, accept_invalid_certs) - } else { - ClientBuilder::new().danger_accept_invalid_certs(accept_invalid_certs) - }; + // 2. HTTPS: a) self-signed: we want to remove the verifications b) signed in a + // tee context: we want to verify the /quote and then only accept the allowed + // certificate -> For efficiency purpose, this verification is made outside + // this call (async with the queries) Only the verified certificate is used + // here c) signed in a non-tee context: we want classic TLS verification + // based on the root ca + let builder = allowed_tee_tls_cert.map_or_else( + || ClientBuilder::new().danger_accept_invalid_certs(accept_invalid_certs), + |certificate| build_tls_client_tee(certificate, accept_invalid_certs), + ); // If a PKCS12 file is provided, use it to build the client let builder = match ssl_client_pkcs12_path { @@ -97,7 +98,9 @@ impl FindexClient { /// This operation requests the server to create a new database. /// The returned secrets could be shared between several users. - pub async fn new_database(&self) -> Result { + /// # Errors + /// It returns an error if the request fails + pub async fn new_database(&self) -> ClientResult { let endpoint = "/new_database"; let server_url = format!("{}{endpoint}", self.server_url); let response = self.client.get(server_url).send().await?; @@ -111,6 +114,10 @@ impl FindexClient { Err(ClientError::RequestFailed(p)) } + /// This operation requests the server to create a new table. + /// The returned secrets could be shared between several users. + /// # Errors + /// It returns an error if the request fails pub async fn version(&self) -> ClientResult { let endpoint = "/version"; let server_url = format!("{}{endpoint}", self.server_url); @@ -126,8 +133,8 @@ impl FindexClient { } } -/// Some errors are returned by the Middleware without going through our own error manager. -/// In that case, we make the error clearer here for the client. +/// Some errors are returned by the Middleware without going through our own +/// error manager. In that case, we make the error clearer here for the client. async fn handle_error(endpoint: &str, response: Response) -> Result { trace!("Error response received on {endpoint}: Response: {response:?}"); let status = response.status(); @@ -149,8 +156,9 @@ async fn handle_error(endpoint: &str, response: Response) -> Result = Result; pub trait ClientResultHelper { + /// Add a context to the error message + /// # Errors + /// It returns the error with the context added fn context(self, context: &str) -> RestClientResult; + + /// Add a context to the error message + /// # Errors + /// It returns the error with the context added fn with_context(self, op: O) -> RestClientResult where D: Display + Send + Sync + 'static, @@ -31,7 +38,7 @@ where impl ClientResultHelper for Option { fn context(self, context: &str) -> RestClientResult { - self.ok_or_else(|| ClientError::Default(context.to_string())) + self.ok_or_else(|| ClientError::Default(context.to_owned())) } fn with_context(self, op: O) -> RestClientResult diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index ac1c0a3..682b923 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -2,12 +2,12 @@ name = "cosmian_findex_server" version.workspace = true authors.workspace = true +categories.workspace = true edition.workspace = true +keywords.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true -categories.workspace = true -keywords.workspace = true description = "Cosmian Findex server" [[bin]] @@ -31,11 +31,9 @@ actix-service = "2.0" actix-tls = "3.4" actix-web = { workspace = true, features = ["macros", "openssl"] } alcoholic_jwt = "4091" -argon2 = "0.5" -async-trait = "0.1" +async-trait = { workspace = true } base64 = { workspace = true } chrono = { workspace = true } -cosmian_logger = { path = "../logger" } clap = { workspace = true, features = [ "help", "env", @@ -45,21 +43,17 @@ clap = { workspace = true, features = [ "derive", "cargo", ] } -cloudproof = { workspace = true } -cloudproof_findex = { version = "5.0", features = ["findex-redis"] } +cloudproof_findex = { workspace = true, features = ["redis-interface"] } +cosmian_logger = { path = "../logger" } dotenvy = "0.15" futures = "0.3" -hex = { workspace = true, default-features = false } -lazy_static = "1.5" num-bigint-dig = { workspace = true, features = [ "std", "rand", "serde", "zeroize", ] } -num_cpus = { workspace = true } openssl = { workspace = true, default-features = false } -rawsql = "0.1" redis = { version = "0.23", features = [ "aio", "ahash", @@ -82,7 +76,6 @@ sqlx = { version = "0.8.2", default-features = false, features = [ "sqlite", ] } thiserror = { workspace = true } -time = { workspace = true, features = ["local-offset", "formatting"] } tokio = { workspace = true, features = ["full"] } toml = "0.8" tracing = { workspace = true } diff --git a/crate/server/src/config/command_line/clap_config.rs b/crate/server/src/config/command_line/clap_config.rs index 2ff91d9..1ed274e 100644 --- a/crate/server/src/config/command_line/clap_config.rs +++ b/crate/server/src/config/command_line/clap_config.rs @@ -3,7 +3,7 @@ use std::fmt::{self}; use clap::Parser; use serde::{Deserialize, Serialize}; -use super::{DBConfig, HttpConfig, JwtAuthConfig, WorkspaceConfig}; +use super::{DBConfig, HttpConfig, JwtAuthConfig}; const DEFAULT_USERNAME: &str = "admin"; @@ -13,7 +13,6 @@ impl Default for ClapConfig { db: DBConfig::default(), http: HttpConfig::default(), auth: JwtAuthConfig::default(), - workspace: WorkspaceConfig::default(), default_username: DEFAULT_USERNAME.to_owned(), force_default_username: false, } @@ -33,15 +32,13 @@ pub struct ClapConfig { #[clap(flatten)] pub auth: JwtAuthConfig, - #[clap(flatten)] - pub workspace: WorkspaceConfig, - /// The default username to use when no authentication method is provided #[clap(long, env = "FINDEX_SERVER_DEFAULT_USERNAME", default_value = DEFAULT_USERNAME)] pub default_username: String, /// When an authentication method is provided, perform the authentication - /// but always use the default username instead of the one provided by the authentication method + /// but always use the default username instead of the one provided by the + /// authentication method #[clap(long, env = "FINDEX_SERVER_FORCE_DEFAULT_USERNAME")] pub force_default_username: bool, } @@ -56,7 +53,6 @@ impl fmt::Debug for ClapConfig { x }; let x = x.field("Findex server http", &self.http); - let x = x.field("workspace", &self.workspace); let x = x.field("default username", &self.default_username); let x = x.field("force default username", &self.force_default_username); x.finish() diff --git a/crate/server/src/config/command_line/db.rs b/crate/server/src/config/command_line/db.rs index 0f2ff92..8455331 100644 --- a/crate/server/src/config/command_line/db.rs +++ b/crate/server/src/config/command_line/db.rs @@ -1,15 +1,20 @@ use std::{fmt::Display, path::PathBuf}; -use clap::Args; -use cloudproof_findex::Label; +use clap::{Args, ValueEnum}; use serde::{Deserialize, Serialize}; use url::Url; -use super::workspace::WorkspaceConfig; -use crate::{ - config::params::DbParams, database::RedisWithFindex, findex_server_bail, findex_server_error, - result::FResult, -}; +use crate::{config::params::DbParams, error::result::FResult, findex_server_error}; + +#[derive(ValueEnum, Clone, Deserialize, Serialize)] +pub enum DatabaseType { + // Sqlite, + Redis, +} + +// todo(manu): convert toml conf in JSON +// todo(manu): remove all sqlite support and leftovers +// todo(manu): support other databases? pub const DEFAULT_SQLITE_PATH: &str = "./sqlite-data"; @@ -18,22 +23,20 @@ pub const DEFAULT_SQLITE_PATH: &str = "./sqlite-data"; #[serde(default)] pub struct DBConfig { /// The database type of the Findex server - /// - sqlite: `SQLite`. The data will be stored at the `sqlite_path` directory - /// - redis-findex: a Redis database with encrypted data and encrypted indexes thanks to Findex. - /// The Redis url must be provided, as well as the redis-master-password and the redis-findex-label - #[clap( - long, - env("FINDEX_SERVER_DATABASE_TYPE"), - value_parser(["sqlite", "redis-findex"]), - verbatim_doc_comment - )] - pub database_type: Option, + /// - sqlite: `SQLite`. The data will be stored at the `sqlite_path` + /// directory + /// - redis-findex: a Redis database with encrypted data and encrypted + /// indexes thanks to Findex. The Redis url must be provided, as well as + /// the redis-master-password and the redis-findex-label + #[clap(long, env("FINDEX_SERVER_DATABASE_TYPE"), verbatim_doc_comment)] + pub database_type: Option, /// The url of the database for findex-redis #[clap( long, env = "FINDEX_SERVER_DATABASE_URL", - required_if_eq_any([("database_type", "redis-findex")]) + required_if_eq_any([("database_type", "redis-findex")]), + default_value = "redis://localhost:6379" )] pub database_url: Option, @@ -42,27 +45,10 @@ pub struct DBConfig { long, env = "FINDEX_SERVER_SQLITE_PATH", default_value = DEFAULT_SQLITE_PATH, - required_if_eq_any([("database_type", "sqlite"), ("database_type", "sqlite-enc")]) + required_if_eq_any([("database_type", "sqlite")]) )] pub sqlite_path: PathBuf, - /// redis-findex: a master password used to encrypt the Redis data and indexes - #[clap( - long, - env = "FINDEX_SERVER_REDIS_MASTER_PASSWORD", - required_if_eq("database_type", "redis-findex") - )] - pub redis_master_password: Option, - - /// redis-findex: a public arbitrary label that can be changed to rotate the Findex ciphertexts - /// without changing the key - #[clap( - long, - env = "FINDEX_SERVER_REDIS_FINDEX_LABEL", - required_if_eq("database_type", "redis-findex") - )] - pub redis_findex_label: Option, - /// Clear the database on start. /// WARNING: This will delete ALL the data in the database #[clap(long, env = "FINDEX_SERVER_CLEAR_DATABASE", verbatim_doc_comment)] @@ -74,10 +60,8 @@ impl Default for DBConfig { Self { sqlite_path: PathBuf::from(DEFAULT_SQLITE_PATH), database_type: None, - database_url: None, + database_url: Some("redis://localhost:6379".to_owned()), clear_database: false, - redis_master_password: None, - redis_findex_label: None, } } } @@ -85,22 +69,15 @@ impl Default for DBConfig { impl Display for DBConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(database_type) = &self.database_type { - match database_type.as_str() { - "sqlite" => write!(f, "sqlite: {}", self.sqlite_path.display()), - "redis-findex" => write!( + match database_type { + DatabaseType::Redis => write!( f, - "redis-findex: {}, password: [****], label: 0x{}", + "redis: {}", &self .database_url .as_ref() .map_or("[INVALID LABEL]", |url| url.as_str()), - hex::encode( - self.redis_findex_label - .as_ref() - .map_or("[INVALID LABEL]", |url| url.as_str()), - ) ), - unknown => write!(f, "Unknown database type: {unknown}"), }?; } else { write!(f, "No database configuration provided")?; @@ -119,47 +96,21 @@ impl DBConfig { /// Initialize the DB parameters based on the command-line parameters /// /// # Parameters - /// - `workspace`: The workspace configuration used to determine the public and shared paths + /// - `workspace`: The workspace configuration used to determine the public + /// and shared paths /// /// # Returns /// - The DB parameters - pub(crate) fn init(&self, workspace: &WorkspaceConfig) -> FResult> { + pub(crate) fn init(&self) -> FResult> { Ok(if let Some(database_type) = &self.database_type { - Some(match database_type.as_str() { - "sqlite" => { - let path = workspace.finalize_directory(&self.sqlite_path)?; - DbParams::Sqlite(path) - } - "redis-findex" => { + Some(match database_type { + DatabaseType::Redis => { let url = ensure_url(self.database_url.as_deref(), "FINDEX_SERVER_REDIS_URL")?; - // Check if a Redis master password was provided - let redis_master_password = ensure_value( - self.redis_master_password.as_deref(), - "redis-master-password", - "FINDEX_SERVER_REDIS_MASTER_PASSWORD", - )?; - - // Generate the symmetric key from the master password - let master_key = - RedisWithFindex::master_key_from_password(&redis_master_password)?; - - let redis_findex_label = ensure_value( - self.redis_findex_label.as_deref(), - "redis-findex-label", - "FINDEX_SERVER_REDIS_FINDEX_LABEL", - )?; - DbParams::RedisFindex( - url, - master_key, - Label::from(redis_findex_label.into_bytes()), - ) + DbParams::Redis(url) } - unknown => findex_server_bail!("Unknown database type: {unknown}"), }) } else { - // No database configuration provided; use the default config - let path = workspace.finalize_directory(&self.sqlite_path)?; - Some(DbParams::Sqlite(path)) + return Err(findex_server_error!("No database configuration provided")); }) } } @@ -170,7 +121,8 @@ fn ensure_url(database_url: Option<&str>, alternate_env_variable: &str) -> FResu std::env::var(alternate_env_variable).map_err(|_e| { findex_server_error!( "No database URL supplied either using the 'database-url' option, or the \ - FINDEX_SERVER_DATABASE_URL or the {alternate_env_variable} environment variables", + FINDEX_SERVER_DATABASE_URL or the {alternate_env_variable} environment \ + variables", ) }) }, @@ -179,22 +131,3 @@ fn ensure_url(database_url: Option<&str>, alternate_env_variable: &str) -> FResu let url = Url::parse(&url)?; Ok(url) } - -fn ensure_value( - value: Option<&str>, - option_name: &str, - env_variable_name: &str, -) -> FResult { - value.map_or_else( - || { - std::env::var(env_variable_name).map_err(|_e| { - findex_server_error!( - "No value supplied either using the {} option, or the {} environment variable", - option_name, - env_variable_name - ) - }) - }, - |value| Ok(value.to_owned()), - ) -} diff --git a/crate/server/src/config/command_line/http_config.rs b/crate/server/src/config/command_line/http_config.rs index 6f7d895..f9f6935 100644 --- a/crate/server/src/config/command_line/http_config.rs +++ b/crate/server/src/config/command_line/http_config.rs @@ -17,7 +17,8 @@ pub struct HttpConfig { #[clap(long, env = "FINDEX_SERVER_HOSTNAME", default_value = DEFAULT_HOSTNAME)] pub hostname: String, - /// The Findex server optional PKCS#12 Certificates and Key file. If provided, this will start the server in HTTPS mode. + /// The Findex server optional PKCS#12 Certificates and Key file. If + /// provided, this will start the server in HTTPS mode. #[clap(long, env = "FINDEX_SERVER_HTTPS_P12_FILE")] pub https_p12_file: Option, @@ -25,9 +26,11 @@ pub struct HttpConfig { #[clap(long, env = "FINDEX_SERVER_HTTPS_P12_PASSWORD")] pub https_p12_password: Option, - /// The server optional authority X509 certificate in PEM format used to validate the client certificate presented for authentication. - /// If provided, this will require clients to present a certificate signed by this authority for authentication. - /// The server must run in TLS mode for this to be used. + /// The server optional authority X509 certificate in PEM format used to + /// validate the client certificate presented for authentication. + /// If provided, this will require clients to present a certificate signed + /// by this authority for authentication. The server must run in TLS + /// mode for this to be used. #[clap(long, env = "FINDEX_SERVER_AUTHORITY_CERT_FILE")] pub authority_cert_file: Option, } diff --git a/crate/server/src/config/command_line/jwt_auth_config.rs b/crate/server/src/config/command_line/jwt_auth_config.rs index ef0a086..f3c29d3 100644 --- a/crate/server/src/config/command_line/jwt_auth_config.rs +++ b/crate/server/src/config/command_line/jwt_auth_config.rs @@ -1,7 +1,7 @@ use clap::Args; use serde::{Deserialize, Serialize}; -use crate::{config::IdpConfig, error::FindexServerError, findex_server_ensure}; +use crate::{config::IdpConfig, error::server::FindexServerError, findex_server_ensure}; // Support for JWT token inspired by the doc at : https://cloud.google.com/api-gateway/docs/authenticating-users-jwt // and following pages @@ -11,16 +11,16 @@ use crate::{config::IdpConfig, error::FindexServerError, findex_server_ensure}; pub struct JwtAuthConfig { /// The issuer URI of the JWT token /// - /// To handle multiple identity managers, add different parameters under each argument - /// (jwt-issuer-uri, jwks-uri and optionally jwt-audience), keeping them in - /// the same order : + /// To handle multiple identity managers, add different parameters under + /// each argument (jwt-issuer-uri, jwks-uri and optionally + /// jwt-audience), keeping them in the same order : /// /// --`jwt_issuer_uri` <`JWT_ISSUER_URI_1`> <`JWT_ISSUER_URI_2`> /// --`jwks_uri` <`JWKS_URI_1`> <`JWKS_URI_2`> /// --`jwt_audience` <`JWT_AUDIENCE_1`> <`JWT_AUDIENCE_2`> /// - /// For Auth0, this is the delegated authority domain configured on Auth0, for instance - /// `https://..auth0.com/` + /// For Auth0, this is the delegated authority domain configured on Auth0, + /// for instance `https://..auth0.com/` /// /// For Google, this would be `https://accounts.google.com` #[clap(long, env = "FINDEX_SERVER_JWT_ISSUER_URI", num_args = 1..)] @@ -28,9 +28,9 @@ pub struct JwtAuthConfig { /// The JWKS (Json Web Key Set) URI of the JWT token /// - /// To handle multiple identity managers, add different parameters under each argument - /// (jwt-issuer-uri, jwks-uri and optionally jwt-audience), keeping them in - /// the same order + /// To handle multiple identity managers, add different parameters under + /// each argument (jwt-issuer-uri, jwks-uri and optionally + /// jwt-audience), keeping them in the same order /// /// For Auth0, this would be `https://..auth0.com/.well-known/jwks.json` /// @@ -42,7 +42,8 @@ pub struct JwtAuthConfig { /// The audience of the JWT token /// - /// Optional: the server will validate the JWT `aud` claim against this value if set + /// Optional: the server will validate the JWT `aud` claim against this + /// value if set #[clap(long, env = "FINDEX_SERVER_JST_AUDIENCE", num_args = 1..)] pub jwt_audience: Option>, } @@ -61,9 +62,11 @@ impl JwtAuthConfig { ) } - /// Parse this configuration into one identity provider configuration per JWT issuer URI. + /// Parse this configuration into one identity provider configuration per + /// JWT issuer URI. /// - /// Assert that when provided, JWKS URI and JWT audience are provided once per JWT issuer URI; + /// Assert that when provided, JWKS URI and JWT audience are provided once + /// per JWT issuer URI; pub(crate) fn extract_idp_configs(self) -> Result>, FindexServerError> { self.jwt_issuer_uri .map(|issuer_uris| { diff --git a/crate/server/src/config/command_line/mod.rs b/crate/server/src/config/command_line/mod.rs index 4a46218..7943446 100644 --- a/crate/server/src/config/command_line/mod.rs +++ b/crate/server/src/config/command_line/mod.rs @@ -2,10 +2,8 @@ mod clap_config; mod db; mod http_config; mod jwt_auth_config; -mod workspace; pub use clap_config::ClapConfig; -pub use db::{DBConfig, DEFAULT_SQLITE_PATH}; +pub use db::{DBConfig, DatabaseType, DEFAULT_SQLITE_PATH}; pub use http_config::HttpConfig; pub use jwt_auth_config::JwtAuthConfig; -pub use workspace::WorkspaceConfig; diff --git a/crate/server/src/config/command_line/workspace.rs b/crate/server/src/config/command_line/workspace.rs deleted file mode 100644 index 584bf38..0000000 --- a/crate/server/src/config/command_line/workspace.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use clap::Args; -use serde::{Deserialize, Serialize}; - -use crate::{findex_server_error, result::FResult}; - -const DEFAULT_ROOT_DATA_PATH: &str = "./cosmian-findex-server"; -const DEFAULT_TMP_PATH: &str = "/tmp"; - -#[derive(Debug, Args, Deserialize, Serialize)] -#[serde(default)] -pub struct WorkspaceConfig { - /// The root folder where the Findex server will store its data - /// A relative path is taken relative to the user HOME directory - #[clap(long, env = "FINDEX_SERVER_ROOT_DATA_PATH", default_value = DEFAULT_ROOT_DATA_PATH)] - pub root_data_path: PathBuf, - - /// The folder to store temporary data (non-persistent data readable by no-one but the current instance during the current execution) - #[clap(long, env = "FINDEX_SERVER_TMP_PATH", default_value = DEFAULT_TMP_PATH)] - pub tmp_path: PathBuf, -} - -impl Default for WorkspaceConfig { - fn default() -> Self { - Self { - root_data_path: PathBuf::from(DEFAULT_ROOT_DATA_PATH), - tmp_path: PathBuf::from(DEFAULT_TMP_PATH), - } - } -} - -impl WorkspaceConfig { - pub(crate) fn init(&self) -> FResult { - let root_data_path = Self::finalize_directory_path(&self.root_data_path, None)?; - let tmp_path = Self::finalize_directory_path(&self.tmp_path, Some(&root_data_path))?; - Ok(Self { - root_data_path, - tmp_path, - }) - } - - /// Transform a relative path to `root_data_path` to an absolute path and ensure that the directory exists. - /// An absolute path is left unchanged. - /// - /// # Arguments - /// - /// * `path` - The path to be transformed. - /// * `relative_root` - The root directory that will be used to make `path` absolute if it's relative. - /// - /// # Returns - /// - /// Returns the canonicalized path. - /// - /// # Errors - /// - /// Returns an error if the directory can't be created or if an error occurs while calling `std::fs::canonicalize` - pub(crate) fn finalize_directory(&self, path: &PathBuf) -> FResult { - Self::finalize_directory_path(path, Some(&self.root_data_path)) - } - - /// Transform a relative path to `root_data_path` to an absolute path and ensure that the directory exists. - /// An absolute path is left unchanged. - /// - /// # Arguments - /// - /// * `path` - The path to be transformed. - /// * `relative_root` - The root directory that will be used to make `path` absolute if it's relative. Or current directory is None. - /// - /// # Returns - /// - /// Returns the canonicalized path. - /// - /// # Errors - /// - /// Returns an error if the directory can't be created or if an error occurs while calling `std::fs::canonicalize` - pub(crate) fn finalize_directory_path( - path: &PathBuf, - relative_root: Option<&Path>, - ) -> FResult { - let path = if path.is_relative() { - relative_root.map_or_else(|| path.clone(), |relative_root| relative_root.join(path)) - } else { - path.clone() - }; - - if !path.exists() { - fs::create_dir_all(&path)?; - } - - fs::canonicalize(path).map_err(|e| findex_server_error!(e)) - } - - /// Transform a relative path to `root_data_path` to an absolute path. - /// An absolute path is left unchanged. - /// - /// # Arguments - /// - /// * `path` - The path to be transformed. - /// - /// # Returns - /// - /// Returns the canonicalized path. - /// - /// # Errors - /// - /// Returns if an error occurs while calling `std::fs::canonicalize` - #[allow(dead_code)] - pub(crate) fn finalize_file_path(&self, path: &PathBuf) -> FResult { - let path = if path.is_relative() { - self.root_data_path.join(path) - } else { - path.clone() - }; - fs::canonicalize(path).map_err(|e| findex_server_error!(e)) - } -} diff --git a/crate/server/src/config/params/db_params.rs b/crate/server/src/config/params/db_params.rs index 4ecde7e..c4fad68 100644 --- a/crate/server/src/config/params/db_params.rs +++ b/crate/server/src/config/params/db_params.rs @@ -1,22 +1,9 @@ -use std::{ - fmt::{self, Display}, - path::PathBuf, -}; +use std::fmt::{self, Display}; -use cloudproof::reexport::crypto_core::SymmetricKey; -use cloudproof_findex::Label; use url::Url; -use crate::database::REDIS_WITH_FINDEX_MASTER_KEY_LENGTH; - pub enum DbParams { - /// contains the dir of the sqlite db file (not the db file itself) - Sqlite(PathBuf), - RedisFindex( - Url, - SymmetricKey, - Label, - ), + Redis(Url), } impl DbParams { @@ -24,8 +11,7 @@ impl DbParams { #[must_use] pub const fn db_name(&self) -> &str { match &self { - Self::Sqlite(_) => "Sqlite", - Self::RedisFindex(_, _, _) => "Redis-Findex", + Self::Redis(_) => "Redis", } } } @@ -33,14 +19,8 @@ impl DbParams { impl Display for DbParams { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Sqlite(path) => write!(f, "sqlite: {}", path.display()), - Self::RedisFindex(url, _, label) => { - write!( - f, - "redis-findex: {}, master key: [****], Findex label: 0x{}", - redact_url(url), - hex::encode(label) - ) + Self::Redis(url) => { + write!(f, "redis: {}", redact_url(url),) } } } diff --git a/crate/server/src/config/params/http_params.rs b/crate/server/src/config/params/http_params.rs index e4f5f5c..c31abbd 100644 --- a/crate/server/src/config/params/http_params.rs +++ b/crate/server/src/config/params/http_params.rs @@ -4,7 +4,7 @@ use openssl::pkcs12::{ParsedPkcs12_2, Pkcs12}; use crate::{ config::HttpConfig, - result::{FResult, FResultHelper}, + error::result::{FResult, FResultHelper}, }; /// The HTTP parameters of the API server @@ -19,15 +19,18 @@ impl HttpParams { /// /// # Arguments /// - /// * `config` - The `HttpConfig` object containing the configuration parameters. + /// * `config` - The `HttpConfig` object containing the configuration + /// parameters. /// /// # Returns /// - /// Returns a `FResult` containing the created `HttpParams` instance on success. + /// Returns a `FResult` containing the created `HttpParams` instance on + /// success. /// /// # Errors /// - /// This function can return an error if there is an issue reading the PKCS#12 file or parsing it. + /// This function can return an error if there is an issue reading the + /// PKCS#12 file or parsing it. pub(crate) fn try_from(config: &HttpConfig) -> FResult { // start in HTTPS mode if a PKCS#12 file is provided if let (Some(p12_file), Some(p12_password)) = @@ -51,7 +54,8 @@ impl HttpParams { /// /// # Returns /// - /// Returns `true` if the server is running in HTTPS mode, `false` otherwise. + /// Returns `true` if the server is running in HTTPS mode, `false` + /// otherwise. #[must_use] pub const fn is_running_https(&self) -> bool { matches!(self, Self::Https(_)) diff --git a/crate/server/src/config/params/server_params.rs b/crate/server/src/config/params/server_params.rs index c5c2f99..fcb4f2e 100644 --- a/crate/server/src/config/params/server_params.rs +++ b/crate/server/src/config/params/server_params.rs @@ -5,8 +5,8 @@ use openssl::x509::X509; use super::{DbParams, HttpParams}; use crate::{ config::{ClapConfig, IdpConfig}, + error::result::FResult, findex_server_bail, - result::FResult, }; /// This structure is the context used by the server @@ -20,7 +20,8 @@ pub struct ServerParams { pub default_username: String, /// When an authentication method is provided, perform the authentication - /// but always use the default username instead of the one provided by the authentication method + /// but always use the default username instead of the one provided by the + /// authentication method pub force_default_username: bool, /// The DB parameters may be supplied on the command line @@ -46,15 +47,18 @@ impl ServerParams { /// /// # Arguments /// - /// * `conf` - The `ClapConfig` object containing the configuration parameters. + /// * `conf` - The `ClapConfig` object containing the configuration + /// parameters. /// /// # Returns /// - /// Returns a `FResult` containing the `ServerParams` instance if successful, or an error if the conversion fails. + /// Returns a `FResult` containing the `ServerParams` instance if + /// successful, or an error if the conversion fails. /// /// # Errors /// - /// Returns an error if the conversion from `ClapConfig` to `ServerParams` fails. + /// Returns an error if the conversion from `ClapConfig` to `ServerParams` + /// fails. pub fn try_from(conf: ClapConfig) -> FResult { let http_params = HttpParams::try_from(&conf.http)?; @@ -76,7 +80,7 @@ impl ServerParams { Ok(Self { identity_provider_configurations: conf.auth.extract_idp_configs()?, - db_params: conf.db.init(&conf.workspace.init()?)?, + db_params: conf.db.init()?, clear_db_on_start: conf.db.clear_database, hostname: conf.http.hostname, port: conf.http.port, @@ -95,11 +99,13 @@ impl ServerParams { /// /// # Returns /// - /// Returns a `FResult` containing the loaded `X509` certificate if successful, or an error if the loading fails. + /// Returns a `FResult` containing the loaded `X509` certificate if + /// successful, or an error if the loading fails. /// /// # Errors /// - /// Returns an error if the certificate file cannot be read or if the parsing of the certificate fails. + /// Returns an error if the certificate file cannot be read or if the + /// parsing of the certificate fails. fn load_cert(authority_cert_file: &PathBuf) -> FResult { // Open and read the file into a byte vector let pem_bytes = std::fs::read(authority_cert_file)?; diff --git a/crate/server/src/core/findex_server.rs b/crate/server/src/core/findex_server.rs deleted file mode 100644 index c5cbf64..0000000 --- a/crate/server/src/core/findex_server.rs +++ /dev/null @@ -1,44 +0,0 @@ -use actix_web::{HttpMessage, HttpRequest}; -use tracing::debug; - -use crate::{ - config::ServerParams, - database::Database, - middlewares::{JwtAuthClaim, PeerCommonName}, -}; - -#[allow(dead_code)] -pub(crate) struct FindexServer { - pub(crate) params: ServerParams, - pub(crate) db: Box, -} - -impl FindexServer { - /// Get the user from the request depending on the authentication method - /// The user is encoded in the JWT `Authorization` header - /// If the header is not present, the user is extracted from the client certificate - /// If the client certificate is not present, the user is extracted from the configuration file - pub(crate) fn get_user(&self, req_http: &HttpRequest) -> String { - let default_username = self.params.default_username.clone(); - - if self.params.force_default_username { - debug!( - "Authenticated using forced default user: {}", - default_username - ); - return default_username; - } - // if there is a JWT token, use it in priority - let user = req_http.extensions().get::().map_or_else( - || { - req_http - .extensions() - .get::() - .map_or(default_username, |claim| claim.common_name.clone()) - }, - |claim| claim.email.clone(), - ); - debug!("Authenticated user: {}", user); - user - } -} diff --git a/crate/server/src/core/implementation.rs b/crate/server/src/core/implementation.rs index 43b0578..d214a37 100644 --- a/crate/server/src/core/implementation.rs +++ b/crate/server/src/core/implementation.rs @@ -1,50 +1,64 @@ -use cloudproof::reexport::crypto_core::FixedSizeCBytes; +use actix_web::{HttpMessage, HttpRequest}; +use tracing::debug; use crate::{ config::{DbParams, ServerParams}, - database::{Database, RedisWithFindex, SqlitePool, REDIS_WITH_FINDEX_MASTER_KEY_LENGTH}, + database::{Database, Redis}, + error::result::FResult, findex_server_bail, - result::FResult, - secret::Secret, + middlewares::{JwtAuthClaim, PeerCommonName}, }; -use super::FindexServer; +#[allow(dead_code)] +pub(crate) struct FindexServer { + pub(crate) params: ServerParams, + pub(crate) db: Box, + // pub(crate) findex_configuration: Configuration, +} impl FindexServer { pub(crate) async fn instantiate(mut shared_config: ServerParams) -> FResult { - let db: Box = if let Some(mut db_params) = - shared_config.db_params.as_mut() - { - match &mut db_params { - DbParams::Sqlite(db_path) => Box::new( - SqlitePool::instantiate( - &db_path.join("findex_server.db"), - shared_config.clear_db_on_start, - ) - .await?, - ), - DbParams::RedisFindex(url, master_key, label) => { - // There is no reason to keep a copy of the key in the shared config - // So we are going to create a "zeroizable" copy which will be passed to Redis with Findex - // and zeroize the one in the shared config - let new_master_key = - Secret::::from_unprotected_bytes( - &mut master_key.to_bytes(), - ); - // `master_key` implements ZeroizeOnDrop so there is no need - // to manually zeroize. - Box::new( - RedisWithFindex::instantiate(url.as_str(), new_master_key, label).await?, - ) + let db: Box = + if let Some(mut db_params) = shared_config.db_params.as_mut() { + match &mut db_params { + DbParams::Redis(url) => Box::new(Redis::instantiate(url.as_str()).await?), } - } - } else { - findex_server_bail!("Fatal: no database configuration provided. Stopping.") - }; + } else { + findex_server_bail!("Fatal: no database configuration provided. Stopping.") + }; Ok(Self { params: shared_config, db, }) } + + /// Get the user from the request depending on the authentication method + /// The user is encoded in the JWT `Authorization` header + /// If the header is not present, the user is extracted from the client + /// certificate If the client certificate is not present, the user is + /// extracted from the configuration file + pub(crate) fn get_user(&self, req_http: &HttpRequest) -> String { + let default_username = self.params.default_username.clone(); + + if self.params.force_default_username { + debug!( + "Authenticated using forced default user: {}", + default_username + ); + return default_username; + } + // if there is a JWT token, use it in priority + let user = req_http.extensions().get::().map_or_else( + || { + req_http + .extensions() + .get::() + .map_or(default_username, |claim| claim.common_name.clone()) + }, + |claim| claim.email.clone(), + ); + debug!("Authenticated user: {}", user); + user + } } diff --git a/crate/server/src/core/mod.rs b/crate/server/src/core/mod.rs index fb90738..f4f5bf6 100644 --- a/crate/server/src/core/mod.rs +++ b/crate/server/src/core/mod.rs @@ -1,4 +1,3 @@ -pub mod findex_server; pub(crate) mod implementation; -pub(crate) use findex_server::FindexServer; +pub(crate) use implementation::FindexServer; diff --git a/crate/server/src/database/database_trait.rs b/crate/server/src/database/database_trait.rs index a57d989..8648e7f 100644 --- a/crate/server/src/database/database_trait.rs +++ b/crate/server/src/database/database_trait.rs @@ -1,13 +1,29 @@ -use crate::result::FResult; use async_trait::async_trait; +use cloudproof_findex::{ + db_interfaces::{redis::FindexTable, rest::UpsertData}, + reexport::cosmian_findex::{ + TokenToEncryptedValueMap, TokenWithEncryptedValueList, Tokens, ENTRY_LENGTH, LINK_LENGTH, + }, +}; -#[async_trait(?Send)] -pub(crate) trait Database { - /// Insert the given Object in the database. - /// - /// A new UUID will be created if none is supplier. - /// This method will fail if a `uid` is supplied - /// and an object with the same id already exists - #[allow(dead_code)] - async fn create(&self) -> FResult<()>; +use crate::error::result::FResult; + +#[async_trait] +pub(crate) trait Database: Sync + Send { + async fn fetch( + &self, + findex_table: FindexTable, + tokens: Tokens, + ) -> FResult>; + + async fn upsert_entries( + &self, + upsert_data: UpsertData, + ) -> FResult>; + + async fn insert_chains(&self, items: TokenToEncryptedValueMap) -> FResult<()>; + + async fn delete(&self, findex_table: FindexTable, entry_uids: Tokens) -> FResult<()>; + + async fn dump_tokens(&self) -> FResult; } diff --git a/crate/server/src/database/mod.rs b/crate/server/src/database/mod.rs index 5606f37..fe10ab6 100644 --- a/crate/server/src/database/mod.rs +++ b/crate/server/src/database/mod.rs @@ -1,24 +1,8 @@ -use lazy_static::lazy_static; -use rawsql::Loader; - mod database_trait; mod redis; -mod sqlite; - -pub(crate) type FServer = crate::core::FindexServer; pub(crate) use database_trait::Database; -pub(crate) use redis::RedisWithFindex; -pub(crate) use redis::REDIS_WITH_FINDEX_MASTER_KEY_LENGTH; -pub(crate) use sqlite::SqlitePool; - -const SQLITE_FILE_QUERIES: &str = include_str!("query.sql"); - -lazy_static! { - static ref SQLITE_QUERIES: Loader = #[allow(clippy::expect_used)] - Loader::get_queries_from(SQLITE_FILE_QUERIES) - .expect("Can't parse the SQL file"); -} +pub(crate) use redis::Redis; #[cfg(test)] mod tests; diff --git a/crate/server/src/database/query.sql b/crate/server/src/database/query.sql deleted file mode 100644 index d300e05..0000000 --- a/crate/server/src/database/query.sql +++ /dev/null @@ -1,140 +0,0 @@ --- name: create-table-context -CREATE TABLE IF NOT EXISTS context ( - version VARCHAR(40) PRIMARY KEY, - state VARCHAR(40) -); - --- name: create-table-objects -CREATE TABLE IF NOT EXISTS objects ( - id VARCHAR(40) PRIMARY KEY, - object json NOT NULL, - attributes json NOT NULL, - state VARCHAR(32), - owner VARCHAR(255) -); --- name: add-column-attributes -ALTER TABLE objects ADD COLUMN attributes json; --- name: has-column-attributes -SELECT attributes from objects; - --- name: create-table-read_access -CREATE TABLE IF NOT EXISTS read_access ( - id VARCHAR(40), - userid VARCHAR(255), - permissions json NOT NULL, - UNIQUE (id, userid) -); - --- name: create-table-tags -CREATE TABLE IF NOT EXISTS tags ( - id VARCHAR(40), - tag VARCHAR(255), - UNIQUE (id, tag) -); - --- name: clean-table-context -DELETE FROM context; - --- name: clean-table-objects -DELETE FROM objects; - --- name: clean-table-read_access -DELETE FROM read_access; - --- name: clean-table-tags -DELETE FROM tags; - --- name: select-context -SELECT * FROM context ORDER BY version ASC LIMIT 1; - --- name: insert-context -INSERT INTO context (version, state) VALUES ($1, $2); - --- name: update-context -UPDATE context SET version=$1, state=$2 WHERE state=$3; - --- name: delete-version -DELETE FROM context WHERE version=$1; - --- name: insert-objects -INSERT INTO objects (id, object, attributes, state, owner) VALUES ($1, $2, $3, $4, $5); - --- name: select-object -SELECT objects.id, objects.object, objects.attributes, objects.owner, objects.state, read_access.permissions - FROM objects - LEFT JOIN read_access - ON objects.id = read_access.id AND ( read_access.userid=$2 OR read_access.userid='*' ) - WHERE objects.id=$1; - --- name: update-object-with-object -UPDATE objects SET object=$1, attributes=$2 WHERE id=$3; - --- name: update-object-with-state -UPDATE objects SET state=$1 WHERE id=$2; - --- name: delete-object -DELETE FROM objects WHERE id=$1 AND owner=$2; - --- name: upsert-object -INSERT INTO objects (id, object, attributes, state, owner) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT(id) - DO UPDATE SET object=$2, attributes=$3, state=$4, owner=$5 - WHERE objects.id=$1; - WHERE objects.owner=$5; - --- name: select-user-accesses-for-object -SELECT permissions - FROM read_access - WHERE id=$1 AND userid=$2; - --- name: upsert-row-read_access -INSERT INTO read_access (id, userid, permissions) VALUES ($1, $2, $3) - ON CONFLICT(id, userid) - DO UPDATE SET permissions=$3 - WHERE read_access.id=$1 AND read_access.userid=$2; - --- name: delete-rows-read_access -DELETE FROM read_access WHERE id=$1 AND userid=$2; - --- name: has-row-objects -SELECT 1 FROM objects WHERE id=$1 AND owner=$2; - --- name: update-rows-read_access-with-permission -UPDATE read_access SET permissions=$3 - WHERE id=$1 AND userid=$2; - --- name: select-rows-read_access-with-object-id -SELECT userid, permissions - FROM read_access - WHERE id=$1; - --- name: select-objects-access-obtained -SELECT objects.id, objects.owner, objects.state, read_access.permissions - FROM objects - INNER JOIN read_access - ON objects.id = read_access.id - WHERE read_access.userid=$1; - --- name: insert-tags -INSERT INTO tags (id, tag) VALUES ($1, $2); - --- name: select-tags -SELECT tag FROM tags WHERE id=$1; - --- name: delete-tags -DELETE FROM tags WHERE id=$1; - - --- name: select-from-tags -SELECT objects.id, objects.object, objects.attributes, objects.owner, objects.state, read_access.permissions -FROM objects -INNER JOIN ( - SELECT id - FROM tags - WHERE tag IN (@TAGS) - GROUP BY id - HAVING COUNT(DISTINCT tag) = @LEN -) AS matched_tags -ON objects.id = matched_tags.id -LEFT JOIN read_access -ON objects.id = read_access.id AND (read_access.userid=@USER OR read_access.userid='*' ); diff --git a/crate/server/src/database/redis/mod.rs b/crate/server/src/database/redis/mod.rs index c13c17f..35c8f78 100644 --- a/crate/server/src/database/redis/mod.rs +++ b/crate/server/src/database/redis/mod.rs @@ -1,130 +1,190 @@ -use argon2::Argon2; -use async_trait::async_trait; -use cloudproof::reexport::crypto_core::{reexport::rand_core::SeedableRng, FixedSizeCBytes}; -use std::{ - collections::HashSet, - sync::{Arc, Mutex}, -}; +use std::collections::{HashMap, HashSet}; -use cloudproof::reexport::crypto_core::{kdf256, Aes256Gcm, CsRng, Instantiable, SymmetricKey}; +use async_trait::async_trait; use cloudproof_findex::{ - implementations::redis::{FindexRedis, FindexRedisError, RemovedLocationsFinder}, - parameters::MASTER_KEY_LENGTH, - Label, Location, + db_interfaces::{ + redis::{build_key, FindexTable, TABLE_PREFIX_LENGTH}, + rest::UpsertData, + }, + reexport::cosmian_findex::{ + CoreError, EncryptedValue, Token, TokenToEncryptedValueMap, TokenWithEncryptedValueList, + Tokens, ENTRY_LENGTH, LINK_LENGTH, + }, }; -use redis::aio::ConnectionManager; - -use crate::{error::FindexServerError, result::FResult, secret::Secret}; +use redis::{aio::ConnectionManager, pipe, AsyncCommands, Script}; +use tracing::{instrument, trace}; use super::Database; - -pub(crate) const REDIS_WITH_FINDEX_MASTER_KEY_LENGTH: usize = 32; -pub(crate) const REDIS_WITH_FINDEX_MASTER_KEY_DERIVATION_SALT: &[u8; 16] = b"rediswithfindex_"; -pub(crate) const REDIS_WITH_FINDEX_MASTER_FINDEX_KEY_DERIVATION_SALT: &[u8; 6] = b"findex"; -pub(crate) const REDIS_WITH_FINDEX_MASTER_DB_KEY_DERIVATION_SALT: &[u8; 2] = b"db"; - -pub(crate) const DB_KEY_LENGTH: usize = 32; +use crate::error::{result::FResult, server::FindexServerError}; + +// TODO(manu): miss the clear_database parameter +// TODO(manu): move secret to client crate + +/// The conditional upsert script used to only update a table if the +/// indexed value matches ARGV[2]. When the value does not match, the +/// indexed value is returned. +const CONDITIONAL_UPSERT_SCRIPT: &str = r" + local value=redis.call('GET',ARGV[1]) + if((value==false) or (not(value == false) and (ARGV[2] == value))) then + redis.call('SET', ARGV[1], ARGV[3]) + return + else + return value + end; + "; #[allow(dead_code)] -pub(crate) struct ObjectsDB { +pub(crate) struct Redis { mgr: ConnectionManager, - dem: Aes256Gcm, - rng: Mutex, + upsert_script: Script, } -impl ObjectsDB { - pub(crate) fn new(mgr: ConnectionManager, db_key: &SymmetricKey) -> Self { - Self { +impl Redis { + pub(crate) async fn instantiate(redis_url: &str) -> FResult { + let client = redis::Client::open(redis_url)?; + let mgr = ConnectionManager::new(client).await?; + Ok(Self { mgr, - dem: Aes256Gcm::new(db_key), - rng: Mutex::new(CsRng::from_entropy()), - } + upsert_script: Script::new(CONDITIONAL_UPSERT_SCRIPT), + }) } } + #[async_trait] -impl RemovedLocationsFinder for ObjectsDB { - async fn find_removed_locations( +impl Database for Redis { + #[instrument(ret(Display), err, skip_all)] + async fn fetch( &self, - _locations: HashSet, - ) -> Result, FindexRedisError> { - // Objects and permissions are never removed from the DB - Ok(HashSet::new()) - } -} + findex_table: FindexTable, + tokens: Tokens, + ) -> FResult> { + trace!( + "fetch: table {findex_table:?}, number of tokens: {}", + tokens.len() + ); + let uids = tokens.into_iter().collect::>(); -#[allow(dead_code)] -pub(crate) struct RedisWithFindex { - objects_db: Arc, - findex: Arc, - findex_key: SymmetricKey, - label: Label, -} + let redis_keys = uids + .iter() + .map(|uid| build_key(findex_table, uid)) + .collect::>(); -impl RedisWithFindex { - pub(crate) async fn instantiate( - redis_url: &str, - master_key: Secret, - label: &[u8], - ) -> FResult { - // derive a Findex Key - let mut findex_key = SymmetricKey::::default(); - kdf256!( - &mut findex_key, - REDIS_WITH_FINDEX_MASTER_FINDEX_KEY_DERIVATION_SALT, - &*master_key - ); - // derive a DB Key - let mut db_key = SymmetricKey::::default(); - kdf256!( - &mut db_key, - REDIS_WITH_FINDEX_MASTER_DB_KEY_DERIVATION_SALT, - &*master_key - ); + let values: Vec> = self.mgr.clone().mget(redis_keys).await?; - let client = redis::Client::open(redis_url)?; - let mgr = ConnectionManager::new(client).await?; - let objects_db = Arc::new(ObjectsDB::new(mgr.clone(), &db_key)); - let findex = - Arc::new(FindexRedis::connect_with_manager(mgr.clone(), objects_db.clone()).await?); - Ok(Self { - objects_db, - findex, - findex_key, - label: Label::from(label), - }) - } + // Zip and filter empty values out. + let res = uids + .into_iter() + .zip(values) + .filter_map(|(k, v)| { + if v.is_empty() { + None + } else { + Some(EncryptedValue::try_from(v.as_slice()).map(|v| (k, v))) + } + }) + .collect::, CoreError>>()?; - pub(crate) fn master_key_from_password( - master_password: &str, - ) -> FResult> { - let output_key_material = derive_key_from_password::( - REDIS_WITH_FINDEX_MASTER_KEY_DERIVATION_SALT, - master_password.as_bytes(), - )?; + trace!("fetch_entry_table non empty tuples len: {}", res.len()); - let master_secret_key: SymmetricKey = - SymmetricKey::try_from_slice(&output_key_material)?; + let result: TokenWithEncryptedValueList = res.into(); - Ok(master_secret_key) + Ok(result) } -} -pub(crate) fn derive_key_from_password( - salt: &[u8; 16], - password: &[u8], -) -> Result, FindexServerError> { - let mut output_key_material = Secret::::new(); + #[instrument(ret(Display), err, skip_all)] + async fn upsert_entries( + &self, + upsert_data: UpsertData, + ) -> FResult> { + trace!( + "upsert_entries: number of upsert data: {}", + upsert_data.len() + ); - Argon2::default() - .hash_password_into(password, salt, output_key_material.as_mut()) - .map_err(|e| FindexServerError::CryptographicError(e.to_string()))?; + let mut old_values = HashMap::new(); + let mut new_values = HashMap::new(); + for (token, (old_value, new_value)) in upsert_data { + if let Some(old_value) = old_value { + old_values.insert(token, old_value); + } + new_values.insert(token, new_value); + } - Ok(output_key_material) -} + trace!( + "upsert_entries: number of old_values {}, number of new_values {}", + old_values.len(), + new_values.len() + ); + + let mut rejected = HashMap::with_capacity(new_values.len()); + for (uid, new_value) in new_values { + let new_value = Vec::from(&new_value); + let old_value = old_values.get(&uid).map(Vec::from).unwrap_or_default(); + let key = build_key(FindexTable::Entry, &uid); + + let indexed_value: Vec<_> = self + .upsert_script + .arg(key) + .arg(old_value) + .arg(new_value) + .invoke_async(&mut self.mgr.clone()) + .await?; + + if !indexed_value.is_empty() { + let encrypted_value = EncryptedValue::try_from(indexed_value.as_slice())?; + rejected.insert(uid, encrypted_value); + } + } -#[async_trait(?Send)] -impl Database for RedisWithFindex { - async fn create(&self) -> FResult<()> { - Ok(()) + trace!("upsert_entries: rejected: {}", rejected.len()); + + Ok(rejected.into()) + } + + #[instrument(ret, err, skip_all)] + async fn insert_chains(&self, items: TokenToEncryptedValueMap) -> FResult<()> { + let mut pipe = pipe(); + for (k, v) in &*items { + pipe.set(build_key(FindexTable::Chain, k), Vec::from(v)); + } + pipe.atomic() + .query_async(&mut self.mgr.clone()) + .await + .map_err(FindexServerError::from) + } + + #[instrument(ret, err, skip_all)] + async fn delete(&self, findex_table: FindexTable, entry_uids: Tokens) -> FResult<()> { + let mut pipeline = pipe(); + for uid in entry_uids { + pipeline.del(build_key(findex_table, &uid)); + } + pipeline + .atomic() + .query_async(&mut self.mgr.clone()) + .await + .map_err(FindexServerError::from) + } + + #[instrument(ret(Display), err, skip_all)] + #[allow(clippy::indexing_slicing)] + async fn dump_tokens(&self) -> FResult { + let keys: Vec> = self + .mgr + .clone() + .keys(build_key(FindexTable::Entry, b"*")) + .await?; + + trace!("dumping {} keywords (ET+CT)", keys.len()); + + let mut tokens_set = HashSet::new(); + for key in keys { + if key[..TABLE_PREFIX_LENGTH] == [0x00, u8::from(FindexTable::Entry)] { + if let Ok(token) = Token::try_from(&key[TABLE_PREFIX_LENGTH..]) { + tokens_set.insert(token); + } + } + } + Ok(Tokens::from(tokens_set)) } } diff --git a/crate/server/src/database/sqlite/mod.rs b/crate/server/src/database/sqlite/mod.rs deleted file mode 100644 index 59afb0b..0000000 --- a/crate/server/src/database/sqlite/mod.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::{path::Path, time::Duration}; - -use async_trait::async_trait; -use sqlx::{ - sqlite::{SqliteConnectOptions, SqlitePoolOptions}, - ConnectOptions, Executor, Pool, Sqlite, -}; - -use crate::{ - database::{Database, SQLITE_QUERIES}, - findex_server_error, - result::FResult, -}; - -#[macro_export] -macro_rules! get_sqlite_query { - ($name:literal) => { - SQLITE_QUERIES - .get($name) - .ok_or_else(|| findex_server_error!("{} SQL query can't be found", $name))? - }; - ($name:expr) => { - SQLITE_QUERIES - .get($name) - .ok_or_else(|| findex_server_error!("{} SQL query can't be found", $name))? - }; -} - -#[allow(dead_code)] -pub(crate) struct SqlitePool { - pool: Pool, -} - -impl SqlitePool { - /// Instantiate a new `SQLite` database - /// and create the appropriate table(s) if need be - pub(crate) async fn instantiate(path: &Path, clear_database: bool) -> FResult { - let options = SqliteConnectOptions::new() - .filename(path) - // Sets a timeout value to wait when the database is locked, before returning a busy timeout error. - .busy_timeout(Duration::from_secs(120)) - .create_if_missing(true) - // disable logging of each query - .disable_statement_logging(); - - let pool = SqlitePoolOptions::new() - .max_connections(u32::try_from(num_cpus::get())?) - .connect_with(options) - .await?; - - sqlx::query(get_sqlite_query!("create-table-context")) - .execute(&pool) - .await?; - - sqlx::query(get_sqlite_query!("create-table-objects")) - .execute(&pool) - .await?; - - sqlx::query(get_sqlite_query!("create-table-read_access")) - .execute(&pool) - .await?; - - sqlx::query(get_sqlite_query!("create-table-tags")) - .execute(&pool) - .await?; - - if clear_database { - clear_database_(&pool).await?; - } - - let sqlite_pool = Self { pool }; - Ok(sqlite_pool) - } -} - -#[async_trait(?Send)] -impl Database for SqlitePool { - async fn create(&self) -> FResult<()> { - Ok(()) - } -} - -pub(crate) async fn clear_database_<'e, E>(executor: E) -> FResult<()> -where - E: Executor<'e, Database = Sqlite> + Copy, -{ - // Erase `context` table - sqlx::query(get_sqlite_query!("clean-table-context")) - .execute(executor) - .await?; - // Erase `objects` table - sqlx::query(get_sqlite_query!("clean-table-objects")) - .execute(executor) - .await?; - // Erase `read_access` table - sqlx::query(get_sqlite_query!("clean-table-read_access")) - .execute(executor) - .await?; - // Erase `tags` table - sqlx::query(get_sqlite_query!("clean-table-tags")) - .execute(executor) - .await?; - Ok(()) -} diff --git a/crate/server/src/error/mod.rs b/crate/server/src/error/mod.rs new file mode 100644 index 0000000..d85381a --- /dev/null +++ b/crate/server/src/error/mod.rs @@ -0,0 +1,2 @@ +pub mod result; +pub mod server; diff --git a/crate/server/src/result.rs b/crate/server/src/error/result.rs similarity index 89% rename from crate/server/src/result.rs rename to crate/server/src/error/result.rs index 82afdb4..827ce2b 100644 --- a/crate/server/src/result.rs +++ b/crate/server/src/error/result.rs @@ -1,21 +1,24 @@ -use crate::error::FindexServerError; +use crate::error::server::FindexServerError; pub type FResult = Result; -/// A helper trait for `FResult` that provides additional methods for error handling. +/// A helper trait for `FResult` that provides additional methods for error +/// handling. pub trait FResultHelper { /// Sets the context for the error. /// /// # Errors /// - /// Returns a `FResult` with the specified context if the original result is an error. + /// Returns a `FResult` with the specified context if the original result is + /// an error. fn context(self, context: &str) -> FResult; /// Sets the context for the error using a closure. /// /// # Errors /// - /// Returns a `FResult` with the context returned by the closure if the original result is an error. + /// Returns a `FResult` with the context returned by the closure if the + /// original result is an error. fn with_context(self, op: O) -> FResult where O: FnOnce() -> String; diff --git a/crate/server/src/error.rs b/crate/server/src/error/server.rs similarity index 89% rename from crate/server/src/error.rs rename to crate/server/src/error/server.rs index c32d781..023fb98 100644 --- a/crate/server/src/error.rs +++ b/crate/server/src/error/server.rs @@ -1,8 +1,10 @@ use std::{array::TryFromSliceError, sync::mpsc::SendError}; use actix_web::{dev::ServerHandle, error::QueryPayloadError}; -use cloudproof::reexport::crypto_core::CryptoCoreError; -use cloudproof_findex::implementations::redis::FindexRedisError; +use cloudproof_findex::{ + db_interfaces::DbInterfaceError, reexport::cosmian_findex::CoreError, + ser_de::SerializationError, +}; use redis::ErrorKind; use thiserror::Error; use x509_parser::prelude::{PEMError, X509Error}; @@ -76,18 +78,6 @@ impl From> for FindexServerError { } } -impl From for FindexServerError { - fn from(e: CryptoCoreError) -> Self { - Self::CryptographicError(e.to_string()) - } -} - -impl From for FindexServerError { - fn from(e: FindexRedisError) -> Self { - Self::Findex(e.to_string()) - } -} - impl From for FindexServerError { fn from(e: std::string::FromUtf8Error) -> Self { Self::ConversionError(e.to_string()) @@ -124,12 +114,6 @@ impl From for FindexServerError { } } -impl From for FindexServerError { - fn from(e: cloudproof::reexport::cover_crypt::Error) -> Self { - Self::InvalidRequest(e.to_string()) - } -} - impl From for FindexServerError { fn from(e: QueryPayloadError) -> Self { Self::InvalidRequest(e.to_string()) @@ -176,6 +160,12 @@ impl From for FindexServerError { } } +impl From for FindexServerError { + fn from(e: SerializationError) -> Self { + Self::Findex(e.to_string()) + } +} + impl From for FindexServerError { fn from(e: base64::DecodeError) -> Self { Self::ConversionError(e.to_string()) @@ -188,6 +178,18 @@ impl From for FindexServerError { } } +impl From for FindexServerError { + fn from(e: DbInterfaceError) -> Self { + Self::DatabaseError(e.to_string()) + } +} + +impl From for FindexServerError { + fn from(e: CoreError) -> Self { + Self::Findex(e.to_string()) + } +} + /// Return early with an error if a condition is not satisfied. /// /// This macro is equivalent to `if !$cond { return Err(From::from($err)); }`. @@ -214,13 +216,13 @@ macro_rules! findex_server_ensure { #[macro_export] macro_rules! findex_server_error { ($msg:literal) => { - $crate::error::FindexServerError::ServerError(::core::format_args!($msg).to_string()) + $crate::error::server::FindexServerError::ServerError(::core::format_args!($msg).to_string()) }; ($err:expr $(,)?) => ({ - $crate::error::FindexServerError::ServerError($err.to_string()) + $crate::error::server::FindexServerError::ServerError($err.to_string()) }); ($fmt:expr, $($arg:tt)*) => { - $crate::error::FindexServerError::ServerError(::core::format_args!($fmt, $($arg)*).to_string()) + $crate::error::server::FindexServerError::ServerError(::core::format_args!($fmt, $($arg)*).to_string()) }; } diff --git a/crate/server/src/findex_server.rs b/crate/server/src/findex_server.rs index c082dad..9242394 100644 --- a/crate/server/src/findex_server.rs +++ b/crate/server/src/findex_server.rs @@ -17,11 +17,13 @@ use tracing::info; use crate::{ config::{self, JwtAuthConfig, ServerParams}, core::FindexServer, + error::result::FResult, findex_server_bail, middlewares::{extract_peer_certificate, AuthTransformer, JwksManager, JwtConfig, SslAuth}, - result::FResult, - routes::get_version, - FServer, + routes::{ + delete_chains, delete_entries, dump_tokens, fetch_chains, fetch_entries, get_version, + insert_chains, upsert_entries, + }, }; /// Starts the Findex server based on the provided configuration. @@ -30,16 +32,20 @@ use crate::{ /// 1. Plain HTTP, /// 2. HTTPS with PKCS#12, /// -/// The method used depends on the server settings specified in the `ServerParams` instance provided. +/// The method used depends on the server settings specified in the +/// `ServerParams` instance provided. /// /// # Arguments /// -/// * `server_params` - An instance of `ServerParams` that contains the settings for the server. -/// * `server_handle_transmitter` - An optional sender channel of type `mpsc::Sender` that can be used to manage server state. +/// * `server_params` - An instance of `ServerParams` that contains the settings +/// for the server. +/// * `server_handle_transmitter` - An optional sender channel of type +/// `mpsc::Sender` that can be used to manage server state. /// /// # Errors /// -/// This function will return an error if any of the server starting methods fails. +/// This function will return an error if any of the server starting methods +/// fails. pub async fn start_findex_server( server_params: ServerParams, findex_server_handle_tx: Option>, @@ -58,12 +64,15 @@ pub async fn start_findex_server( /// Start a plain HTTP Findex server /// -/// This function will instantiate and prepare the Findex server and run it on a plain HTTP connection +/// This function will instantiate and prepare the Findex server and run it on a +/// plain HTTP connection /// /// # Arguments /// -/// * `server_params` - An instance of `ServerParams` that contains the settings for the server. -/// * `server_handle_transmitter` - An optional sender channel of type `mpsc::Sender` that can be used to manage server state. +/// * `server_params` - An instance of `ServerParams` that contains the settings +/// for the server. +/// * `server_handle_transmitter` - An optional sender channel of type +/// `mpsc::Sender` that can be used to manage server state. /// /// # Errors /// @@ -75,7 +84,7 @@ async fn start_plain_http_findex_server( server_handle_transmitter: Option>, ) -> FResult<()> { // Instantiate and prepare the Findex server - let findex_server = Arc::new(FServer::instantiate(server_params).await?); + let findex_server = Arc::new(FindexServer::instantiate(server_params).await?); // Prepare the server let server = prepare_findex_server(findex_server, None).await?; @@ -94,8 +103,10 @@ async fn start_plain_http_findex_server( /// /// # Arguments /// -/// * `server_params` - An instance of `ServerParams` that contains the settings for the server. -/// * `server_handle_transmitter` - An optional sender channel of type `mpsc::Sender` that can be used to manage server state. +/// * `server_params` - An instance of `ServerParams` that contains the settings +/// for the server. +/// * `server_handle_transmitter` - An optional sender channel of type +/// `mpsc::Sender` that can be used to manage server state. /// /// # Errors /// @@ -103,7 +114,8 @@ async fn start_plain_http_findex_server( /// - The path to the PKCS#12 certificate file is not provided in the config /// - The file cannot be opened or read /// - The file is not a valid PKCS#12 format or the password is incorrect -/// - The SSL acceptor cannot be created or configured with the certificate and key +/// - The SSL acceptor cannot be created or configured with the certificate and +/// key /// - The Findex server cannot be instantiated or prepared /// - The server fails to run async fn start_https_findex_server( @@ -137,7 +149,7 @@ async fn start_https_findex_server( } // Instantiate and prepare the Findex server - let findex_server = Arc::new(FServer::instantiate(server_params).await?); + let findex_server = Arc::new(FindexServer::instantiate(server_params).await?); let server = prepare_findex_server(findex_server, Some(builder)).await?; // send the server handle to the caller @@ -152,30 +164,35 @@ async fn start_https_findex_server( } /** - * This function prepares a server for the application. It creates an `HttpServer` instance, - * configures the routes for the application, and sets the request timeout. The server can be - * configured to use OpenSSL for SSL encryption by providing an `SslAcceptorBuilder`. + * This function prepares a server for the application. It creates an + * `HttpServer` instance, configures the routes for the application, and + * sets the request timeout. The server can be configured to use OpenSSL for + * SSL encryption by providing an `SslAcceptorBuilder`. * * # Arguments * - * * `findex_server`: A shared reference to the `Findex server` instance to be used by the application. - * * `builder`: An optional `SslAcceptorBuilder` to configure the SSL encryption for the server. + * * `findex_server`: A shared reference to the `Findex server` instance to + * be used by the application. + * * `builder`: An optional `SslAcceptorBuilder` to configure the SSL + * encryption for the server. * * # Returns * - * Returns a `Result` type that contains a `Server` instance if successful, or an error if - * something went wrong. + * Returns a `Result` type that contains a `Server` instance if successful, + * or an error if something went wrong. * * # Errors * * This function can return the following errors: - * - `FindexServerError::ServerError` - If there is an error in the server configuration or preparation. + * - `FindexServerError::ServerError` - If there is an error in the server + * configuration or preparation. */ pub(crate) async fn prepare_findex_server( findex_server: Arc, builder: Option, ) -> FResult { - // Prepare the JWT configurations and the JWKS manager if the server is using JWT for authentication. + // Prepare the JWT configurations and the JWKS manager if the server is using + // JWT for authentication. let (jwt_configurations, _jwks_manager) = if let Some(identity_provider_configurations) = &findex_server.params.identity_provider_configurations { @@ -230,6 +247,13 @@ pub(crate) async fn prepare_findex_server( // CORS middleware is the last one so that the auth middlewares do not run on // preflight (OPTION) requests. .wrap(Cors::permissive()) + .service(fetch_entries) + .service(fetch_chains) + .service(upsert_entries) + .service(insert_chains) + .service(delete_entries) + .service(delete_chains) + .service(dump_tokens) .service(get_version); app.service(default_scope) diff --git a/crate/server/src/lib.rs b/crate/server/src/lib.rs index 666073f..9c30d9d 100644 --- a/crate/server/src/lib.rs +++ b/crate/server/src/lib.rs @@ -40,6 +40,7 @@ clippy::unseparated_literal_suffix, clippy::map_err_ignore, clippy::redundant_clone, + clippy::todo )] #![allow( clippy::module_name_repetitions, @@ -57,12 +58,9 @@ pub mod database; pub mod error; pub mod findex_server; pub mod middlewares; -pub mod result; pub mod routes; pub mod secret; -pub(crate) use database::FServer; - #[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used, unsafe_code)] #[cfg(test)] mod tests; diff --git a/crate/server/src/main.rs b/crate/server/src/main.rs index 8caa930..e2960b6 100644 --- a/crate/server/src/main.rs +++ b/crate/server/src/main.rs @@ -3,10 +3,9 @@ use std::path::PathBuf; use clap::Parser; use cosmian_findex_server::{ config::{ClapConfig, ServerParams}, - error::FindexServerError, + error::{result::FResult, server::FindexServerError}, findex_server::start_findex_server, findex_server_bail, - result::FResult, }; use cosmian_logger::log_utils::log_init; use dotenvy::dotenv; @@ -16,8 +15,8 @@ const FINDEX_SERVER_CONF: &str = "/etc/cosmian_findex_server/server.toml"; /// The main entrypoint of the program. /// -/// This function sets up the necessary environment variables and logging options, -/// then parses the command line arguments using [`ClapConfig::parse()`](https://docs.rs/clap/latest/clap/struct.ClapConfig.html#method.parse). +/// This function sets up the necessary environment variables and logging +/// options, then parses the command line arguments using [`ClapConfig::parse()`](https://docs.rs/clap/latest/clap/struct.ClapConfig.html#method.parse). #[tokio::main] #[allow(clippy::needless_return)] async fn main() -> FResult<()> { @@ -46,8 +45,8 @@ async fn main() -> FResult<()> { let conf_path = PathBuf::from(conf_path); if !conf_path.exists() { findex_server_bail!(FindexServerError::ServerError(format!( - "Cannot read findex server config at specified path: {conf_path:?} - file does not \ - exist" + "Cannot read findex server config at specified path: {conf_path:?} - file does \ + not exist" ))); } conf_path @@ -77,7 +76,8 @@ async fn main() -> FResult<()> { ClapConfig::parse() }; - // Instantiate a config object using the env variables and the args of the binary + // Instantiate a config object using the env variables and the args of the + // binary debug!("Command line config: {clap_config:#?}"); // Parse the Server Config from the command line arguments diff --git a/crate/server/src/middlewares/jwks.rs b/crate/server/src/middlewares/jwks.rs index d462abe..1b2a9e5 100644 --- a/crate/server/src/middlewares/jwks.rs +++ b/crate/server/src/middlewares/jwks.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, sync::RwLock}; use alcoholic_jwt::{JWK, JWKS}; use chrono::{DateTime, Duration, Utc}; -use crate::{error::FindexServerError, result::FResult}; +use crate::error::{result::FResult, server::FindexServerError}; static REFRESH_INTERVAL: i64 = 60; // in secs diff --git a/crate/server/src/middlewares/jwt.rs b/crate/server/src/middlewares/jwt.rs index 39f076f..589fa40 100644 --- a/crate/server/src/middlewares/jwt.rs +++ b/crate/server/src/middlewares/jwt.rs @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, trace}; use super::JwksManager; -use crate::{error::FindexServerError, findex_server_ensure, result::FResult}; +use crate::{ + error::{result::FResult, server::FindexServerError}, + findex_server_ensure, +}; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct UserClaim { diff --git a/crate/server/src/middlewares/jwt_token_auth.rs b/crate/server/src/middlewares/jwt_token_auth.rs index 2c66baa..c287189 100644 --- a/crate/server/src/middlewares/jwt_token_auth.rs +++ b/crate/server/src/middlewares/jwt_token_auth.rs @@ -11,7 +11,10 @@ use actix_web::{ use tracing::{debug, error, trace}; use super::UserClaim; -use crate::{error::FindexServerError, middlewares::jwt::JwtConfig, result::FResult}; +use crate::{ + error::{result::FResult, server::FindexServerError}, + middlewares::jwt::JwtConfig, +}; pub(crate) async fn manage_jwt_request( service: Rc, @@ -73,7 +76,8 @@ pub(crate) async fn manage_jwt( trace!("Checking JWT identity: {identity}"); let mut private_claim = extract_user_claim(&configs, &identity); - // If no configuration could get the claim, try refreshing them and extract user claim again + // If no configuration could get the claim, try refreshing them and extract user + // claim again if private_claim.is_err() { configs .first() diff --git a/crate/server/src/middlewares/ssl_auth.rs b/crate/server/src/middlewares/ssl_auth.rs index ba9847a..6a3fc45 100644 --- a/crate/server/src/middlewares/ssl_auth.rs +++ b/crate/server/src/middlewares/ssl_auth.rs @@ -19,7 +19,7 @@ use futures::{ use openssl::{nid::Nid, x509::X509}; use tracing::{debug, error, trace}; -use crate::{findex_server_bail, result::FResult}; +use crate::{error::result::FResult, findex_server_bail}; // see this https://github.com/actix/actix-web/pull/1754#issuecomment-716192605 // for inspiration @@ -35,8 +35,9 @@ pub(crate) struct PeerCertificate { /// Extract the peer certificate from the TLS stream and pass it to middleware. /// -/// This function extracts the peer certificate from the TLS stream and passes it to the middleware. -/// The middleware can then use the peer certificate to authenticate the client. +/// This function extracts the peer certificate from the TLS stream and passes +/// it to the middleware. The middleware can then use the peer certificate to +/// authenticate the client. pub(crate) fn extract_peer_certificate(cnx: &dyn Any, extensions: &mut Extensions) { // Check if the connection is a TLS connection. if let Some(cnx) = cnx.downcast_ref::>() { @@ -57,10 +58,12 @@ pub(crate) struct PeerCommonName { pub(crate) common_name: String, } -/// The middleware that checks the peer certificate and extracts the common name. +/// The middleware that checks the peer certificate and extracts the common +/// name. /// -/// This middleware checks the peer certificate for a common name and extracts it. -/// The common name is then added to the request context so that it can be used by other middleware or handlers. +/// This middleware checks the peer certificate for a common name and extracts +/// it. The common name is then added to the request context so that it can be +/// used by other middleware or handlers. pub(crate) struct SslAuth; impl Transform for SslAuth @@ -104,9 +107,11 @@ where /// Call the `SslAuthMiddleware`. /// - /// This function calls the underlying service and checks the peer certificate for a common name. - /// If the common name is found, it is added to the request context so that it can be used by other middleware or handlers. - /// If the common name is not found, an unauthorized response is returned. + /// This function calls the underlying service and checks the peer + /// certificate for a common name. If the common name is found, it is + /// added to the request context so that it can be used by other middleware + /// or handlers. If the common name is not found, an unauthorized + /// response is returned. #[allow(clippy::cognitive_complexity)] fn call(&self, req: ServiceRequest) -> Self::Future { // Log that the middleware is being called. diff --git a/crate/server/src/routes/error.rs b/crate/server/src/routes/error.rs new file mode 100644 index 0000000..2f90a9c --- /dev/null +++ b/crate/server/src/routes/error.rs @@ -0,0 +1,46 @@ +use actix_web::{ + http::{header, StatusCode}, + web::Json, + HttpResponse, HttpResponseBuilder, +}; +use tracing::{error, warn}; + +use crate::error::server::FindexServerError; + +pub(crate) type Response = Result, FindexServerError>; +pub(crate) type ResponseBytes = Result; + +impl actix_web::error::ResponseError for FindexServerError { + fn status_code(&self) -> StatusCode { + match self { + Self::Unauthorized(_) => StatusCode::UNAUTHORIZED, + + Self::DatabaseError(_) + | Self::ConversionError(_) + | Self::CryptographicError(_) + | Self::Redis(_) + | Self::Findex(_) + | Self::Certificate(_) + | Self::ServerError(_) => StatusCode::INTERNAL_SERVER_ERROR, + + Self::InvalidRequest(_) | Self::ClientConnectionError(_) | Self::UrlError(_) => { + StatusCode::UNPROCESSABLE_ENTITY + } + } + } + + fn error_response(&self) -> HttpResponse { + let status_code = self.status_code(); + let message = self.to_string(); + + if status_code >= StatusCode::INTERNAL_SERVER_ERROR { + error!("{status_code} - {message}"); + } else { + warn!("{status_code} - {message}"); + } + + HttpResponseBuilder::new(status_code) + .insert_header((header::CONTENT_TYPE, "text/html; charset=utf-8")) + .body(message) + } +} diff --git a/crate/server/src/routes/findex.rs b/crate/server/src/routes/findex.rs new file mode 100644 index 0000000..8f1bef0 --- /dev/null +++ b/crate/server/src/routes/findex.rs @@ -0,0 +1,159 @@ +use std::sync::Arc; + +use actix_web::{ + post, + web::{Bytes, Data, Json}, + HttpRequest, HttpResponse, +}; +use cloudproof_findex::{ + db_interfaces::{redis::FindexTable, rest::UpsertData}, + reexport::{ + cosmian_crypto_core::bytes_ser_de::Serializable, cosmian_findex::TokenToEncryptedValueMap, + }, + ser_de::ffi_ser_de::deserialize_token_set, +}; +use tracing::{info, trace}; + +use crate::{ + core::FindexServer, + routes::error::{Response, ResponseBytes}, +}; + +// todo(manu): warning: remove eol in imports + +#[post("/indexes/fetch_entries")] +pub(crate) async fn fetch_entries( + req: HttpRequest, + bytes: Bytes, + findex_server: Data>, +) -> ResponseBytes { + info!("POST /fetch_entries {}", findex_server.get_user(&req)); + let bytes = bytes.into_iter().collect::>(); + let tokens = deserialize_token_set(&bytes)?; + trace!("fetch_entries: number of tokens: {}:", tokens.len()); + + // Collect into a vector to fix the order. + let uids_and_values = findex_server.db.fetch(FindexTable::Entry, tokens).await?; + trace!( + "fetch_entries: number of uids_and_values: {}:", + uids_and_values.len() + ); + + let bytes = uids_and_values.serialize()?.to_vec(); + trace!("fetch_entries: number of bytes: {}:", bytes.len()); + + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(bytes)) +} + +#[post("/indexes/fetch_chains")] +pub(crate) async fn fetch_chains( + req: HttpRequest, + bytes: Bytes, + findex_server: Data>, +) -> ResponseBytes { + info!("POST /fetch_chains {}", findex_server.get_user(&req)); + + let bytes = bytes.into_iter().collect::>(); + let tokens = deserialize_token_set(&bytes)?; + trace!("fetch_chains: number of tokens: {}:", tokens.len()); + + let uids_and_values = findex_server.db.fetch(FindexTable::Chain, tokens).await?; + trace!( + "fetch_chains: number of uids_and_values: {}:", + uids_and_values.len() + ); + + let bytes = uids_and_values.serialize()?.to_vec(); + + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(bytes)) +} + +#[post("/indexes/upsert_entries")] +pub(crate) async fn upsert_entries( + req: HttpRequest, + bytes: Bytes, + findex_server: Data>, +) -> ResponseBytes { + info!("POST /upsert_entries {}", findex_server.get_user(&req)); + let bytes = bytes.into_iter().collect::>(); + let upsert_data = UpsertData::deserialize(&bytes)?; + + trace!("upsert_entries: num upsert data: {}", upsert_data.len()); + + let rejected = findex_server.db.upsert_entries(upsert_data).await?; + + let bytes = rejected.serialize()?.to_vec(); + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(bytes)) +} + +#[post("/indexes/insert_chains")] +pub(crate) async fn insert_chains( + req: HttpRequest, + bytes: Bytes, + findex_server: Data>, +) -> Response<()> { + info!("POST /insert_chains {}", findex_server.get_user(&req)); + let bytes = bytes.into_iter().collect::>(); + let token_to_value_encrypted_value_map = TokenToEncryptedValueMap::deserialize(&bytes)?; + + findex_server + .db + .insert_chains(token_to_value_encrypted_value_map) + .await?; + + Ok(Json(())) +} + +#[post("/indexes/delete_entries")] +pub(crate) async fn delete_entries( + req: HttpRequest, + bytes: Bytes, + findex_server: Data>, +) -> Response<()> { + info!("POST /delete_entries {}", findex_server.get_user(&req)); + let bytes = bytes.into_iter().collect::>(); + let tokens = deserialize_token_set(&bytes)?; + trace!("delete_entries: number of tokens: {}:", tokens.len()); + + findex_server.db.delete(FindexTable::Entry, tokens).await?; + + Ok(Json(())) +} + +#[post("/indexes/delete_chains")] +pub(crate) async fn delete_chains( + req: HttpRequest, + bytes: Bytes, + findex_server: Data>, +) -> Response<()> { + info!("POST /delete_chains {}", findex_server.get_user(&req)); + let bytes = bytes.into_iter().collect::>(); + let tokens = deserialize_token_set(&bytes)?; + trace!("delete_chains: number of tokens: {}:", tokens.len()); + + findex_server.db.delete(FindexTable::Chain, tokens).await?; + + Ok(Json(())) +} + +#[post("/indexes/dump_tokens")] +pub(crate) async fn dump_tokens( + req: HttpRequest, + findex_server: Data>, +) -> ResponseBytes { + info!("POST /dump_tokens {}", findex_server.get_user(&req)); + let tokens = findex_server.db.dump_tokens().await?; + trace!("dump_tokens: number of tokens: {}:", tokens.len()); + + let bytes = tokens.serialize()?.to_vec(); + + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(bytes)) +} diff --git a/crate/server/src/routes/mod.rs b/crate/server/src/routes/mod.rs index 6c432da..ceac025 100644 --- a/crate/server/src/routes/mod.rs +++ b/crate/server/src/routes/mod.rs @@ -1,62 +1,9 @@ -use std::sync::Arc; +mod error; +mod findex; +mod version; -use actix_web::{ - get, - http::{header, StatusCode}, - web::{Data, Json}, - HttpRequest, HttpResponse, HttpResponseBuilder, +pub(crate) use findex::{ + delete_chains, delete_entries, dump_tokens, fetch_chains, fetch_entries, insert_chains, + upsert_entries, }; -use clap::crate_version; -use openssl::version; -use tracing::{error, info, warn}; - -use crate::{database::FServer, error::FindexServerError, result::FResult}; - -impl actix_web::error::ResponseError for FindexServerError { - fn status_code(&self) -> StatusCode { - match self { - Self::Unauthorized(_) => StatusCode::UNAUTHORIZED, - - Self::DatabaseError(_) - | Self::ConversionError(_) - | Self::CryptographicError(_) - | Self::Redis(_) - | Self::Findex(_) - | Self::Certificate(_) - | Self::ServerError(_) => StatusCode::INTERNAL_SERVER_ERROR, - - Self::InvalidRequest(_) | Self::ClientConnectionError(_) | Self::UrlError(_) => { - StatusCode::UNPROCESSABLE_ENTITY - } - } - } - - fn error_response(&self) -> HttpResponse { - let status_code = self.status_code(); - let message = self.to_string(); - - if status_code >= StatusCode::INTERNAL_SERVER_ERROR { - error!("{status_code} - {message}"); - } else { - warn!("{status_code} - {message}"); - } - - HttpResponseBuilder::new(status_code) - .insert_header((header::CONTENT_TYPE, "text/html; charset=utf-8")) - .body(message) - } -} - -/// Get the Findex server version -#[get("/version")] -pub(crate) async fn get_version( - req: HttpRequest, - findex_server: Data>, -) -> FResult> { - info!("GET /version {}", findex_server.get_user(&req)); - Ok(Json(format!( - "{} ({})", - crate_version!().to_owned(), - version::version() - ))) -} +pub(crate) use version::get_version; diff --git a/crate/server/src/routes/version.rs b/crate/server/src/routes/version.rs new file mode 100644 index 0000000..afcd20a --- /dev/null +++ b/crate/server/src/routes/version.rs @@ -0,0 +1,26 @@ +use std::sync::Arc; + +use actix_web::{ + get, + web::{Data, Json}, + HttpRequest, +}; +use clap::crate_version; +use openssl::version; +use tracing::info; + +use crate::{core::FindexServer, error::result::FResult}; + +/// Get the Findex server version +#[get("/version")] +pub(crate) async fn get_version( + req: HttpRequest, + findex_server: Data>, +) -> FResult> { + info!("GET /version {}", findex_server.get_user(&req)); + Ok(Json(format!( + "{} ({})", + crate_version!().to_owned(), + version::version() + ))) +} diff --git a/crate/server/src/secret.rs b/crate/server/src/secret.rs index 88626d2..f6901d6 100644 --- a/crate/server/src/secret.rs +++ b/crate/server/src/secret.rs @@ -8,7 +8,7 @@ use openssl::rand::rand_bytes; use serde::Deserialize; use zeroize::{Zeroize, ZeroizeOnDrop}; -use crate::result::FResult; +use crate::error::result::FResult; /// Guarantees to be zeroized on drop with /// feature `zeroize` enabled from `num_bigint_dig` crate. diff --git a/crate/server/src/tests/mod.rs b/crate/server/src/tests/mod.rs index c052ead..0f5eade 100644 --- a/crate/server/src/tests/mod.rs +++ b/crate/server/src/tests/mod.rs @@ -1,16 +1,14 @@ use std::path::PathBuf; -use crate::config::{ClapConfig, DBConfig, HttpConfig, JwtAuthConfig, WorkspaceConfig}; +use crate::config::{ClapConfig, DBConfig, DatabaseType, HttpConfig, JwtAuthConfig}; #[test] fn test_toml() { let config = ClapConfig { db: DBConfig { - database_type: Some("[redis-findex, ...]".to_owned()), + database_type: Some(DatabaseType::Redis), database_url: Some("[redis urls]".to_owned()), sqlite_path: PathBuf::from("[sqlite path]"), - redis_master_password: Some("[redis master password]".to_owned()), - redis_findex_label: Some("[redis findex label]".to_owned()), clear_database: false, }, http: HttpConfig { @@ -31,10 +29,6 @@ fn test_toml() { "[jwt audience 2]".to_owned(), ]), }, - workspace: WorkspaceConfig { - root_data_path: PathBuf::from("[root data path]"), - tmp_path: PathBuf::from("[tmp path]"), - }, default_username: "[default username]".to_owned(), force_default_username: false, }; @@ -44,11 +38,9 @@ default_username = "[default username]" force_default_username = false [db] -database_type = "[redis-findex, ...]" +database_type = "Redis" database_url = "[redis urls]" sqlite_path = "[sqlite path]" -redis_master_password = "[redis master password]" -redis_findex_label = "[redis findex label]" clear_database = false [http] @@ -63,10 +55,6 @@ jwt_issuer_uri = ["[jwt issuer uri 1]", "[jwt issuer uri 2]"] jwks_uri = ["[jwks uri 1]", "[jwks uri 2]"] jwt_audience = ["[jwt audience 1]", "[jwt audience 2]"] -[workspace] -root_data_path = "[root data path]" -tmp_path = "[tmp path]" - "#; assert_eq!(toml_string.trim(), toml::to_string(&config).unwrap().trim()); diff --git a/crate/test_server/Cargo.toml b/crate/test_server/Cargo.toml index 582f609..f582565 100644 --- a/crate/test_server/Cargo.toml +++ b/crate/test_server/Cargo.toml @@ -19,7 +19,6 @@ cosmian_findex_server = { path = "../server", features = [ "insecure", ], default-features = false } cosmian_logger = { path = "../logger" } -tempfile = "3.1" tokio = { workspace = true, features = ["rt-multi-thread"] } tracing = { workspace = true } diff --git a/crate/test_server/src/lib.rs b/crate/test_server/src/lib.rs index 00e02db..b51d6a2 100644 --- a/crate/test_server/src/lib.rs +++ b/crate/test_server/src/lib.rs @@ -1,4 +1,4 @@ -pub use cosmian_findex_server::config::{DBConfig, DEFAULT_SQLITE_PATH}; +pub use cosmian_findex_server::config::{DBConfig, DatabaseType, DEFAULT_SQLITE_PATH}; pub use test_server::{ start_default_test_findex_server, start_default_test_findex_server_with_cert_auth, start_test_server_with_options, AuthenticationOptions, TestsContext, diff --git a/crate/test_server/src/test_server.rs b/crate/test_server/src/test_server.rs index 7f24b57..5bbffe0 100644 --- a/crate/test_server/src/test_server.rs +++ b/crate/test_server/src/test_server.rs @@ -11,10 +11,11 @@ use cosmian_findex_client::{ client_bail, client_error, write_json_object_to_file, ClientConf, ClientError, FindexClient, }; use cosmian_findex_server::{ - config::{ClapConfig, DBConfig, HttpConfig, HttpParams, JwtAuthConfig, ServerParams}, + config::{ + ClapConfig, DBConfig, DatabaseType, HttpConfig, HttpParams, JwtAuthConfig, ServerParams, + }, findex_server::start_findex_server, }; -use tempfile::TempDir; use tokio::sync::OnceCell; use tracing::{info, trace}; @@ -28,23 +29,7 @@ use crate::test_jwt::{get_auth0_jwt_config, AUTH0_TOKEN}; pub(crate) static ONCE: OnceCell = OnceCell::const_new(); pub(crate) static ONCE_SERVER_WITH_AUTH: OnceCell = OnceCell::const_new(); -fn sqlite_db_config() -> DBConfig { - trace!("TESTS: using sqlite"); - let tmp_dir = TempDir::new().unwrap(); - let file_path = tmp_dir.path().join("test_sqlite.db"); - // let file_path = PathBuf::from("test_sqlite.db"); - if file_path.exists() { - std::fs::remove_file(&file_path).unwrap(); - } - DBConfig { - database_type: Some("sqlite".to_string()), - clear_database: true, - sqlite_path: file_path, - ..DBConfig::default() - } -} - -fn redis_findex_db_config() -> DBConfig { +fn redis_db_config() -> DBConfig { trace!("TESTS: using redis-findex"); let url = if let Ok(var_env) = env::var("REDIS_HOST") { format!("redis://{var_env}:6379") @@ -52,22 +37,17 @@ fn redis_findex_db_config() -> DBConfig { "redis://localhost:6379".to_owned() }; DBConfig { - database_type: Some("redis-findex".to_string()), + database_type: Some(DatabaseType::Redis), clear_database: true, database_url: Some(url), sqlite_path: Default::default(), - redis_master_password: Some("password".to_string()), - redis_findex_label: Some("label".to_string()), } } fn get_db_config() -> DBConfig { - env::var_os("FINDEX_TEST_DB").map_or_else(sqlite_db_config, |v| { - match v.to_str().unwrap_or("") { - "redis-findex" => redis_findex_db_config(), - "sqlite" => sqlite_db_config(), - _ => sqlite_db_config(), - } + env::var_os("FINDEX_TEST_DB").map_or_else(redis_db_config, |v| match v.to_str().unwrap_or("") { + "redis" => redis_db_config(), + _ => redis_db_config(), }) } @@ -193,8 +173,9 @@ fn start_test_findex_server( /// Wait for the server to start by reading the version async fn wait_for_server_to_start(findex_client: &FindexClient) -> Result<(), ClientError> { - // Depending on the running environment, the server could take a bit of time to start - // We try to query it with a dummy request until be sure it is started. + // Depending on the running environment, the server could take a bit of time to + // start We try to query it with a dummy request until be sure it is + // started. let mut retry = true; let mut timeout = 5; let mut waiting = 1; @@ -320,7 +301,8 @@ fn generate_owner_conf(server_params: &ServerParams) -> Result<(String, ClientCo None }, - // We use the private key since the private key is the public key with additional information. + // We use the private key since the private key is the public key with additional + // information. ..ClientConf::default() }; // write the conf to a file @@ -330,7 +312,8 @@ fn generate_owner_conf(server_params: &ServerParams) -> Result<(String, ClientCo Ok((owner_client_conf_path, owner_client_conf)) } -/// Generate a user configuration for user.client@acme.com and return the file path +/// Generate a user configuration for user.client@acme.com and return the file +/// path fn generate_user_conf(port: u16, owner_client_conf: &ClientConf) -> Result { // This create root dir let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -362,7 +345,7 @@ fn generate_user_conf(port: u16, owner_client_conf: &ClientConf) -> Result Result<(), ClientError> { let context = start_test_server_with_options( - sqlite_db_config(), + redis_db_config(), 6660, AuthenticationOptions { use_jwt_token: false, diff --git a/deny.toml b/deny.toml index 255e30f..23900bb 100644 --- a/deny.toml +++ b/deny.toml @@ -105,7 +105,7 @@ allow = [ "AGPL-3.0", "CC0-1.0", "MPL-2.0", - "OpenSSL" + "OpenSSL", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the @@ -136,8 +136,8 @@ expression = "MIT AND ISC AND OpenSSL" # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration license-files = [ - # Each entry is a crate relative path, and the (opaque) hash of its contents - { path = "LICENSE", hash = 0xbd0eed23 }, + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 }, ] [licenses.private] diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..a872b04 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +stable-2024-10-17