diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45481eb3..f87f442c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: stable + toolchain: 1.89.0 components: rustfmt, clippy - name: Run test script run: | @@ -21,7 +21,7 @@ jobs: uses: Cosmian/reusable_workflows/.github/workflows/cargo-publish.yml@develop if: startsWith(github.ref, 'refs/tags/') with: - toolchain: stable + toolchain: 1.89.0 secrets: inherit cleanup: needs: diff --git a/.gitignore b/.gitignore index a91b5abe..56cedb12 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ *nix* /*.sh /.vscode -Cargo.lock **/.#* **/#*# **/*~ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..f6dc25b8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1055 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cosmian_cover_crypt" +version = "15.0.0" +dependencies = [ + "cosmian_crypto_core", + "cosmian_openssl_provider", + "cosmian_rust_curve25519_provider", + "criterion", + "ml-kem", + "serde", + "serde_json", + "zeroize", +] + +[[package]] +name = "cosmian_crypto_core" +version = "10.4.0" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" +dependencies = [ + "aead", + "aes-gcm", + "curve25519-dalek", + "ed25519-dalek", + "getrandom", + "leb128", + "rand_chacha", + "rand_core", + "sha2", + "signature", + "tiny-keccak", + "zeroize", +] + +[[package]] +name = "cosmian_openssl_provider" +version = "1.0.0" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" +dependencies = [ + "cosmian_crypto_core", + "openssl", + "zeroize", +] + +[[package]] +name = "cosmian_rust_curve25519_provider" +version = "1.0.0" +source = "git+https://github.com/Cosmian/crypto_core.git?branch=tbz%2Ffix-memory-allocation-error-upon-deserialization#24afc12b77b03ce476909a0835216a01bee32c5b" +dependencies = [ + "cosmian_crypto_core", + "curve25519-dalek", + "zeroize", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +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", + "rand_core", + "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 = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hybrid-array" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +dependencies = [ + "typenum", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "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 = "kem" +version = "0.3.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8645470337db67b01a7f966decf7d0bafedbae74147d33e641c67a91df239f" +dependencies = [ + "rand_core", + "zeroize", +] + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "ml-kem" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee19a45f916d98f24a551cc9a2cdae705a040e66f3cbc4f3a282ea6a2e982" +dependencies = [ + "hybrid-array", + "kem", + "rand_core", + "sha3", + "zeroize", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 144bfef2..69c46cbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,27 +41,24 @@ required-features = ["test-utils"] default = ["mlkem-512", "curve25519"] mlkem-512 = [] mlkem-768 = [] -p-256 = ["elliptic-curve", "p256", "subtle"] -curve25519 = ["cosmian_crypto_core/curve25519"] +p-256 = [] +curve25519 = [] test-utils = [] [dependencies] -cosmian_crypto_core = { version = "10.0.1", default-features = false, features = [ - "ser", - "sha3", +cosmian_openssl_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/fix-memory-allocation-error-upon-deserialization" } +cosmian_rust_curve25519_provider = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/fix-memory-allocation-error-upon-deserialization" } +cosmian_crypto_core = { git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/fix-memory-allocation-error-upon-deserialization", default-features = false, features = [ "aes", + "ser", + "sha3" ] } -elliptic-curve = { version = "0.13.8", optional = true } -ml-kem = { version = "0.2.1", features = ["zeroize"] } -p256 = { version = "0.13.2", optional = true } +ml-kem = { version = "0.2", features = ["zeroize"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } -subtle = { version = "2.6.1", optional = true } -tiny-keccak = { version = "2.0.2", features = ["kmac", "sha3"] } -zeroize = "1.6.0" +zeroize = "1.8" [dev-dependencies] -base64 = { version = "0.21.0" } -criterion = { version = "0.5", features = [ +criterion = { version = "0.7", features = [ "html_reports", ], default-features = false } diff --git a/benches/benches.rs b/benches/benches.rs index f0eaf5a7..1b949bb0 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -83,12 +83,12 @@ macro_rules! gen_usk { }}; } -fn bench_classical_encapsulation(c: &mut Criterion) { +fn bench_pre_quantum_encapsulation(c: &mut Criterion) { let cc = Covercrypt::default(); let (_, mpk) = cc_keygen(&cc, true).unwrap(); { - let mut group = c.benchmark_group("Classic encapsulation"); + let mut group = c.benchmark_group("Pre-quantum encapsulation"); for (enc_ap, cnt_enc) in C_ENC_APS { let _ = gen_enc!(cc, mpk, enc_ap, cnt_enc); let eap = AccessPolicy::parse(enc_ap).unwrap(); @@ -99,7 +99,7 @@ fn bench_classical_encapsulation(c: &mut Criterion) { } } -fn bench_classical_decapsulation(c: &mut Criterion) { +fn bench_pre_quantum_decapsulation(c: &mut Criterion) { let cc = Covercrypt::default(); let (mut msk, mpk) = cc_keygen(&cc, true).unwrap(); @@ -188,8 +188,8 @@ criterion_group!( name = benches; config = Criterion::default().sample_size(5000); targets = - bench_classical_encapsulation, - bench_classical_decapsulation, + bench_pre_quantum_encapsulation, + bench_pre_quantum_decapsulation, bench_hybridized_encapsulation, bench_hybridized_decapsulation ); diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..98d44e9b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +# Pin to a stable toolchain (no nightly) because dependencies rely on +# `core::hint::select_unpredictable`, which is available on stable. +channel = "1.89.0" +profile = "minimal" +components = ["rustfmt", "clippy"] diff --git a/src/abe.rs b/src/abe.rs new file mode 100644 index 00000000..4daa330d --- /dev/null +++ b/src/abe.rs @@ -0,0 +1,17 @@ +mod policy; + +pub mod api; +pub mod core; +pub mod traits; + +pub mod encrypted_header; + +pub use api::Covercrypt; +pub use core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}; +pub use policy::{ + AccessPolicy, AccessStructure, Attribute, Dimension, EncryptionHint, EncryptionStatus, + QualifiedAttribute, +}; + +#[cfg(any(test, feature = "test-utils"))] +pub use policy::gen_structure; diff --git a/src/api.rs b/src/abe/api.rs similarity index 81% rename from src/api.rs rename to src/abe/api.rs index f833de15..3ff4732d 100644 --- a/src/api.rs +++ b/src/abe/api.rs @@ -1,36 +1,39 @@ -use std::sync::{Mutex, MutexGuard}; - -use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret, SymmetricKey}; -use zeroize::Zeroizing; - -use super::{ - core::primitives::{prune, update_msk, usk_keygen}, - core::MIN_TRACING_LEVEL, - traits::AE, -}; use crate::{ - core::{ - primitives::{self, full_decaps, refresh, rekey, setup}, - MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, SHARED_SECRET_LENGTH, + abe::{ + core::{ + primitives::{ + self, master_decaps, prune, refresh, rekey, setup, update_msk, usk_keygen, + }, + MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, MIN_TRACING_LEVEL, + SHARED_SECRET_LENGTH, + }, + policy::AccessPolicy, + traits::{KemAc, PkeAc}, }, - traits::{KemAc, PkeAc}, - AccessPolicy, Error, + Error, }; -#[derive(Debug)] +use cosmian_crypto_core::{ + reexport::rand_core::{RngCore, SeedableRng}, + traits::AE, + CsRng, Secret, SymmetricKey, +}; +use std::sync::{Arc, Mutex, MutexGuard}; + +#[derive(Debug, Clone)] pub struct Covercrypt { - rng: Mutex, + rng: Arc>, } impl Default for Covercrypt { fn default() -> Self { Self { - rng: Mutex::new(CsRng::from_entropy()), + rng: Arc::new(Mutex::new(CsRng::from_entropy())), } } } impl Covercrypt { - pub fn rng(&self) -> MutexGuard { + pub fn rng(&self) -> MutexGuard<'_, CsRng> { self.rng.lock().expect("poisoned mutex") } @@ -155,7 +158,7 @@ impl Covercrypt { mpk: &MasterPublicKey, encapsulation: &XEnc, ) -> Result<(Secret<32>, XEnc), Error> { - let (_ss, rights) = full_decaps(msk, encapsulation)?; + let (_ss, rights) = master_decaps(msk, encapsulation, true)?; primitives::encaps( &mut *self.rng.lock().expect("Mutex lock failed!"), mpk, @@ -191,12 +194,18 @@ impl KemAc for Covercrypt { } } -impl> PkeAc - for Covercrypt +impl< + const KEY_LENGTH: usize, + const NONCE_LENGTH: usize, + const TAG_LENGTH: usize, + E: AE, + > PkeAc for Covercrypt +where + Error: From, { type EncryptionKey = MasterPublicKey; type DecryptionKey = UserSecretKey; - type Ciphertext = (XEnc, Vec); + type Ciphertext = (XEnc, E::Ciphertext); type Error = Error; fn encrypt( @@ -209,19 +218,22 @@ impl> PkeAc::derive(&seed, b"Covercrypt AE key")?; + let mut nonce = [0; NONCE_LENGTH]; + rng.fill_bytes(&mut nonce); + let ctx = E::encrypt(&key, ptx, &nonce)?; + Ok((enc, ctx)) } fn decrypt( &self, usk: &Self::DecryptionKey, ctx: &Self::Ciphertext, - ) -> Result>>, Self::Error> { + ) -> Result, Self::Error> { self.decaps(usk, &ctx.0)? .map(|seed| { let key = SymmetricKey::derive(&seed, b"Covercrypt AE key")?; - E::decrypt(&key, &ctx.1) + E::decrypt(&key, ctx.1.as_ref()).map_err(Self::Error::from) }) .transpose() } diff --git a/src/core/mod.rs b/src/abe/core/mod.rs similarity index 56% rename from src/core/mod.rs rename to src/abe/core/mod.rs index bbaf9453..da0482e3 100644 --- a/src/core/mod.rs +++ b/src/abe/core/mod.rs @@ -1,23 +1,18 @@ #![allow(non_snake_case)] -use std::{ - collections::{HashMap, HashSet, LinkedList}, - hash::Hash, -}; - -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, SymmetricKey}; -use kem::MlKem; -use nike::ElGamal; - use crate::{ - abe_policy::{AccessStructure, Right}, + abe::policy::{AccessStructure, EncryptionHint, EncryptionStatus, Right}, data_struct::{RevisionMap, RevisionVec}, - traits::{Kem, Nike, Sampling, Zero}, + providers::{ElGamal, MlKem}, Error, }; +use cosmian_crypto_core::{ + reexport::{rand_core::CryptoRngCore, zeroize::ZeroizeOnDrop}, + traits::{Sampling, Zero, KEM, NIKE}, + SymmetricKey, +}; +use std::collections::{HashMap, HashSet, LinkedList}; -mod kem; -mod nike; mod serialization; #[cfg(test)] @@ -53,86 +48,131 @@ type Tag = [u8; TAG_LENGTH]; pub const MIN_TRACING_LEVEL: usize = 1; /// The Covercrypt subkeys hold the DH secret key associated to a right. -/// Subkeys can be hybridized, in which case they also hold a PQ-KEM secret key. +/// +/// Subkeys can be hybridized in which case they also hold a PQ-KEM secret key, +/// or post-quantum in which case they only hold a PQ-KEM secret key. #[derive(Clone, Debug, PartialEq)] enum RightSecretKey { - Hybridized { - sk: ::SecretKey, - dk: ::DecapsulationKey, + PreQuantum { + sk: ::SecretKey, + }, + PostQuantum { + dk: >::DecapsulationKey, }, - Classic { - sk: ::SecretKey, + Hybridized { + sk: ::SecretKey, + dk: >::DecapsulationKey, }, } impl RightSecretKey { /// Generates a new random right secret key cryptographically bound to the Covercrypt binding /// point `h`. - fn random(rng: &mut impl CryptoRngCore, hybridize: bool) -> Result { - let sk = ::SecretKey::random(rng); - if hybridize { - let (dk, _) = MlKem::keygen(rng)?; - Ok(Self::Hybridized { sk, dk }) - } else { - Ok(Self::Classic { sk }) + fn random(rng: &mut impl CryptoRngCore, security_mode: EncryptionHint) -> Result { + match security_mode { + EncryptionHint::Classic => { + let sk = ::SecretKey::random(rng); + Ok(Self::PreQuantum { sk }) + } + EncryptionHint::PostQuantum => { + let (dk, _) = MlKem::keygen(rng)?; + Ok(Self::PostQuantum { dk }) + } + EncryptionHint::Hybridized => { + let sk = ::SecretKey::random(rng); + let (dk, _) = MlKem::keygen(rng)?; + Ok(Self::Hybridized { sk, dk }) + } } } /// Generates the associated right public key. #[must_use] - fn cpk(&self, h: &::PublicKey) -> RightPublicKey { + fn cpk(&self, h: &::PublicKey) -> RightPublicKey { match self { Self::Hybridized { sk, dk } => RightPublicKey::Hybridized { H: h * sk, ek: dk.ek(), }, - Self::Classic { sk } => RightPublicKey::Classic { H: h * sk }, + Self::PostQuantum { dk } => RightPublicKey::PostQuantum { ek: dk.ek() }, + Self::PreQuantum { sk } => RightPublicKey::PreQuantum { H: h * sk }, } } - /// Returns true if this right secret key is hybridized. - fn is_hybridized(&self) -> bool { + /// Returns the security mode of this right secret key. + fn security_mode(&self) -> EncryptionHint { match self { - Self::Hybridized { .. } => true, - Self::Classic { .. } => false, + Self::Hybridized { .. } => EncryptionHint::Hybridized, + Self::PostQuantum { .. } => EncryptionHint::PostQuantum, + Self::PreQuantum { .. } => EncryptionHint::Classic, } } - fn drop_hybridization(&self) -> Self { - match self { - Self::Hybridized { sk: x_i, .. } => Self::Classic { sk: x_i.clone() }, - Self::Classic { .. } => self.clone(), - } + /// Sets the security mode of this right secret key. + fn set_security_mode( + self, + security_mode: EncryptionHint, + rng: &mut impl CryptoRngCore, + ) -> Result { + Ok(match (self, security_mode) { + (Self::Hybridized { sk, .. }, EncryptionHint::Classic) => Self::PreQuantum { sk }, + (Self::Hybridized { dk, .. }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk }, + (Self::Hybridized { sk, dk }, EncryptionHint::Hybridized) => { + Self::Hybridized { sk, dk } + } + (Self::PostQuantum { .. }, EncryptionHint::Classic) => Self::PostQuantum { + dk: >::keygen(rng)?.0, + }, + (Self::PostQuantum { dk }, EncryptionHint::PostQuantum) => Self::PostQuantum { dk }, + (Self::PostQuantum { dk }, EncryptionHint::Hybridized) => Self::Hybridized { + sk: ::keygen(rng)?.0, + dk, + }, + (Self::PreQuantum { sk }, EncryptionHint::Classic) => Self::PreQuantum { sk }, + (Self::PreQuantum { .. }, EncryptionHint::PostQuantum) => Self::PostQuantum { + dk: >::keygen(rng)?.0, + }, + (Self::PreQuantum { sk }, EncryptionHint::Hybridized) => Self::Hybridized { + sk, + dk: >::keygen(rng)?.0, + }, + }) } } -/// The Covercrypt public keys hold the DH secret public key associated to a right. -/// Subkeys can be hybridized, in which case they also hold a PQ-KEM public key. +/// The Covercrypt public keys hold the DH secret public key associated to a +/// right. +/// +/// Subkeys can be hybridized in which case they also hold a PQ-KEM public key, +/// or post-quantum, in which case they only hold a PQ-KEM public key. #[derive(Clone, Debug, PartialEq)] enum RightPublicKey { - Hybridized { - H: ::PublicKey, - ek: ::EncapsulationKey, + PreQuantum { + H: ::PublicKey, }, - Classic { - H: ::PublicKey, + PostQuantum { + ek: >::EncapsulationKey, + }, + Hybridized { + H: ::PublicKey, + ek: >::EncapsulationKey, }, } impl RightPublicKey { - pub fn is_hybridized(&self) -> bool { + /// Returns the security mode of this right public key. + pub fn security_mode(&self) -> EncryptionHint { match self { - Self::Hybridized { .. } => true, - Self::Classic { .. } => false, + Self::Hybridized { .. } => EncryptionHint::Hybridized, + Self::PostQuantum { .. } => EncryptionHint::PostQuantum, + Self::PreQuantum { .. } => EncryptionHint::Classic, } } } /// Covercrypt user IDs are used to make user keys unique and traceable. -/// -/// They are composed of a sequence of `LENGTH` scalars. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] -struct UserId(LinkedList<::SecretKey>); +#[derive(Clone, Debug, PartialEq, Eq, Default)] +struct UserId(LinkedList<::SecretKey>); impl UserId { /// Returns the tracing level of the USK. @@ -140,7 +180,7 @@ impl UserId { self.0.len() - 1 } - fn iter(&self) -> impl Iterator::SecretKey> { + fn iter(&self) -> impl Iterator::SecretKey> { self.0.iter() } } @@ -161,18 +201,29 @@ impl UserId { /// - the set of known user IDs. #[derive(Debug, PartialEq, Eq)] struct TracingSecretKey { - s: ::SecretKey, - tracers: LinkedList<(::SecretKey, ::PublicKey)>, - users: HashSet, + s: ::SecretKey, + tracers: LinkedList<(::SecretKey, ::PublicKey)>, + // Since `Hash` is not a fallible operation, it cannot be implemented on FFI + // providers like OpenSSL. And since `Zeroizing>` does not implement + // `Hash` either, a `HashSet` cannot be using without extracting the raw + // bytes which then requires manually zeroizing them everywhere they may be + // leaking. Using a linked list implies a linear complexity in the number of + // comparisons, which themselves have a linear complexity in the tracing + // dimension. This is not ideal, but it is safe. + // + // Since this is an internal implementation detail, the container used may + // be change later without breaking change as long as it serializes to the + // same bytes. + users: LinkedList, } impl TracingSecretKey { fn new_with_level(level: usize, rng: &mut impl CryptoRngCore) -> Result { - let s = ::SecretKey::random(rng); + let s = ::SecretKey::random(rng); let tracers = (0..=level) - .map(|_| ElGamal::keygen(rng)) + .map(|_| ::keygen(rng)) .collect::>()?; - let users = HashSet::new(); + let users = LinkedList::new(); Ok(Self { s, tracers, users }) } @@ -182,20 +233,16 @@ impl TracingSecretKey { self.tracers.len() - 1 } - fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { - self.tracers.iter().map(|(_, Pi)| Pi * r).collect() - } - /// Generates a new tracer. Returns the associated trap. fn _increase_tracing(&mut self, rng: &mut impl CryptoRngCore) -> Result<(), Error> { - self.tracers.push_back(ElGamal::keygen(rng)?); + self.tracers.push_back(::keygen(rng)?); Ok(()) } /// Drops the oldest tracer and returns it. fn _decrease_tracing( &mut self, - ) -> Result<(::SecretKey, ::PublicKey), Error> { + ) -> Result<(::SecretKey, ::PublicKey), Error> { if self.tracing_level() == MIN_TRACING_LEVEL { Err(Error::OperationNotPermitted(format!( "tracing level cannot be lower than {MIN_TRACING_LEVEL}" @@ -233,14 +280,17 @@ impl TracingSecretKey { /// Adds the given user ID to the list of known users. fn add_user(&mut self, id: UserId) { - self.users.insert(id); + self.users.push_front(id); } /// Removes the given user ID from the list of known users. /// /// Returns true if the user was in the list. fn del_user(&mut self, id: &UserId) -> bool { - self.users.remove(id) + self.users + .extract_if(|id_| id == id_) + .collect::>() + .is_empty() } /// Generates the associated tracing public key. @@ -250,7 +300,7 @@ impl TracingSecretKey { } /// Returns the binding points. - fn binding_point(&self) -> ::PublicKey { + fn binding_point(&self) -> ::PublicKey { (&self.s).into() } @@ -262,7 +312,7 @@ impl TracingSecretKey { .tracers .iter() .take(self.tracers.len() - 1) - .map(|_| ::SecretKey::random(rng)) + .map(|_| ::SecretKey::random(rng)) .collect::>(); let last_marker = ((&self.s @@ -271,7 +321,7 @@ impl TracingSecretKey { .iter() .zip(markers.iter()) .map(|((sk_i, _), a_i)| sk_i * a_i) - .fold(::SecretKey::zero(), |acc, x_i| acc + x_i)) + .fold(::SecretKey::zero(), |acc, x_i| acc + x_i)) / last_tracer)?; markers.push_back(last_marker); @@ -319,8 +369,8 @@ impl TracingSecretKey { } /// Covercrypt tracing public key. -#[derive(Debug, PartialEq, Eq, Default)] -struct TracingPublicKey(LinkedList<::PublicKey>); +#[derive(Debug, Clone, PartialEq, Eq, Default)] +struct TracingPublicKey(LinkedList<::PublicKey>); impl TracingPublicKey { /// Returns the tracing level tracing of this key. @@ -340,11 +390,14 @@ impl TracingPublicKey { #[derive(Debug, PartialEq)] pub struct MasterSecretKey { tsk: TracingSecretKey, - secrets: RevisionMap, + secrets: RevisionMap, signing_key: Option>, pub access_structure: AccessStructure, } +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for MasterSecretKey {} + impl MasterSecretKey { /// Returns the most recent secret key associated to each given right. /// @@ -364,6 +417,10 @@ impl MasterSecretKey { }) } + fn tracing_points(&self) -> impl IntoIterator::PublicKey> { + self.tsk.tracers.iter().map(|(_, P)| P) + } + /// Generates a new MPK holding the latest public information of each right in Omega. pub fn mpk(&self) -> Result { let h = self.tsk.binding_point(); @@ -373,8 +430,8 @@ impl MasterSecretKey { .secrets .iter() .filter_map(|(r, secrets)| { - secrets.front().and_then(|(is_activated, csk)| { - if *is_activated { + secrets.front().and_then(|(status, csk)| { + if &EncryptionStatus::EncryptDecrypt == status { Some((r.clone(), csk.cpk(&h))) } else { None @@ -393,7 +450,7 @@ impl MasterSecretKey { /// - the tracing public key; /// - the public keys for each right in Omega; /// - the access structure. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct MasterPublicKey { tpk: TracingPublicKey, encryption_keys: HashMap, @@ -402,42 +459,57 @@ pub struct MasterPublicKey { impl MasterPublicKey { /// Returns the tracing level of this MPK. - #[inline(always)] pub fn tracing_level(&self) -> usize { self.tpk.tracing_level() } /// Generates traps for the given scalar. // TODO: find a better concept. - fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { + fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { self.tpk.0.iter().map(|Pi| Pi * r).collect() } /// Returns the subkeys associated with the given rights in this public key, /// alongside a boolean value that is true if all of them are hybridized. + /// + /// # Error + /// + /// Returns an error in case a key is missing for one of the target rights + /// or these rights do not define an homogeneous set of keys. fn select_subkeys( &self, targets: &HashSet, - ) -> Result<(bool, Vec<&RightPublicKey>), Error> { - // This mutable variable is set to false if at least one sub-key is not - // hybridized. - let mut is_hybridized = true; - + ) -> Result<(EncryptionHint, Vec<&RightPublicKey>), Error> { let subkeys = targets .iter() .map(|r| { - let subkey = self - .encryption_keys + self.encryption_keys .get(r) - .ok_or_else(|| Error::KeyError(format!("no public key for right '{r:#?}'")))?; - if !subkey.is_hybridized() { - is_hybridized = false; + .ok_or_else(|| Error::KeyError(format!("no public key for right '{r:#?}'"))) + }) + .collect::, Error>>()?; + + let (security_mode, is_homogeneous) = subkeys + .iter() + .map(|k| (k.security_mode(), true)) + .reduce(|(lhs_mode, lhs_bool), (rhs_mode, rhs_bool)| { + if lhs_mode == rhs_mode { + (lhs_mode, lhs_bool && rhs_bool) + } else { + (lhs_mode, false) } - Ok(subkey) }) - .collect::>()?; + .ok_or_else(|| { + Error::OperationNotPermitted("target set cannot be empty".to_string()) + })?; - Ok((is_hybridized, subkeys)) + if is_homogeneous { + Ok((security_mode, subkeys)) + } else { + Err(Error::OperationNotPermitted( + "cannot select subkeys with different security modes".to_string(), + )) + } } } @@ -450,11 +522,14 @@ impl MasterPublicKey { #[derive(Clone, Debug, PartialEq)] pub struct UserSecretKey { id: UserId, - ps: Vec<::PublicKey>, + ps: Vec<::PublicKey>, secrets: RevisionVec, signature: Option, } +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for UserSecretKey {} + impl UserSecretKey { /// Returns the tracing level of this user secret key. pub fn tracing_level(&self) -> usize { @@ -466,17 +541,11 @@ impl UserSecretKey { self.secrets.len() } - fn set_traps(&self, r: &::SecretKey) -> Vec<::PublicKey> { - self.ps.iter().map(|Pi| Pi * r).collect() + fn tracing_points(&self) -> &[::PublicKey] { + &self.ps } } -#[derive(Debug, Clone, PartialEq)] -enum Encapsulations { - HEncs(Vec<(::Encapsulation, [u8; SHARED_SECRET_LENGTH])>), - CEncs(Vec<[u8; SHARED_SECRET_LENGTH]>), -} - /// Covercrypt encapsulation. /// /// It is created for a subset of rights from Omega. @@ -486,22 +555,52 @@ enum Encapsulations { /// - the traps used to select users that can open this encapsulation; /// - the right encapsulations. #[derive(Debug, Clone, PartialEq)] -pub struct XEnc { - tag: Tag, - c: Vec<::PublicKey>, - encapsulations: Encapsulations, +pub enum XEnc { + PreQuantum { + tag: Tag, + c: Vec<::PublicKey>, + encapsulations: Vec<[u8; SHARED_SECRET_LENGTH]>, + }, + PostQuantum { + tag: Tag, + encapsulations: Vec<( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )>, + }, + Hybridized { + tag: Tag, + c: Vec<::PublicKey>, + encapsulations: Vec<( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )>, + }, } impl XEnc { /// Returns the tracing level of this encapsulation. pub fn tracing_level(&self) -> usize { - self.c.len() - 1 + match self { + Self::PreQuantum { c, .. } => c.len() - 1, + Self::PostQuantum { .. } => 0, + Self::Hybridized { c, .. } => c.len() - 1, + } } pub fn count(&self) -> usize { - match &self.encapsulations { - Encapsulations::HEncs(vec) => vec.len(), - Encapsulations::CEncs(vec) => vec.len(), + match self { + Self::Hybridized { encapsulations, .. } => encapsulations.len(), + Self::PostQuantum { encapsulations, .. } => encapsulations.len(), + Self::PreQuantum { encapsulations, .. } => encapsulations.len(), + } + } + + pub fn security_mode(&self) -> EncryptionHint { + match self { + Self::Hybridized { .. } => EncryptionHint::Hybridized, + Self::PostQuantum { .. } => EncryptionHint::PostQuantum, + Self::PreQuantum { .. } => EncryptionHint::Classic, } } } diff --git a/src/abe/core/primitives.rs b/src/abe/core/primitives.rs new file mode 100644 index 00000000..f8dde602 --- /dev/null +++ b/src/abe/core/primitives.rs @@ -0,0 +1,997 @@ +use crate::{ + abe::{ + core::{ + KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, + TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, SHARED_SECRET_LENGTH, + SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, + }, + policy::{AccessStructure, EncryptionHint, EncryptionStatus, Right}, + }, + data_struct::{RevisionMap, RevisionVec}, + providers::{ElGamal, MlKem}, + Error, +}; +use cosmian_crypto_core::{ + bytes_ser_de::Serializable, + reexport::{ + rand_core::{CryptoRngCore, RngCore}, + tiny_keccak::{Hasher, Kmac, Sha3}, + }, + traits::{Seedable, KEM, NIKE}, + RandomFixedSizeCBytes, Secret, SymmetricKey, +}; +use std::{ + collections::{HashMap, HashSet, LinkedList}, + mem::take, +}; + +fn xor_2(lhs: &[u8; LENGTH], rhs: &[u8; LENGTH]) -> [u8; LENGTH] { + let mut out = [0; LENGTH]; + for pos in 0..LENGTH { + out[pos] = lhs[pos] ^ rhs[pos]; + } + out +} + +fn xor_in_place( + mut lhs: Secret, + rhs: &[u8; LENGTH], +) -> Secret { + for pos in 0..LENGTH { + lhs[pos] ^= rhs[pos]; + } + lhs +} + +fn shuffle(xs: &mut [T], rng: &mut impl RngCore) { + for i in 0..xs.len() { + let j = rng.next_u32() as usize % xs.len(); + xs.swap(i, j); + } +} + +/// Computes the signature of the given USK using the MSK. +fn sign( + msk: &MasterSecretKey, + id: &UserId, + keys: &RevisionVec, +) -> Result, Error> { + if let Some(kmac_key) = &msk.signing_key { + // Subkeys ordering needs to be deterministic to allow deterministic + // signatures. This explains why a hash-map is not used in USK. + let mut res = [0; SIGNATURE_LENGTH]; + let mut kmac = Kmac::v256(&**kmac_key, b"USK signature"); + kmac.update(&id.serialize()?); + kmac.update(&keys.serialize()?); + kmac.finalize(&mut res); + Ok(Some(res)) + } else { + Ok(None) + } +} + +/// Verifies the integrity of the given USK using the MSK. +fn verify(msk: &MasterSecretKey, usk: &UserSecretKey) -> Result<(), Error> { + let fresh_signature = sign(msk, &usk.id, &usk.secrets)?; + if fresh_signature != usk.signature { + Err(Error::KeyError( + "USK failed the integrity check".to_string(), + )) + } else { + Ok(()) + } +} + +fn G_hash(seed: &Secret) -> Result<::SecretKey, Error> { + Ok(<::SecretKey as Seedable< + SHARED_SECRET_LENGTH, + >>::from_seed(seed)) +} + +fn H_hash( + K1: Option<&::PublicKey>, + K2: Option<&SymmetricKey>, + T: &Secret, +) -> Result, Error> { + // Additional check to enforce the constraint on the SHARED_SECRET_LENGTH + // constant that is defined in another file. + // + // NOTE: it would be nice to perform this check at compile-time instead. + assert_eq!(SHARED_SECRET_LENGTH, 32); + + let mut hasher = Sha3::v256(); + let mut H = Secret::::new(); + if let Some(K1) = K1 { + hasher.update(&K1.serialize()?); + } + if let Some(K2) = K2 { + hasher.update(&**K2); + } + hasher.update(&**T); + hasher.finalize(&mut *H); + Ok(H) +} + +fn J_hash( + S: &Secret, + U: &Secret, +) -> ([u8; TAG_LENGTH], Secret) { + let mut hasher = Sha3::v384(); + let mut bytes = [0; 384 / 8]; + hasher.update(&**S); + hasher.update(&**U); + hasher.finalize(&mut bytes); + + let mut tag = [0; TAG_LENGTH]; + let mut seed = Secret::::default(); + tag.copy_from_slice(&bytes[..TAG_LENGTH]); + seed.copy_from_slice(&bytes[TAG_LENGTH..]); + (tag, seed) +} + +fn generate_T<'a>( + c: Option<&[::PublicKey]>, + encapsulations: Option< + impl IntoIterator>::Encapsulation>, + >, +) -> Result, Error> { + let mut hasher = Sha3::v256(); + let mut T = Secret::new(); + if let Some(c) = c { + c.iter().try_for_each(|ck| { + hasher.update(&ck.serialize()?); + Ok::<_, Error>(()) + })?; + } + if let Some(encapsulations) = encapsulations { + encapsulations.into_iter().try_for_each(|E| { + hasher.update(&E.serialize()?); + Ok::<_, Error>(()) + })?; + } + hasher.finalize(&mut *T); + Ok(T) +} + +fn generate_U<'a>( + T: &Secret, + encapsulations: impl IntoIterator, +) -> Secret { + let mut U = Secret::::new(); + let mut hasher = Sha3::v256(); + hasher.update(&**T); + encapsulations.into_iter().for_each(|F| hasher.update(F)); + hasher.finalize(&mut *U); + U +} + +/// Generates new MSK with the given tracing level. +pub fn setup(tracing_level: usize, rng: &mut impl CryptoRngCore) -> Result { + if tracing_level < MIN_TRACING_LEVEL { + return Err(Error::OperationNotPermitted(format!( + "tracing level cannot be lower than {MIN_TRACING_LEVEL}" + ))); + } + + let tsk = TracingSecretKey::new_with_level(tracing_level, rng)?; + let policy = AccessStructure::default(); + + Ok(MasterSecretKey { + tsk, + secrets: RevisionMap::new(), + signing_key: Some(SymmetricKey::::new(rng)), + access_structure: policy, + }) +} + +/// Generates a USK for the given set of coordinates. +/// +/// The generated key is provided with the last version of the key for each +/// coordinate in the given set. The USK can then open any up-to-date key +/// encapsulation for any such coordinate (provided the coordinate was not +/// re-keyed in-between). +/// +/// If the MSK has a signing key, signs the USK. +pub fn usk_keygen( + rng: &mut impl CryptoRngCore, + msk: &mut MasterSecretKey, + coordinates: HashSet, +) -> Result { + // Extract keys first to avoid unnecessary computation in case those cannot be found. + let coordinate_keys = msk + .get_latest_right_sk(coordinates.into_iter()) + .collect::, Error>>()?; + let id = msk.tsk.generate_user_id(rng)?; + let signature = sign(msk, &id, &coordinate_keys)?; + + Ok(UserSecretKey { + id, + ps: msk.tsk.tracers.iter().map(|(_, Pi)| Pi).cloned().collect(), + secrets: coordinate_keys, + signature, + }) +} + +/// Generates a hybridized encapsulation of the given secret S with the given +/// marker c, ElGamal random r and subkeys. +fn h_encaps<'a>( + S: Secret, + c: Vec<::PublicKey>, + r: ::SecretKey, + subkeys: impl IntoIterator< + Item = ( + &'a ::PublicKey, + &'a >::EncapsulationKey, + ), + >, + rng: &mut impl CryptoRngCore, +) -> Result<(Secret, XEnc), Error> { + let encs = subkeys + .into_iter() + .map(|(H, ek)| { + let K1 = ElGamal::shared_secret(&r, H)?; + let (K2, E) = MlKem::enc(ek, rng)?; + Ok((K1, K2, E)) + }) + .collect::, Error>>()?; + + let T = generate_T(Some(&c), Some(encs.iter().map(|(_, _, E)| E)))?; + + let encapsulations = encs + .into_iter() + .map(|(K1, K2, E)| -> Result<_, _> { + let F = xor_2(&S, &*H_hash(Some(&K1), Some(&K2), &T)?); + Ok((E, F)) + }) + .collect::, Error>>()?; + + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); + + let (tag, ss) = J_hash(&S, &U); + + Ok(( + ss, + XEnc::Hybridized { + tag, + c, + encapsulations, + }, + )) +} + +/// Generates post-quantum encapsulation of the given secret S with the given +/// subkeys. +fn post_quantum_encaps<'a>( + S: Secret, + subkeys: impl IntoIterator>::EncapsulationKey>, + rng: &mut impl CryptoRngCore, +) -> Result<(Secret, XEnc), Error> { + let encs = subkeys + .into_iter() + .map(|ek| MlKem::enc(ek, rng)) + .collect::, Error>>()?; + + let T = generate_T(None, Some(encs.iter().map(|(_, E)| E)))?; + + let encapsulations = encs + .into_iter() + .map(|(K2, E)| -> Result<_, _> { + let F = xor_2(&S, &*H_hash(None, Some(&K2), &T)?); + Ok((E, F)) + }) + .collect::, Error>>()?; + + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); + + let (tag, ss) = J_hash(&S, &U); + + Ok(( + ss, + XEnc::PostQuantum { + tag, + encapsulations, + }, + )) +} + +/// Generates a pre-quantum encapsulation of the given secret S with the given +/// marker c, ElGamal random r and subkeys. +fn pre_quantum_encaps<'a>( + S: Secret, + c: Vec<::PublicKey>, + r: ::SecretKey, + subkeys: impl IntoIterator::PublicKey>, +) -> Result<(Secret, XEnc), Error> { + let T = generate_T(Some(&c), None::>)?; + + let encapsulations = subkeys + .into_iter() + .map(|H| -> Result<_, _> { + let K1 = ElGamal::shared_secret(&r, H)?; + let F = xor_2(&S, &*H_hash(Some(&K1), None, &T)?); + Ok(F) + }) + .collect::, Error>>()?; + + let U = generate_U(&T, &encapsulations); + + let (tag, ss) = J_hash(&S, &U); + + Ok(( + ss, + XEnc::PreQuantum { + tag, + c, + encapsulations, + }, + )) +} + +/// Generates a Covercrypt encapsulation of a random `SHARED_SECRET_LENGTH`-byte +/// session key for the coordinates in the encryption set. +/// +/// Returns both the key and its encapsulation. +/// +/// # Error +/// +/// Returns an error in case the public key is missing for some coordinate. +pub fn encaps( + rng: &mut impl CryptoRngCore, + mpk: &MasterPublicKey, + encryption_set: &HashSet, +) -> Result<(Secret, XEnc), Error> { + // A typed key container would avoid the need for casting in the match arms + // but would also involve additional overhead. + let (is_hybridized, mut coordinate_keys) = mpk.select_subkeys(encryption_set)?; + + // Shuffling must be performed *before* generating the final encapsulations + // in order to have a deterministic digest. + shuffle(&mut coordinate_keys, rng); + + let S = Secret::random(rng); + + match is_hybridized { + EncryptionHint::Classic => { + let r = G_hash(&S)?; + let c = mpk.set_traps(&r); + + let subkeys = coordinate_keys.into_iter().map(|subkey| { + if let RightPublicKey::PreQuantum { H } = subkey { + H + } else { + panic!("select_subkeys already ensures homogeneity") + } + }); + pre_quantum_encaps(S, c, r, subkeys) + } + EncryptionHint::PostQuantum => { + let subkeys = coordinate_keys.into_iter().map(|subkey| { + if let RightPublicKey::PostQuantum { ek } = subkey { + ek + } else { + panic!("select_subkeys already ensures homogeneity") + } + }); + post_quantum_encaps(S, subkeys, rng) + } + EncryptionHint::Hybridized => { + let r = G_hash(&S)?; + let c = mpk.set_traps(&r); + + let subkeys = coordinate_keys.into_iter().map(|subkey| { + if let RightPublicKey::Hybridized { H, ek } = subkey { + (H, ek) + } else { + panic!("select_subkeys already ensures homogeneity") + } + }); + h_encaps(S, c, r, subkeys, rng) + } + } +} + +#[allow(clippy::too_many_arguments)] +fn attempt_pre_quantum_decaps<'a>( + secret: &RightSecretKey, + A: &::PublicKey, + U: &Secret, + T: &Secret, + F: &[u8; 32], + c: &[::PublicKey], + tag: &[u8; TAG_LENGTH], + tracing_points: impl IntoIterator::PublicKey>, +) -> Result>, Error> { + if let RightSecretKey::PreQuantum { sk } = secret { + let K1 = ElGamal::shared_secret(sk, A)?; + let S = xor_in_place(H_hash(Some(&K1), None, T)?, F); + let (tag_ij, ss) = J_hash(&S, U); + if tag == &tag_ij { + // Fujisaki-Okamoto + let r = G_hash(&S)?; + let c_ij = tracing_points + .into_iter() + .map(|P| P * &r) + .collect::>(); + if c == c_ij { + return Ok(Some(ss)); + } + } + } + Ok(None) +} + +#[allow(clippy::too_many_arguments)] +fn attempt_hybridized_decaps<'a>( + secret: &RightSecretKey, + A: &::PublicKey, + U: &Secret, + T: &Secret, + E: &>::Encapsulation, + F: &[u8; 32], + c: &[::PublicKey], + tag: &[u8; TAG_LENGTH], + tracing_points: impl IntoIterator::PublicKey>, +) -> Result>, Error> { + if let RightSecretKey::Hybridized { sk, dk } = secret { + let K1 = ElGamal::shared_secret(sk, A)?; + let K2 = MlKem::dec(dk, E)?; + let S_ij = xor_in_place(H_hash(Some(&K1), Some(&K2), T)?, F); + let (tag_ij, ss) = J_hash(&S_ij, U); + if tag == &tag_ij { + // Fujisaki-Okamoto + let r = G_hash(&S_ij)?; + let c_ij = tracing_points + .into_iter() + .map(|P| P * &r) + .collect::>(); + if c == c_ij { + return Ok(Some(ss)); + } + } + } + Ok(None) +} + +fn attempt_post_quantum_decaps( + secret: &RightSecretKey, + U: &Secret, + T: &Secret, + E: &>::Encapsulation, + F: &[u8; 32], + tag: &[u8; TAG_LENGTH], +) -> Result>, Error> { + if let RightSecretKey::PostQuantum { dk } = secret { + let K2 = MlKem::dec(dk, E)?; + let S_ij = xor_in_place(H_hash(None, Some(&K2), T)?, F); + let (tag_ij, ss) = J_hash(&S_ij, U); + if tag == &tag_ij { + return Ok(Some(ss)); + } + } + Ok(None) +} + +/// Attempts opening the Covercrypt encapsulation using the given USK. Returns +/// the encapsulated key upon success, otherwise returns `None`. +pub fn decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + encapsulation: &XEnc, +) -> Result>, Error> { + fn generate_tracing_closure( + usk: &UserSecretKey, + c: &[::PublicKey], + ) -> ::PublicKey { + usk.id + .iter() + .zip(c.iter()) + .map(|(marker, trap)| trap * marker) + .sum::<::PublicKey>() + } + + fn partial_post_quantum_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + tag: &[u8; TAG_LENGTH], + encs: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], + ) -> Result>, Error> { + let T = generate_T(None, Some(encs.iter().map(|(E, _)| E)))?; + let U = generate_U(&T, encs.iter().map(|(_, F)| F)); + + // Shuffle encapsulation to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for (E, F) in &encs { + for (_, secret) in &revision { + if let Some(ss) = attempt_post_quantum_decaps(secret, &U, &T, E, F, tag)? { + return Ok(Some(ss)); + } + } + } + } + + Ok(None) + } + + fn partial_hybridized_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + c: &[::PublicKey], + tag: &[u8; TAG_LENGTH], + encs: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], + ) -> Result>, Error> { + let A = generate_tracing_closure(usk, c); + let T = generate_T(Some(c), Some(encs.iter().map(|(E, _)| E)))?; + let U = generate_U(&T, encs.iter().map(|(_, F)| F)); + + // Shuffle encapsulation to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for (E, F) in &encs { + for (_, secret) in &revision { + if let Some(ss) = attempt_hybridized_decaps( + secret, + &A, + &U, + &T, + E, + F, + c, + tag, + usk.tracing_points(), + )? { + return Ok(Some(ss)); + } + } + } + } + + Ok(None) + } + + fn partial_pre_quantum_decaps( + rng: &mut impl CryptoRngCore, + usk: &UserSecretKey, + c: &[::PublicKey], + tag: &[u8; TAG_LENGTH], + encs: &Vec<[u8; SHARED_SECRET_LENGTH]>, + ) -> Result>, Error> { + let A = generate_tracing_closure(usk, c); + let T = generate_T(Some(c), None::>)?; + let U = generate_U(&T, encs); + + // Shuffle encapsulations to counter timing attacks attempting to determine + // which right was used to open an encapsulation. + let mut encs = encs.iter().collect::>(); + shuffle(&mut encs, rng); + + // Loop order matters: this ordering is faster. + for mut revision in usk.secrets.revisions() { + // Shuffle secrets to counter timing attacks attempting to determine + // whether successive encapsulations target the same user right. + shuffle(&mut revision, rng); + for F in &encs { + for (_, secret) in &revision { + if let Some(ss) = attempt_pre_quantum_decaps( + secret, + &A, + &U, + &T, + F, + c, + tag, + usk.tracing_points(), + )? { + return Ok(Some(ss)); + } + } + } + } + + Ok(None) + } + + match encapsulation { + XEnc::Hybridized { + tag, + c, + encapsulations, + } => partial_hybridized_decaps(rng, usk, c, tag, encapsulations), + XEnc::PostQuantum { + tag, + encapsulations, + } => partial_post_quantum_decaps(rng, usk, tag, encapsulations), + XEnc::PreQuantum { + tag, + c, + encapsulations, + } => partial_pre_quantum_decaps(rng, usk, c, tag, encapsulations), + } +} + +/// Recover the encapsulated shared secret and set of rights used in the +/// encapsulation. +pub fn master_decaps( + msk: &MasterSecretKey, + encapsulation: &XEnc, + full: bool, +) -> Result<(Secret, HashSet), Error> { + /// Opens the given encapsulation with the provided secrets. Returns both + /// the encapsulated secret and the right associated to the first secret + /// allowing opening this encapsulation, or returns an error if no secret + /// allow opening this encapsulation. + fn open( + secrets: &RevisionMap, + attempt_opening: impl Fn(&RightSecretKey) -> Result>, Error>, + ) -> Result<(Secret, Right), Error> { + for (right, secret_set) in secrets.iter() { + for (_, secret) in secret_set { + if let Some(ss) = attempt_opening(secret)? { + return Ok::<_, Error>((ss, right.clone())); + } + } + } + Err(Error::Kem("could not open the encapsulation".to_string())) + } + + fn generate_tracing_closure( + msk: &MasterSecretKey, + c: &[::PublicKey], + ) -> Result<::PublicKey, Error> { + let c_0 = c + .first() + .ok_or_else(|| Error::Kem("invalid encapsulation: C is empty".to_string()))?; + let t_0 = msk + .tsk + .tracers + .front() + .map(|(si, _)| si) + .ok_or_else(|| Error::KeyError("MSK has no tracer".to_string()))?; + + Ok(c_0 * &(&msk.tsk.s / t_0)?) + } + + fn pre_quantum_decapsulation( + msk: &MasterSecretKey, + tag: &[u8; TAG_LENGTH], + c: &[::PublicKey], + encapsulations: &[[u8; 32]], + full: bool, + ) -> Result<(Secret, HashSet), Error> { + let A = generate_tracing_closure(msk, c)?; + let T = generate_T(Some(c), None::>)?; + let U = generate_U(&T, encapsulations); + + // Attempts opening the encapsulation F with this right secret key. + let attempt_opening = |F, secret: &RightSecretKey| { + attempt_pre_quantum_decaps(secret, &A, &U, &T, F, c, tag, msk.tracing_points()) + }; + + let mut enc_ss = None; + let mut rights = HashSet::with_capacity(encapsulations.len()); + let mut secrets = msk.secrets.clone(); + + for F in encapsulations { + let (ss, right) = open(&secrets, |secret| attempt_opening(F, secret))?; + if let Some(enc_ss) = &enc_ss { + if &ss != enc_ss { + return Err(Error::Kem( + "malformed encapsulation: different encapsulated secrets found".to_string(), + )); + } + } + // Removes this right since well-formed encapsulations use rights + // only once. This should allow a ~2x speed-up. + secrets.remove(&right); + enc_ss = Some(ss); + rights.insert(right); + + if !full { + break; + } + } + + enc_ss + .map(|ss| (ss, rights)) + // An empty encapsulation should be the only way to raise this error + // since the function `open` either errors upon failure to open. + .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) + } + + fn post_quantum_decapsulation( + msk: &MasterSecretKey, + tag: &[u8; TAG_LENGTH], + encapsulations: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], + full: bool, + ) -> Result<(Secret, HashSet), Error> { + let T = generate_T(None, Some(encapsulations.iter().map(|(E, _)| E)))?; + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); + + let attempt_opening = + |E, F, secret: &RightSecretKey| attempt_post_quantum_decaps(secret, &U, &T, E, F, tag); + + let mut enc_ss = None; + let mut rights = HashSet::with_capacity(encapsulations.len()); + let mut secrets = msk.secrets.clone(); + + for (E, F) in encapsulations { + let (ss, right) = open(&secrets, |secret| attempt_opening(E, F, secret))?; + if let Some(enc_ss) = &enc_ss { + if &ss != enc_ss { + return Err(Error::Kem( + "malformed encapsulation: different encapsulated secrets found".to_string(), + )); + } + } + // Removes this right since well-formed encapsulations use rights + // only once. This should allow a ~2x speed-up. + secrets.remove(&right); + enc_ss = Some(ss); + rights.insert(right); + + if !full { + break; + } + } + + enc_ss + .map(|ss| (ss, rights)) + // An empty encapsulation should be the only way to raise this error + // since the function `open` either errors upon failure to open. + .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) + } + + fn hybrid_decapsulation( + msk: &MasterSecretKey, + tag: &[u8; TAG_LENGTH], + c: &[::PublicKey], + encapsulations: &[( + >::Encapsulation, + [u8; SHARED_SECRET_LENGTH], + )], + full: bool, + ) -> Result<(Secret, HashSet), Error> { + let A = generate_tracing_closure(msk, c)?; + let T = generate_T(Some(c), Some(encapsulations.iter().map(|(E, _)| E)))?; + let U = generate_U(&T, encapsulations.iter().map(|(_, F)| F)); + + let attempt_opening = |E, F, secret: &RightSecretKey| { + attempt_hybridized_decaps(secret, &A, &U, &T, E, F, c, tag, msk.tracing_points()) + }; + + let mut enc_ss = None; + let mut rights = HashSet::with_capacity(encapsulations.len()); + let mut secrets = msk.secrets.clone(); + + for (E, F) in encapsulations { + let (ss, right) = open(&secrets, |secret| attempt_opening(E, F, secret))?; + if let Some(enc_ss) = &enc_ss { + if &ss != enc_ss { + return Err(Error::Kem( + "malformed encapsulation: different encapsulated secrets found".to_string(), + )); + } + } + // Removes this right since well-formed encapsulations use rights + // only once. This should allow a ~2x speed-up. + secrets.remove(&right); + enc_ss = Some(ss); + rights.insert(right); + + if !full { + break; + } + } + + enc_ss + .map(|ss| (ss, rights)) + // An empty encapsulation should be the only way to raise this error + // since the function `open` either errors upon failure to open. + .ok_or_else(|| Error::Kem("empty encapsulation".to_string())) + } + + match encapsulation { + XEnc::PreQuantum { + tag, + c, + encapsulations, + } => pre_quantum_decapsulation(msk, tag, c, encapsulations, full), + XEnc::PostQuantum { + tag, + encapsulations, + } => post_quantum_decapsulation(msk, tag, encapsulations, full), + XEnc::Hybridized { + tag, + c, + encapsulations, + } => hybrid_decapsulation(msk, tag, c, encapsulations, full), + } +} + +/// Updates the MSK such that it has at least one secret per right given, and no +/// secret for rights that are not given. Updates hybridization of the remaining +/// secrets when required. +pub fn update_msk( + rng: &mut impl CryptoRngCore, + msk: &mut MasterSecretKey, + rights: HashMap, +) -> Result<(), Error> { + let mut secrets = take(&mut msk.secrets); + secrets.retain(|r| rights.contains_key(r)); + + for (r, (mode, status)) in rights { + if let Some(revisions) = secrets.get_mut(&r) { + if let Some((_, secret)) = revisions.pop_front() { + revisions.push_front((status, secret.set_security_mode(mode, rng)?)) + } else { + return Err(Error::OperationNotPermitted( + "empty revision list is illegal".to_string(), + )); + } + } else { + if EncryptionStatus::DecryptOnly == status { + return Err(Error::OperationNotPermitted( + "cannot add decrypt only secret".to_string(), + )); + } + let secret = RightSecretKey::random(rng, mode)?; + secrets.insert(r, (status, secret)); + } + } + msk.secrets = secrets; + Ok(()) +} + +/// Generates a new secret for each right in the given set that belongs to the MSK. +pub fn rekey( + rng: &mut impl CryptoRngCore, + msk: &mut MasterSecretKey, + rights: HashSet, +) -> Result<(), Error> { + for r in rights { + if msk.secrets.contains_key(&r) { + let security_mode = msk + .secrets + .get_latest(&r) + .map(|(_, k)| k.security_mode()) + .ok_or_else(|| { + Error::OperationNotPermitted(format!("no current key for coordinate {r:#?}")) + })?; + + msk.secrets.insert( + r, + ( + EncryptionStatus::default(), + RightSecretKey::random(rng, security_mode)?, + ), + ); + } else { + return Err(Error::OperationNotPermitted( + "cannot re-key a right not belonging to the MSK".to_string(), + )); + } + } + Ok(()) +} + +/// Removes old keys associated all coordinates in the given set from the MSK. +/// +/// # Safety +/// +/// This operation *permanently* deletes old keys, this is thus not reversible! +pub fn prune(msk: &mut MasterSecretKey, coordinates: &HashSet) { + for coordinate in coordinates { + msk.secrets.keep(coordinate, 1); + } +} + +/// Refreshes the USK relatively to the given MSK. +/// +/// For each coordinate in the USK: +/// - if `keep_old_rights` is set to false, the last secret from MSK is given to +/// the USK, all secrets previously owned by the USK are removed; +/// - otherwise, secrets from the USK that do not belong to the MSK are removed, +/// and secrets from the MSK that do not belong to the USK are added. +pub fn refresh( + rng: &mut impl CryptoRngCore, + msk: &mut MasterSecretKey, + usk: &mut UserSecretKey, + keep_old_rights: bool, +) -> Result<(), Error> { + verify(msk, usk)?; + + let usk_id = take(&mut usk.id); + let new_id = msk.tsk.refresh_id(rng, usk_id)?; + + let usk_rights = take(&mut usk.secrets); + let new_rights = if keep_old_rights { + refresh_coordinate_keys(msk, usk_rights) + } else { + msk.get_latest_right_sk(usk_rights.into_keys()) + .collect::, Error>>()? + }; + + let signature = sign(msk, &new_id, &new_rights)?; + + usk.id = new_id; + usk.secrets = new_rights; + usk.signature = signature; + + Ok(()) +} + +/// For each coordinate given, filters out associated secrets that do not belong +/// to the MSK and add the most recent ones from the MSK to the associated list +/// of secret. +/// +/// Removes coordinates that do not belong to the MSK. +/// +/// Preserves the following invariant: +/// > 1. most recent coordinate secrets are listed first +/// > 2) USK secrets are a strict sub-sequence of the MSK ones +fn refresh_coordinate_keys( + msk: &MasterSecretKey, + coordinate_keys: RevisionVec, +) -> RevisionVec { + coordinate_keys + .into_iter() + .filter_map(|(coordinate, user_chain)| { + msk.secrets.get(&coordinate).and_then(|msk_chain| { + let mut updated_chain = LinkedList::new(); + let mut msk_secrets = msk_chain.iter(); + let mut usk_secrets = user_chain.into_iter(); + let first_secret = usk_secrets.next()?; + + // Add the most recent secrets from the MSK that do not belong + // to the USK at the front of the updated chain (cf Invariant.1) + for (_, msk_secret) in msk_secrets.by_ref() { + if msk_secret == &first_secret { + break; + } + updated_chain.push_back(msk_secret.clone()); + } + + // Push the first USK secret since it was consumed from the USK + // chain iterator. + updated_chain.push_back(first_secret); + + // Push the secrets already stored in the USK that also belong + // to the MSK keypairs. + for coordinate_sk in usk_secrets { + if let Some((_, msk_secret)) = msk_secrets.next() { + if msk_secret == &coordinate_sk { + updated_chain.push_back(msk_secret.clone()); + continue; + } + } + // No more shared secret after the first divergence (cf Invariant.2). + break; + } + Some((coordinate, updated_chain)) + }) + }) + .collect::>() +} diff --git a/src/abe/core/serialization/mod.rs b/src/abe/core/serialization/mod.rs new file mode 100644 index 00000000..ec090b28 --- /dev/null +++ b/src/abe/core/serialization/mod.rs @@ -0,0 +1,364 @@ +//! Implements the serialization methods for the `Covercrypt` objects. + +use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; + +use crate::{ + abe::core::{ + MasterPublicKey, MasterSecretKey, RightPublicKey, RightSecretKey, TracingPublicKey, + TracingSecretKey, UserId, UserSecretKey, XEnc, + }, + Error, +}; + +impl Serializable for TracingPublicKey { + type Error = Error; + + fn length(&self) -> usize { + self.0.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + #[allow(clippy::needless_question_mark)] + Ok(self.0.write(ser)?) + } + + fn read(de: &mut Deserializer) -> Result { + #[allow(clippy::needless_question_mark)] + Ok(Self(de.read()?)) + } +} + +impl Serializable for RightPublicKey { + type Error = Error; + + fn length(&self) -> usize { + 1 + match self { + Self::PreQuantum { H } => H.length(), + Self::PostQuantum { ek } => ek.length(), + Self::Hybridized { H, ek } => H.length() + ek.length(), + } + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::PreQuantum { H } => Ok(0usize.write(ser)? + H.write(ser)?), + Self::PostQuantum { ek } => Ok(2usize.write(ser)? + ek.write(ser)?), + Self::Hybridized { H, ek } => Ok(1usize.write(ser)? + H.write(ser)? + ek.write(ser)?), + } + } + + fn read(de: &mut Deserializer) -> Result { + let is_hybridized = de.read::()?; + match is_hybridized { + 0 => Ok(Self::PreQuantum { H: de.read()? }), + 2 => Ok(Self::PostQuantum { ek: de.read()? }), + 1 => Ok(Self::Hybridized { + H: de.read()?, + ek: de.read()?, + }), + n => Err(Error::ConversionFailed(format!( + "invalid hybridization flag {n}" + ))), + } + } +} + +impl Serializable for MasterPublicKey { + type Error = Error; + + fn length(&self) -> usize { + self.tpk.length() + self.encryption_keys.length() + self.access_structure.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(self.tpk.write(ser)? + + self.encryption_keys.write(ser)? + + self.access_structure.write(ser)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self { + tpk: de.read()?, + encryption_keys: de.read()?, + access_structure: de.read()?, + }) + } +} + +impl Serializable for TracingSecretKey { + type Error = Error; + + fn length(&self) -> usize { + self.s.length() + self.users.length() + self.tracers.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(self.s.write(ser)? + self.tracers.write(ser)? + self.users.write(ser)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self { + s: de.read()?, + tracers: de.read()?, + users: de.read()?, + }) + } +} + +impl Serializable for MasterSecretKey { + type Error = Error; + + fn length(&self) -> usize { + self.tsk.length() + + self.secrets.length() + + self.signing_key.length() + + self.access_structure.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(self.tsk.write(ser)? + + self.secrets.write(ser)? + + self.signing_key.write(ser)? + + self.access_structure.write(ser)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self { + tsk: de.read()?, + secrets: de.read()?, + signing_key: de.read()?, + access_structure: de.read()?, + }) + } +} + +impl Serializable for UserId { + type Error = Error; + + fn length(&self) -> usize { + self.0.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + #[allow(clippy::needless_question_mark)] + Ok(self.0.write(ser)?) + } + + fn read(de: &mut Deserializer) -> Result { + #[allow(clippy::needless_question_mark)] + Ok(Self(de.read()?)) + } +} + +impl Serializable for RightSecretKey { + type Error = Error; + + fn length(&self) -> usize { + 1 + match self { + Self::Hybridized { sk, dk } => sk.length() + dk.length(), + Self::PreQuantum { sk } => sk.length(), + Self::PostQuantum { dk } => dk.length(), + } + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::PreQuantum { sk } => Ok(0usize.write(ser)? + sk.write(ser)?), + Self::PostQuantum { dk } => Ok(2usize.write(ser)? + dk.write(ser)?), + Self::Hybridized { sk, dk } => { + Ok(1usize.write(ser)? + sk.write(ser)? + dk.write(ser)?) + } + } + } + + fn read(de: &mut Deserializer) -> Result { + let mode = de.read_leb128_u64()?; + match mode { + 0 => Ok(Self::PreQuantum { sk: de.read()? }), + 1 => Ok(Self::Hybridized { + sk: de.read()?, + dk: de.read()?, + }), + 2 => Ok(Self::PostQuantum { dk: de.read()? }), + _ => Err(Error::ConversionFailed(format!( + "invalid hybridization flag {mode}" + ))), + } + } +} + +impl Serializable for UserSecretKey { + type Error = Error; + + fn length(&self) -> usize { + self.id.length() + self.ps.length() + self.secrets.length() + self.signature.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(self.id.write(ser)? + + self.ps.write(ser)? + + self.secrets.write(ser)? + + self.signature.write(ser)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self { + id: de.read()?, + ps: de.read()?, + secrets: de.read()?, + signature: de.read()?, + }) + } +} + +impl Serializable for XEnc { + type Error = Error; + + fn length(&self) -> usize { + 1 + match self { + Self::PreQuantum { + tag, + c, + encapsulations, + } => tag.length() + c.length() + encapsulations.length(), + Self::PostQuantum { + tag, + encapsulations, + } => tag.length() + encapsulations.length(), + Self::Hybridized { + tag, + c, + encapsulations, + } => tag.length() + c.length() + encapsulations.length(), + } + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + XEnc::PreQuantum { + tag, + c, + encapsulations, + } => Ok(0usize.write(ser)? + + tag.write(ser)? + + c.write(ser)? + + encapsulations.write(ser)?), + XEnc::PostQuantum { + tag, + encapsulations, + } => Ok(1usize.write(ser)? + tag.write(ser)? + encapsulations.write(ser)?), + XEnc::Hybridized { + tag, + c, + encapsulations, + } => Ok(2usize.write(ser)? + + tag.write(ser)? + + c.write(ser)? + + encapsulations.write(ser)?), + } + } + + fn read(de: &mut Deserializer) -> Result { + let mode = usize::read(de)?; + match mode { + 0 => Ok(Self::PreQuantum { + tag: de.read()?, + c: de.read()?, + encapsulations: de.read()?, + }), + 1 => Ok(Self::PostQuantum { + tag: de.read()?, + encapsulations: de.read()?, + }), + 2 => Ok(Self::Hybridized { + tag: de.read()?, + c: de.read()?, + encapsulations: de.read()?, + }), + n => Err(Error::ConversionFailed(format!( + "invalid encapsulation type: {n}" + ))), + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::{HashMap, HashSet}; + + use cosmian_crypto_core::{ + bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, + }; + + use crate::{ + abe::{ + api::Covercrypt, + core::{ + primitives::{encaps, rekey, setup, update_msk, usk_keygen}, + MIN_TRACING_LEVEL, + }, + policy::{AccessPolicy, EncryptionHint, EncryptionStatus, Right}, + traits::KemAc, + }, + test_utils::cc_keygen, + }; + + #[test] + fn test_serializations() { + { + let mut rng = CsRng::from_entropy(); + let coordinate_1 = Right::random(&mut rng); + let coordinate_2 = Right::random(&mut rng); + let coordinate_3 = Right::random(&mut rng); + + let universe = HashMap::from([ + ( + coordinate_1.clone(), + (EncryptionHint::Hybridized, EncryptionStatus::EncryptDecrypt), + ), + ( + coordinate_2.clone(), + (EncryptionHint::Hybridized, EncryptionStatus::EncryptDecrypt), + ), + ( + coordinate_3.clone(), + (EncryptionHint::Hybridized, EncryptionStatus::EncryptDecrypt), + ), + ]); + + let user_set = HashSet::from([coordinate_1.clone(), coordinate_3.clone()]); + let target_set = HashSet::from([coordinate_1, coordinate_3]); + let mut rng = CsRng::from_entropy(); + + let mut msk = setup(MIN_TRACING_LEVEL + 2, &mut rng).unwrap(); + update_msk(&mut rng, &mut msk, universe.clone()).unwrap(); + let mpk = msk.mpk().unwrap(); + let usk = usk_keygen(&mut rng, &mut msk, user_set).unwrap(); + let (_, enc) = encaps(&mut rng, &mpk, &target_set).unwrap(); + + test_serialization(&msk).unwrap(); + test_serialization(&mpk).unwrap(); + test_serialization(&usk).unwrap(); + test_serialization(&enc).unwrap(); + + rekey(&mut rng, &mut msk, universe.keys().cloned().collect()).unwrap(); + test_serialization(&msk).unwrap(); + } + + { + let cc = Covercrypt::default(); + let (mut msk, mpk) = cc_keygen(&cc, false).unwrap(); + let usk = cc + .generate_user_secret_key(&mut msk, &AccessPolicy::parse("SEC::TOP").unwrap()) + .unwrap(); + let (_, enc) = cc + .encaps(&mpk, &AccessPolicy::parse("DPT::MKG").unwrap()) + .unwrap(); + + test_serialization(&msk).unwrap(); + test_serialization(&mpk).unwrap(); + test_serialization(&usk).unwrap(); + test_serialization(&enc).unwrap(); + } + } +} diff --git a/src/core/tests.rs b/src/abe/core/tests.rs similarity index 78% rename from src/core/tests.rs rename to src/abe/core/tests.rs index 5579e414..ffe38412 100644 --- a/src/core/tests.rs +++ b/src/abe/core/tests.rs @@ -1,13 +1,18 @@ use std::collections::{HashMap, HashSet}; -use cosmian_crypto_core::{reexport::rand_core::SeedableRng, Aes256Gcm, CsRng}; +use cosmian_crypto_core::{reexport::rand_core::SeedableRng, traits::AE_InPlace, Aes256Gcm, CsRng}; use crate::{ - abe_policy::{AccessPolicy, AttributeStatus, EncryptionHint, Right}, - api::Covercrypt, - core::primitives::{decaps, encaps, refresh, rekey, update_msk}, + abe::{ + core::{ + primitives::{decaps, encaps, refresh, rekey, update_msk}, + EncryptionHint, + }, + policy::{AccessPolicy, EncryptionStatus, Right}, + traits::{KemAc, PkeAc}, + Covercrypt, + }, test_utils::cc_keygen, - traits::{KemAc, PkeAc}, }; use super::{ @@ -15,6 +20,12 @@ use super::{ MIN_TRACING_LEVEL, }; +#[test] +fn security_mode_ordering() { + assert!(EncryptionHint::Classic < EncryptionHint::PostQuantum); + assert!(EncryptionHint::PostQuantum < EncryptionHint::Hybridized); +} + /// This test asserts that it is possible to encapsulate a key for a given /// coordinate and that different users which key is associated with this /// coordinate can open the resulting encapsulation. @@ -31,11 +42,11 @@ fn test_encapsulation() { HashMap::from_iter([ ( other_coordinate.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( target_coordinate.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -93,7 +104,7 @@ fn test_update() { .map(|_| { ( Right::random(&mut rng), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ) }) .collect::>(); @@ -112,7 +123,7 @@ fn test_update() { .enumerate() .for_each(|(i, (_, (_, status)))| { if i % 2 == 0 { - *status = AttributeStatus::DecryptOnly; + *status = EncryptionStatus::DecryptOnly; } }); update_msk(&mut rng, &mut msk, coordinates.clone()).unwrap(); @@ -147,11 +158,11 @@ fn test_rekey() { HashMap::from_iter([ ( coordinate_1.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -231,11 +242,11 @@ fn test_integrity_check() { HashMap::from_iter([ ( coordinate_1.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ( coordinate_2.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + (EncryptionHint::Classic, EncryptionStatus::EncryptDecrypt), ), ]), ) @@ -290,6 +301,33 @@ fn test_reencrypt_with_msk() { #[test] fn test_covercrypt_kem() { + // Classic encapsulations. + let ap = AccessPolicy::parse("DPT::FIN && SEC::LOW").unwrap(); + let cc = Covercrypt::default(); + let (mut msk, _mpk) = cc_keygen(&cc, false).unwrap(); + let mpk = cc.update_msk(&mut msk).expect("cannot update master keys"); + let usk = cc + .generate_user_secret_key(&mut msk, &ap) + .expect("cannot generate usk"); + let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); + assert_eq!(enc.security_mode(), EncryptionHint::Classic); + let res = cc.decaps(&usk, &enc).unwrap(); + assert_eq!(secret, res.unwrap()); + + // Post-quantum encapsulations. + let ap = AccessPolicy::parse("DPT::FIN && SEC::MED").unwrap(); + let cc = Covercrypt::default(); + let (mut msk, _mpk) = cc_keygen(&cc, false).unwrap(); + let mpk = cc.update_msk(&mut msk).expect("cannot update master keys"); + let usk = cc + .generate_user_secret_key(&mut msk, &ap) + .expect("cannot generate usk"); + let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); + assert_eq!(enc.security_mode(), EncryptionHint::PostQuantum); + let res = cc.decaps(&usk, &enc).unwrap(); + assert_eq!(secret, res.unwrap()); + + // Hybridized encapsulation. let ap = AccessPolicy::parse("DPT::FIN && SEC::TOP").unwrap(); let cc = Covercrypt::default(); let (mut msk, _mpk) = cc_keygen(&cc, false).unwrap(); @@ -298,6 +336,7 @@ fn test_covercrypt_kem() { .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); let (secret, enc) = cc.encaps(&mpk, &ap).unwrap(); + assert_eq!(enc.security_mode(), EncryptionHint::Hybridized); let res = cc.decaps(&usk, &enc).unwrap(); assert_eq!(secret, res.unwrap()); } @@ -310,12 +349,22 @@ fn test_covercrypt_pke() { let ptx = "testing encryption/decryption".as_bytes(); - let ctx = PkeAc::<{ Aes256Gcm::KEY_LENGTH }, Aes256Gcm>::encrypt(&cc, &mpk, &ap, ptx) - .expect("cannot encrypt!"); + let ctx = PkeAc::< + { Aes256Gcm::KEY_LENGTH }, + { Aes256Gcm::NONCE_LENGTH }, + { Aes256Gcm::TAG_LENGTH }, + Aes256Gcm, + >::encrypt(&cc, &mpk, &ap, ptx) + .expect("cannot encrypt!"); let usk = cc .generate_user_secret_key(&mut msk, &ap) .expect("cannot generate usk"); - let ptx1 = PkeAc::<{ Aes256Gcm::KEY_LENGTH }, Aes256Gcm>::decrypt(&cc, &usk, &ctx) - .expect("cannot decrypt the ciphertext"); + let ptx1 = PkeAc::< + { Aes256Gcm::KEY_LENGTH }, + { Aes256Gcm::NONCE_LENGTH }, + { Aes256Gcm::TAG_LENGTH }, + Aes256Gcm, + >::decrypt(&cc, &usk, &ctx) + .expect("cannot decrypt the ciphertext"); assert_eq!(ptx, &*ptx1.unwrap()); } diff --git a/src/encrypted_header.rs b/src/abe/encrypted_header.rs similarity index 98% rename from src/encrypted_header.rs rename to src/abe/encrypted_header.rs index 4afc1ce5..0e7f326c 100644 --- a/src/encrypted_header.rs +++ b/src/abe/encrypted_header.rs @@ -4,8 +4,12 @@ use cosmian_crypto_core::{ }; use crate::{ - abe_policy::AccessPolicy, api::Covercrypt, core::SHARED_SECRET_LENGTH, traits::KemAc, Error, - MasterPublicKey, UserSecretKey, XEnc, + abe::{ + core::{XEnc, SHARED_SECRET_LENGTH}, + traits::KemAc, + AccessPolicy, Covercrypt, MasterPublicKey, UserSecretKey, + }, + Error, }; /// Encrypted header holding a `Covercrypt` encapsulation of a 256-byte secret, and metadata diff --git a/src/abe/error.rs b/src/abe/error.rs new file mode 100644 index 00000000..191b08c9 --- /dev/null +++ b/src/abe/error.rs @@ -0,0 +1,56 @@ +//! Error type for the crate. + +use core::{fmt::Display, num::TryFromIntError}; + +use cosmian_crypto_core::CryptoCoreError; + +#[derive(Debug)] +pub enum Error { + Kem(String), + CryptoCoreError(CryptoCoreError), + KeyError(String), + AttributeNotFound(String), + ExistingDimension(String), + OperationNotPermitted(String), + InvalidBooleanExpression(String), + InvalidAttribute(String), + DimensionNotFound(String), + ConversionFailed(String), + Tracing(String), +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Kem(err) => write!(f, "Kyber error: {err}"), + Self::CryptoCoreError(err) => write!(f, "CryptoCore error{err}"), + Self::KeyError(err) => write!(f, "{err}"), + Self::AttributeNotFound(err) => write!(f, "attribute not found: {err}"), + Self::ExistingDimension(dimension) => { + write!(f, "dimension {dimension} already exists") + } + Self::InvalidBooleanExpression(expr_str) => { + write!(f, "invalid boolean expression: {expr_str}") + } + Self::InvalidAttribute(attr) => write!(f, "invalid attribute: {attr}"), + Self::DimensionNotFound(dim_str) => write!(f, "cannot find dimension: {dim_str}"), + Self::ConversionFailed(err) => write!(f, "Conversion failed: {err}"), + Self::OperationNotPermitted(err) => write!(f, "Operation not permitted: {err}"), + Self::Tracing(err) => write!(f, "tracing error: {err}"), + } + } +} + +impl From for Error { + fn from(e: TryFromIntError) -> Self { + Self::ConversionFailed(e.to_string()) + } +} + +impl From for Error { + fn from(e: CryptoCoreError) -> Self { + Self::CryptoCoreError(e) + } +} + +impl std::error::Error for Error {} diff --git a/src/abe/policy.rs b/src/abe/policy.rs new file mode 100644 index 00000000..9f29ad8c --- /dev/null +++ b/src/abe/policy.rs @@ -0,0 +1,50 @@ +mod access_policy; +mod access_structure; +mod attribute; +mod dimension; +mod rights; + +#[cfg(any(test, feature = "test-utils"))] +mod tests; + +pub use access_policy::AccessPolicy; +pub use access_structure::AccessStructure; +pub use attribute::{EncryptionHint, EncryptionStatus, QualifiedAttribute}; +pub use dimension::{Attribute, Dimension}; +pub use rights::Right; + +#[cfg(any(test, feature = "test-utils"))] +pub use tests::gen_structure; + +use crate::Error; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Version { + V1, +} + +impl Serializable for Version { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + Version::V1 => Ok(ser.write(&1usize)?), + } + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let version = de.read::()?; + match version { + 1 => Ok(Self::V1), + n => Err(Error::ConversionFailed(format!("invalid version: {n}"))), + } + } +} diff --git a/src/abe_policy/access_policy.rs b/src/abe/policy/access_policy.rs similarity index 78% rename from src/abe_policy/access_policy.rs rename to src/abe/policy/access_policy.rs index 4d85acad..b8ca195d 100644 --- a/src/abe_policy/access_policy.rs +++ b/src/abe/policy/access_policy.rs @@ -7,10 +7,12 @@ use std::{ collections::LinkedList, fmt::Debug, - ops::{BitAnd, BitOr}, + ops::{BitAnd, BitOr, Deref}, }; -use crate::{abe_policy::QualifiedAttribute, Error}; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +use crate::{abe::policy::QualifiedAttribute, Error}; /// An access policy is a boolean expression of qualified attributes. #[derive(Debug, Clone, PartialEq, Eq)] @@ -216,8 +218,63 @@ impl BitOr for AccessPolicy { } } +impl Serializable for AccessPolicy { + type Error = Error; + + fn length(&self) -> usize { + match self { + AccessPolicy::Broadcast => 1, + AccessPolicy::Term(qualified_attribute) => 1 + qualified_attribute.length(), + AccessPolicy::Conjunction(access_policy, access_policy1) => { + 1 + access_policy.length() + access_policy1.length() + } + AccessPolicy::Disjunction(access_policy, access_policy1) => { + 1 + access_policy.length() + access_policy1.length() + } + } + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + AccessPolicy::Broadcast => ser.write(&0_u64).map_err(Error::from), + AccessPolicy::Term(qualified_attribute) => { + Ok(ser.write(&1_u64)? + ser.write(qualified_attribute)?) + } + AccessPolicy::Conjunction(access_policy, access_policy1) => Ok(ser.write(&2_u64)? + + ser.write(access_policy.deref())? + + ser.write(access_policy1.deref())?), + AccessPolicy::Disjunction(access_policy, access_policy1) => Ok(ser.write(&3_u64)? + + ser.write(access_policy.deref())? + + ser.write(access_policy1.deref())?), + } + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + match de.read::()? { + 0 => Ok(Self::Broadcast), + 1 => Ok(Self::Term(de.read()?)), + 2 => Ok(Self::Conjunction( + Box::new(de.read()?), + Box::new(de.read()?), + )), + 3 => Ok(Self::Disjunction( + Box::new(de.read()?), + Box::new(de.read()?), + )), + n => Err(Error::ConversionFailed(format!( + "{n} is not a valid access policy tag" + ))), + } + } +} + #[cfg(test)] mod tests { + use cosmian_crypto_core::bytes_ser_de::test_serialization; + use super::AccessPolicy; #[test] @@ -225,13 +282,20 @@ mod tests { // These are valid access policies. let ap = AccessPolicy::parse("(D1::A && (D2::A) || D2::B)").unwrap(); println!("{ap:#?}"); + test_serialization(&ap).unwrap(); let ap = AccessPolicy::parse("D1::A && D2::A || D2::B").unwrap(); println!("{ap:#?}"); + test_serialization(&ap).unwrap(); let ap = AccessPolicy::parse("D1::A && (D2::A || D2::B)").unwrap(); println!("{ap:#?}"); + test_serialization(&ap).unwrap(); let ap = AccessPolicy::parse("D1::A (D2::A || D2::B)").unwrap(); println!("{ap:#?}"); - assert_eq!(AccessPolicy::parse("*").unwrap(), AccessPolicy::Broadcast); + test_serialization(&ap).unwrap(); + let ap = AccessPolicy::parse("*").unwrap(); + test_serialization(&ap).unwrap(); + assert_eq!(ap, AccessPolicy::Broadcast); + assert!(AccessPolicy::parse("").is_err()); // These are invalid access policies. diff --git a/src/abe_policy/access_structure.rs b/src/abe/policy/access_structure.rs similarity index 85% rename from src/abe_policy/access_structure.rs rename to src/abe/policy/access_structure.rs index 13259657..50ac8859 100644 --- a/src/abe_policy/access_structure.rs +++ b/src/abe/policy/access_structure.rs @@ -1,16 +1,14 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use crate::{ - abe_policy::{ - AccessPolicy, Attribute, AttributeStatus, Dimension, EncryptionHint, QualifiedAttribute, - Right, + abe::policy::{ + attribute::EncryptionHint, AccessPolicy, Attribute, Dimension, EncryptionStatus, + QualifiedAttribute, Right, Version, }, data_struct::Dict, Error, }; -use super::Version; - #[derive(Clone, PartialEq, Eq, Debug)] pub struct AccessStructure { version: Version, @@ -104,7 +102,7 @@ impl AccessStructure { pub fn add_attribute( &mut self, attribute: QualifiedAttribute, - encryption_hint: EncryptionHint, + security_mode: EncryptionHint, after: Option<&str>, ) -> Result<(), Error> { let cnt = self @@ -116,7 +114,7 @@ impl AccessStructure { self.dimensions .get_mut(&attribute.dimension) .ok_or_else(|| Error::DimensionNotFound(attribute.dimension.clone()))? - .add_attribute(attribute.name, encryption_hint, after, cnt)?; + .add_attribute(attribute.name, security_mode, after, cnt)?; Ok(()) } @@ -172,13 +170,13 @@ impl AccessStructure { /// Generates all rights defined by this access structure and return their /// hybridization and activation status. - pub(crate) fn omega(&self) -> Result, Error> { + pub(crate) fn omega( + &self, + ) -> Result, Error> { let universe = self.dimensions.iter().collect::>(); combine(universe.as_slice()) .into_iter() - .map(|(ids, is_hybridized, is_readonly)| { - Right::from_point(ids).map(|r| (r, (is_hybridized, is_readonly))) - }) + .map(|(ids, mode, status)| Right::from_point(ids).map(|r| (r, (mode, status)))) .collect() } } @@ -309,10 +307,12 @@ impl AccessStructure { } } -/// Combines all attributes IDs from the given dimensions using at most one attribute for each -/// dimensions. Returns the disjunction of the associated hybridization and activation status. +/// Combines all attributes IDs from the given dimensions using at most one +/// attribute for each dimensions. Returns the disjunction of the associated +/// hybridization and activation status. /// -/// As an example, if dimensions D1::A1 and D2::(A2,B2) are given, the following combinations will be created: +/// As an example, if dimensions D1::A1 and D2::(A2,B2) are given, the following +/// combinations will be created: /// - D1::A1 /// - D1::A1 && D2::A2 /// - D1::A1 && D2::B2 @@ -320,27 +320,27 @@ impl AccessStructure { /// - D2::B2 fn combine( dimensions: &[(&String, &Dimension)], -) -> Vec<(Vec, EncryptionHint, AttributeStatus)> { +) -> Vec<(Vec, EncryptionHint, EncryptionStatus)> { if dimensions.is_empty() { vec![( vec![], EncryptionHint::Classic, - AttributeStatus::EncryptDecrypt, + EncryptionStatus::EncryptDecrypt, )] } else { let (_, current_dimension) = &dimensions[0]; let partial_combinations = combine(&dimensions[1..]); let mut res = vec![]; for component in current_dimension.attributes() { - for (ids, is_hybridized, is_activated) in &partial_combinations { + for (ids, security_mode, encryption_status) in partial_combinations.clone() { res.push(( - [vec![component.get_id()], ids.clone()].concat(), - *is_hybridized | component.get_encryption_hint(), - *is_activated | component.get_status(), + [vec![component.get_id()], ids].concat(), + security_mode.max(component.get_security_mode()), + encryption_status | component.get_encryption_status(), )); } } - [partial_combinations.clone(), res].concat() + [partial_combinations, res].concat() } } @@ -356,62 +356,30 @@ impl Default for AccessStructure { mod serialization { use super::*; - use cosmian_crypto_core::bytes_ser_de::{ - to_leb128_len, Deserializer, Serializable, Serializer, - }; + use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; impl Serializable for AccessStructure { type Error = Error; fn length(&self) -> usize { - 1 + to_leb128_len(self.dimensions.len()) - + self - .dimensions - .iter() - .map(|(name, dimension)| { - let l = name.len(); - to_leb128_len(l) + l + dimension.length() - }) - .sum::() + self.version.length() + self.dimensions.length() } fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.version as u64)?; - n += ser.write_leb128_u64(self.dimensions.len() as u64)?; - self.dimensions.iter().try_for_each(|(name, dimension)| { - n += ser.write_vec(name.as_bytes())?; - n += ser.write(dimension)?; - Ok::<_, Self::Error>(()) - })?; - Ok(n) + Ok(self.version.write(ser)? + self.dimensions.write(ser)?) } fn read(de: &mut Deserializer) -> Result { - let version = de.read_leb128_u64()?; - let dimensions = if version == Version::V1 as u64 { - (0..de.read_leb128_u64()?) - .map(|_| { - let name = String::from_utf8(de.read_vec()?) - .map_err(|e| Error::ConversionFailed(e.to_string()))?; - let dimension = de.read::()?; - Ok((name, dimension)) - }) - .collect::, Error>>() - } else { - Err(Error::ConversionFailed( - "unable to deserialize versions prior to V3".to_string(), - )) - }?; Ok(Self { - version: Version::V1, - dimensions, + version: de.read()?, + dimensions: de.read()?, }) } } #[test] fn test_access_structure_serialization() { - use crate::abe_policy::gen_structure; + use crate::abe::gen_structure; use cosmian_crypto_core::bytes_ser_de::test_serialization; let mut structure = AccessStructure::new(); @@ -423,7 +391,7 @@ mod serialization { #[cfg(test)] mod tests { use super::*; - use crate::abe_policy::gen_structure; + use crate::abe::gen_structure; #[test] fn test_combine() { @@ -447,8 +415,8 @@ mod tests { ("Spain", EncryptionHint::Classic), ] .into_iter() - .try_for_each(|(attribute, hint)| { - structure.add_attribute(QualifiedAttribute::new("Country", attribute), hint, None) + .try_for_each(|(attribute, mode)| { + structure.add_attribute(QualifiedAttribute::new("Country", attribute), mode, None) }) .unwrap(); @@ -500,6 +468,12 @@ mod tests { name: "LOW".to_string(), }, )?])?); + rights.insert(Right::from_point(vec![structure.get_attribute_id( + &QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, + )?])?); rights.insert(Right::from_point(vec![structure.get_attribute_id( &QualifiedAttribute { dimension: "SEC".to_string(), @@ -528,6 +502,28 @@ mod tests { })?, ])?); + rights.insert(Right::from_point(vec![ + structure.get_attribute_id(&QualifiedAttribute { + dimension: "DPT".to_string(), + name: "HR".to_string(), + })?, + structure.get_attribute_id(&QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + })?, + ])?); + + rights.insert(Right::from_point(vec![ + structure.get_attribute_id(&QualifiedAttribute { + dimension: "DPT".to_string(), + name: "FIN".to_string(), + })?, + structure.get_attribute_id(&QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + })?, + ])?); + rights.insert(Right::from_point(vec![ structure.get_attribute_id(&QualifiedAttribute { dimension: "DPT".to_string(), @@ -560,12 +556,12 @@ mod tests { structure .generate_complementary_rights(&AccessPolicy::parse(ap)?)? .len(), - // There are 2 rights in the security dimension, plus the + // There are 3 rights in the security dimension, plus the // broadcast for this dimension. This is the restricted // space. There is only one projection of DPT::HR, which is the // universal broadcast. The complementary space is generated by // extending these two points with the restricted space. - 2 * (1 + 2) + 2 * (1 + 3) ); let ap = "SEC::LOW"; diff --git a/src/abe_policy/attribute.rs b/src/abe/policy/attribute.rs similarity index 53% rename from src/abe_policy/attribute.rs rename to src/abe/policy/attribute.rs index fcea1993..ec394fef 100644 --- a/src/abe_policy/attribute.rs +++ b/src/abe/policy/attribute.rs @@ -1,56 +1,57 @@ use std::{convert::TryFrom, fmt::Debug, ops::BitOr}; +use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; use serde::{Deserialize, Serialize}; use crate::Error; -/// Hint the user about which kind of encryption to use. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum EncryptionHint { - /// Hybridized encryption should be used. - Hybridized, - /// Classic encryption should be used. Classic, + PostQuantum, + Hybridized, } -impl BitOr for EncryptionHint { - type Output = Self; +impl Serializable for EncryptionHint { + type Error = Error; - fn bitor(self, rhs: Self) -> Self::Output { - if self == Self::Hybridized || rhs == Self::Hybridized { - Self::Hybridized - } else { - Self::Classic - } + fn length(&self) -> usize { + 1 } -} -impl EncryptionHint { - #[must_use] - pub fn new(is_hybridized: bool) -> Self { - if is_hybridized { - Self::Hybridized - } else { - Self::Classic + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::Classic => ser.write(&0usize), + Self::Hybridized => ser.write(&1usize), + Self::PostQuantum => ser.write(&2usize), } + .map_err(Error::from) } -} -impl From for bool { - fn from(val: EncryptionHint) -> Self { - val == EncryptionHint::Hybridized + fn read(de: &mut Deserializer) -> Result { + let status = de.read::()?; + match status { + 0 => Ok(Self::Classic), + 1 => Ok(Self::Hybridized), + 2 => Ok(Self::PostQuantum), + n => Err(Error::ConversionFailed(format!( + "invalid security mode: {}", + n + ))), + } } } /// Whether to provide an encryption key in the master public key for this /// attribute. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum AttributeStatus { +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub enum EncryptionStatus { + #[default] EncryptDecrypt, DecryptOnly, } -impl BitOr for AttributeStatus { +impl BitOr for EncryptionStatus { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { @@ -62,9 +63,37 @@ impl BitOr for AttributeStatus { } } -impl From for bool { - fn from(val: AttributeStatus) -> Self { - val == AttributeStatus::EncryptDecrypt +impl From for bool { + fn from(val: EncryptionStatus) -> Self { + val == EncryptionStatus::EncryptDecrypt + } +} + +impl Serializable for EncryptionStatus { + type Error = Error; + + fn length(&self) -> usize { + 1 + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::DecryptOnly => ser.write(&0usize), + Self::EncryptDecrypt => ser.write(&1usize), + } + .map_err(Error::from) + } + + fn read(de: &mut Deserializer) -> Result { + let status = de.read::()?; + match status { + 0 => Ok(Self::DecryptOnly), + 1 => Ok(Self::EncryptDecrypt), + n => Err(Error::ConversionFailed(format!( + "invalid attribute-status value: {}", + n + ))), + } } } @@ -146,3 +175,22 @@ impl TryFrom<&str> for QualifiedAttribute { Ok(Self::new(dimension.trim(), component.trim())) } } + +impl Serializable for QualifiedAttribute { + type Error = Error; + + fn length(&self) -> usize { + self.dimension.length() + self.name.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.dimension)? + ser.write(&self.name)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self { + dimension: de.read()?, + name: de.read()?, + }) + } +} diff --git a/src/abe_policy/dimension.rs b/src/abe/policy/dimension.rs similarity index 66% rename from src/abe_policy/dimension.rs rename to src/abe/policy/dimension.rs index b6a85a4f..19c91b8c 100644 --- a/src/abe_policy/dimension.rs +++ b/src/abe/policy/dimension.rs @@ -5,7 +5,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use super::{attribute::EncryptionHint, AttributeStatus}; +use super::{EncryptionHint, EncryptionStatus}; use crate::{data_struct::Dict, Error}; type Name = String; @@ -13,16 +13,16 @@ type Name = String; #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct Attribute { pub(crate) id: usize, - pub(crate) encryption_hint: EncryptionHint, - pub(crate) write_status: AttributeStatus, + pub(crate) security_mode: EncryptionHint, + pub(crate) encryption_status: EncryptionStatus, } impl Attribute { - pub fn new(encryption_hint: EncryptionHint, id: usize) -> Self { + pub fn new(security_mode: EncryptionHint, id: usize) -> Self { Self { id, - encryption_hint, - write_status: AttributeStatus::EncryptDecrypt, + security_mode, + encryption_status: EncryptionStatus::EncryptDecrypt, } } @@ -30,12 +30,12 @@ impl Attribute { self.id } - pub fn get_encryption_hint(&self) -> EncryptionHint { - self.encryption_hint + pub fn get_security_mode(&self) -> EncryptionHint { + self.security_mode } - pub fn get_status(&self) -> AttributeStatus { - self.write_status + pub fn get_encryption_status(&self) -> EncryptionStatus { + self.encryption_status } } @@ -60,13 +60,6 @@ impl Dimension { } } - pub fn is_ordered(&self) -> bool { - match self { - Self::Anarchy(_) => false, - Self::Hierarchy(_) => true, - } - } - /// Returns an iterator over the attributes name. /// /// If the dimension is ordered, the names are returned in this order, otherwise they are @@ -113,14 +106,14 @@ impl Dimension { pub fn add_attribute( &mut self, attribute: Name, - hint: EncryptionHint, + security_mode: EncryptionHint, after: Option<&str>, id: usize, ) -> Result<(), Error> { match self { Self::Anarchy(attributes) => { if let Entry::Vacant(entry) = attributes.entry(attribute) { - entry.insert(Attribute::new(hint, id)); + entry.insert(Attribute::new(security_mode, id)); Ok(()) } else { Err(Error::OperationNotPermitted( @@ -157,7 +150,7 @@ impl Dimension { .take_while(|a| Some(a) != higher_attributes.last()) .collect::>(); - new_attributes.insert(attribute, Attribute::new(hint, id)); + new_attributes.insert(attribute, Attribute::new(security_mode, id)); higher_attributes.into_iter().rev().for_each(|(name, dim)| { new_attributes.insert(name, dim); }); @@ -192,11 +185,11 @@ impl Dimension { match self { Self::Anarchy(attributes) => attributes .get_mut(name) - .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) + .map(|attr| attr.encryption_status = EncryptionStatus::DecryptOnly) .ok_or(Error::AttributeNotFound(name.to_string())), Self::Hierarchy(attributes) => attributes .get_mut(name) - .map(|attr| attr.write_status = AttributeStatus::DecryptOnly) + .map(|attr| attr.encryption_status = EncryptionStatus::DecryptOnly) .ok_or(Error::AttributeNotFound(name.to_string())), } } @@ -239,9 +232,7 @@ impl Dimension { } mod serialization { - use cosmian_crypto_core::bytes_ser_de::{ - to_leb128_len, Deserializer, Serializable, Serializer, - }; + use cosmian_crypto_core::bytes_ser_de::Serializable; use super::*; @@ -249,44 +240,25 @@ mod serialization { type Error = Error; fn length(&self) -> usize { - 2 + to_leb128_len(self.id) + self.id.length() + self.security_mode.length() + self.encryption_status.length() } - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.id as u64)?; - n += ser.write_leb128_u64(::from(self.encryption_hint) as u64)?; - n += ser.write_leb128_u64(::from(self.write_status) as u64)?; - Ok(n) + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.id.write(ser)? + + self.security_mode.write(ser)? + + self.encryption_status.write(ser)?) } - fn read(de: &mut Deserializer) -> Result { - let id = de.read_leb128_u64()?.try_into()?; - let hint = de.read_leb128_u64()?; - let encryption_hint = if 0 == hint { - EncryptionHint::Classic - } else if 1 == hint { - EncryptionHint::Hybridized - } else { - return Err(Error::ConversionFailed(format!( - "erroneous hint value {hint}" - ))); - }; - - let status = de.read_leb128_u64()?; - let write_status = if 0 == status { - AttributeStatus::DecryptOnly - } else if 1 == status { - AttributeStatus::EncryptDecrypt - } else { - return Err(Error::ConversionFailed(format!( - "erroneous status value {hint}" - ))); - }; - + fn read( + de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer, + ) -> Result { Ok(Self { - id, - encryption_hint, - write_status, + id: de.read()?, + security_mode: de.read()?, + encryption_status: de.read()?, }) } } @@ -306,21 +278,9 @@ mod serialization { type Error = Error; fn length(&self) -> usize { - let f = |attributes: Box>| { - attributes - .map(|(name, attribute)| { - let l = name.len(); - to_leb128_len(l) + l + attribute.length() - }) - .sum::() - }; 1 + match self { - Dimension::Anarchy(attributes) => { - to_leb128_len(attributes.len()) + f(Box::new(attributes.iter())) - } - Dimension::Hierarchy(attributes) => { - to_leb128_len(attributes.len()) + f(Box::new(attributes.iter())) - } + Dimension::Anarchy(inner) => inner.length(), + Dimension::Hierarchy(inner) => inner.length(), } } @@ -328,52 +288,27 @@ mod serialization { &self, ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, ) -> Result { - let write_attributes = - |mut attributes: Box>, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer| - -> Result { - attributes.try_fold(0, |mut n, (name, attribute)| { - n += ser.write_vec(name.as_bytes())?; - n += ser.write(attribute)?; - Ok(n) - }) - }; - - let mut n = ser.write_leb128_u64(self.is_ordered() as u64)?; match self { - Dimension::Anarchy(attributes) => { - n += ser.write_leb128_u64(attributes.len() as u64)?; - n += write_attributes(Box::new(attributes.iter()), ser)?; - } - Dimension::Hierarchy(attributes) => { - n += ser.write_leb128_u64(attributes.len() as u64)?; - n += write_attributes(Box::new(attributes.iter()), ser)?; - } - }; - - Ok(n) + Dimension::Anarchy(inner) => Ok(ser.write(&0usize)? + ser.write(inner)?), + Dimension::Hierarchy(inner) => Ok(ser.write(&1usize)? + + ser + .write(inner) + .map_err(|e| Error::ConversionFailed(e.to_string()))?), + } } fn read( de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer, ) -> Result { - let is_ordered = de.read_leb128_u64()?; - let l = de.read_leb128_u64()?; - let attributes = (0..l).map(|_| { - let name = String::from_utf8(de.read_vec()?) - .map_err(|e| Error::ConversionFailed(e.to_string()))?; - let attribute = de.read::()?; - Ok::<_, Error>((name, attribute)) - }); - - if 0 == is_ordered { - attributes.collect::>().map(Self::Anarchy) - } else if 1 == is_ordered { - attributes.collect::>().map(Self::Hierarchy) - } else { - Err(Error::ConversionFailed(format!( - "invalid boolean value {is_ordered}" - ))) + let t = de.read::()?; + match t { + 0 => Ok(Self::Anarchy(de.read()?)), + 1 => Ok(Self::Hierarchy(de.read().map_err( + |e: crate::data_struct::error::Error| Error::ConversionFailed(e.to_string()), + )?)), + n => Err(Error::ConversionFailed(format!( + "invalid dimension type: {n}" + ))), } } } diff --git a/src/abe_policy/rights.rs b/src/abe/policy/rights.rs similarity index 100% rename from src/abe_policy/rights.rs rename to src/abe/policy/rights.rs diff --git a/src/abe_policy/tests.rs b/src/abe/policy/tests.rs similarity index 83% rename from src/abe_policy/tests.rs rename to src/abe/policy/tests.rs index e085f98d..8467e3d8 100644 --- a/src/abe_policy/tests.rs +++ b/src/abe/policy/tests.rs @@ -1,12 +1,13 @@ -use crate::{abe_policy::AccessStructure, Error}; - -use super::EncryptionHint; +use crate::{ + abe::policy::{AccessStructure, EncryptionHint}, + Error, +}; pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), Error> { policy.add_hierarchy("SEC".to_string())?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "LOW".to_string(), }, @@ -14,12 +15,20 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), None, )?; policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, + EncryptionHint::PostQuantum, + Some("LOW"), + )?; + policy.add_attribute( + crate::abe::policy::QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), }, EncryptionHint::Hybridized, - Some("LOW"), + Some("MED"), )?; policy.add_anarchy("DPT".to_string())?; @@ -31,13 +40,13 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), ("DEV", EncryptionHint::Classic), ] .into_iter() - .try_for_each(|(attribute, hint)| { + .try_for_each(|(attribute, mode)| { policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "DPT".to_string(), name: attribute.to_string(), }, - hint, + mode, None, ) })?; @@ -52,13 +61,13 @@ pub fn gen_structure(policy: &mut AccessStructure, complete: bool) -> Result<(), ("SP", EncryptionHint::Classic), ] .into_iter() - .try_for_each(|(attribute, hint)| { + .try_for_each(|(attribute, mode)| { policy.add_attribute( - crate::abe_policy::QualifiedAttribute { + crate::abe::policy::QualifiedAttribute { dimension: "CTR".to_string(), name: attribute.to_string(), }, - hint, + mode, None, ) })?; @@ -74,7 +83,7 @@ fn test_edit_anarchic_attributes() { let mut structure = AccessStructure::new(); gen_structure(&mut structure, false).unwrap(); - assert_eq!(structure.attributes().count(), 7); + assert_eq!(structure.attributes().count(), 8); // Try renaming Research to already used name MKG assert!(structure @@ -95,14 +104,14 @@ fn test_edit_anarchic_attributes() { .map(|a| a.name) .collect(); - assert!(order.len() == 2); + assert!(order.len() == 3); // Add new attribute Sales let new_attr = QualifiedAttribute::new("DPT", "Sales"); assert!(structure .add_attribute(new_attr.clone(), EncryptionHint::Classic, None) .is_ok()); - assert_eq!(structure.attributes().count(), 8); + assert_eq!(structure.attributes().count(), 9); // Try adding already existing attribute HR let duplicate_attr = QualifiedAttribute::new("DPT", "HR"); @@ -119,7 +128,7 @@ fn test_edit_anarchic_attributes() { // Remove research attribute let delete_attr = QualifiedAttribute::new("DPT", "Research"); structure.del_attribute(&delete_attr).unwrap(); - assert_eq!(structure.attributes().count(), 7); + assert_eq!(structure.attributes().count(), 8); // Duplicate remove assert!(structure.del_attribute(&delete_attr).is_err()); @@ -183,6 +192,10 @@ fn test_edit_hierarchic_attributes() { dimension: "SEC".to_string(), name: "LOW".to_string(), }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), @@ -206,7 +219,7 @@ fn test_edit_hierarchic_attributes() { structure .add_attribute( - QualifiedAttribute::new("SEC", "MID"), + QualifiedAttribute::new("SEC", "OTHER"), EncryptionHint::Classic, None, ) @@ -220,7 +233,11 @@ fn test_edit_hierarchic_attributes() { vec![ QualifiedAttribute { dimension: "SEC".to_string(), - name: "MID".to_string(), + name: "OTHER".to_string(), + }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), }, QualifiedAttribute { dimension: "SEC".to_string(), @@ -249,7 +266,11 @@ fn test_edit_hierarchic_attributes() { }, QualifiedAttribute { dimension: "SEC".to_string(), - name: "MID".to_string(), + name: "OTHER".to_string(), + }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), }, QualifiedAttribute { dimension: "SEC".to_string(), @@ -259,7 +280,7 @@ fn test_edit_hierarchic_attributes() { ); structure - .del_attribute(&QualifiedAttribute::new("SEC", "MID")) + .del_attribute(&QualifiedAttribute::new("SEC", "OTHER")) .unwrap(); structure @@ -284,6 +305,10 @@ fn test_edit_hierarchic_attributes() { dimension: "SEC".to_string(), name: "MID".to_string(), }, + QualifiedAttribute { + dimension: "SEC".to_string(), + name: "MED".to_string(), + }, QualifiedAttribute { dimension: "SEC".to_string(), name: "TOP".to_string(), diff --git a/src/abe/traits.rs b/src/abe/traits.rs new file mode 100644 index 00000000..2b7ef1c4 --- /dev/null +++ b/src/abe/traits.rs @@ -0,0 +1,66 @@ +use crate::AccessPolicy; +use cosmian_crypto_core::{traits::AE, Secret}; + +pub trait KemAc { + type EncapsulationKey; + type DecapsulationKey; + type Encapsulation; + type Error: std::error::Error; + + /// Generates a new encapsulation for the given access policy. + /// + /// # Error + /// + /// Returns an error if the access policy is not valid. + fn encaps( + &self, + ek: &Self::EncapsulationKey, + ap: &AccessPolicy, + ) -> Result<(Secret, Self::Encapsulation), Self::Error>; + + /// Attempts opening the given encapsulation with the given key. + /// + /// Returns the encapsulated secret upon success or `None` if this key was + /// not authorized to open this encapsulation. + fn decaps( + &self, + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result>, Self::Error>; +} + +pub trait PkeAc< + const KEY_LENGTH: usize, + const NONCE_LENGTH: usize, + const TAG_LENGTH: usize, + E: AE, +> +{ + type EncryptionKey; + type DecryptionKey; + type Ciphertext; + + type Error: std::error::Error; + + /// Encrypts the given plaintext under the given access policy. + /// + /// # Error + /// + /// Returns an error if the access policy is not valid. + fn encrypt( + &self, + ek: &Self::EncryptionKey, + ap: &AccessPolicy, + ptx: &[u8], + ) -> Result; + + /// Attempts decrypting the given ciphertext with the given key. + /// + /// Returns the plaintext upon success, or `None` if this key was not + /// authorized to decrypt this ciphertext. + fn decrypt( + &self, + dk: &Self::DecryptionKey, + ctx: &Self::Ciphertext, + ) -> Result, Self::Error>; +} diff --git a/src/abe_policy/mod.rs b/src/abe_policy/mod.rs deleted file mode 100644 index 3f28693e..00000000 --- a/src/abe_policy/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod access_policy; -mod access_structure; -mod attribute; -mod dimension; -mod rights; - -#[cfg(any(test, feature = "test-utils"))] -mod tests; - -pub use access_policy::AccessPolicy; -pub use access_structure::AccessStructure; -pub use attribute::{AttributeStatus, EncryptionHint, QualifiedAttribute}; -pub use dimension::{Attribute, Dimension}; -pub use rights::Right; -#[cfg(any(test, feature = "test-utils"))] -pub use tests::gen_structure; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Version { - V1, -} diff --git a/src/ae.rs b/src/ae.rs deleted file mode 100644 index 9b657dc4..00000000 --- a/src/ae.rs +++ /dev/null @@ -1,37 +0,0 @@ -use cosmian_crypto_core::{ - reexport::rand_core::CryptoRngCore, Aes256Gcm, Dem, FixedSizeCBytes, Instantiable, Nonce, - RandomFixedSizeCBytes, SymmetricKey, -}; -use zeroize::Zeroizing; - -use crate::{traits::AE, Error}; - -impl AE<{ Self::KEY_LENGTH }> for Aes256Gcm { - type Error = Error; - - fn encrypt( - rng: &mut impl CryptoRngCore, - key: &SymmetricKey<{ Self::KEY_LENGTH }>, - ptx: &[u8], - ) -> Result, Error> { - let nonce = Nonce::<{ Self::NONCE_LENGTH }>::new(&mut *rng); - let ciphertext = Self::new(key).encrypt(&nonce, ptx, None)?; - Ok([nonce.as_bytes(), &ciphertext].concat()) - } - - fn decrypt( - key: &SymmetricKey<{ Self::KEY_LENGTH }>, - ctx: &[u8], - ) -> Result>, Error> { - if ctx.len() < Self::NONCE_LENGTH { - return Err(Error::CryptoCoreError( - cosmian_crypto_core::CryptoCoreError::DecryptionError, - )); - } - let nonce = Nonce::try_from_slice(&ctx[..Self::NONCE_LENGTH])?; - Self::new(key) - .decrypt(&nonce, &ctx[Self::NONCE_LENGTH..], None) - .map_err(Error::CryptoCoreError) - .map(Zeroizing::new) - } -} diff --git a/src/base.rs b/src/base.rs new file mode 100644 index 00000000..dd9acca9 --- /dev/null +++ b/src/base.rs @@ -0,0 +1,367 @@ +use crate::{ + abe::{traits::KemAc, AccessPolicy}, + providers::{MlKem, PreQuantumKem}, + AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, +}; +use cosmian_crypto_core::{ + bytes_ser_de::Serializable, + kdf::Hasher, + reexport::{rand_core::CryptoRngCore, tiny_keccak::Sha3, zeroize::ZeroizeOnDrop}, + traits::{kem_to_pke::GenericPKE, KEM}, + Secret, SymmetricKey, +}; + +#[derive(Debug)] +pub enum AbeDKey { + Master(Covercrypt, MasterSecretKey), + User(Covercrypt, UserSecretKey), +} + +// All secret keys are zeroized on drop. +impl ZeroizeOnDrop for AbeDKey {} + +#[derive(Debug, ZeroizeOnDrop)] +pub enum DKey { + AbeScheme(AbeDKey), + PreQuantum(>::DecapsulationKey), + PostQuantum(>::DecapsulationKey), + Hybridized( + >::DecapsulationKey, + >::DecapsulationKey, + ), +} + +impl DKey { + pub fn access_structure(&mut self) -> Result<&mut AccessStructure, Error> { + match self { + DKey::AbeScheme(AbeDKey::Master(_, msk)) => Ok(&mut msk.access_structure), + _ => Err(Error::KeyError( + "no access structure associated to non-ABE key type".to_string(), + )), + } + } + + pub fn update_msk(&mut self) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let mpk = cc.update_msk(msk)?; + Ok(EKey::AbeScheme(cc.clone(), mpk, None)) + } + _ => Err(Error::KeyError( + "cannot update non ABE master key".to_string(), + )), + } + } + + pub fn rekey(&mut self, ap: &AccessPolicy) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let mpk = cc.rekey(msk, ap)?; + Ok(EKey::AbeScheme(cc.clone(), mpk, None)) + } + _ => Err(Error::KeyError( + "cannot re-key non ABE master key".to_string(), + )), + } + } + + pub fn prune_master_key(&mut self, ap: &AccessPolicy) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let mpk = cc.prune_master_secret_key(msk, ap)?; + Ok(EKey::AbeScheme(cc.clone(), mpk, None)) + } + _ => Err(Error::KeyError( + "cannot prune non ABE master key".to_string(), + )), + } + } + + pub fn generate_user_secret_key(&mut self, ap: &AccessPolicy) -> Result { + match self { + DKey::AbeScheme(AbeDKey::Master(cc, msk)) => { + let usk = cc.generate_user_secret_key(msk, ap)?; + Ok(DKey::AbeScheme(AbeDKey::User(cc.clone(), usk))) + } + _ => Err(Error::KeyError( + "cannot generate user secret key using a non ABE master key".to_string(), + )), + } + } + + pub fn refresh_user_secret_key( + &mut self, + usk: &mut DKey, + keep_old_secrets: bool, + ) -> Result<(), Error> { + match (self, usk) { + (DKey::AbeScheme(AbeDKey::Master(cc, msk)), DKey::AbeScheme(AbeDKey::User(_, usk))) => { + cc.refresh_usk(msk, usk, keep_old_secrets) + } + _ => Err(Error::KeyError( + "cannot refresh user secret key: invalid key types".to_string(), + )), + } + } + + pub fn recaps( + &mut self, + mpk: &EKey, + enc: &Enc, + ) -> Result<(SymmetricKey<{ ConfigurableKEM::KEY_LENGTH }>, Enc), Error> { + match (self, mpk, enc) { + ( + DKey::AbeScheme(AbeDKey::Master(cc, msk)), + EKey::AbeScheme(_, mpk, _), + Enc::AbeScheme(enc), + ) => { + let (ss, enc) = cc.recaps(msk, mpk, enc)?; + Ok((SymmetricKey::from(ss), Enc::AbeScheme(enc))) + } + _ => Err(Error::KeyError( + "cannot re-encapsulate: invalid object types".to_string(), + )), + } + } +} + +#[derive(Debug, Clone)] +pub enum EKey { + AbeScheme(Covercrypt, MasterPublicKey, Option), + PreQuantum(>::EncapsulationKey), + PostQuantum(>::EncapsulationKey), + Hybridized( + >::EncapsulationKey, + >::EncapsulationKey, + ), +} + +impl EKey { + /// Sets the encapsulation key to use the provided access polity. + pub fn set_access_policy(&mut self, access_policy: AccessPolicy) -> Result<(), Error> { + match self { + Self::AbeScheme(_, _, ap) => { + *ap = Some(access_policy); + Ok(()) + } + _ => Err(Error::KeyError( + "cannot set access policy for non-ABE encapsulation keys".to_string(), + )), + } + } +} + +#[derive(Debug, Clone)] +pub enum Enc { + AbeScheme(XEnc), + PreQuantum(>::Encapsulation), + PostQuantum(>::Encapsulation), + Hybridized( + >::Encapsulation, + >::Encapsulation, + ), +} + +#[derive(Debug, Clone)] +pub enum Configuration { + AbeScheme, + PreQuantum, + PostQuantum, + Hybridized, +} + +impl Configuration { + pub fn keygen(&self, rng: &mut impl CryptoRngCore) -> Result<(DKey, EKey), Error> { + match self { + Self::AbeScheme => { + let cc = Covercrypt::default(); + let (msk, mpk) = cc.setup()?; + Ok(( + DKey::AbeScheme(AbeDKey::Master(cc.clone(), msk)), + EKey::AbeScheme(cc, mpk, None), + )) + } + Self::PreQuantum => { + let (dk, ek) = PreQuantumKem::keygen(rng)?; + Ok((DKey::PreQuantum(dk), EKey::PreQuantum(ek))) + } + Self::PostQuantum => { + let (dk, ek) = MlKem::keygen(rng)?; + Ok((DKey::PostQuantum(dk), EKey::PostQuantum(ek))) + } + Self::Hybridized => { + let (pre_dk, pre_ek) = PreQuantumKem::keygen(rng)?; + let (post_dk, post_ek) = MlKem::keygen(rng)?; + Ok(( + DKey::Hybridized(pre_dk, post_dk), + EKey::Hybridized(pre_ek, post_ek), + )) + } + } + } +} + +/// Interface of a CCA KEM that can be configured to be either: +/// - a pre-quantum KEM based on the ElGamal provider; +/// - a post-quantum KEM based on the MlKem provider; +/// - a hybridized KEM based on both the ElGamal and MlKem provider and +/// guaranteeing bast-of-both security; +/// - an ABE KEM based on Covercrypt and in which case the security against a +/// post-quantum adversary is defined on a per-attribute basis similarly to +/// Covercrypt. +/// +/// Note while the constant-time characteristic of these variant depends on the +/// specific provider, the ABE variant *is not* constant-time as Covercrypt is +/// not. +#[derive(Debug, Clone)] +pub struct ConfigurableKEM; + +impl KEM<32> for ConfigurableKEM { + type Encapsulation = Enc; + + type EncapsulationKey = EKey; + + type DecapsulationKey = DKey; + + type Error = Error; + + fn keygen( + _rng: &mut impl CryptoRngCore, + ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error> { + Err(Error::Kem( + "key generation is not implemented for ConfigurableKEM, use the KEMConfiguration instead" + .to_string(), + )) + } + + fn enc( + ek: &Self::EncapsulationKey, + rng: &mut impl CryptoRngCore, + ) -> Result<(SymmetricKey<32>, Self::Encapsulation), Self::Error> { + match ek { + EKey::AbeScheme(_, _, None) => Err(Error::Kem( + "access policy must be provided for encapsulation".to_string(), + )), + EKey::AbeScheme(cc, mpk, Some(ap)) => cc + .encaps(mpk, ap) + .map(|(key, enc)| (SymmetricKey::from(key), Enc::AbeScheme(enc))), + EKey::PreQuantum(ek) => { + let (key, enc) = + PreQuantumKem::enc(ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok((key, Enc::PreQuantum(enc))) + } + EKey::PostQuantum(ek) => { + let (key, enc) = MlKem::enc(ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok((key, Enc::PostQuantum(enc))) + } + EKey::Hybridized(pre_ek, post_ek) => { + let (k1, enc1) = + PreQuantumKem::enc(pre_ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + let (k2, enc2) = MlKem::enc(post_ek, rng).map_err(|e| Error::Kem(e.to_string()))?; + let mut key = SymmetricKey::default(); + let mut hasher = Sha3::v256(); + hasher.update(&*k1); + hasher.update(&*k2); + hasher.update(&enc1.serialize()?); + hasher.update(&enc2); + hasher.finalize(&mut *key); + Ok((key, Enc::Hybridized(enc1, enc2))) + } + } + } + + fn dec( + dk: &Self::DecapsulationKey, + enc: &Self::Encapsulation, + ) -> Result, Self::Error> { + match (dk, enc) { + (DKey::AbeScheme(dk), Enc::AbeScheme(xenc)) => match dk { + AbeDKey::Master(_cc, msk) => { + let (ss, _) = crate::abe::core::primitives::master_decaps(msk, xenc, false)?; + Ok(SymmetricKey::from(ss)) + } + AbeDKey::User(cc, usk) => cc.decaps(usk, xenc).map(|res| { + // If the user does not have the right to decapsulate, + // return a random key to preserve the KEM semantics. + let ss = res.unwrap_or_else(|| Secret::random(&mut *cc.rng())); + SymmetricKey::from(ss) + }), + }, + (DKey::PreQuantum(dk), Enc::PreQuantum(enc)) => { + PreQuantumKem::dec(dk, enc).map_err(|e| Error::Kem(e.to_string())) + } + (DKey::PostQuantum(dk), Enc::PostQuantum(enc)) => { + MlKem::dec(dk, enc).map_err(|e| Error::Kem(e.to_string())) + } + (DKey::Hybridized(dk1, dk2), Enc::Hybridized(enc1, enc2)) => { + let k1 = PreQuantumKem::dec(dk1, enc1).map_err(|e| Error::Kem(e.to_string()))?; + let k2 = MlKem::dec(dk2, enc2).map_err(|e| Error::Kem(e.to_string()))?; + let mut key = SymmetricKey::default(); + let mut hasher = Sha3::v256(); + hasher.update(&*k1); + hasher.update(&*k2); + hasher.update(&enc1.serialize()?); + hasher.update(enc2); + hasher.finalize(&mut *key); + Ok(key) + } + _ => Err(Error::KeyError( + "cannot proceed with decapsulation: incompatible types".to_string(), + )), + } + } +} + +pub type ConfigurablePKE = GenericPKE<{ ConfigurableKEM::KEY_LENGTH }, ConfigurableKEM, AE>; + +#[cfg(test)] +mod tests { + use crate::test_utils::cc_keygen; + + use super::*; + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; + + #[test] + fn test_abe_kem() { + let mut rng = CsRng::from_entropy(); + let config = Configuration::AbeScheme; + let (mut msk, _) = config.keygen(&mut rng).unwrap(); + + // Load the test access structure used in other tests. + let access_structure = msk.access_structure().unwrap(); + let (_msk, _) = cc_keygen(&Covercrypt::default(), true).unwrap(); + *access_structure = _msk.access_structure.clone(); + let mut mpk = msk.update_msk().unwrap(); + + let user_ap = AccessPolicy::parse("(DPT::MKG || DPT::FIN) && SEC::TOP").unwrap(); + let ok_ap = AccessPolicy::parse("DPT::MKG && SEC::TOP").unwrap(); + let ko_ap = AccessPolicy::parse("DPT::DEV").unwrap(); + + let usk = msk.generate_user_secret_key(&user_ap).unwrap(); + + // Check user *can* decrypt the OK access policy. + mpk.set_access_policy(ok_ap).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&mpk, &mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert_eq!(key, key_); + + // Check user *cannot* decrypt the KO access policy. + mpk.set_access_policy(ko_ap).unwrap(); + let (_key, enc) = ConfigurableKEM::enc(&mpk, &mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert!(key != key_); + } + + #[test] + fn test_hybridized_kem() { + let mut rng = CsRng::from_entropy(); + let (sk, pk) = Configuration::Hybridized.keygen(&mut rng).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&pk, &mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&sk, &enc).unwrap(); + assert_eq!(key, key_); + + let (sk, _) = Configuration::Hybridized.keygen(&mut rng).unwrap(); + let key_ = ConfigurableKEM::dec(&sk, &enc).unwrap(); + assert!(key != key_); + } +} diff --git a/src/core/nike/p256.rs b/src/core/nike/p256.rs deleted file mode 100644 index e640ade6..00000000 --- a/src/core/nike/p256.rs +++ /dev/null @@ -1,428 +0,0 @@ -use std::hash::Hash; -use std::iter::Sum; -use std::ops::Add; -use std::ops::AddAssign; -use std::ops::Div; -use std::ops::Mul; -use std::ops::MulAssign; -use std::ops::Sub; -use std::ops::SubAssign; - -use cosmian_crypto_core::bytes_ser_de::Deserializer; -use cosmian_crypto_core::bytes_ser_de::Serializable; -use cosmian_crypto_core::bytes_ser_de::Serializer; -use cosmian_crypto_core::CryptoCoreError; -use elliptic_curve::group::GroupEncoding; -use elliptic_curve::rand_core::CryptoRngCore; -use elliptic_curve::Field; -use elliptic_curve::PrimeField; -use p256::{ProjectivePoint, Scalar}; -use subtle::ConstantTimeEq; -use tiny_keccak::Hasher; -use tiny_keccak::Sha3; -use zeroize::Zeroize; - -use crate::traits::Group; -use crate::traits::KeyHomomorphicNike; -use crate::traits::Nike; -use crate::traits::One; -use crate::traits::Ring; -use crate::traits::Sampling; -use crate::traits::Zero; -use crate::Error; - -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] -pub struct P256Point(ProjectivePoint); - -impl Zero for P256Point { - fn zero() -> Self { - Self(ProjectivePoint::IDENTITY) - } - - fn is_zero(&self) -> bool { - self.0.ct_eq(&ProjectivePoint::IDENTITY).into() - } -} - -impl Add for P256Point { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - &self + &rhs - } -} - -impl Add<&P256Point> for P256Point { - type Output = Self; - - fn add(self, rhs: &P256Point) -> Self::Output { - &self + rhs - } -} - -impl Add<&P256Point> for &P256Point { - type Output = P256Point; - - fn add(self, rhs: &P256Point) -> Self::Output { - P256Point(self.0 + rhs.0) - } -} - -impl AddAssign for P256Point { - fn add_assign(&mut self, rhs: Self) { - self.0 = self.0 + rhs.0; - } -} - -impl Sub for P256Point { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - &self - &rhs - } -} - -impl SubAssign for P256Point { - fn sub_assign(&mut self, rhs: Self) { - self.0 = self.0 - rhs.0 - } -} - -impl Sub<&P256Point> for P256Point { - type Output = Self; - - fn sub(self, rhs: &P256Point) -> Self::Output { - &self - rhs - } -} - -impl Sub<&P256Point> for &P256Point { - type Output = P256Point; - - fn sub(self, rhs: &P256Point) -> Self::Output { - P256Point(self.0 - rhs.0) - } -} - -impl Group for P256Point {} - -impl Serializable for P256Point { - type Error = CryptoCoreError; - - fn length(&self) -> usize { - 33 - } - - fn write(&self, ser: &mut Serializer) -> Result { - ser.write_array(&self.0.to_bytes()) - } - - fn read(de: &mut Deserializer) -> Result { - let bytes = de.read_array::<33>()?; - let point = ProjectivePoint::from_bytes(&bytes.into()) - .into_option() - .ok_or_else(|| { - CryptoCoreError::GenericDeserializationError("cannot deserialize point".to_string()) - })?; - Ok(Self(point)) - } -} - -impl Sum for P256Point { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, p| a + p) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] -pub struct P256Scalar(Scalar); - -impl Hash for P256Scalar { - fn hash(&self, state: &mut H) { - state.write(&self.0.to_bytes()); - } -} - -impl Zero for P256Scalar { - fn zero() -> Self { - Self(Scalar::ZERO) - } - - fn is_zero(&self) -> bool { - self.0.ct_eq(&Scalar::ZERO).into() - } -} - -impl One for P256Scalar { - fn one() -> Self { - Self(Scalar::ONE) - } - - fn is_one(&self) -> bool { - self.0.ct_eq(&Scalar::ONE).into() - } -} - -impl Add for P256Scalar { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - &self + &rhs - } -} - -impl AddAssign for P256Scalar { - fn add_assign(&mut self, rhs: Self) { - self.0 = self.0 + rhs.0; - } -} - -impl Add<&P256Scalar> for P256Scalar { - type Output = Self; - - fn add(self, rhs: &P256Scalar) -> Self::Output { - &self + rhs - } -} - -impl Add<&P256Scalar> for &P256Scalar { - type Output = P256Scalar; - - fn add(self, rhs: &P256Scalar) -> Self::Output { - P256Scalar(self.0 + rhs.0) - } -} - -impl Sub for P256Scalar { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - &self - &rhs - } -} - -impl SubAssign for P256Scalar { - fn sub_assign(&mut self, rhs: Self) { - self.0 = self.0 - rhs.0 - } -} - -impl Sub<&P256Scalar> for P256Scalar { - type Output = Self; - - fn sub(self, rhs: &P256Scalar) -> Self::Output { - &self - rhs - } -} - -impl Sub<&P256Scalar> for &P256Scalar { - type Output = P256Scalar; - - fn sub(self, rhs: &P256Scalar) -> Self::Output { - P256Scalar(self.0 - rhs.0) - } -} - -impl Mul for P256Scalar { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - &self * &rhs - } -} - -impl MulAssign for P256Scalar { - fn mul_assign(&mut self, rhs: Self) { - self.0 = self.0 * rhs.0 - } -} - -impl Mul<&P256Scalar> for P256Scalar { - type Output = Self; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - &self * rhs - } -} - -impl Mul<&P256Scalar> for &P256Scalar { - type Output = P256Scalar; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - P256Scalar(self.0 * rhs.0) - } -} - -impl Div for P256Scalar { - type Output = Result; - - fn div(self, rhs: Self) -> Self::Output { - &self / &rhs - } -} - -impl Div<&P256Scalar> for P256Scalar { - type Output = Result; - - fn div(self, rhs: &P256Scalar) -> Self::Output { - &self / rhs - } -} - -impl Div<&P256Scalar> for &P256Scalar { - type Output = Result; - - fn div(self, rhs: &P256Scalar) -> Self::Output { - rhs.0 - .invert() - .map(|rhs| self.0 * rhs) - .map(P256Scalar) - .into_option() - .ok_or_else(|| Error::OperationNotPermitted("Division by zero".to_string())) - } -} - -impl Sum for P256Scalar { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, s| a + s) - } -} - -impl Group for P256Scalar {} - -impl Ring for P256Scalar { - type DivError = Error; -} - -impl Serializable for P256Scalar { - type Error = CryptoCoreError; - - fn length(&self) -> usize { - 32 - } - - fn write(&self, ser: &mut Serializer) -> Result { - ser.write_array(&self.0.to_bytes()) - } - - fn read(de: &mut Deserializer) -> Result { - let bytes = de.read_array::<32>()?; - let scalar = Scalar::from_repr(bytes.into()) - .into_option() - .ok_or_else(|| { - CryptoCoreError::GenericDeserializationError( - "cannot deserialize scalar".to_string(), - ) - })?; - Ok(Self(scalar)) - } -} - -impl Sampling for P256Scalar { - fn random(rng: &mut impl CryptoRngCore) -> Self { - Self(Scalar::random(rng)) - } - - fn hash(seed: &[u8]) -> Self { - let mut i = 0u32; - loop { - let mut hasher = Sha3::v256(); - let mut bytes = [0; 32]; - hasher.update(seed); - hasher.update(&i.to_be_bytes()); - hasher.finalize(&mut bytes); - let s = Self::deserialize(&bytes); - bytes.zeroize(); - if let Ok(s) = s { - return s; - } else { - i += 1; - } - } - } -} - -impl From<&P256Scalar> for P256Point { - fn from(s: &P256Scalar) -> Self { - P256Point(ProjectivePoint::GENERATOR * s.0) - } -} - -impl Mul for P256Point { - type Output = Self; - - fn mul(self, rhs: P256Scalar) -> Self::Output { - &self * &rhs - } -} - -impl MulAssign for P256Point { - fn mul_assign(&mut self, rhs: P256Scalar) { - self.0 = self.0 * rhs.0 - } -} - -impl Mul<&P256Scalar> for P256Point { - type Output = Self; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - &self * rhs - } -} - -impl Mul<&P256Scalar> for &P256Point { - type Output = P256Point; - - fn mul(self, rhs: &P256Scalar) -> Self::Output { - P256Point(self.0 * rhs.0) - } -} - -pub struct P256; - -impl Nike for P256 { - type SecretKey = P256Scalar; - type PublicKey = P256Point; - type SessionKey = P256Point; - type Error = Error; - - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error> { - let sk = Self::SecretKey::random(rng); - let pk = Self::PublicKey::from(&sk); - Ok((sk, pk)) - } - - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result { - Ok(pk * sk) - } -} - -impl KeyHomomorphicNike for P256 {} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, - }; - - use super::*; - - #[test] - fn test_p256() { - let mut rng = CsRng::from_entropy(); - let (sk1, pk1) = P256::keygen(&mut rng).unwrap(); - let (sk2, pk2) = P256::keygen(&mut rng).unwrap(); - test_serialization(&sk1).unwrap(); - test_serialization(&pk1).unwrap(); - test_serialization(&sk2).unwrap(); - test_serialization(&pk2).unwrap(); - let ss1 = P256::session_key(&sk1, &pk2).unwrap(); - let ss2 = P256::session_key(&sk2, &pk1).unwrap(); - assert_eq!(ss1, ss2); - } -} diff --git a/src/core/nike/r25519.rs b/src/core/nike/r25519.rs deleted file mode 100644 index abb4208d..00000000 --- a/src/core/nike/r25519.rs +++ /dev/null @@ -1,401 +0,0 @@ -use std::iter::Sum; -use std::ops::Add; -use std::ops::AddAssign; -use std::ops::Deref; -use std::ops::Div; -use std::ops::Mul; -use std::ops::MulAssign; -use std::ops::Sub; -use std::ops::SubAssign; - -use cosmian_crypto_core::bytes_ser_de::Deserializer; -use cosmian_crypto_core::bytes_ser_de::Serializable; -use cosmian_crypto_core::bytes_ser_de::Serializer; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; - -use cosmian_crypto_core::CryptoCoreError; -pub use cosmian_crypto_core::R25519PrivateKey as Scalar; -pub use cosmian_crypto_core::R25519PublicKey as EcPoint; -use tiny_keccak::Hasher; -use tiny_keccak::Sha3; -use zeroize::Zeroize; - -use crate::traits::Group; -use crate::traits::KeyHomomorphicNike; -use crate::traits::Nike; -use crate::traits::One; -use crate::traits::Ring; -use crate::traits::Sampling; -use crate::traits::Zero; -use crate::Error; - -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] -pub struct R25519Point(EcPoint); - -impl Zero for R25519Point { - fn zero() -> Self { - Self(EcPoint::identity()) - } - - fn is_zero(&self) -> bool { - self == &Self::zero() - } -} - -impl Add for R25519Point { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + &rhs.0) - } -} - -impl Add<&R25519Point> for R25519Point { - type Output = Self; - - fn add(self, rhs: &R25519Point) -> Self::Output { - Self(self.0 + &rhs.0) - } -} - -impl Add<&R25519Point> for &R25519Point { - type Output = R25519Point; - - fn add(self, rhs: &R25519Point) -> Self::Output { - R25519Point(&self.0 + &rhs.0) - } -} - -impl AddAssign for R25519Point { - fn add_assign(&mut self, rhs: Self) { - self.0 = &self.0 + &rhs.0; - } -} - -impl Sub for R25519Point { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl SubAssign for R25519Point { - fn sub_assign(&mut self, rhs: Self) { - self.0 = &self.0 - &rhs.0 - } -} - -impl Sub<&R25519Point> for R25519Point { - type Output = Self; - - fn sub(self, rhs: &R25519Point) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl Sub<&R25519Point> for &R25519Point { - type Output = R25519Point; - - fn sub(self, rhs: &R25519Point) -> Self::Output { - R25519Point(&self.0 - &rhs.0) - } -} - -impl Group for R25519Point {} - -impl Serializable for R25519Point { - type Error = Error; - - fn length(&self) -> usize { - self.0.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - self.0.write(ser).map_err(Self::Error::from) - } - - fn read(de: &mut Deserializer) -> Result { - de.read().map(Self).map_err(Self::Error::from) - } -} - -impl Sum for R25519Point { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, p| a + p) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct R25519Scalar(Scalar); - -impl Deref for R25519Scalar { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.as_bytes() - } -} - -impl Zero for R25519Scalar { - fn zero() -> Self { - Self(Scalar::zero()) - } - - fn is_zero(&self) -> bool { - self == &Self::zero() - } -} - -impl One for R25519Scalar { - fn one() -> Self { - Self(Scalar::one()) - } - - fn is_one(&self) -> bool { - self == &Self::one() - } -} - -impl Add for R25519Scalar { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for R25519Scalar { - fn add_assign(&mut self, rhs: Self) { - self.0 = &self.0 + &rhs.0; - } -} - -impl Add<&R25519Scalar> for R25519Scalar { - type Output = Self; - - fn add(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 + &rhs.0) - } -} - -impl Add<&R25519Scalar> for &R25519Scalar { - type Output = R25519Scalar; - - fn add(self, rhs: &R25519Scalar) -> Self::Output { - R25519Scalar(&self.0 + &rhs.0) - } -} - -impl Sub for R25519Scalar { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl SubAssign for R25519Scalar { - fn sub_assign(&mut self, rhs: Self) { - self.0 = &self.0 - &rhs.0 - } -} - -impl Sub<&R25519Scalar> for R25519Scalar { - type Output = Self; - - fn sub(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 - &rhs.0) - } -} - -impl Sub<&R25519Scalar> for &R25519Scalar { - type Output = R25519Scalar; - - fn sub(self, rhs: &R25519Scalar) -> Self::Output { - R25519Scalar(&self.0 - &rhs.0) - } -} - -impl Mul for R25519Scalar { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl MulAssign for R25519Scalar { - fn mul_assign(&mut self, rhs: Self) { - self.0 = &self.0 * &rhs.0 - } -} - -impl Mul<&R25519Scalar> for R25519Scalar { - type Output = Self; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl Mul<&R25519Scalar> for &R25519Scalar { - type Output = R25519Scalar; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - R25519Scalar(&self.0 * &rhs.0) - } -} - -impl Div for R25519Scalar { - type Output = Result; - - fn div(self, rhs: Self) -> Self::Output { - &self / &rhs - } -} - -impl Div<&R25519Scalar> for R25519Scalar { - type Output = Result; - - fn div(self, rhs: &R25519Scalar) -> Self::Output { - &self / rhs - } -} - -impl Div<&R25519Scalar> for &R25519Scalar { - type Output = Result; - - fn div(self, rhs: &R25519Scalar) -> Self::Output { - (&self.0 / &rhs.0).map(R25519Scalar) - } -} - -impl Sum for R25519Scalar { - fn sum>(iter: I) -> Self { - iter.fold(Self::zero(), |a, s| a + s) - } -} - -impl Group for R25519Scalar {} - -impl Ring for R25519Scalar { - type DivError = CryptoCoreError; -} - -impl Serializable for R25519Scalar { - type Error = Error; - - fn length(&self) -> usize { - self.0.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - self.0.write(ser).map_err(Self::Error::from) - } - - fn read(de: &mut Deserializer) -> Result { - de.read().map(Self).map_err(Self::Error::from) - } -} - -impl Sampling for R25519Scalar { - fn random(rng: &mut impl CryptoRngCore) -> Self { - Self(Scalar::new(rng)) - } - - fn hash(seed: &[u8]) -> Self { - let mut hasher = Sha3::v512(); - let mut bytes = [0; 512 / 8]; - hasher.update(seed); - hasher.finalize(&mut bytes); - let s = Self(Scalar::from_raw_bytes(&bytes)); - bytes.zeroize(); - s - } -} - -impl From<&R25519Scalar> for R25519Point { - fn from(s: &R25519Scalar) -> Self { - Self(EcPoint::from(&s.0)) - } -} - -impl Mul for R25519Point { - type Output = Self; - - fn mul(self, rhs: R25519Scalar) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl MulAssign for R25519Point { - fn mul_assign(&mut self, rhs: R25519Scalar) { - self.0 = &self.0 * &rhs.0 - } -} - -impl Mul<&R25519Scalar> for R25519Point { - type Output = Self; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - Self(&self.0 * &rhs.0) - } -} - -impl Mul<&R25519Scalar> for &R25519Point { - type Output = R25519Point; - - fn mul(self, rhs: &R25519Scalar) -> Self::Output { - R25519Point(&self.0 * &rhs.0) - } -} - -pub struct R25519; - -impl Nike for R25519 { - type SecretKey = R25519Scalar; - type PublicKey = R25519Point; - type SessionKey = R25519Point; - type Error = Error; - - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error> { - let sk = Self::SecretKey::random(rng); - let pk = Self::PublicKey::from(&sk); - Ok((sk, pk)) - } - - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result { - Ok(pk * sk) - } -} - -impl KeyHomomorphicNike for R25519 {} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, - }; - - use super::*; - - #[test] - fn test_r25519() { - let mut rng = CsRng::from_entropy(); - let (sk1, pk1) = R25519::keygen(&mut rng).unwrap(); - let (sk2, pk2) = R25519::keygen(&mut rng).unwrap(); - test_serialization(&sk1).unwrap(); - test_serialization(&pk1).unwrap(); - test_serialization(&sk2).unwrap(); - test_serialization(&pk2).unwrap(); - let ss1 = R25519::session_key(&sk1, &pk2).unwrap(); - let ss2 = R25519::session_key(&sk2, &pk1).unwrap(); - assert_eq!(ss1, ss2); - } -} diff --git a/src/core/primitives.rs b/src/core/primitives.rs deleted file mode 100644 index 718fc317..00000000 --- a/src/core/primitives.rs +++ /dev/null @@ -1,766 +0,0 @@ -use std::{ - collections::{HashMap, HashSet, LinkedList}, - mem::take, -}; - -use cosmian_crypto_core::{ - bytes_ser_de::Serializable, - reexport::rand_core::{CryptoRngCore, RngCore}, - RandomFixedSizeCBytes, Secret, SymmetricKey, -}; - -use tiny_keccak::{Hasher, Kmac, Sha3}; -use zeroize::Zeroize; - -use crate::{ - abe_policy::{AccessStructure, AttributeStatus, EncryptionHint, Right}, - core::{ - kem::{self, MlKem}, - Encapsulations, KmacSignature, MasterPublicKey, MasterSecretKey, RightPublicKey, - RightSecretKey, TracingSecretKey, UserId, UserSecretKey, XEnc, MIN_TRACING_LEVEL, - SHARED_SECRET_LENGTH, SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, - }, - data_struct::{RevisionMap, RevisionVec}, - traits::{Kem, Nike, Sampling}, - Error, -}; - -use super::nike::ElGamal; - -fn xor_2(lhs: &[u8; LENGTH], rhs: &[u8; LENGTH]) -> [u8; LENGTH] { - let mut out = [0; LENGTH]; - for pos in 0..LENGTH { - out[pos] = lhs[pos] ^ rhs[pos]; - } - out -} - -fn xor_in_place( - mut lhs: Secret, - rhs: &[u8; LENGTH], -) -> Secret { - for pos in 0..LENGTH { - lhs[pos] ^= rhs[pos]; - } - lhs -} - -fn shuffle(xs: &mut [T], rng: &mut impl RngCore) { - for i in 0..xs.len() { - let j = rng.next_u32() as usize % xs.len(); - xs.swap(i, j); - } -} - -/// Computes the signature of the given USK using the MSK. -fn sign( - msk: &MasterSecretKey, - id: &UserId, - keys: &RevisionVec, -) -> Result, Error> { - if let Some(kmac_key) = &msk.signing_key { - let mut kmac = Kmac::v256(&**kmac_key, b"USK signature"); - for marker in id.iter() { - kmac.update(&marker.serialize()?) - } - // Subkeys ordering needs to be deterministic to allow deterministic - // signatures. This explains why a hash-map is not used in USK. - for (coordinate, keys) in keys.iter() { - kmac.update(coordinate); - for subkey in keys.iter() { - match subkey { - RightSecretKey::Hybridized { sk: s_i, dk: dk_i } => { - kmac.update(&s_i.serialize()?); - kmac.update(&dk_i.serialize()?); - } - RightSecretKey::Classic { sk: s_i } => { - kmac.update(&s_i.serialize()?); - } - } - } - } - let mut res = [0; SIGNATURE_LENGTH]; - kmac.finalize(&mut res); - Ok(Some(res)) - } else { - Ok(None) - } -} - -/// Verifies the integrity of the given USK using the MSK. -fn verify(msk: &MasterSecretKey, usk: &UserSecretKey) -> Result<(), Error> { - let fresh_signature = sign(msk, &usk.id, &usk.secrets)?; - if fresh_signature != usk.signature { - Err(Error::KeyError( - "USK failed the integrity check".to_string(), - )) - } else { - Ok(()) - } -} - -fn G_hash(seed: &Secret) -> Result<::SecretKey, Error> { - Ok(<::SecretKey as Sampling>::hash(&**seed)) -} - -fn H_hash( - K1: &::PublicKey, - K2: Option<&Secret>, - T: &Secret, -) -> Result, Error> { - let mut hasher = Sha3::v256(); - // SHARED_SECRET_LENGTH = 32 = 256 / 8 - let mut H = Secret::::new(); - hasher.update(&K1.serialize()?); - if let Some(K2) = K2 { - hasher.update(&**K2); - } - hasher.update(&**T); - hasher.finalize(&mut *H); - Ok(H) -} - -fn J_hash( - S: &Secret, - U: &Secret, -) -> ([u8; TAG_LENGTH], Secret) { - let mut hasher = Sha3::v384(); - let mut bytes = [0; 384 / 8]; - hasher.update(&**S); - hasher.update(&**U); - hasher.finalize(&mut bytes); - - let mut tag = [0; TAG_LENGTH]; - let mut seed = Secret::::default(); - tag.copy_from_slice(&bytes[..TAG_LENGTH]); - seed.copy_from_slice(&bytes[TAG_LENGTH..]); - (tag, seed) -} - -/// Generates new MSK with the given tracing level. -pub fn setup(tracing_level: usize, rng: &mut impl CryptoRngCore) -> Result { - if tracing_level < MIN_TRACING_LEVEL { - return Err(Error::OperationNotPermitted(format!( - "tracing level cannot be lower than {MIN_TRACING_LEVEL}" - ))); - } - - let tsk = TracingSecretKey::new_with_level(tracing_level, rng)?; - let policy = AccessStructure::default(); - - Ok(MasterSecretKey { - tsk, - secrets: RevisionMap::new(), - signing_key: Some(SymmetricKey::::new(rng)), - access_structure: policy, - }) -} - -/// Generates a USK for the given set of coordinates. -/// -/// The generated key is provided with the last version of the key for each -/// coordinate in the given set. The USK can then open any up-to-date key -/// encapsulation for any such coordinate (provided the coordinate was not -/// re-keyed in-between). -/// -/// If the MSK has a signing key, signs the USK. -pub fn usk_keygen( - rng: &mut impl CryptoRngCore, - msk: &mut MasterSecretKey, - coordinates: HashSet, -) -> Result { - // Extract keys first to avoid unnecessary computation in case those cannot be found. - let coordinate_keys = msk - .get_latest_right_sk(coordinates.into_iter()) - .collect::, Error>>()?; - let id = msk.tsk.generate_user_id(rng)?; - let signature = sign(msk, &id, &coordinate_keys)?; - - Ok(UserSecretKey { - id, - ps: msk.tsk.tracers.iter().map(|(_, Pi)| Pi).cloned().collect(), - secrets: coordinate_keys, - signature, - }) -} - -/// Generates a hybridized encapsulation of the given secret S with the given -/// marker c, ElGamal random r and subkeys. -fn h_encaps( - S: Secret, - c: Vec<::PublicKey>, - r: ::SecretKey, - subkeys: &[&RightPublicKey], - rng: &mut impl CryptoRngCore, -) -> Result<(Secret, XEnc), Error> { - let encs = subkeys - .iter() - .map(|subkey| match subkey { - RightPublicKey::Hybridized { H, ek } => { - let K1 = ElGamal::session_key(&r, H)?; - let (K2, E) = MlKem::enc(ek, rng)?; - Ok((K1, K2, E)) - } - RightPublicKey::Classic { .. } => { - Err(Error::Kem("all subkeys should be hybridized".to_string())) - } - }) - .collect::, Error>>()?; - - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - encs.iter().try_for_each(|(_, _, E)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let encs = encs - .into_iter() - .map(|(mut K1, K2, E)| -> Result<_, _> { - let F = xor_2(&S, &*H_hash(&K1, Some(&K2), &T)?); - K1.zeroize(); - Ok((E, F)) - }) - .collect::, Error>>()?; - - let U = { - let mut U = Secret::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|(_, F)| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - let (tag, ss) = J_hash(&S, &U); - - Ok(( - ss, - XEnc { - tag, - c, - encapsulations: Encapsulations::HEncs(encs), - }, - )) -} - -/// Generates a classic encapsulation of the given secret S with the given -/// marker c, ElGamal random r and subkeys. -fn c_encaps( - S: Secret, - c: Vec<::PublicKey>, - r: ::SecretKey, - subkeys: Vec<&RightPublicKey>, -) -> Result<(Secret, XEnc), Error> { - // In classic mode, T is only updated with c. - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let encs = subkeys - .into_iter() - .map(|subkey| -> Result<_, _> { - let H = match subkey { - RightPublicKey::Hybridized { H, .. } => H, - RightPublicKey::Classic { H } => H, - }; - let K1 = ElGamal::session_key(&r, H)?; - let F = xor_2(&S, &*H_hash(&K1, None, &T)?); - Ok(F) - }) - .collect::, Error>>()?; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|F| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - let (tag, ss) = J_hash(&S, &U); - - Ok(( - ss, - XEnc { - tag, - c, - encapsulations: Encapsulations::CEncs(encs), - }, - )) -} - -/// Generates a Covercrypt encapsulation of a random `SHARED_SECRET_LENGTH`-byte -/// session key for the coordinates in the encryption set. -/// -/// Returns both the key and its encapsulation. -/// -/// # Error -/// -/// Returns an error in case the public key is missing for some coordinate. -pub fn encaps( - rng: &mut impl CryptoRngCore, - mpk: &MasterPublicKey, - encryption_set: &HashSet, -) -> Result<(Secret, XEnc), Error> { - let (is_hybridized, mut coordinate_keys) = mpk.select_subkeys(encryption_set)?; - - // Shuffling must be performed *before* generating the encapsulations since - // rights are hashed in-order. If shuffling is performed after generating - // the encapsulations, there would be no way to know in which order to - // perform hashing upon decapsulation. - shuffle(&mut coordinate_keys, rng); - - let S = Secret::random(rng); - let r = G_hash(&S)?; - let c = mpk.set_traps(&r); - - if is_hybridized { - h_encaps(S, c, r, &coordinate_keys, rng) - } else { - c_encaps(S, c, r, coordinate_keys) - } -} - -/// Attempts to open the given hybridized encapsulations with this user secret -/// key. -fn h_decaps( - rng: &mut impl CryptoRngCore, - usk: &UserSecretKey, - A: &::PublicKey, - c: &[::PublicKey], - tag: &[u8; TAG_LENGTH], - encs: &[( - ::Encapsulation, - [u8; SHARED_SECRET_LENGTH], - )], -) -> Result>, Error> { - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - encs.iter().try_for_each(|(E, _)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|(_, F)| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - // Shuffle encapsulation to counter timing attacks attempting to determine - // which right was used to open an encapsulation. - let mut encs = encs.iter().collect::>(); - shuffle(&mut encs, rng); - - // Loop order matters: this ordering is faster. - for mut revision in usk.secrets.revisions() { - // Shuffle secrets to counter timing attacks attempting to determine - // whether successive encapsulations target the same user right. - shuffle(&mut revision, rng); - for (E, F) in &encs { - for (_, secret) in &revision { - if let RightSecretKey::Hybridized { sk, dk } = secret { - let mut K1 = ElGamal::session_key(sk, A)?; - let K2 = MlKem::dec(dk, E)?; - let S_ij = xor_in_place(H_hash(&K1, Some(&K2), &T)?, F); - let (tag_ij, ss) = J_hash(&S_ij, &U); - if tag == &tag_ij { - // Fujisaki-Okamoto - let r = G_hash(&S_ij)?; - let c_ij = usk.set_traps(&r); - if c == c_ij { - K1.zeroize(); - return Ok(Some(ss)); - } - } - } - } - } - } - - Ok(None) -} - -/// Attempts to open the given classic encapsulations with this user secret key. -fn c_decaps( - rng: &mut impl CryptoRngCore, - usk: &UserSecretKey, - A: &::PublicKey, - c: &[::PublicKey], - tag: &[u8; TAG_LENGTH], - encs: &Vec<[u8; SHARED_SECRET_LENGTH]>, -) -> Result>, Error> { - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - encs.iter().for_each(|F| hasher.update(F)); - hasher.finalize(&mut *U); - U - }; - - // Shuffle encapsulation to counter timing attacks attempting to determine - // which right was used to open an encapsulation. - let mut encs = encs.iter().collect::>(); - shuffle(&mut encs, rng); - - // Loop order matters: this ordering is faster. - for mut revision in usk.secrets.revisions() { - // Shuffle secrets to counter timing attacks attempting to determine - // whether successive encapsulations target the same user right. - shuffle(&mut revision, rng); - for F in &encs { - for (_, secret) in &revision { - let sk = match secret { - RightSecretKey::Hybridized { sk, .. } => sk, - RightSecretKey::Classic { sk } => sk, - }; - let mut K1 = ElGamal::session_key(sk, A)?; - let S = xor_in_place(H_hash(&K1, None, &T)?, F); - K1.zeroize(); - let (tag_ij, ss) = J_hash(&S, &U); - if tag == &tag_ij { - // Fujisaki-Okamoto - let r = G_hash(&S)?; - let c_ij = usk.set_traps(&r); - if c == c_ij { - return Ok(Some(ss)); - } - } - } - } - } - - Ok(None) -} - -/// Attempts opening the Covercrypt encapsulation using the given USK. Returns -/// the encapsulated key upon success, otherwise returns `None`. -pub fn decaps( - rng: &mut impl CryptoRngCore, - usk: &UserSecretKey, - encapsulation: &XEnc, -) -> Result>, Error> { - // A = ⊙ _i (α_i. c_i) - let A = usk - .id - .iter() - .zip(encapsulation.c.iter()) - .map(|(marker, trap)| trap * marker) - .sum(); - - match &encapsulation.encapsulations { - Encapsulations::HEncs(encs) => { - h_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) - } - Encapsulations::CEncs(encs) => { - c_decaps(rng, usk, &A, &encapsulation.c, &encapsulation.tag, encs) - } - } -} - -/// Recover the encapsulated shared secret and set of rights used in the -/// encapsulation. -pub fn full_decaps( - msk: &MasterSecretKey, - encapsulation: &XEnc, -) -> Result<(Secret, HashSet), Error> { - let A = { - let c_0 = encapsulation - .c - .first() - .ok_or_else(|| Error::Kem("invalid encapsulation: C is empty".to_string()))?; - let t_0 = msk - .tsk - .tracers - .front() - .map(|(si, _)| si) - .ok_or_else(|| Error::KeyError("MSK has no tracer".to_string()))?; - - c_0 * &(&msk.tsk.s / t_0)? - }; - - let T = { - let mut hasher = Sha3::v256(); - let mut T = Secret::::new(); - encapsulation.c.iter().try_for_each(|ck| { - hasher.update(&ck.serialize()?); - Ok::<_, Error>(()) - })?; - - if let Encapsulations::HEncs(encs) = &encapsulation.encapsulations { - encs.iter().try_for_each(|(E, _)| { - hasher.update(&E.serialize()?); - Ok::<_, Error>(()) - })?; - } - hasher.finalize(&mut *T); - T - }; - - let U = { - let mut U = Secret::::new(); - let mut hasher = Sha3::v256(); - hasher.update(&*T); - match &encapsulation.encapsulations { - Encapsulations::HEncs(encs) => encs.iter().for_each(|(_, F)| hasher.update(F)), - Encapsulations::CEncs(encs) => encs.iter().for_each(|F| hasher.update(F)), - } - hasher.finalize(&mut *U); - U - }; - - let mut enc_ss = None; - let mut rights = HashSet::with_capacity(encapsulation.count()); - let mut try_decaps = |right: &Right, - K1: &mut ::PublicKey, - K2: Option>, - F| { - let S_ij = xor_in_place(H_hash(K1, K2.as_ref(), &T)?, F); - let (tag_ij, ss) = J_hash(&S_ij, &U); - if encapsulation.tag == tag_ij { - // Fujisaki-Okamoto - let r = G_hash(&S_ij)?; - let c_ij = msk.tsk.set_traps(&r); - if encapsulation.c == c_ij { - K1.zeroize(); - enc_ss = Some(ss); - rights.insert(right.clone()); - } - } - Ok::<_, Error>(()) - }; - - match &encapsulation.encapsulations { - Encapsulations::HEncs(encs) => { - for (E, F) in encs { - for (right, secret_set) in msk.secrets.iter() { - for (is_activated, secret) in secret_set { - if *is_activated { - if let RightSecretKey::Hybridized { sk, dk } = secret { - let mut K1 = ElGamal::session_key(sk, &A)?; - let K2 = MlKem::dec(dk, E)?; - try_decaps(right, &mut K1, Some(K2), F)?; - } - } - } - } - } - } - Encapsulations::CEncs(encs) => { - for F in encs { - for (right, secret_set) in msk.secrets.iter() { - for (is_activated, secret) in secret_set { - if *is_activated { - let sk = match secret { - RightSecretKey::Hybridized { sk, .. } => sk, - RightSecretKey::Classic { sk } => sk, - }; - let mut K1 = ElGamal::session_key(sk, &A)?; - try_decaps(right, &mut K1, None, F)?; - } - } - } - } - } - } - enc_ss - .map(|ss| (ss, rights)) - .ok_or_else(|| Error::Kem("could not open the encapsulation".to_string())) -} - -/// Updates the MSK such that it has at least one secret per right given, and no -/// secret for rights that are not given. Updates hybridization of the remaining -/// secrets when required. -pub fn update_msk( - rng: &mut impl CryptoRngCore, - msk: &mut MasterSecretKey, - rights: HashMap, -) -> Result<(), Error> { - let mut secrets = take(&mut msk.secrets); - secrets.retain(|r| rights.contains_key(r)); - - for (r, (hint, status)) in rights { - if let Some((is_activated, coordinate_secret)) = secrets.get_latest_mut(&r) { - *is_activated = AttributeStatus::EncryptDecrypt == status; - if EncryptionHint::Classic == hint { - *coordinate_secret = coordinate_secret.drop_hybridization(); - } - } else { - if AttributeStatus::DecryptOnly == status { - return Err(Error::OperationNotPermitted( - "cannot add decrypt only secret".to_string(), - )); - } - let secret = RightSecretKey::random(rng, EncryptionHint::Hybridized == hint)?; - secrets.insert(r, (true, secret)); - } - } - msk.secrets = secrets; - Ok(()) -} - -/// Generates a new secret for each right in the given set that belongs to the MSK. -pub fn rekey( - rng: &mut impl CryptoRngCore, - msk: &mut MasterSecretKey, - rights: HashSet, -) -> Result<(), Error> { - for r in rights { - if msk.secrets.contains_key(&r) { - let is_hybridized = msk - .secrets - .get_latest(&r) - .map(|(_, k)| k.is_hybridized()) - .ok_or_else(|| { - Error::OperationNotPermitted(format!("no current key for coordinate {r:#?}")) - })?; - - msk.secrets - .insert(r, (true, RightSecretKey::random(rng, is_hybridized)?)); - } else { - return Err(Error::OperationNotPermitted( - "cannot re-key a right not belonging to the MSK".to_string(), - )); - } - } - Ok(()) -} - -/// Removes old keys associated all coordinates in the given set from the MSK. -/// -/// # Safety -/// -/// This operation *permanently* deletes old keys, this is thus not reversible! -pub fn prune(msk: &mut MasterSecretKey, coordinates: &HashSet) { - for coordinate in coordinates { - msk.secrets.keep(coordinate, 1); - } -} - -/// Refreshes the USK relatively to the given MSK. -/// -/// For each coordinate in the USK: -/// - if `keep_old_rights` is set to false, the last secret from MSK is given to -/// the USK, all secrets previously owned by the USK are removed; -/// - otherwise, secrets from the USK that do not belong to the MSK are removed, -/// and secrets from the MSK that do not belong to the USK are added. -pub fn refresh( - rng: &mut impl CryptoRngCore, - msk: &mut MasterSecretKey, - usk: &mut UserSecretKey, - keep_old_rights: bool, -) -> Result<(), Error> { - verify(msk, usk)?; - - let usk_id = take(&mut usk.id); - let new_id = msk.tsk.refresh_id(rng, usk_id)?; - - let usk_rights = take(&mut usk.secrets); - let new_rights = if keep_old_rights { - refresh_coordinate_keys(msk, usk_rights) - } else { - msk.get_latest_right_sk(usk_rights.into_keys()) - .collect::, Error>>()? - }; - - let signature = sign(msk, &new_id, &new_rights)?; - - usk.id = new_id; - usk.secrets = new_rights; - usk.signature = signature; - - Ok(()) -} - -/// For each coordinate given, filters out associated secrets that do not belong -/// to the MSK and add the most recent ones from the MSK to the associated list -/// of secret. -/// -/// Removes coordinates that do not belong to the MSK. -/// -/// Preserves the following invariant: -/// > 1. most recent coordinate secrets are listed first -/// > 2) USK secrets are a strict sub-sequence of the MSK ones -fn refresh_coordinate_keys( - msk: &MasterSecretKey, - coordinate_keys: RevisionVec, -) -> RevisionVec { - coordinate_keys - .into_iter() - .filter_map(|(coordinate, user_chain)| { - msk.secrets.get(&coordinate).and_then(|msk_chain| { - let mut updated_chain = LinkedList::new(); - let mut msk_secrets = msk_chain.iter(); - let mut usk_secrets = user_chain.into_iter(); - let first_secret = usk_secrets.next()?; - - // Add the most recent secrets from the MSK that do not belong - // to the USK at the front of the updated chain (cf Invariant.1) - for (_, msk_secret) in msk_secrets.by_ref() { - if msk_secret == &first_secret { - break; - } - updated_chain.push_back(msk_secret.clone()); - } - - // Push the first USK secret since it was consumed from the USK - // chain iterator. - updated_chain.push_back(first_secret); - - // Push the secrets already stored in the USK that also belong - // to the MSK keypairs. - for coordinate_sk in usk_secrets { - if let Some((_, msk_secret)) = msk_secrets.next() { - if msk_secret == &coordinate_sk { - updated_chain.push_back(msk_secret.clone()); - continue; - } - } - // No more shared secret after the first divergence (cf Invariant.2). - break; - } - Some((coordinate, updated_chain)) - }) - }) - .collect::>() -} diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs deleted file mode 100644 index e7289101..00000000 --- a/src/core/serialization/mod.rs +++ /dev/null @@ -1,588 +0,0 @@ -//! Implements the serialization methods for the `Covercrypt` objects. - -use std::collections::{HashMap, HashSet, LinkedList}; - -use cosmian_crypto_core::{ - bytes_ser_de::{to_leb128_len, Deserializer, Serializable, Serializer}, - FixedSizeCBytes, SymmetricKey, -}; - -use super::{ - Encapsulations, RightPublicKey, RightSecretKey, TracingPublicKey, TracingSecretKey, UserId, - SIGNATURE_LENGTH, SIGNING_KEY_LENGTH, TAG_LENGTH, -}; -use crate::{ - abe_policy::{AccessStructure, Right}, - core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc, SHARED_SECRET_LENGTH}, - data_struct::{RevisionMap, RevisionVec}, - Error, -}; - -impl Serializable for TracingPublicKey { - type Error = Error; - - fn length(&self) -> usize { - to_leb128_len(self.0.len()) + self.0.iter().map(Serializable::length).sum::() - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.0.len() as u64)?; - for pk in self.0.iter() { - n += pk.write(ser)?; - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let n_pk = ::try_from(de.read_leb128_u64()?)?; - let mut tracers = LinkedList::new(); - for _ in 0..n_pk { - let tracer = de.read()?; - tracers.push_back(tracer); - } - Ok(Self(tracers)) - } -} - -impl Serializable for RightPublicKey { - type Error = Error; - - fn length(&self) -> usize { - 1 + match self { - Self::Hybridized { H, ek } => H.length() + ek.length(), - Self::Classic { H } => H.length(), - } - } - - fn write(&self, ser: &mut Serializer) -> Result { - match self { - Self::Hybridized { H, ek } => { - let mut n = ser.write_leb128_u64(1)?; - n += ser.write(H)?; - n += ser.write(ek)?; - Ok(n) - } - Self::Classic { H } => { - let mut n = ser.write_leb128_u64(0)?; - n += ser.write(H)?; - Ok(n) - } - } - } - - fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - let H = de.read()?; - if 1 == is_hybridized { - let ek = de.read()?; - Ok(Self::Hybridized { H, ek }) - } else if 0 == is_hybridized { - Ok(Self::Classic { H }) - } else { - Err(Error::ConversionFailed(format!( - "invalid hybridization flag {is_hybridized}" - ))) - } - } -} - -impl Serializable for MasterPublicKey { - type Error = Error; - - fn length(&self) -> usize { - self.tpk.length() - + to_leb128_len(self.encryption_keys.len()) - + self - .encryption_keys - .iter() - .map(|(coordinate, pk)| coordinate.length() + pk.length()) - .sum::() - + self.access_structure.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write(&self.tpk)?; - n += ser.write_leb128_u64(self.encryption_keys.len() as u64)?; - for (coordinate, pk) in &self.encryption_keys { - n += ser.write(coordinate)?; - n += ser.write(pk)?; - } - n += ser.write(&self.access_structure)?; - - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let tpk = de.read::()?; - let n_coordinates = ::try_from(de.read_leb128_u64()?)?; - let mut coordinate_keys = HashMap::with_capacity(n_coordinates); - for _ in 0..n_coordinates { - let coordinate = de.read::()?; - let pk = de.read::()?; - coordinate_keys.insert(coordinate, pk); - } - let access_structure = de.read::()?; - Ok(Self { - tpk, - encryption_keys: coordinate_keys, - access_structure, - }) - } -} - -impl Serializable for TracingSecretKey { - type Error = Error; - - fn length(&self) -> usize { - self.s.length() - + to_leb128_len(self.users.len()) - + self.users.iter().map(Serializable::length).sum::() - + to_leb128_len(self.tracers.len()) - + self - .tracers - .iter() - .map(|(sk, pk)| sk.length() + pk.length()) - .sum::() - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = self.s.write(ser)?; - - n += ser.write_leb128_u64(self.tracers.len() as u64)?; - for (sk, pk) in &self.tracers { - n += ser.write(sk)?; - n += ser.write(pk)?; - } - - n = ser.write_leb128_u64(self.users.len() as u64)?; - for id in &self.users { - n += ser.write(id)?; - } - - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let s = de.read()?; - - let n_tracers = ::try_from(de.read_leb128_u64()?)?; - let mut tracers = LinkedList::new(); - for _ in 0..n_tracers { - let sk = de.read()?; - let pk = de.read()?; - tracers.push_back((sk, pk)); - } - - let n_users = ::try_from(de.read_leb128_u64()?)?; - let mut users = HashSet::with_capacity(n_users); - for _ in 0..n_users { - let id = de.read()?; - users.insert(id); - } - Ok(Self { s, tracers, users }) - } -} - -impl Serializable for MasterSecretKey { - type Error = Error; - - fn length(&self) -> usize { - self.tsk.length() - + to_leb128_len(self.secrets.len()) - + self - .secrets - .iter() - .map(|(coordinate, chain)| { - coordinate.length() - + to_leb128_len(chain.len()) - + chain.iter().map(|(_, k)| 1 + k.length()).sum::() - }) - .sum::() - + self.signing_key.as_ref().map_or_else(|| 0, |key| key.len()) - + self.access_structure.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write(&self.tsk)?; - n += ser.write_leb128_u64(self.secrets.len() as u64)?; - for (coordinate, chain) in &self.secrets.map { - n += ser.write(coordinate)?; - n += ser.write_leb128_u64(chain.len() as u64)?; - for (is_activated, sk) in chain { - n += ser.write_leb128_u64((*is_activated).into())?; - n += ser.write(sk)?; - } - } - if let Some(kmac_key) = &self.signing_key { - n += ser.write_array(&**kmac_key)?; - } - n += ser.write(&self.access_structure)?; - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let tsk = de.read::()?; - let n_coordinates = ::try_from(de.read_leb128_u64()?)?; - let mut coordinate_keypairs = RevisionMap::with_capacity(n_coordinates); - for _ in 0..n_coordinates { - let coordinate = de.read()?; - let n_keys = ::try_from(de.read_leb128_u64()?)?; - let chain = (0..n_keys) - .map(|_| -> Result<_, Error> { - let is_activated = de.read_leb128_u64()? == 1; - let sk = de.read::()?; - Ok((is_activated, sk)) - }) - .collect::, _>>()?; - coordinate_keypairs.map.insert(coordinate, chain); - } - - let signing_key = if de.value().len() < SIGNING_KEY_LENGTH { - None - } else { - Some(SymmetricKey::try_from_bytes( - de.read_array::()?, - )?) - }; - - let access_structure = de.read()?; - - Ok(Self { - tsk, - secrets: coordinate_keypairs, - signing_key, - access_structure, - }) - } -} - -impl Serializable for UserId { - type Error = Error; - - fn length(&self) -> usize { - to_leb128_len(self.0.len()) + self.iter().map(|marker| marker.length()).sum::() - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_leb128_u64(self.0.len() as u64)?; - for marker in &self.0 { - n += ser.write(marker)?; - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let length = ::try_from(de.read_leb128_u64()?)?; - let mut id = LinkedList::new(); - for _ in 0..length { - let marker = de.read()?; - id.push_back(marker); - } - Ok(Self(id)) - } -} - -impl Serializable for RightSecretKey { - type Error = Error; - - fn length(&self) -> usize { - 1 + match self { - Self::Hybridized { sk, dk } => sk.length() + dk.length(), - Self::Classic { sk } => sk.length(), - } - } - - fn write(&self, ser: &mut Serializer) -> Result { - match self { - Self::Hybridized { sk, dk } => { - let mut n = ser.write_leb128_u64(1)?; - n += ser.write(sk)?; - n += ser.write(dk)?; - Ok(n) - } - Self::Classic { sk } => { - let mut n = ser.write_leb128_u64(0)?; - n += ser.write(sk)?; - Ok(n) - } - } - } - - fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - let sk = de.read()?; - if 1 == is_hybridized { - let dk = de.read()?; - Ok(Self::Hybridized { sk, dk }) - } else if 0 == is_hybridized { - Ok(Self::Classic { sk }) - } else { - Err(Error::ConversionFailed(format!( - "invalid hybridization flag {is_hybridized}" - ))) - } - } -} - -impl Serializable for UserSecretKey { - type Error = Error; - - fn length(&self) -> usize { - self.id.length() - + to_leb128_len(self.ps.len()) - + self.ps.iter().map(|p| p.length()).sum::() - + to_leb128_len(self.secrets.len()) - + self - .secrets - .iter() - .map(|(coordinate, chain)| { - coordinate.length() - + to_leb128_len(chain.len()) - + chain.iter().map(|sk| sk.length()).sum::() - }) - .sum::() - + self.signature.as_ref().map_or_else(|| 0, |kmac| kmac.len()) - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write(&self.id)?; - - n += ser.write_leb128_u64(self.ps.len() as u64)?; - for p in &self.ps { - n += ser.write(p)?; - } - - n += ser.write_leb128_u64(self.secrets.len() as u64)?; - for (coordinate, chain) in self.secrets.iter() { - n += ser.write(coordinate)?; - n += ser.write_leb128_u64(chain.len() as u64)?; - for sk in chain { - n += ser.write(sk)?; - } - } - if let Some(kmac) = &self.signature { - n += ser.write_array(kmac)?; - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let id = de.read::()?; - - let n_ps = usize::try_from(de.read_leb128_u64()?)?; - - let mut ps = Vec::with_capacity(n_ps); - for _ in 0..n_ps { - let p = de.read()?; - ps.push(p); - } - - let n_coordinates = ::try_from(de.read_leb128_u64()?)?; - let mut coordinate_keys = RevisionVec::with_capacity(n_coordinates); - for _ in 0..n_coordinates { - let coordinate = de.read()?; - let n_keys = ::try_from(de.read_leb128_u64()?)?; - let new_chain = (0..n_keys) - .map(|_| de.read::()) - .collect::>()?; - coordinate_keys.insert_new_chain(coordinate, new_chain); - } - - let msk_signature = if de.value().len() < SIGNATURE_LENGTH { - None - } else { - Some(de.read_array::()?) - }; - - Ok(Self { - id, - ps, - secrets: coordinate_keys, - signature: msk_signature, - }) - } -} - -impl Serializable for Encapsulations { - type Error = Error; - - fn length(&self) -> usize { - 1 + match self { - Encapsulations::HEncs(vec) => { - to_leb128_len(vec.len()) - + vec.iter().map(|(E, F)| E.length() + F.len()).sum::() - } - Encapsulations::CEncs(vec) => { - to_leb128_len(vec.len()) + vec.iter().map(|F| F.len()).sum::() - } - } - } - - fn write(&self, ser: &mut Serializer) -> Result { - match self { - Encapsulations::HEncs(vec) => { - let mut n = ser.write_leb128_u64(1)?; - n += ser.write_leb128_u64(vec.len() as u64)?; - for (E, F) in vec.iter() { - n += ser.write(E)?; - n += ser.write_array(F)?; - } - Ok(n) - } - Encapsulations::CEncs(vec) => { - let mut n = ser.write_leb128_u64(0)?; - n += ser.write_leb128_u64(vec.len() as u64)?; - for F in vec.iter() { - n += ser.write_array(F)?; - } - Ok(n) - } - } - } - - fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - if is_hybridized == 1 { - let len = usize::try_from(de.read_leb128_u64()?)?; - let vec = (0..len) - .map(|_| { - let E = de.read()?; - let F = de.read_array::()?; - Ok::<_, Error>((E, F)) - }) - .collect::, _>>()?; - Ok(Self::HEncs(vec)) - } else if 0 == is_hybridized { - let len = usize::try_from(de.read_leb128_u64()?)?; - let vec = (0..len) - .map(|_| { - let F = de.read_array::()?; - Ok::<_, Error>(F) - }) - .collect::, _>>()?; - Ok(Self::CEncs(vec)) - } else { - Err(Error::ConversionFailed(format!( - "invalid hybridization flag {is_hybridized}" - ))) - } - } -} - -impl Serializable for XEnc { - type Error = Error; - - fn length(&self) -> usize { - TAG_LENGTH - + to_leb128_len(self.c.len()) - + self.c.iter().map(Serializable::length).sum::() - + self.encapsulations.length() - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(&self.tag)?; - n += ser.write_leb128_u64(self.c.len() as u64)?; - for trap in &self.c { - n += ser.write(trap)?; - } - n += ser.write(&self.encapsulations)?; - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let tag = de.read_array::()?; - let n_traps = ::try_from(de.read_leb128_u64()?)?; - let mut traps = Vec::with_capacity(n_traps); - for _ in 0..n_traps { - let trap = de.read()?; - traps.push(trap); - } - let encapsulations = Encapsulations::read(de)?; - Ok(Self { - tag, - c: traps, - encapsulations, - }) - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use cosmian_crypto_core::{ - bytes_ser_de::test_serialization, reexport::rand_core::SeedableRng, CsRng, - }; - - use super::*; - use crate::{ - abe_policy::{AttributeStatus, EncryptionHint}, - api::Covercrypt, - core::{ - primitives::{encaps, rekey, setup, update_msk, usk_keygen}, - MIN_TRACING_LEVEL, - }, - test_utils::cc_keygen, - traits::KemAc, - AccessPolicy, - }; - - #[test] - fn test_serializations() { - { - let mut rng = CsRng::from_entropy(); - let coordinate_1 = Right::random(&mut rng); - let coordinate_2 = Right::random(&mut rng); - let coordinate_3 = Right::random(&mut rng); - - let universe = HashMap::from([ - ( - coordinate_1.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - ), - ( - coordinate_2.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - ), - ( - coordinate_3.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - ), - ]); - - let user_set = HashSet::from([coordinate_1.clone(), coordinate_3.clone()]); - let target_set = HashSet::from([coordinate_1, coordinate_3]); - let mut rng = CsRng::from_entropy(); - - let mut msk = setup(MIN_TRACING_LEVEL + 2, &mut rng).unwrap(); - update_msk(&mut rng, &mut msk, universe.clone()).unwrap(); - let mpk = msk.mpk().unwrap(); - let usk = usk_keygen(&mut rng, &mut msk, user_set).unwrap(); - let (_, enc) = encaps(&mut rng, &mpk, &target_set).unwrap(); - - test_serialization(&msk).unwrap(); - test_serialization(&mpk).unwrap(); - test_serialization(&usk).unwrap(); - test_serialization(&enc).unwrap(); - - rekey(&mut rng, &mut msk, universe.keys().cloned().collect()).unwrap(); - test_serialization(&msk).unwrap(); - } - - { - let cc = Covercrypt::default(); - let (mut msk, mpk) = cc_keygen(&cc, false).unwrap(); - let usk = cc - .generate_user_secret_key(&mut msk, &AccessPolicy::parse("SEC::TOP").unwrap()) - .unwrap(); - let (_, enc) = cc - .encaps(&mpk, &AccessPolicy::parse("DPT::MKG").unwrap()) - .unwrap(); - - test_serialization(&msk).unwrap(); - test_serialization(&mpk).unwrap(); - test_serialization(&usk).unwrap(); - test_serialization(&enc).unwrap(); - } - } -} diff --git a/src/data_struct/dictionary.rs b/src/data_struct/dictionary.rs index d3390ba8..f9977571 100644 --- a/src/data_struct/dictionary.rs +++ b/src/data_struct/dictionary.rs @@ -7,6 +7,7 @@ use std::{ mem::swap, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; use serde::{ de::{MapAccess, Visitor}, ser::SerializeMap, @@ -265,6 +266,31 @@ where } } +impl Serializable for Dict +where + K: Hash + PartialEq + Eq + Clone + Debug, +{ + type Error = Error; + + fn length(&self) -> usize { + self.indices.length() + self.entries.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.indices.write(ser)? + self.entries.write(ser)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { + indices: de.read()?, + entries: de.read()?, + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/data_struct/error.rs b/src/data_struct/error.rs index 18e011ac..47feac2f 100644 --- a/src/data_struct/error.rs +++ b/src/data_struct/error.rs @@ -1,5 +1,7 @@ use std::fmt::{Debug, Display}; +use cosmian_crypto_core::CryptoCoreError; + type Key = String; #[derive(Debug)] @@ -7,6 +9,7 @@ pub enum Error { EntryNotFound(Key), ExistingEntry(Key), AlreadyHasChild(Key), + Serialization(CryptoCoreError), } impl Display for Error { @@ -17,6 +20,7 @@ impl Display for Error { Self::AlreadyHasChild(key) => { write!(f, "Entry with key {key} already has a child.") } + Self::Serialization(e) => write!(f, "Serialization error: {e}"), } } } @@ -43,3 +47,11 @@ impl Error { Self::AlreadyHasChild(format!("{key:?}")) } } + +impl std::error::Error for Error {} + +impl From for Error { + fn from(error: CryptoCoreError) -> Self { + Self::Serialization(error) + } +} diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs index a0bc35b7..72d71c97 100644 --- a/src/data_struct/revision_map.rs +++ b/src/data_struct/revision_map.rs @@ -8,6 +8,10 @@ use std::{ hash::Hash, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +use crate::Error; + /// A `RevisionMap` is a `HashMap` which keys are mapped to sequences of values. /// Upon insertion for an existing key, the new value is prepended to the /// sequence of older values instead of replacing it. @@ -22,7 +26,7 @@ use std::{ /// Deletions can only happen at the end of the linked list. /// /// This guarantees that the entry versions are always ordered. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct RevisionMap where K: Debug + PartialEq + Eq + Hash, @@ -136,14 +140,23 @@ where self.map.iter() } - /// Iterates through all revisions of a given key starting with the more - /// recent one. + /// Returns the list of revisions, starting with the more recent one. pub fn get(&self, key: &Q) -> Option<&LinkedList> where K: Borrow, Q: Hash + Eq + ?Sized, { - self.map.get(key) //.map(RevisionList::iter) + self.map.get(key) + } + + /// Returns a mutable reference on the list of revisions, starting with the + /// more recent one. + pub fn get_mut(&mut self, key: &Q) -> Option<&mut LinkedList> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + self.map.get_mut(key) } /// Removes and returns an iterator over all revisions from a given key. @@ -176,6 +189,29 @@ where } } +impl Serializable for RevisionMap +where + K: Hash + PartialEq + Eq + Debug + Serializable, + V: Debug + Serializable, +{ + type Error = Error; + + fn length(&self) -> usize { + self.map.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.map.write(ser)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { map: de.read()? }) + } +} + #[cfg(test)] mod tests { #![allow(clippy::unnecessary_to_owned)] diff --git a/src/data_struct/revision_vec.rs b/src/data_struct/revision_vec.rs index 1922cf0d..ac337fab 100644 --- a/src/data_struct/revision_vec.rs +++ b/src/data_struct/revision_vec.rs @@ -3,6 +3,10 @@ use std::collections::{ LinkedList, VecDeque, }; +use cosmian_crypto_core::bytes_ser_de::Serializable; + +use crate::Error; + /// A `RevisionVec` is a vector that stores pairs containing a key /// and a sequence of values. Inserting a new value in the sequence /// associated to an existing key prepends this value to the sequence. @@ -131,7 +135,7 @@ impl RevisionVec { /// Iterates through all versions of all entry in a breadth-first manner. #[must_use] - pub fn bfs(&self) -> BfsQueue { + pub fn bfs(&self) -> BfsQueue<'_, T> { BfsQueue::new(self) } @@ -202,6 +206,25 @@ impl FromIterator<(K, T)> for RevisionVec { } } +impl Serializable for RevisionVec { + type Error = Error; + + fn length(&self) -> usize { + self.chains.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + Ok(self.chains.write(ser)?) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + Ok(Self { chains: de.read()? }) + } +} + #[cfg(test)] mod tests { diff --git a/src/error.rs b/src/error.rs index 191b08c9..797244dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,7 +23,7 @@ impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Kem(err) => write!(f, "Kyber error: {err}"), - Self::CryptoCoreError(err) => write!(f, "CryptoCore error{err}"), + Self::CryptoCoreError(err) => write!(f, "CryptoCore error: {err}"), Self::KeyError(err) => write!(f, "{err}"), Self::AttributeNotFound(err) => write!(f, "attribute not found: {err}"), Self::ExistingDimension(dimension) => { diff --git a/src/kem.rs b/src/kem.rs new file mode 100644 index 00000000..7ce583b4 --- /dev/null +++ b/src/kem.rs @@ -0,0 +1,805 @@ +#![allow(clippy::type_complexity)] +use crate::{ + providers::kem::mlkem::{MlKem512, MlKem768}, + traits::KemAc, + AccessPolicy, AccessStructure, Covercrypt, Error, MasterPublicKey, MasterSecretKey, + UserSecretKey, XEnc, +}; +use cosmian_crypto_core::{ + bytes_ser_de::{Deserializer, Serializable, Serializer}, + reexport::rand_core::SeedableRng, + traits::{cyclic_group_to_kem::GenericKem, KEM}, + CryptoCoreError, CsRng, +}; +use cosmian_openssl_provider::{hash::Sha256, kem::MonadicKEM, p256::P256}; +use cosmian_rust_curve25519_provider::R25519; +use zeroize::Zeroizing; + +// In order to enforce type safety, KEM objects must be tagged by the concrete +// KEM used. + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PreQuantumKemTag { + P256, + R25519, +} + +impl Serializable for PreQuantumKemTag { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + 1 + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::P256 => ser.write(&1_u64), + Self::R25519 => ser.write(&2_u64), + } + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => Ok(Self::P256), + 2 => Ok(Self::R25519), + n => Err(CryptoCoreError::GenericDeserializationError(format!( + "{n} is not a valid pre-quantum-KEM tag" + ))), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PostQuantumKemTag { + MlKem512, + MlKem768, +} + +impl Serializable for PostQuantumKemTag { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + 1 + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + Self::MlKem512 => ser.write(&1_u64), + Self::MlKem768 => ser.write(&2_u64), + } + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => Ok(Self::MlKem512), + 2 => Ok(Self::MlKem768), + n => Err(CryptoCoreError::GenericDeserializationError(format!( + "{n} is not a valid post-quantum-KEM tag" + ))), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum KemTag { + PreQuantum(PreQuantumKemTag), + PostQuantum(PostQuantumKemTag), + Hybridized(PreQuantumKemTag, PostQuantumKemTag), + Abe, +} + +impl Serializable for KemTag { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + match self { + Self::PreQuantum(_) | Self::PostQuantum(_) => 2, + Self::Hybridized(_, _) => 3, + Self::Abe => 1, + } + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + match self { + Self::PreQuantum(tag) => Ok(ser.write(&1_u64)? + ser.write(tag)?), + Self::PostQuantum(tag) => Ok(ser.write(&2_u64)? + ser.write(tag)?), + Self::Hybridized(tag1, tag2) => { + Ok(ser.write(&3_u64)? + ser.write(tag1)? + ser.write(tag2)?) + } + Self::Abe => Ok(ser.write(&4_u64)?), + } + } + + fn read(de: &mut Deserializer) -> Result { + match de.read::()? { + 1 => de.read::().map(Self::PreQuantum), + 2 => de.read::().map(Self::PostQuantum), + 3 => de + .read::<(PreQuantumKemTag, PostQuantumKemTag)>() + .map(|(tag1, tag2)| Self::Hybridized(tag1, tag2)), + 4 => Ok(Self::Abe), + n => Err(CryptoCoreError::GenericDeserializationError(format!( + "{n} is not a valid KEM tag" + ))), + } + } +} + +// In order to avoid defining one enumeration type per KEM object with one +// variant per concrete KEM option, this module uses dynamic typing on the +// concrete key and encapsulation types by to consuming and returning byte +// strings. Serialization can be used once the concrete KEM is chosen to +// retrieve the typed objects. +// +// The following functions implement this logic: they are parametric on a KEM +// type -- and thus need to be called once the concrete KEM implementation is +// known, and perform both the KEM operation and serialization/deserialization +// of the key and encapsulation objects. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigurableKemDk(KemTag, Zeroizing>); + +impl ConfigurableKemDk { + pub fn new(tag: KemTag, bytes: Zeroizing>) -> Result { + // Check whether the given bytes are a valid serialization of the + // decapsulation key of the tagged KEM. + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + >::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::DecapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Abe => { + // For Covercrypt, the bytes can either be a valid MSK or USK. + MasterSecretKey::deserialize(&bytes) + .map_or_else( + |_| UserSecretKey::deserialize(&bytes).map(|_| ()), + |_| Ok(()), + ) + .map_err(|e| CryptoCoreError::GenericDeserializationError(e.to_string())) + } + } + .map_err(|_| { + Error::KeyError(format!( + "failed to construct a configurable-KEM decapsulation key: \ + the given bytes are not a valid decapsulation key for the KEM with tag: {tag:?}" + )) + })?; + + Ok(Self(tag, bytes)) + } + + pub fn get_tag(&self) -> KemTag { + self.0 + } + + pub fn get_bytes(&self) -> &Zeroizing> { + &self.1 + } +} + +impl Serializable for ConfigurableKemDk { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.0)? + ser.write(&self.1)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(de.read()?, de.read()?)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigurableKemEk(KemTag, Zeroizing>); + +impl ConfigurableKemEk { + pub fn new(tag: KemTag, bytes: Zeroizing>) -> Result { + // Check whether the given bytes are a valid serialization of the + // encapsulation key of the tagged KEM. + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + >::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::EncapsulationKey::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Abe => MasterPublicKey::deserialize(&bytes) + .map(|_| ()) + .map_err(|e| CryptoCoreError::GenericDeserializationError(e.to_string())), + } + .map_err(|_| { + Error::KeyError(format!( + "failed to construct a configurable-KEM encapsulation key: \ + the given bytes are not a valid encapsulation key for the KEM with tag: {tag:?}" + )) + })?; + + Ok(Self(tag, bytes)) + } + + pub fn get_tag(&self) -> KemTag { + self.0 + } + + pub fn get_bytes(&self) -> &Zeroizing> { + &self.1 + } +} + +impl Serializable for ConfigurableKemEk { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.0)? + ser.write(&self.1)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(de.read()?, de.read()?)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigurableKemEnc(KemTag, Zeroizing>); + +impl ConfigurableKemEnc { + pub fn new(tag: KemTag, bytes: Zeroizing>) -> Result { + // Check whether the given bytes are a valid serialization of the + // encapsulation of the tagged KEM. + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + >::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + as KEM< + { P256Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + as KEM< + { R25519Kem::KEY_LENGTH }, + >>::Encapsulation::deserialize(&bytes) + .map(|_| ()) + } + KemTag::Abe => { + // For Covercrypt, the bytes can either be a valid MSK or USK. + XEnc::deserialize(&bytes) + .map(|_| ()) + .map_err(|e| CryptoCoreError::GenericDeserializationError(e.to_string())) + } + } + .map_err(|_| { + Error::KeyError(format!( + "failed to construct a configurable-KEM encapsulation: \ + the given bytes are not a valid encapsulation for the KEM with tag: {tag:?}" + )) + })?; + + Ok(Self(tag, bytes)) + } + + pub fn get_tag(&self) -> KemTag { + self.0 + } + + pub fn get_bytes(&self) -> &Zeroizing> { + &self.1 + } +} + +impl Serializable for ConfigurableKemEnc { + type Error = CryptoCoreError; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write(&self, ser: &mut Serializer) -> Result { + Ok(ser.write(&self.0)? + ser.write(&self.1)?) + } + + fn read(de: &mut Deserializer) -> Result { + Ok(Self(de.read()?, de.read()?)) + } +} + +#[allow(clippy::type_complexity)] +fn generic_keygen>( + tag: KemTag, +) -> Result<(ConfigurableKemDk, ConfigurableKemEk), Error> +where + Kem::DecapsulationKey: Serializable, +{ + let mut rng = CsRng::from_entropy(); + let (dk, ek) = Kem::keygen(&mut rng).map_err(|e| Error::Kem(e.to_string()))?; + Ok(( + ConfigurableKemDk( + tag, + dk.serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the decapsulation key in configurable KEM: {e}" + )) + })?, + ), + ConfigurableKemEk( + tag, + ek.serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the encapsulation key in configurable KEM: {e}" + )) + })?, + ), + )) +} + +fn generic_enc>( + ek: &ConfigurableKemEk, +) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { + let tag = ek.get_tag(); + let ek = + >::EncapsulationKey::deserialize(ek.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the encapsulation key in configurable KEM: {e}" + )) + })?; + + let mut rng = CsRng::from_entropy(); + let (key, enc) = Kem::enc(&ek, &mut rng) + .map_err(|e| Error::Kem(format!("configurable-KEM encapsulation error: {e}")))?; + + Ok(( + key.serialize()?, + ConfigurableKemEnc( + tag, + enc.serialize().map_err(|e| { + Error::ConversionFailed(format!( + "failed serializing the encapsulation in configurable KEM: {e}" + )) + })?, + ), + )) +} + +fn generic_dec>( + dk: &ConfigurableKemDk, + enc: &ConfigurableKemEnc, +) -> Result>, Error> +where + Kem::DecapsulationKey: Serializable, +{ + let tag = dk.get_tag(); + + if tag != enc.get_tag() { + return Err(Error::OperationNotPermitted(format!( + "heterogeneous decapsulation-key and encapsulation tags: {tag:?} != {:?}", + enc.get_tag() + ))); + } + + let dk = + >::DecapsulationKey::deserialize(dk.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the decapsulation key in configurable KEM: {e}" + )) + })?; + + let enc = + >::Encapsulation::deserialize(enc.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the encapsulation in configurable KEM: {e}" + )) + })?; + + let key = Kem::dec(&dk, &enc) + .map_err(|e| Error::Kem(format!("configurable-KEM decapsulation error: {e}")))?; + + Ok(key.serialize()?) +} + +// We can now implement a KEM-like interface for our configurable KEM which +// deserializes KEM objects as couple (tag, bytes), checks tag legality and +// compatibility across objects before the KEM operation with corresponding +// implementation, and finally serializes returned objects as (tag, bytes) +// couples. + +type P256Kem = MonadicKEM<32, P256, Sha256>; +type R25519Kem = GenericKem<32, R25519, Sha256>; + +// Even though lengths of the keys encapsulated by the two combined KEM schemes +// can vary, it is much simpler to enforce their equality, which is performed +// here by binding the three key lengths required by the KEM combiner to the +// same one. +type KemCombiner = + cosmian_crypto_core::traits::kem_combiner::KemCombiner< + LENGTH, + LENGTH, + LENGTH, + Kem1, + Kem2, + Sha256, // SHA256 from the OpenSSL provider. + >; + +pub struct ConfigurableKEM; + +impl ConfigurableKEM { + #[allow(clippy::too_many_arguments)] + pub fn keygen( + tag: KemTag, + access_structure: Option, + ) -> Result<(ConfigurableKemDk, ConfigurableKemEk), Error> { + match tag { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_keygen::<{ P256Kem::KEY_LENGTH }, P256Kem>(tag) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_keygen::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(tag) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_keygen::<{ MlKem512::KEY_LENGTH }, MlKem512>(tag) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_keygen::<{ MlKem768::KEY_LENGTH }, MlKem768>(tag) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_keygen::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(tag) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_keygen::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(tag) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_keygen::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(tag) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_keygen::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(tag) + } + KemTag::Abe => { + let access_structure = access_structure.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot execute a Covercrypt key generation without an access structure" + .to_owned(), + ) + })?; + let cc = Covercrypt::default(); + let (mut msk, _) = cc.setup()?; + msk.access_structure = access_structure; + let mpk = cc.update_msk(&mut msk)?; + Ok(( + ConfigurableKemDk(tag, msk.serialize()?), + ConfigurableKemEk(tag, mpk.serialize()?), + )) + } + } + } + + pub fn enc( + ek: &ConfigurableKemEk, + access_policy: Option<&AccessPolicy>, + ) -> Result<(Zeroizing>, ConfigurableKemEnc), Error> { + match ek.get_tag() { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_enc::<{ P256Kem::KEY_LENGTH }, P256Kem>(ek) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_enc::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(ek) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_enc::<{ MlKem512::KEY_LENGTH }, MlKem512>(ek) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_enc::<{ MlKem768::KEY_LENGTH }, MlKem768>(ek) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_enc::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(ek) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_enc::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(ek) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_enc::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(ek) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_enc::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(ek) + } + KemTag::Abe => { + let ap = access_policy.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot create a Covercrypt encapsulation without an access policy" + .to_owned(), + ) + })?; + let tag = ek.get_tag(); + let mpk = MasterPublicKey::deserialize(ek.get_bytes())?; + let (key, enc) = Covercrypt::default().encaps(&mpk, ap)?; + Ok((key.serialize()?, ConfigurableKemEnc(tag, enc.serialize()?))) + } + } + } + + pub fn dec( + dk: &ConfigurableKemDk, + enc: &ConfigurableKemEnc, + ) -> Result>, Error> { + match dk.get_tag() { + KemTag::PreQuantum(PreQuantumKemTag::P256) => { + generic_dec::<{ P256Kem::KEY_LENGTH }, P256Kem>(dk, enc) + } + KemTag::PreQuantum(PreQuantumKemTag::R25519) => { + generic_dec::<{ R25519Kem::KEY_LENGTH }, R25519Kem>(dk, enc) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem512) => { + generic_dec::<{ MlKem512::KEY_LENGTH }, MlKem512>(dk, enc) + } + KemTag::PostQuantum(PostQuantumKemTag::MlKem768) => { + generic_dec::<{ MlKem768::KEY_LENGTH }, MlKem768>(dk, enc) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem512) => { + generic_dec::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem512>, + >(dk, enc) + } + KemTag::Hybridized(PreQuantumKemTag::P256, PostQuantumKemTag::MlKem768) => { + generic_dec::< + { P256Kem::KEY_LENGTH }, + KemCombiner<{ P256Kem::KEY_LENGTH }, P256Kem, MlKem768>, + >(dk, enc) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem512) => { + generic_dec::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem512>, + >(dk, enc) + } + KemTag::Hybridized(PreQuantumKemTag::R25519, PostQuantumKemTag::MlKem768) => { + generic_dec::< + { R25519Kem::KEY_LENGTH }, + KemCombiner<{ R25519Kem::KEY_LENGTH }, R25519Kem, MlKem768>, + >(dk, enc) + } + KemTag::Abe => { + if enc.get_tag() != KemTag::Abe { + return Err(Error::OperationNotPermitted(format!( + "heterogeneous decapsulation-key and encapsulation tags: {:?} != {:?}", + KemTag::Abe, + enc.get_tag(), + ))); + } + + let enc = XEnc::deserialize(enc.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; + + let usk = UserSecretKey::deserialize(dk.get_bytes()).map_err(|e| { + Error::ConversionFailed(format!( + "failed deserializing the CoverCrypt encapsulation in configurable KEM: {e}" + )) + })?; + + let key = Covercrypt::default().decaps(&usk, &enc)?.ok_or_else(|| { + Error::OperationNotPermitted( + "cannot open Covercrypt encapsulation: incompatible access rights" + .to_owned(), + ) + })?; + + Ok(key.serialize()?) + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::gen_structure; + + use super::*; + use cosmian_crypto_core::bytes_ser_de::test_serialization; + + #[test] + fn test_tag_serialization() { + // Exhaustively test serializations. + test_serialization(&KemTag::PreQuantum(PreQuantumKemTag::P256)).unwrap(); + test_serialization(&KemTag::PreQuantum(PreQuantumKemTag::R25519)).unwrap(); + test_serialization(&KemTag::PostQuantum(PostQuantumKemTag::MlKem512)).unwrap(); + test_serialization(&KemTag::Hybridized( + PreQuantumKemTag::P256, + PostQuantumKemTag::MlKem512, + )) + .unwrap(); + test_serialization(&KemTag::Hybridized( + PreQuantumKemTag::R25519, + PostQuantumKemTag::MlKem512, + )) + .unwrap(); + test_serialization(&KemTag::Abe).unwrap(); + } + + #[test] + fn test_configurable_kem() { + fn run_test(tag: KemTag) { + let (dk, ek) = ConfigurableKEM::keygen(tag, None).unwrap(); + test_serialization(&dk).unwrap(); + test_serialization(&ek).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&ek, None).unwrap(); + test_serialization(&enc).unwrap(); + let key_ = ConfigurableKEM::dec(&dk, &enc).unwrap(); + assert_eq!(key, key_); + } + + run_test(KemTag::PreQuantum(PreQuantumKemTag::P256)); + + run_test(KemTag::PreQuantum(PreQuantumKemTag::R25519)); + + run_test(KemTag::PostQuantum(PostQuantumKemTag::MlKem512)); + + run_test(KemTag::PostQuantum(PostQuantumKemTag::MlKem768)); + + run_test(KemTag::Hybridized( + PreQuantumKemTag::P256, + PostQuantumKemTag::MlKem512, + )); + + run_test(KemTag::Hybridized( + PreQuantumKemTag::P256, + PostQuantumKemTag::MlKem768, + )); + + run_test(KemTag::Hybridized( + PreQuantumKemTag::R25519, + PostQuantumKemTag::MlKem512, + )); + run_test(KemTag::Hybridized( + PreQuantumKemTag::R25519, + PostQuantumKemTag::MlKem768, + )); + + println!("testing CoverCrypt ABE..."); + let mut access_structure = AccessStructure::new(); + gen_structure(&mut access_structure, true).unwrap(); + let usk_access_policy = AccessPolicy::parse("DPT::MKG").unwrap(); + let enc_access_policy = AccessPolicy::parse("*").unwrap(); + + let (msk, mpk) = ConfigurableKEM::keygen(KemTag::Abe, Some(access_structure)).unwrap(); + let tag = msk.get_tag(); + let mut msk = MasterSecretKey::deserialize(msk.get_bytes()).unwrap(); + let usk = Covercrypt::default() + .generate_user_secret_key(&mut msk, &usk_access_policy) + .unwrap(); + let usk = ConfigurableKemDk::new(tag, usk.serialize().unwrap()).unwrap(); + let (key, enc) = ConfigurableKEM::enc(&mpk, Some(&enc_access_policy)).unwrap(); + let key_ = ConfigurableKEM::dec(&usk, &enc).unwrap(); + assert_eq!(key, key_); + } +} diff --git a/src/lib.rs b/src/lib.rs index b900d535..f8e3cb81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,29 +8,15 @@ //! the DDH and LWE", T. Brézot, P. de Perthuis and D. Pointcheval 2023. //! [2] "A Proposal for an ISO Standard for Public Key Encryption (version 2.1)", Shoup 2001. -mod error; - -mod abe_policy; -mod ae; -mod core; +mod abe; mod data_struct; -mod encrypted_header; - -pub mod api; -pub mod traits; - -pub use abe_policy::{AccessStructure, EncryptionHint, QualifiedAttribute}; +mod error; +mod kem; +mod providers; #[cfg(any(test, feature = "test-utils"))] -pub mod test_utils; - -#[cfg(feature = "test-utils")] -pub use abe_policy::gen_structure; - -#[cfg(feature = "test-utils")] -pub use test_utils::cc_keygen; +mod test_utils; -pub use self::core::{MasterPublicKey, MasterSecretKey, UserSecretKey, XEnc}; -pub use abe_policy::AccessPolicy; -pub use encrypted_header::{CleartextHeader, EncryptedHeader}; +pub use abe::*; pub use error::Error; +pub use kem::*; diff --git a/src/providers.rs b/src/providers.rs new file mode 100644 index 00000000..a50688f8 --- /dev/null +++ b/src/providers.rs @@ -0,0 +1,5 @@ +pub mod kem; +mod nike; + +pub(crate) use kem::MlKem; +pub(crate) use nike::ElGamal; diff --git a/src/core/kem.rs b/src/providers/kem.rs similarity index 71% rename from src/core/kem.rs rename to src/providers/kem.rs index f798da4a..47e9576e 100644 --- a/src/core/kem.rs +++ b/src/providers/kem.rs @@ -4,7 +4,7 @@ compile_error!("only one MLKEM version can be chosen at a time"); pub mod mlkem; #[cfg(feature = "mlkem-512")] -pub use mlkem::MlKem512 as MlKem; +pub(crate) use mlkem::MlKem512 as MlKem; #[cfg(feature = "mlkem-768")] -pub use mlkem::MlKem768 as MlKem; +pub(crate) use mlkem::MlKem768 as MlKem; diff --git a/src/core/kem/mlkem.rs b/src/providers/kem/mlkem.rs similarity index 80% rename from src/core/kem/mlkem.rs rename to src/providers/kem/mlkem.rs index e3722163..da7932c4 100644 --- a/src/core/kem/mlkem.rs +++ b/src/providers/kem/mlkem.rs @@ -1,22 +1,35 @@ -use cosmian_crypto_core::bytes_ser_de::{Deserializer, Serializable, Serializer}; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret}; +use crate::Error; +use core::ops::Deref; +use cosmian_crypto_core::{ + bytes_ser_de::{Deserializer, Serializable, Serializer}, + reexport::{ + rand_core::CryptoRngCore, + zeroize::{Zeroize, ZeroizeOnDrop}, + }, + traits::KEM, + CryptoCoreError, Secret, SymmetricKey, +}; use ml_kem::{ array::Array, kem::{Decapsulate, Encapsulate}, EncodedSizeUser, KemCore, }; -use zeroize::Zeroize; -use crate::traits::Kem; -use crate::{core::SHARED_SECRET_LENGTH, Error}; +pub const KEY_LENGTH: usize = 32; macro_rules! make_mlkem { ($base: ident, $ek: ident, $ek_len: literal, $dk: ident, $dk_len: literal, $enc: ident, $enc_len:literal) => { #[derive(Debug, PartialEq, Clone)] pub struct $ek(Box<::EncapsulationKey>); + impl From<&$dk> for $ek { + fn from(dk: &$dk) -> Self { + Self(Box::new(dk.0.encapsulation_key().clone())) + } + } + impl Serializable for $ek { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $ek_len @@ -40,6 +53,9 @@ macro_rules! make_mlkem { #[derive(Debug, Clone, PartialEq)] pub struct $dk(Box<::DecapsulationKey>); + // DecapsulationKey implements ZeroizeOnDrop. + impl ZeroizeOnDrop for $dk {} + #[allow(dead_code)] impl $dk { pub fn ek(&self) -> $ek { @@ -48,7 +64,7 @@ macro_rules! make_mlkem { } impl Serializable for $dk { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $dk_len @@ -72,15 +88,23 @@ macro_rules! make_mlkem { #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct $enc(Box::CiphertextSize>>); + impl Deref for $enc { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } + } + impl Serializable for $enc { - type Error = Error; + type Error = CryptoCoreError; fn length(&self) -> usize { $enc_len } fn write(&self, ser: &mut Serializer) -> Result { - Ok(ser.write_array(&self.0)?) + ser.write_array(&self.0) } fn read(de: &mut Deserializer) -> Result { @@ -91,15 +115,13 @@ macro_rules! make_mlkem { } } + #[derive(Debug, Copy, Clone)] pub struct $base; - impl Kem for $base { + impl KEM for $base { type EncapsulationKey = $ek; type DecapsulationKey = $dk; - type SessionKey = Secret; - type Encapsulation = $enc; - type Error = Error; fn keygen( @@ -112,23 +134,23 @@ macro_rules! make_mlkem { fn enc( ek: &Self::EncapsulationKey, rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error> { + ) -> Result<(SymmetricKey, Self::Encapsulation), Self::Error> { let (enc, mut ss) = ek.0.encapsulate(rng) .map_err(|e| Error::Kem(format!("{:?}", e)))?; let ss = Secret::from_unprotected_bytes(ss.as_mut()); - Ok((ss, $enc(Box::new(enc)))) + Ok((ss.into(), $enc(Box::new(enc)))) } fn dec( dk: &Self::DecapsulationKey, enc: &Self::Encapsulation, - ) -> Result { + ) -> Result, Self::Error> { let mut ss = dk.0.decapsulate(&enc.0) .map_err(|e| Self::Error::Kem(format!("{e:?}")))?; let ss = Secret::from_unprotected_bytes(ss.as_mut()); - Ok(ss) + Ok(ss.into()) } } }; diff --git a/src/core/nike.rs b/src/providers/nike.rs similarity index 55% rename from src/core/nike.rs rename to src/providers/nike.rs index 6ad18b96..6f496341 100644 --- a/src/core/nike.rs +++ b/src/providers/nike.rs @@ -2,13 +2,7 @@ compile_error!("only one elliptic curve can be chosen at a time"); #[cfg(feature = "curve25519")] -mod r25519; - -#[cfg(feature = "curve25519")] -pub use r25519::R25519 as ElGamal; - -#[cfg(feature = "p-256")] -mod p256; +pub use cosmian_rust_curve25519_provider::R25519 as ElGamal; #[cfg(feature = "p-256")] -pub use p256::P256 as ElGamal; +pub use cosmian_openssl_provider::p256::P256 as ElGamal; diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index f45aa7b7..bb16f4ae 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -1,6 +1,8 @@ -use crate::{abe_policy::gen_structure, api::Covercrypt, Error, MasterPublicKey, MasterSecretKey}; - -//pub mod non_regression; +use crate::{ + abe::gen_structure, + abe::{Covercrypt, MasterPublicKey, MasterSecretKey}, + Error, +}; /// Creates the test access structure. pub fn cc_keygen( @@ -17,11 +19,9 @@ pub fn cc_keygen( mod tests { use super::*; - use crate::{ - abe_policy::{AccessPolicy, EncryptionHint, QualifiedAttribute}, - api::Covercrypt, - traits::KemAc, - EncryptedHeader, + use crate::abe::{ + encrypted_header::EncryptedHeader, traits::KemAc, AccessPolicy, Covercrypt, EncryptionHint, + QualifiedAttribute, }; #[test] diff --git a/src/traits.rs b/src/traits.rs index 0e23dc19..8b137891 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,196 +1 @@ -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret, SymmetricKey}; -use std::ops::Add; -use std::ops::AddAssign; -use std::ops::Div; -use std::ops::Mul; -use std::ops::MulAssign; -use std::ops::Sub; -use std::ops::SubAssign; -use zeroize::Zeroizing; -use crate::AccessPolicy; - -pub trait KemAc { - type EncapsulationKey; - type DecapsulationKey; - type Encapsulation; - type Error: std::error::Error; - - /// Generates a new encapsulation for the given access policy. - /// - /// # Error - /// - /// Returns an error if the access policy is not valid. - fn encaps( - &self, - ek: &Self::EncapsulationKey, - ap: &AccessPolicy, - ) -> Result<(Secret, Self::Encapsulation), Self::Error>; - - /// Attempts opening the given encapsulation with the given key. Returns the encapsulated - /// secret upon success or `None` if this key was not authorized to open this encapsulation. - fn decaps( - &self, - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result>, Self::Error>; -} - -pub trait AE { - type Error: std::error::Error; - - /// Encrypts the given plaintext using the given key. - fn encrypt( - rng: &mut impl CryptoRngCore, - key: &SymmetricKey, - ptx: &[u8], - ) -> Result, Self::Error>; - - /// Decrypts the given ciphertext using the given key. - /// - /// # Error - /// - /// Returns an error if the integrity of the ciphertext could not be verified. - fn decrypt( - key: &SymmetricKey, - ctx: &[u8], - ) -> Result>, Self::Error>; -} - -pub trait PkeAc> { - type EncryptionKey; - type DecryptionKey; - type Ciphertext; - type Error: std::error::Error; - - /// Encrypts the given plaintext under the given access policy. - /// - /// # Error - /// - /// Returns an error if the access policy is not valid. - fn encrypt( - &self, - ek: &Self::EncryptionKey, - ap: &AccessPolicy, - ptx: &[u8], - ) -> Result; - - /// Attempts decrypting the given ciphertext with the given key. Returns the - /// plaintext upon success, or `None` if this key was not authorized to - /// decrypt this ciphertext. - fn decrypt( - &self, - dk: &Self::DecryptionKey, - ctx: &Self::Ciphertext, - ) -> Result>>, Self::Error>; -} - -pub trait Kem { - type EncapsulationKey; - type DecapsulationKey; - type SessionKey; - type Encapsulation; - type Error: std::error::Error; - - /// Generates a new random keypair. - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), Self::Error>; - - /// Generates an encapsulation of a random session key, and returns both the - /// key and its encapsulation. - fn enc( - ek: &Self::EncapsulationKey, - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SessionKey, Self::Encapsulation), Self::Error>; - - /// Attempts opening the given encapsulation. Upon failure to decapsulate, - /// returns a random session key. - fn dec( - dk: &Self::DecapsulationKey, - enc: &Self::Encapsulation, - ) -> Result; -} - -pub trait Nike { - type SecretKey: Sampling; - type PublicKey: for<'a> From<&'a Self::SecretKey>; - type SessionKey; - type Error: std::error::Error; - - /// Generates a new random keypair. - fn keygen( - rng: &mut impl CryptoRngCore, - ) -> Result<(Self::SecretKey, Self::PublicKey), Self::Error>; - - /// Generates the session key associated to the given keypair. - fn session_key( - sk: &Self::SecretKey, - pk: &Self::PublicKey, - ) -> Result; -} - -pub trait Sampling { - fn random(rng: &mut impl CryptoRngCore) -> Self; - fn hash(seed: &[u8]) -> Self; -} - -pub trait Zero { - fn zero() -> Self; - fn is_zero(&self) -> bool; -} - -pub trait One { - fn one() -> Self; - fn is_one(&self) -> bool; -} - -pub trait Group: - Sized - + Zero - + Add - + AddAssign - + Sub - + SubAssign - + for<'a> Add<&'a Self, Output = Self> - + for<'a> Sub<&'a Self, Output = Self> -where - for<'a, 'b> &'a Self: Add<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Sub<&'b Self, Output = Self>, -{ -} - -pub trait Ring: - Group - + Zero - + Mul - + MulAssign - + Div> - + for<'a> Mul<&'a Self, Output = Self> - + for<'a> Div<&'a Self, Output = Result> -where - for<'a, 'b> &'a Self: Add<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Sub<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Mul<&'b Self, Output = Self>, - for<'a, 'b> &'a Self: Div<&'b Self, Output = Result>, -{ - type DivError; -} - -pub trait KeyHomomorphicNike: Nike -where - Self::PublicKey: Group, - Self::SecretKey: Ring, - Self::PublicKey: Mul, - for<'a> Self::PublicKey: Mul<&'a Self::SecretKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::PublicKey: Add<&'b Self::PublicKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::PublicKey: Sub<&'b Self::PublicKey, Output = Self::PublicKey>, - for<'a, 'b> &'a Self::SecretKey: Add<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Sub<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Mul<&'b Self::SecretKey, Output = Self::SecretKey>, - for<'a, 'b> &'a Self::SecretKey: Div< - &'b Self::SecretKey, - Output = Result::DivError>, - >, -{ -}