From 527a4f4eeff070108aba796f129f761f4288d963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 31 Jan 2024 09:38:05 +0100 Subject: [PATCH] fix: serialization --- .gitignore | 3 +- Cargo.toml | 12 +- examples/decrypt.rs | 45 +- examples/encrypt.rs | 117 ++- .../{partitions.rs => coordinates.rs} | 32 +- src/abe_policy/mod.rs | 4 +- src/abe_policy/policy_versions.rs | 4 +- src/core/api.rs | 21 +- src/core/mod.rs | 11 +- src/core/pke/elgamal.rs~ | 52 -- src/core/pke/elgamal/mod.rs | 78 +- src/core/pke/elgamal/mod.rs~ | 1 - src/core/pke/elgamal/ristretto_25519.rs~ | 69 -- src/core/pke/mod.rs~ | 1 - src/core/pke/postquantum.rs~ | 38 - src/core/pke/postquantum/kyber.rs | 91 +- src/core/pke/postquantum/kyber.rs~ | 54 -- src/core/pke/postquantum/mod.rs | 46 +- src/core/pke/postquantum/mod.rs~ | 1 - src/core/pq_kem/kyber.rs | 1 - src/core/pq_kem/kyber.rs~ | 25 - src/core/pq_kem/mod.rs | 1 - src/core/pq_kem/mod.rs~ | 1 - src/core/primitives.rs | 316 +------ src/core/serialization.rs | 538 ------------ src/core/serialization/mod.rs | 815 ++++++++++++++++++ src/core/tests.rs | 252 +++--- src/core/tests.rs~ | 116 --- src/data_struct/revision_map.rs | 4 +- src/lib.rs | 2 - src/test_utils/mod.rs | 688 +++++++-------- src/test_utils/non_regression.rs | 30 +- .../tests_data/non_regression_vector.json | 2 +- 33 files changed, 1646 insertions(+), 1825 deletions(-) rename src/abe_policy/{partitions.rs => coordinates.rs} (82%) delete mode 100644 src/core/pke/elgamal.rs~ delete mode 100644 src/core/pke/elgamal/mod.rs~ delete mode 100644 src/core/pke/elgamal/ristretto_25519.rs~ delete mode 100644 src/core/pke/mod.rs~ delete mode 100644 src/core/pke/postquantum.rs~ delete mode 100644 src/core/pke/postquantum/kyber.rs~ delete mode 100644 src/core/pke/postquantum/mod.rs~ delete mode 100644 src/core/pq_kem/kyber.rs delete mode 100644 src/core/pq_kem/kyber.rs~ delete mode 100644 src/core/pq_kem/mod.rs delete mode 100644 src/core/pq_kem/mod.rs~ delete mode 100644 src/core/serialization.rs create mode 100644 src/core/serialization/mod.rs delete mode 100644 src/core/tests.rs~ diff --git a/.gitignore b/.gitignore index 62f1ff88..ebddeaec 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /.vscode Cargo.lock **/.#* -**/#*# \ No newline at end of file +**/#*# +**/*~ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 36e13043..5ad83579 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,21 @@ name = "cosmian_cover_crypt" # The cdylib is only interesting if the `--features ffi` flag is set on build # This does not seem to be actionable conditionally https://github.com/rust-lang/cargo/issues/4881 +[[example]] +name = "encrypt" +required-features = ["serialization"] + +[[example]] +name = "decrypt" +required-features = ["serialization"] + [features] full_bench = [] hybridized_bench = [] +serialization = [] [dependencies] -cosmian_crypto_core = { git="https://github.com/Cosmian/crypto_core.git", branch="feat/add_neutral_scalars", default-features = false, features = ["ser", "sha3", "aes", "curve25519"] } +cosmian_crypto_core = { git="https://github.com/Cosmian/crypto_core.git", branch="develop", default-features = false, features = ["ser", "sha3", "aes", "curve25519"] } pqc_kyber = { version = "0.4", features = ["std", "hazmat"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -33,4 +42,3 @@ zeroize = "1.6.0" [dev-dependencies] base64 = { version = "0.21.0" } -criterion = { version = "0.4", features = ["html_reports"], default_features = false } diff --git a/examples/decrypt.rs b/examples/decrypt.rs index bd8867b6..d21d8c55 100644 --- a/examples/decrypt.rs +++ b/examples/decrypt.rs @@ -1,31 +1,26 @@ -fn main() { - #[cfg(feature = "serialization")] - { - use base64::{ - alphabet::STANDARD, - engine::{GeneralPurpose, GeneralPurposeConfig}, - Engine, - }; - use cosmian_cover_crypt::{Covercrypt, EncryptedHeader, UserSecretKey}; - use cosmian_crypto_core::bytes_ser_de::Serializable; +const USK: &str = "sUBriVEsM8HowAKOsIs9Y5p+Xa8JwJf84rlBHXzfkg+BJ6PzK79cT37Wsku5pMVKrfi/eIWhufC2pjYNMBOUAgMAH+waKc6AuwtAaYXXUAJA0K/uc5A22nXEShVXykjZ8QIBuNMsBrpSQaW0hAZ0AFvJTlwSM2BgI8m5k4wj3kQQ9NaP4cK6VvwyFlx7ZwwMZkJoCSgfiPvN4XqeWmiw/hehgjU2HkgYDQJf9QABaUoa74qM42SBnNubVVVnPvO5zrZ++NgoIHt4VPJfsMu157i6bakhOEWJy/CduXYGqedOGsNpj+gq0VsanjqeJ9gZ6sKjCknGoTxbEipIrPUO5jqJLzmJilS0ycAhR1mYlGU9yApPithxeIMaQFAAegOUTVFDFausKPbDZ1IYEWyftONyaaelMyJF4eCQIWILEihLuoscwGkVlYmRttcNVrLDW3yHN/cFQgiBSNVOi/RTLSpdsaMtattwDcaEiRUvCxe2gfaCYQV9zPIsdfAG2TIf2MPLcqUu0EBnIohX/TKyOTavt7HB76d/pCWT7sceu/YldifCYUoWABA8GbnF6DurlVwhfYUKhThnchbP3mgsEUsy+vQlWIwvrwxgpgqaabNSwJIUCXttY6kYNjJbL5pkahaAnxYLRMcvvKfGtzwlX7ewINAFrMSUBua8abWzgIa2NWlt2/ON7tKrVIaqRMYs8KFJSSsQ4bGEhZfBjWEYacAho8bFMuqamQgxaJhyj/J+tZrA24A5ckmPSPeGOIKealWkQmy8f0oAPVsOGaQFcBwLMPe4qRl49tpM6kRgpsyOkeQYOdpQ7Rue0xMKR+UhdPJkzYcGoVlAIGRmvSQzamw0kEYYV6yWr7AHiiqPZ/AuS7RcYUzPB0tSZ9BSdJKSX1jGf2aCbPPD8DxQ6VVPqWOekHshGGBOEzqBPhxtpfQB/lhl2KG/XOgUB6Kqs/zF14oLvlBL2XU2fCaj76ydrdOlo5JSSLKU2pkWASMLNGIM5TUqBsi/D4A9lLlqW5dDOik4ujNLbMAl4Yt4HxOe+pGKhFexfwJBgdCB6IBsatK/nWnIMuW9ZZFbUQG7oTOuFMKvurQ2OsyWa9VA06ldDFWIOQyzgydm2pp3XnXKaJElyYMSE1Ef4KRgEqgYA6J8yNIoI0o092y/HUxAoWWc5PesDmeqbbaLFEFdAqJ4gerNbtS1uAQWIsJzHAC2pGgxmLE7WkaD1IQ3fFgTHPOR97EdlbbM5YGa78mUpHqS0NpYQpbHluRUbyJ9uyIcvAxHewmIKIfDKnZ1z+YG/sIccCY+tFlGxBApSym1tgRBREcgkcYUAogq5nsKY5mvhXxICTCU4WtxZLxl17CC6du5pRSH+9hAMvI2C5R/6udvXkaYc0Ki35WsO5QAHlOVLBwbKLfM9xlhPkFf9qkqgKoMfqEJDcOn79QfOVFFqSfDI9Ep7Wl2YMK3r1apC2gw9xmwjBsMkEYLq2lNH4l0OqGpXDpAQCMwyTEOs5XHjWpsgxIvDId3HDu2DCBKYhUavtYHSgHNUeITVraWmhI9ygkBVMJKLfUGIVq/UwQvhzeVFePNhhN59HuWSjCyonWG6iFQeCCb9Dq0z2DNowiOnVdDWIM5g7BRTgSKBBlE/kArtfaDPyCXENUnwOZDpka63VMCNGak45IkSPT5SN7pdkPW9uIQBaAb04uYPbnALTij4xUzLwkAqXQvjNzwE8/0JzoWmWfdf9zdi1jdzpG1ZTqir0h74gc="; - const USK: &str = "sUBriVEsM8HowAKOsIs9Y5p+Xa8JwJf84rlBHXzfkg+BJ6PzK79cT37Wsku5pMVKrfi/eIWhufC2pjYNMBOUAgMAH+waKc6AuwtAaYXXUAJA0K/uc5A22nXEShVXykjZ8QIBuNMsBrpSQaW0hAZ0AFvJTlwSM2BgI8m5k4wj3kQQ9NaP4cK6VvwyFlx7ZwwMZkJoCSgfiPvN4XqeWmiw/hehgjU2HkgYDQJf9QABaUoa74qM42SBnNubVVVnPvO5zrZ++NgoIHt4VPJfsMu157i6bakhOEWJy/CduXYGqedOGsNpj+gq0VsanjqeJ9gZ6sKjCknGoTxbEipIrPUO5jqJLzmJilS0ycAhR1mYlGU9yApPithxeIMaQFAAegOUTVFDFausKPbDZ1IYEWyftONyaaelMyJF4eCQIWILEihLuoscwGkVlYmRttcNVrLDW3yHN/cFQgiBSNVOi/RTLSpdsaMtattwDcaEiRUvCxe2gfaCYQV9zPIsdfAG2TIf2MPLcqUu0EBnIohX/TKyOTavt7HB76d/pCWT7sceu/YldifCYUoWABA8GbnF6DurlVwhfYUKhThnchbP3mgsEUsy+vQlWIwvrwxgpgqaabNSwJIUCXttY6kYNjJbL5pkahaAnxYLRMcvvKfGtzwlX7ewINAFrMSUBua8abWzgIa2NWlt2/ON7tKrVIaqRMYs8KFJSSsQ4bGEhZfBjWEYacAho8bFMuqamQgxaJhyj/J+tZrA24A5ckmPSPeGOIKealWkQmy8f0oAPVsOGaQFcBwLMPe4qRl49tpM6kRgpsyOkeQYOdpQ7Rue0xMKR+UhdPJkzYcGoVlAIGRmvSQzamw0kEYYV6yWr7AHiiqPZ/AuS7RcYUzPB0tSZ9BSdJKSX1jGf2aCbPPD8DxQ6VVPqWOekHshGGBOEzqBPhxtpfQB/lhl2KG/XOgUB6Kqs/zF14oLvlBL2XU2fCaj76ydrdOlo5JSSLKU2pkWASMLNGIM5TUqBsi/D4A9lLlqW5dDOik4ujNLbMAl4Yt4HxOe+pGKhFexfwJBgdCB6IBsatK/nWnIMuW9ZZFbUQG7oTOuFMKvurQ2OsyWa9VA06ldDFWIOQyzgydm2pp3XnXKaJElyYMSE1Ef4KRgEqgYA6J8yNIoI0o092y/HUxAoWWc5PesDmeqbbaLFEFdAqJ4gerNbtS1uAQWIsJzHAC2pGgxmLE7WkaD1IQ3fFgTHPOR97EdlbbM5YGa78mUpHqS0NpYQpbHluRUbyJ9uyIcvAxHewmIKIfDKnZ1z+YG/sIccCY+tFlGxBApSym1tgRBREcgkcYUAogq5nsKY5mvhXxICTCU4WtxZLxl17CC6du5pRSH+9hAMvI2C5R/6udvXkaYc0Ki35WsO5QAHlOVLBwbKLfM9xlhPkFf9qkqgKoMfqEJDcOn79QfOVFFqSfDI9Ep7Wl2YMK3r1apC2gw9xmwjBsMkEYLq2lNH4l0OqGpXDpAQCMwyTEOs5XHjWpsgxIvDId3HDu2DCBKYhUavtYHSgHNUeITVraWmhI9ygkBVMJKLfUGIVq/UwQvhzeVFePNhhN59HuWSjCyonWG6iFQeCCb9Dq0z2DNowiOnVdDWIM5g7BRTgSKBBlE/kArtfaDPyCXENUnwOZDpka63VMCNGak45IkSPT5SN7pdkPW9uIQBaAb04uYPbnALTij4xUzLwkAqXQvjNzwE8/0JzoWmWfdf9zdi1jdzpG1ZTqir0h74gc="; +const HEADER: &str = "dkKN4Ga3BcJyspF5FntqHYwXI/yoUugzqcF0DPCluyUckuqbpXDRgSWdnyh5k0wCNAD6BgOR3WXjjrg6bF/mR/ICzF1XqtqK5Fn0uZ7FgYkBAbjlkpf7VV3M7BrwEB1FIJ2ZhekRLPayJvvTTdE6DY9ddr3IbuzEiMASs2P5nRabJe+p1xLJ9Yp1+8roV6GgWy37m7ysu24nxtKKPiyCIh29zxt4WjG+rJZOS0iJ2ETEjGPG5yyY7LYsOwsp6tQlb/XGmqd91TxjobKOnRceYRV29E1vj/uwzyT8OtRfWVITK1v1ku4knbWPhXFGVdiUpME+QqZIrjulMPI/AKoABa925Eq8h7UM64p91288I5Lq+S8+ysIaJv2nlydxb9BZBg/NeG5YGD+NIKm0EXoDekmbX91rLktO0cgFYEhNm+43BvLLXoyD7iU2bT7dQKNMUxMyoDvhB40aOkTWKJGCFnnrLf+oSxGDkCWTNaWYCJ3qygxjETGdigQzV6bzaGh2f/pjAzRHwKJHgywW4E2BCGrM4BJZvjhl44sZ14HWLofl3hNzLfrdJLFZz7Ua3PxHlidQ+ZsZSOW61G9Xt5vwHYZDtQOMVcX7tyNTNmMoIlghHjm66H8JDtuiduy08VE+7/Bk2anWhljNX2f2a/+hOjMbzqG+zaAtgpppylkvG/WlR1xLohGlLKdAWYRETIrI0oKi8RKFNHdnuPCZRDHlHDjhc273rPvsn7FlolSSTxbjvyTacB7jNwKfxW32eEK82p04GQyVN5uEBQBZUG4heNnoba54ybeU66PbOOclgE+LoblnvE0lqv8VxbCvbM3VVxTJ0rMj8ZshcxEWDv1v8E1JkGeq3QcDK/Ww1wBDuwLhxY8xbKmD40bmS8nGkRTLDuyhtg009oboT4+SL83Dz5WXEBcwO2m8Noor0QgtMjuyCAR92gvNSvewk03Q6X80W5Rd+tvw6HMwnoevpKge56akgXnQPnx76h78jIrEZ8FDZ7g6dlqPf7Lr9PnWQKhVHZwh1CBN0Rbb0ylumpvkiSCV7kF1tqPE89LO31jmYdULuQmMclKRst+SdXWSM7t2J/CeFmTgSiJ896P95imsC3XcWvmJqk+qlBNynP/Azmn775H59UHj21t8z2sFYwYkDfDRyBSxVLW/08nz+9vVy05qmrUqej/Iv4sHHjE3cgLCD+rjPOHssoFdr4ubG441N+1HpvoxFbo0Yk/fNH9JzqQj09eA4Wx6t3fjOWYJ8CESyK7lKBANYrQ5IdYuY3tk/Z3uR/uYff2pKu6xuisLF66FVC80WokFlvCEoYLLJs/q2/16mMo7cqkbhI0SLJgaAK8wIvJUS64Xxb3+SVVPYkeVFywrAb89J0kg6txXIuOfHxGcDZu7QaEPQUos7pwnbZBeXb/Q1RHbEE6cXr1pGB6wU+0gABNzLLTRNHuu2qm33myLkNQRilOyR7EqrDmLwY3ysGE5tmxYH5+zEu7nR/rkozqwpueGmrDMibRRw14kLRCNh6YkqvKsbSg1Tg0EKZe2eLGG0mmgj5jcYqusRcVtAA=="; - const HEADER: &str = "dkKN4Ga3BcJyspF5FntqHYwXI/yoUugzqcF0DPCluyUckuqbpXDRgSWdnyh5k0wCNAD6BgOR3WXjjrg6bF/mR/ICzF1XqtqK5Fn0uZ7FgYkBAbjlkpf7VV3M7BrwEB1FIJ2ZhekRLPayJvvTTdE6DY9ddr3IbuzEiMASs2P5nRabJe+p1xLJ9Yp1+8roV6GgWy37m7ysu24nxtKKPiyCIh29zxt4WjG+rJZOS0iJ2ETEjGPG5yyY7LYsOwsp6tQlb/XGmqd91TxjobKOnRceYRV29E1vj/uwzyT8OtRfWVITK1v1ku4knbWPhXFGVdiUpME+QqZIrjulMPI/AKoABa925Eq8h7UM64p91288I5Lq+S8+ysIaJv2nlydxb9BZBg/NeG5YGD+NIKm0EXoDekmbX91rLktO0cgFYEhNm+43BvLLXoyD7iU2bT7dQKNMUxMyoDvhB40aOkTWKJGCFnnrLf+oSxGDkCWTNaWYCJ3qygxjETGdigQzV6bzaGh2f/pjAzRHwKJHgywW4E2BCGrM4BJZvjhl44sZ14HWLofl3hNzLfrdJLFZz7Ua3PxHlidQ+ZsZSOW61G9Xt5vwHYZDtQOMVcX7tyNTNmMoIlghHjm66H8JDtuiduy08VE+7/Bk2anWhljNX2f2a/+hOjMbzqG+zaAtgpppylkvG/WlR1xLohGlLKdAWYRETIrI0oKi8RKFNHdnuPCZRDHlHDjhc273rPvsn7FlolSSTxbjvyTacB7jNwKfxW32eEK82p04GQyVN5uEBQBZUG4heNnoba54ybeU66PbOOclgE+LoblnvE0lqv8VxbCvbM3VVxTJ0rMj8ZshcxEWDv1v8E1JkGeq3QcDK/Ww1wBDuwLhxY8xbKmD40bmS8nGkRTLDuyhtg009oboT4+SL83Dz5WXEBcwO2m8Noor0QgtMjuyCAR92gvNSvewk03Q6X80W5Rd+tvw6HMwnoevpKge56akgXnQPnx76h78jIrEZ8FDZ7g6dlqPf7Lr9PnWQKhVHZwh1CBN0Rbb0ylumpvkiSCV7kF1tqPE89LO31jmYdULuQmMclKRst+SdXWSM7t2J/CeFmTgSiJ896P95imsC3XcWvmJqk+qlBNynP/Azmn775H59UHj21t8z2sFYwYkDfDRyBSxVLW/08nz+9vVy05qmrUqej/Iv4sHHjE3cgLCD+rjPOHssoFdr4ubG441N+1HpvoxFbo0Yk/fNH9JzqQj09eA4Wx6t3fjOWYJ8CESyK7lKBANYrQ5IdYuY3tk/Z3uR/uYff2pKu6xuisLF66FVC80WokFlvCEoYLLJs/q2/16mMo7cqkbhI0SLJgaAK8wIvJUS64Xxb3+SVVPYkeVFywrAb89J0kg6txXIuOfHxGcDZu7QaEPQUos7pwnbZBeXb/Q1RHbEE6cXr1pGB6wU+0gABNzLLTRNHuu2qm33myLkNQRilOyR7EqrDmLwY3ysGE5tmxYH5+zEu7nR/rkozqwpueGmrDMibRRw14kLRCNh6YkqvKsbSg1Tg0EKZe2eLGG0mmgj5jcYqusRcVtAA=="; +fn main() { + use base64::{ + alphabet::STANDARD, + engine::{GeneralPurpose, GeneralPurposeConfig}, + Engine, + }; + use cosmian_cover_crypt::{Covercrypt, EncryptedHeader, UserSecretKey}; + use cosmian_crypto_core::bytes_ser_de::Serializable; - let config: GeneralPurposeConfig = GeneralPurposeConfig::default(); - let transcoder: GeneralPurpose = GeneralPurpose::new(&STANDARD, config); + let config: GeneralPurposeConfig = GeneralPurposeConfig::default(); + let transcoder: GeneralPurpose = GeneralPurpose::new(&STANDARD, config); - let cc = Covercrypt::default(); - let usk = UserSecretKey::deserialize(&transcoder.decode(USK.as_bytes()).unwrap()).unwrap(); - let encrypted_header = - EncryptedHeader::deserialize(&transcoder.decode(HEADER.as_bytes()).unwrap()).unwrap(); - for _ in 0..1000 { - encrypted_header - .decrypt(&cc, &usk, None) - .expect("cannot decrypt hybrid header"); - } + let cc = Covercrypt::default(); + let usk = UserSecretKey::deserialize(&transcoder.decode(USK.as_bytes()).unwrap()).unwrap(); + let encrypted_header = + EncryptedHeader::deserialize(&transcoder.decode(HEADER.as_bytes()).unwrap()).unwrap(); + for _ in 0..1000 { + encrypted_header + .decrypt(&cc, &usk, None) + .expect("cannot decrypt hybrid header"); } - #[cfg(not(feature = "serialization"))] - println!("Use the `serialization` feature to run this example") } diff --git a/examples/encrypt.rs b/examples/encrypt.rs index f705327a..b352672b 100644 --- a/examples/encrypt.rs +++ b/examples/encrypt.rs @@ -1,80 +1,65 @@ -fn main() { - // create policy - #[cfg(all(feature = "serialization", feature = "test_utils"))] +use cosmian_cover_crypt::{ + abe_policy::{AccessPolicy, Policy}, + test_utils::policy, + Covercrypt, EncryptedHeader, MasterPublicKey, MasterSecretKey, +}; + +/// Generates a new USK and encrypted header and prints them. +fn generate_new( + cc: &Covercrypt, + policy: &Policy, + _msk: &mut MasterSecretKey, + mpk: &MasterPublicKey, +) { + let access_policy = + AccessPolicy::parse("Department::FIN && Security Level::Top Secret").unwrap(); + + let (_, _header) = EncryptedHeader::generate(cc, policy, mpk, &access_policy, None, None) + .expect("cannot encrypt header"); + + #[cfg(feature = "serialization")] { use base64::{ alphabet::STANDARD, engine::{GeneralPurpose, GeneralPurposeConfig}, Engine, }; - use cosmian_cover_crypt::{ - abe_policy::{AccessPolicy, Policy}, - test_utils::policy, - Covercrypt, EncryptedHeader, MasterPublicKey, MasterSecretKey, - }; - use cosmian_crypto_core::bytes_ser_de::Serializable; - fn generate_new( - cc: &Covercrypt, - policy: &Policy, - _msk: &MasterSecretKey, - mpk: &MasterPublicKey, - ) { - let access_policy = AccessPolicy::from_boolean_expression( - "Department::FIN && Security Level::Top Secret", + use cosmian_crypto_core::bytes_ser_de::Serializable; + let config: GeneralPurposeConfig = GeneralPurposeConfig::default(); + let transcoder: GeneralPurpose = GeneralPurpose::new(&STANDARD, config); + println!( + "USK = {}", + transcoder.encode( + cc.generate_user_secret_key(_msk, &access_policy, policy) + .unwrap() + .serialize() + .unwrap() ) - .unwrap(); - - let (_, _header) = - EncryptedHeader::generate(cc, policy, mpk, &access_policy, None, None) - .expect("cannot encrypt header"); - - #[cfg(feature = "serialization")] - { - let config: GeneralPurposeConfig = GeneralPurposeConfig::default(); - let transcoder: GeneralPurpose = GeneralPurpose::new(&STANDARD, config); - - println!( - "usk = {}", - transcoder.encode( - cc.generate_user_secret_key(_msk, &access_policy, policy) - .unwrap() - .serialize() - .unwrap() - ) - ); - println!( - "header = {}", - transcoder.encode(_header.serialize().unwrap()) - ); - } - } - - let policy = policy().expect("cannot generate policy"); + ); + println!( + "header = {}", + transcoder.encode(_header.serialize().unwrap()) + ); + } +} - let cc = Covercrypt::default(); - let (_msk, mpk) = cc - .generate_master_keys(&policy) - .expect("cannot generate master keys"); +fn main() { + let policy = policy().expect("cannot generate policy"); + let ap = AccessPolicy::parse("Department::FIN && Security Level::Top Secret") + .expect("cannot parse given access policy"); - // Encryption of a hybridized ciphertext - let access_policy = - AccessPolicy::from_boolean_expression("Department::FIN && Security Level::Top Secret") - .unwrap(); + let cc = Covercrypt::default(); + let (mut msk, _) = cc.setup().expect("cannot generate master keys"); + let mpk = cc + .update_master_keys(&policy, &mut msk) + .expect("cannot update master keys"); - // - // Use the following to update `examples/decrypt.rs` constants. - // - generate_new(&cc, &policy, &_msk, &mpk); + generate_new(&cc, &policy, &mut msk, &mpk); - // encrypt header, use loop to add weight in the flamegraph on it - for _ in 0..1000 { - let _encrypted_header = - EncryptedHeader::generate(&cc, &policy, &mpk, &access_policy, None, None) - .expect("cannot encrypt header"); - } + // Encrypt header, use loop to increase its wight in the flame graph. + for _ in 0..1000 { + EncryptedHeader::generate(&cc, &policy, &mpk, &ap, None, None) + .expect("cannot encrypt header"); } - - #[cfg(not(all(feature = "test_utils", feature = "serialization")))] - println!("Use the `serialization` feature to run this example") } diff --git a/src/abe_policy/partitions.rs b/src/abe_policy/coordinates.rs similarity index 82% rename from src/abe_policy/partitions.rs rename to src/abe_policy/coordinates.rs index 490a4402..9259a03b 100644 --- a/src/abe_policy/partitions.rs +++ b/src/abe_policy/coordinates.rs @@ -1,6 +1,6 @@ use std::{hash::Hash, ops::Deref}; -use cosmian_crypto_core::bytes_ser_de::Serializer; +use cosmian_crypto_core::bytes_ser_de::{to_leb128_len, Serializable, Serializer}; use crate::Error; @@ -56,12 +56,10 @@ impl Coordinate { // guard against overflow of the 1024 bytes buffer below if attribute_ids.len() > 200 { return Err(Error::InvalidAttribute( - "The current implementation does not currently support more than 200 attributes \ - for a partition" - .to_string(), + "The current implementation does not currently support more than 200 attributes for a coordinate".to_string(), )); } - // the sort operation allows to get the same `Partition` for : + // the sort operation allows to get the same `Coordinate` for : // `Department::HR && Level::Secret` // and // `Level::Secret && Department::HR` @@ -95,6 +93,23 @@ impl From<&[u8]> for Coordinate { } } +impl Serializable for Coordinate { + type Error = Error; + + fn length(&self) -> usize { + to_leb128_len(self.len()) + self.len() + } + + fn write(&self, ser: &mut Serializer) -> Result { + ser.write_vec(self).map_err(Self::Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let bytes = de.read_vec()?; + Ok(Self(bytes)) + } +} + #[cfg(test)] mod tests { use cosmian_crypto_core::{bytes_ser_de::Deserializer, reexport::rand_core::CryptoRngCore}; @@ -110,12 +125,11 @@ mod tests { } #[test] - fn test_partitions() -> Result<(), Error> { + fn test_coordinates() -> Result<(), Error> { let mut values: Vec = vec![12, 0, u32::MAX, 1]; - let partition = Coordinate::from_attribute_ids(values.clone())?; - // values are sorted n Partition + let coordinate = Coordinate::from_attribute_ids(values.clone())?; values.sort_unstable(); - let mut de = Deserializer::new(&partition); + let mut de = Deserializer::new(&coordinate); for v in values { let val = de.read_leb128_u64().unwrap() as u32; assert_eq!(v, val); diff --git a/src/abe_policy/mod.rs b/src/abe_policy/mod.rs index d11ff5a3..4aefaffb 100644 --- a/src/abe_policy/mod.rs +++ b/src/abe_policy/mod.rs @@ -9,15 +9,15 @@ mod access_policy; mod attribute; +mod coordinates; mod dimension; -mod partitions; mod policy; mod policy_versions; pub use access_policy::AccessPolicy; pub use attribute::{Attribute, AttributeStatus, Attributes, EncryptionHint}; +pub use coordinates::Coordinate; pub use dimension::{AttributeParameters, Dimension, DimensionBuilder}; -pub use partitions::Coordinate; pub use policy_versions::{LegacyPolicy, PolicyV1, PolicyV2 as Policy}; use serde::{Deserialize, Serialize}; diff --git a/src/abe_policy/policy_versions.rs b/src/abe_policy/policy_versions.rs index 5084aecd..d8ccb46f 100644 --- a/src/abe_policy/policy_versions.rs +++ b/src/abe_policy/policy_versions.rs @@ -15,8 +15,8 @@ pub struct PolicyV2 { /// Version number pub(crate) version: PolicyVersion, /// Last value taken by the attribute. - /// TODO: after some mutations, this counter will become very high, which implies the size of - /// the partitions will become huge (d * log_2(id) * 8/7). + /// TODO: after some mutations, this counter will become very high, which + /// implies the size of the coordinates will become huge (d * log_2(id) * 8/7). pub(crate) last_attribute_value: u32, /// Policy axes: maps axes name to the list of associated attribute names diff --git a/src/core/api.rs b/src/core/api.rs index 9b67e845..17b021af 100644 --- a/src/core/api.rs +++ b/src/core/api.rs @@ -1,6 +1,6 @@ //! Defines the `Covercrypt` API. -use std::{fmt::Debug, sync::Mutex}; +use std::{collections::HashMap, fmt::Debug, sync::Mutex}; use cosmian_crypto_core::{ reexport::rand_core::SeedableRng, Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, @@ -12,7 +12,7 @@ use super::{ MIN_TRACING_LEVEL, }; use crate::{ - abe_policy::{AccessPolicy, Policy}, + abe_policy::{AccessPolicy, AttributeStatus, Coordinate, EncryptionHint, Policy}, core::{ primitives::{decaps, encaps, refresh, rekey, setup}, Encapsulation, MasterPublicKey, MasterSecretKey, UserSecretKey, SEED_LENGTH, @@ -47,11 +47,26 @@ impl Covercrypt { /// They only hold keys for the origin coordinate: only broadcast /// encapsulations can be created. pub fn setup(&self) -> Result<(MasterSecretKey, MasterPublicKey), Error> { - let msk = setup( + let mut msk = setup( &mut *self.rng.lock().expect("Mutex lock failed!"), MIN_TRACING_LEVEL, )?; + + // Add broadcast coordinate with classic encryption level. + // + // TODO replace this function by `add_coordinates`, + // `remove_coordinates`, `hybridize_coordinates` and + // `deprecate_coordinates`. + update_coordinate_keys( + &mut *self.rng.lock().expect("Mutex lock failed!"), + &mut msk, + HashMap::from_iter([( + Coordinate::from_attribute_ids(vec![])?, + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + )]), + )?; let mpk = mpk_keygen(&msk)?; + Ok((msk, mpk)) } diff --git a/src/core/mod.rs b/src/core/mod.rs index 2a50ecd9..10f0ad18 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -18,9 +18,10 @@ use crate::{ pub mod macros; pub mod api; -pub mod pq_kem; pub mod primitives; -// pub mod serialization; + +#[cfg(feature = "serialization")] +pub mod serialization; mod pke; #[cfg(test)] @@ -178,7 +179,7 @@ impl CoordinateKeypair { /// Future MPK will be generated without any key for this coordinate, thus /// disabling encryption for this coordinate. fn drop_encryption_key(&mut self) { - self.elgamal_keypair.drop_pk(); + self.elgamal_keypair.deprecate(); } /// Drop the post-quantum part of this coordinate keypair. @@ -416,9 +417,9 @@ impl MasterSecretKey { ) -> impl Iterator + 'a { self.coordinate_keypairs .iter() - .filter_map(|(coordinate, mut keypairs)| { + .filter_map(|(coordinate, keypairs)| { let pk: Option = - keypairs.next().and_then(|keypair| keypair.public_key()); + keypairs.front().and_then(|keypair| keypair.public_key()); pk.map(|pk| (coordinate.clone(), pk)) }) } diff --git a/src/core/pke/elgamal.rs~ b/src/core/pke/elgamal.rs~ deleted file mode 100644 index d04d3398..00000000 --- a/src/core/pke/elgamal.rs~ +++ /dev/null @@ -1,52 +0,0 @@ -/// Encrypts the given secret using a classic KEM. -/// -/// # Security -/// -/// This is a KEM-DEM PKE implementation using an ElGammal KEM and the one-time -/// pad as DEM. Security therefore relies on the fact that different secrets are -/// never encrypted using the same ElGammal keypair. -/// -/// Provides 128-bit of classical security. -fn elgamal_pke_encrypt( - rng: &mut impl CryptoRngCore, - sk: &R25519PrivateKey, - pk: &R25519PublicKey, - secret: &[u8], -) -> Result<[u8; LENGTH], Error> { - if LENGTH < secret.len() { - return Err(Error::OperationNotPermitted(format!( - "cannot encrypt a secret of size {} in a ciphertext of size {LENGTH}", - secret.len() - ))); - } - let mut shared_secret = [0; LENGTH]; - kdf256!(&mut shared_secret, &(pk * &sk).to_bytes()); - xor_in_place(&mut shared_secret, secret); - Ok(shared_secret) -} - -/// Decrypts the given secret using a classic KEM. -/// -/// # Security -/// -/// This is a KEM-DEM PKE implementation using an ElGammal KEM and the one-time -/// pad as DEM. Security therefore relies on the fact that different secrets are -/// never encrypted using the same ElGammal keypair. -/// -/// Provides 128-bit of classical security. -fn elgamal_pke_decrypt( - sk: &R25519PrivateKey, - pk: &R25519PublicKey, - ctx: &[u8], -) -> Result, Error> { - if ctx.len() < LENGTH { - return Err(Error::OperationNotPermitted(format!( - "cannot decrypt a secret of size {LENGTH} from a ciphertext of size {}", - ctx.len() - ))); - } - let mut ptx = Secret::::default(); - kdf256!(&mut *ptx, &(pk * sk).to_bytes()); - xor_in_place(&mut *ptx, &*ctx); - Ok(ptx) -} diff --git a/src/core/pke/elgamal/mod.rs b/src/core/pke/elgamal/mod.rs index 29c42e36..54f6e684 100644 --- a/src/core/pke/elgamal/mod.rs +++ b/src/core/pke/elgamal/mod.rs @@ -6,24 +6,33 @@ //! Curve points MUST provide: //! - group operations (addition, neutral element); //! - external multiplication with scalars. +//! - implement `Serializable` //! //! Scalars MUST provide: //! - field operations (a fortiori the multiplicative inverse). +//! - implement `Serializable` mod ristretto_25519; -use cosmian_crypto_core::{kdf256, reexport::rand_core::CryptoRngCore, Secret, SymmetricKey}; +use cosmian_crypto_core::{ + bytes_ser_de::Serializable, kdf256, reexport::rand_core::CryptoRngCore, Secret, SymmetricKey, +}; use zeroize::Zeroize; pub use ristretto_25519::{EcPoint, Scalar}; use crate::Error; +/// ElGamal keypair. +/// +/// The public key is optional. The following invariant is maintained: +/// > the public key is `None` iff the keypair is deprecated #[derive(Debug, Clone, PartialEq, Eq)] pub struct Keypair(Scalar, Option); impl Keypair { /// Creates a new keypair. + #[inline(always)] pub fn new(sk: Scalar, pk: EcPoint) -> Self { Self(sk, Some(pk)) } @@ -37,26 +46,74 @@ impl Keypair { } /// Returns a reference on the secret key. + #[inline(always)] pub fn sk(&self) -> &Scalar { &self.0 } - /// Returns a reference on the public key. + /// Returns a reference on the public key if the key is not + /// deprecated. Returns `None` otherwise. + #[inline(always)] pub fn pk(&self) -> Option<&EcPoint> { self.1.as_ref() } - /// Drops the public key. This operation is *irreversible*. - pub fn drop_pk(&mut self) { + /// Deprecate this keypair. + #[inline(always)] + pub fn deprecate(&mut self) { self.1 = None } /// Returns true if the given secret key is contained in this keypair. + #[inline(always)] pub fn contains(&self, sk: &Scalar) -> bool { &self.0 == sk } } +impl Serializable for Keypair { + type Error = Error; + + fn length(&self) -> usize { + self.0.length() + + 1 // option encoding overhead + + self + .1 + .as_ref() + .map(Serializable::length) + .unwrap_or_default() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + let mut n = ser.write(&self.0)?; + if let Some(pk) = &self.1 { + n += ser.write_leb128_u64(0)?; + n += ser.write(pk)?; + } else { + n += ser.write_leb128_u64(1)?; + } + Ok(n) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let sk = de.read::()?; + let is_deprecated = de.read_leb128_u64()?; + if 1 == is_deprecated { + Ok(Self(sk, None)) + } else if 0 == is_deprecated { + let pk = de.read::()?; + Ok(Self(sk, Some(pk))) + } else { + Err(Error::ConversionFailed(format!( + "invalid option encoding {is_deprecated}" + ))) + } + } +} + /// One-Time Pad (OTP) encryption of the given plaintext. fn otp_encrypt( key: &SymmetricKey, @@ -72,7 +129,7 @@ fn otp_encrypt( /// One-Time Pad (OTP) decryption of the given ciphertext. fn otp_decrypt( key: &SymmetricKey, - ctx: &[u8], + ctx: &[u8], // authorize byte slices from any object ) -> Result, Error> { if ctx.len() < LENGTH { return Err(Error::OperationNotPermitted(format!( @@ -132,6 +189,7 @@ pub fn decrypt( #[cfg(test)] mod tests { use cosmian_crypto_core::{ + bytes_ser_de::Serializable, reexport::rand_core::{CryptoRngCore, SeedableRng}, CsRng, Secret, }; @@ -174,4 +232,14 @@ mod tests { recipient keypair {recipient_keypair:#?} and plaintext {ptx:#?}", ) } + + #[test] + fn test_elgamal_serialization() { + let mut rng = CsRng::from_entropy(); + let keypair = Keypair::random(&mut rng); + let bytes = keypair.serialize().unwrap(); + assert_eq!(bytes.len(), keypair.length()); + let keypair_ = Keypair::deserialize(&bytes).unwrap(); + assert_eq!(keypair, keypair_); + } } diff --git a/src/core/pke/elgamal/mod.rs~ b/src/core/pke/elgamal/mod.rs~ deleted file mode 100644 index 7486c023..00000000 --- a/src/core/pke/elgamal/mod.rs~ +++ /dev/null @@ -1 +0,0 @@ -mod ristretto_25519; diff --git a/src/core/pke/elgamal/ristretto_25519.rs~ b/src/core/pke/elgamal/ristretto_25519.rs~ deleted file mode 100644 index 91987d2a..00000000 --- a/src/core/pke/elgamal/ristretto_25519.rs~ +++ /dev/null @@ -1,69 +0,0 @@ -use cosmian_crypto_core::{kdf256, reexport::rand_core::CryptoRngCore, Secret, SymmetricKey}; - -pub use R25519PrivateKey as Scalar; -pub use R25519PublicKey as EcPoint; - -pub type ElGamalKeypair = (Scalar, EcPoint); - -/// One-Time Pad (OTP) encryption of the given plaintext. -fn otp_encrypt( - key: &SymmetricKey, - ptx: &Secret, -) -> [u8; LENGTH] { - let mut ctx = [0; LENGTH]; - for pos in 0..LENGTH { - ctx[pos] = key[pos] ^ ptx[pos]; - } - ctx -} - -/// One-Time Pad (OTP) decryption of the given ciphertext. -fn otp_decrypt( - key: &SymmetricKey, - ctx: &[u8; LENGTH], -) -> Secret { - let mut ptx = Secret::::new(); - for pos in 0..LENGTH { - ctx[pos] = key[pos] ^ ptx[pos]; - } - ptx -} - -/// Encrypts the given secret using a classic KEM. -/// -/// # Security -/// -/// This is a KEM-DEM PKE implementation using an ElGammal KEM and the one-time -/// pad as DEM. Security therefore relies on the fact that different secrets are -/// never encrypted using the same ElGammal keypair. -/// -/// Provides 128-bit of classical security. -pub fn encrypt( - rng: &mut impl CryptoRngCore, - sk: &Scalar, - pk: &EcPoint, - ptx: &Secret, -) -> [u8; LENGTH] { - let mut shared_secret = SymmetricKey::::default(); - kdf256!(&mut shared_secret, &(pk * &sk).to_bytes()); - otp_encrypt(&shared_secret, &*ptx) -} - -/// Decrypts the given secret using a classic KEM. -/// -/// # Security -/// -/// This is a KEM-DEM PKE implementation using an ElGammal KEM and the one-time -/// pad as DEM. Security therefore relies on the fact that different secrets are -/// never encrypted using the same ElGammal keypair. -/// -/// Provides 128-bit of classical security. -pub fn decrypt( - sk: &Scalar, - pk: &EcPoint, - ctx: &[u8; LENGTH], -) -> Secret { - let mut shared_secret = SymmetricKey::::default(); - kdf256!(&mut *shared_secret, &(pk * sk).to_bytes()); - otp_decrypt(&*shared_secret, ctx) -} diff --git a/src/core/pke/mod.rs~ b/src/core/pke/mod.rs~ deleted file mode 100644 index 99146c1b..00000000 --- a/src/core/pke/mod.rs~ +++ /dev/null @@ -1 +0,0 @@ -mod elg diff --git a/src/core/pke/postquantum.rs~ b/src/core/pke/postquantum.rs~ deleted file mode 100644 index 79386198..00000000 --- a/src/core/pke/postquantum.rs~ +++ /dev/null @@ -1,38 +0,0 @@ -/// Encrypts the given secret using a post-quantum secure PKE. -/// -/// # Security -/// -/// The current implementation uses IND-CPA-Kyber 768, but plan for generalizing -/// it is on the way. It provides TODO bits of post-quantum security. -fn postquantum_pke_encrypt( - rng: &mut impl CryptoRngCore, - postquantum_pk: &KyberPublicKey, - secret: &[u8], -) -> Result { - if KYBER_INDCPA_BYTES < secret.len() { - return Err(Error::OperationNotPermitted(format!( - "cannot encrypt a secret of size {} in a ciphertext of size {KYBER_INDCPA_BYTES}", - secret.len() - ))); - } - let mut ctx = KyberCiphertext::new(); - let mut coin = Zeroizing::new([0; KYBER_SYMBYTES]); - rng.fill_bytes(&mut *coin); - indcpa_enc(&mut ctx, secret, postquantum_pk, &*coin); - Ok(ctx) -} - -/// Decrypts the given secret using a post-quantum secure PKE. -/// -/// # Security -/// -/// The current implementation uses IND-CPA-Kyber 768, but plan for generalizing -/// it is on the way. It provides TODO bits of post-quantum security. -fn postquantum_pke_decrypt( - postquantum_sk: &KyberSecretKey, - ctx: &KyberCiphertext, -) -> Secret { - let mut secret = Secret::::default(); - indcpa_dec(&mut *secret, ctx, postquantum_sk); - secret -} diff --git a/src/core/pke/postquantum/kyber.rs b/src/core/pke/postquantum/kyber.rs index 42178b26..9a24d1c8 100644 --- a/src/core/pke/postquantum/kyber.rs +++ b/src/core/pke/postquantum/kyber.rs @@ -6,16 +6,19 @@ use std::ops::{Deref, DerefMut}; use crate::Error; -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret}; +use cosmian_crypto_core::{bytes_ser_de::Serializable, reexport::rand_core::CryptoRngCore, Secret}; use pqc_kyber::{ indcpa::{indcpa_dec, indcpa_enc, indcpa_keypair}, - KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES, + KYBER_INDCPA_BYTES, KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES, KYBER_SYMBYTES, }; -pub use pqc_kyber::{KYBER_INDCPA_BYTES, KYBER_SYMBYTES}; /// Kyber public key length #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PublicKey(Box<[u8; KYBER_INDCPA_PUBLICKEYBYTES]>); +pub struct PublicKey(Box<[u8; Self::LENGTH]>); + +impl PublicKey { + pub const LENGTH: usize = KYBER_INDCPA_PUBLICKEYBYTES; +} impl Deref for PublicKey { type Target = [u8]; @@ -31,9 +34,35 @@ impl DerefMut for PublicKey { } } +impl Serializable for PublicKey { + type Error = Error; + + fn length(&self) -> usize { + Self::LENGTH + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + ser.write_array(self).map_err(Self::Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + de.read_array() + .map(Box::new) + .map(Self) + .map_err(Self::Error::from) + } +} + /// Kyber secret key. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SecretKey(Secret); +pub struct SecretKey(Secret<{ Self::LENGTH }>); + +impl SecretKey { + pub const LENGTH: usize = KYBER_INDCPA_SECRETKEYBYTES; +} impl Deref for SecretKey { type Target = [u8]; @@ -49,8 +78,29 @@ impl DerefMut for SecretKey { } } +impl Serializable for SecretKey { + type Error = Error; + + fn length(&self) -> usize { + Self::LENGTH + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + ser.write_array(self).map_err(Self::Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let mut bytes = de.read_array()?; + let secret = Secret::from_unprotected_bytes(&mut bytes); + Ok(Self(secret)) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Ciphertext(Box<[u8; KYBER_INDCPA_BYTES]>); +pub struct Ciphertext(Box<[u8; Self::LENGTH]>); impl Deref for Ciphertext { type Target = [u8]; @@ -68,7 +118,32 @@ impl DerefMut for Ciphertext { impl Default for Ciphertext { fn default() -> Self { - Self(Box::new([0; KYBER_INDCPA_BYTES])) + Self(Box::new([0; Self::LENGTH])) + } +} + +impl Ciphertext { + pub const LENGTH: usize = KYBER_INDCPA_BYTES; +} + +impl Serializable for Ciphertext { + type Error = Error; + + fn length(&self) -> usize { + Self::LENGTH + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + ser.write_array(self).map_err(Self::Error::from) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + de.read_array::<{ Self::LENGTH }>() + .map(|bytes| Self(Box::new(bytes))) + .map_err(Self::Error::from) } } @@ -76,7 +151,7 @@ impl Default for Ciphertext { pub fn keygen(rng: &mut impl CryptoRngCore) -> (SecretKey, PublicKey) { let (mut sk, mut pk) = ( SecretKey(Secret::new()), - PublicKey(Box::new([0; KYBER_INDCPA_PUBLICKEYBYTES])), + PublicKey(Box::new([0; PublicKey::LENGTH])), ); indcpa_keypair(&mut pk, &mut sk, None, rng); (sk, pk) diff --git a/src/core/pke/postquantum/kyber.rs~ b/src/core/pke/postquantum/kyber.rs~ deleted file mode 100644 index c095b62d..00000000 --- a/src/core/pke/postquantum/kyber.rs~ +++ /dev/null @@ -1,54 +0,0 @@ -//! Exposes a post-quantum PKE for use in the Covercrypt scheme. -//! -//! Current implementation only uses Krystal Kyber, but support for more -//! algorithms may be developed in the future. - -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, Secret}; -use pqc_kyber::{ - indcpa::{indcpa_dec, indcpa_enc}, - KYBER_INDCPA_BYTES, KYBER_SYMBYTES, -}; - -use crate::{ - core::pq_kem::kyber::{KyberCiphertext, KyberPublicKey, KyberSecretKey}, - Error, -}; - -/// Encrypts the given secret using a post-quantum secure PKE. -/// -/// # Security -/// -/// The current implementation uses IND-CPA-Kyber 768, but plan for generalizing -/// it is on the way. It provides TODO bits of post-quantum security. -pub fn encrypt( - rng: &mut impl CryptoRngCore, - postquantum_pk: &KyberPublicKey, - secret: &[u8], -) -> Result { - if KYBER_INDCPA_BYTES < secret.len() { - return Err(Error::OperationNotPermitted(format!( - "cannot encrypt a secret of size {} in a ciphertext of size {KYBER_INDCPA_BYTES}", - secret.len() - ))); - } - let mut ctx = KyberCiphertext::default(); - let mut coin = Secret::::default(); - rng.fill_bytes(&mut *coin); - indcpa_enc(&mut ctx, secret, postquantum_pk, &*coin); - Ok(ctx) -} - -/// Decrypts the given secret using a post-quantum secure PKE. -/// -/// # Security -/// -/// The current implementation uses IND-CPA-Kyber 768, but plan for generalizing -/// it is on the way. It provides TODO bits of post-quantum security. -pub fn decrypt( - postquantum_sk: &KyberSecretKey, - ctx: &KyberCiphertext, -) -> Secret { - let mut secret = Secret::::default(); - indcpa_dec(&mut *secret, ctx, postquantum_sk); - secret -} diff --git a/src/core/pke/postquantum/mod.rs b/src/core/pke/postquantum/mod.rs index 586949b9..7cbe03be 100644 --- a/src/core/pke/postquantum/mod.rs +++ b/src/core/pke/postquantum/mod.rs @@ -1,8 +1,10 @@ mod kyber; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; +use cosmian_crypto_core::{bytes_ser_de::Serializable, reexport::rand_core::CryptoRngCore}; pub use kyber::{decrypt, encrypt, keygen, Ciphertext, PublicKey, SecretKey}; +use crate::Error; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct Keypair(SecretKey, PublicKey); @@ -31,3 +33,45 @@ impl Keypair { &self.0 == sk } } + +impl Serializable for Keypair { + type Error = Error; + + fn length(&self) -> usize { + self.0.length() + self.1.length() + } + + fn write( + &self, + ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, + ) -> Result { + let mut n = ser.write(&self.0)?; + n += ser.write(&self.1)?; + Ok(n) + } + + fn read(de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer) -> Result { + let sk = de.read()?; + let pk = de.read()?; + Ok(Self(sk, pk)) + } +} + +#[cfg(test)] +mod tests { + use cosmian_crypto_core::{ + bytes_ser_de::Serializable, reexport::rand_core::SeedableRng, CsRng, + }; + + use super::Keypair; + + #[test] + fn test_postquantum_keypair_serialization() { + let mut rng = CsRng::from_entropy(); + let keypair = Keypair::random(&mut rng); + let bytes = keypair.serialize().unwrap(); + assert_eq!(bytes.len(), keypair.length()); + let keypair_ = Keypair::deserialize(&bytes).unwrap(); + assert_eq!(keypair, keypair_); + } +} diff --git a/src/core/pke/postquantum/mod.rs~ b/src/core/pke/postquantum/mod.rs~ deleted file mode 100644 index 8fbc3122..00000000 --- a/src/core/pke/postquantum/mod.rs~ +++ /dev/null @@ -1 +0,0 @@ -mod kyber; diff --git a/src/core/pq_kem/kyber.rs b/src/core/pq_kem/kyber.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/core/pq_kem/kyber.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/core/pq_kem/kyber.rs~ b/src/core/pq_kem/kyber.rs~ deleted file mode 100644 index 22cfccdb..00000000 --- a/src/core/pq_kem/kyber.rs~ +++ /dev/null @@ -1,25 +0,0 @@ -use pqc_kyber::{KYBER_INDCPA_BYTES, KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES}; - -/// Kyber public key length -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct KyberPublicKey([u8; KYBER_INDCPA_PUBLICKEYBYTES]); - -impl Deref for KyberPublicKey { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Kyber secret key. -#[derive(Debug, Clone, PartialEq, Eq, Hash, ZeroizeOnDrop)] -pub struct KyberSecretKey([u8; KYBER_INDCPA_SECRETKEYBYTES]); - -impl Deref for KyberSecretKey { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/src/core/pq_kem/mod.rs b/src/core/pq_kem/mod.rs deleted file mode 100644 index 19c6bc24..00000000 --- a/src/core/pq_kem/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod kyber; diff --git a/src/core/pq_kem/mod.rs~ b/src/core/pq_kem/mod.rs~ deleted file mode 100644 index 8fbc3122..00000000 --- a/src/core/pq_kem/mod.rs~ +++ /dev/null @@ -1 +0,0 @@ -mod kyber; diff --git a/src/core/primitives.rs b/src/core/primitives.rs index 6721118f..164f34b7 100644 --- a/src/core/primitives.rs +++ b/src/core/primitives.rs @@ -320,6 +320,10 @@ pub fn rekey( coordinate, CoordinateKeypair::random(rng, &h, is_hybridized), ); + } else { + return Err(Error::OperationNotPermitted( + "cannot re-key coordinate that does not belong to the MSK".to_string(), + )); } } Ok(()) @@ -420,315 +424,3 @@ fn refresh_coordinate_keys( }) .collect::>() } - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; - - #[test] - fn test_kyber() { - let mut rng = CsRng::from_entropy(); - let keypair = pqc_kyber::keypair(&mut rng); - let (ct, ss) = pqc_kyber::encapsulate(&keypair.public, &mut rng).unwrap(); - let res = pqc_kyber::decapsulate(&ct, &keypair.secret).unwrap(); - assert_eq!(ss, res, "Decapsulation failed!"); - } - - // #[test] - // fn test_cover_crypt() -> Result<(), Error> { - // let mut rng = CsRng::from_entropy(); - // let admin_partition = Coordinate::random(&mut rng); - // let dev_partition = Coordinate::random(&mut rng); - - // let universal_coordinates = HashMap::from([ - // ( - // admin_partition.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // dev_partition.clone(), - // (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // let decryption_set = HashSet::from([admin_partition.clone(), dev_partition.clone()]); - // let target_set = HashSet::from([admin_partition.clone()]); - - // let msk = setup(&mut rng, MIN_TRACING_LEVEL); - - // let admin_secret_subkeys = msk.coordinate_keys.get_latest(&admin_partition); - // assert!(admin_secret_subkeys.is_some()); - // assert!(admin_secret_subkeys.unwrap().0.is_some()); - - // // The developer partition matches a classic sub-key. - // let dev_secret_subkeys = msk.coordinate_keys.get_latest(&dev_partition); - // assert!(dev_secret_subkeys.is_some()); - // assert!(dev_secret_subkeys.unwrap().0.is_none()); - - // // Generate user secret keys. - // let mut dev_usk = keygen(&mut rng, &msk, &decryption_set[0])?; - // let admin_usk = keygen(&mut rng, &msk, &decryption_set[1])?; - - // // Encapsulate key for the admin target set. - // let (sym_key, encapsulation) = encaps(&mut rng, &mpk, &target_set).unwrap(); - - // // The encapsulation holds a unique, hybridized key encapsulation. - // assert_eq!(encapsulation.encs.len(), 1); - // for key_encapsulation in &encapsulation.encs { - // if let SeedEncapsulation::Classic(_) = key_encapsulation { - // panic!("Wrong hybridization type"); - // } - // } - - // // Developer is unable to decapsulate. - // let res0 = decaps(&dev_usk, &encapsulation); - // assert!(res0.is_err(), "User 0 shouldn't be able to decapsulate!"); - - // // Admin is able to decapsulate. - // let res1 = decaps(&admin_usk, &encapsulation)?; - // assert_eq!(sym_key, res1, "Wrong decapsulation for user 1!"); - - // // Change partitions - // let client_partition = Coordinate(b"client".to_vec()); - // let new_partitions_set = HashMap::from([ - // ( - // dev_partition.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // client_partition.clone(), - // (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // let client_target_set = HashSet::from([client_partition.clone()]); - - // update_coordinate_keys(&mut rng, &mut msk, &mut mpk, new_partitions_set)?; - // refresh(&msk, &mut dev_usk, true)?; - - // // The dev partition matches a hybridized sub-key. - // let dev_secret_subkeys = msk.coordinate_keys.get_latest(&dev_partition); - // assert!(dev_secret_subkeys.is_some()); - // assert!(dev_secret_subkeys.unwrap().0.is_some()); - - // // The client partition matches a classic sub-key. - // let client_secret_subkeys = msk.coordinate_keys.get_latest(&client_partition); - // assert!(client_secret_subkeys.is_some()); - // assert!(client_secret_subkeys.unwrap().0.is_none()); - - // // The developer now has a hybridized key. - // assert_eq!(dev_usk.coordinate_keys.count_elements(), 1); - // for key_encapsulation in &encapsulation.encs { - // if let SeedEncapsulation::Classic(_) = key_encapsulation { - // panic!("Wrong hybridization type"); - // } - // } - - // let (sym_key, new_encapsulation) = encaps(&mut rng, &mpk, &client_target_set)?; - - // // Client encapsulation holds a unique, classic key encapsulation. - // assert_eq!(new_encapsulation.encs.len(), 1); - // for key_encapsulation in &new_encapsulation.encs { - // if let SeedEncapsulation::Hybridized(_) = key_encapsulation { - // panic!("Wrong hybridization type"); - // } - // } - - // // The developer is unable to decapsulate. - // let res0 = decaps(&dev_usk, &encapsulation); - // assert!( - // res0.is_err(), - // "User 0 should not be able to decapsulate the old encapsulation." - // ); - - // // The admin is unable to decapsulate. - // let res1 = decaps(&admin_usk, &new_encapsulation); - // assert!( - // res1.is_err(), - // "User 1 should not be able to decapsulate the new encapsulation." - // ); - - // // Client is able to decapsulate. - // let client_usk = keygen(&mut rng, &msk, &HashSet::from([client_partition]))?; - // let res0 = decaps(&client_usk, &new_encapsulation); - // match res0 { - // Err(err) => panic!("Client should be able to decapsulate: {err:?}"), - // Ok(res) => assert_eq!(sym_key, res, "Wrong decapsulation."), - // } - - // Ok(()) - // } - - // #[test] - // fn test_master_keys_update() -> Result<(), Error> { - // let partition_1 = Coordinate(b"1".to_vec()); - // let partition_2 = Coordinate(b"2".to_vec()); - // // partition list - // let partitions_set = HashMap::from([ - // ( - // partition_1.clone(), - // (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - // ), - // ( - // partition_2.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // // secure random number generator - // let mut rng = CsRng::from_entropy(); - // // setup scheme - // let (mut msk, mut mpk) = setup(&mut rng, partitions_set); - - // // now remove partition 1 and add partition 3 - // let partition_3 = Coordinate(b"3".to_vec()); - // let new_partitions_set = HashMap::from([ - // ( - // partition_2.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // partition_3.clone(), - // (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // update_coordinate_keys(&mut rng, &mut msk, &mut mpk, new_partitions_set)?; - // assert!(!msk.coordinate_keys.contains_key(&partition_1)); - // assert!(msk.coordinate_keys.contains_key(&partition_2)); - // assert!(msk.coordinate_keys.contains_key(&partition_3)); - // assert!(!mpk.coordinate_keys.contains_key(&partition_1)); - // assert!(mpk.coordinate_keys.contains_key(&partition_2)); - // assert!(mpk.coordinate_keys.contains_key(&partition_3)); - // Ok(()) - // } - - // #[test] - // fn test_user_key_refresh() -> Result<(), Error> { - // let partition_1 = Coordinate(b"1".to_vec()); - // let partition_2 = Coordinate(b"2".to_vec()); - // let partition_3 = Coordinate(b"3".to_vec()); - // // partition list - // let partitions_set = HashMap::from([ - // ( - // partition_1.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // partition_2.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // partition_3.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // // secure random number generator - // let mut rng = CsRng::from_entropy(); - // // setup scheme - // let (mut msk, mut mpk) = setup(&mut rng, partitions_set); - // // create a user key with access to partition 1 and 2 - // let mut usk = keygen( - // &mut rng, - // &msk, - // &HashSet::from([partition_1.clone(), partition_2.clone()]), - // )?; - - // // now remove partition 1 and remove hybrid key from partition 3 - // let new_partition_set = HashMap::from([ - // ( - // partition_2.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // partition_3.clone(), - // (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // //Covercrypt the master keys - - // let old_msk = MasterSecretKey::deserialize(msk.serialize()?.as_slice())?; - // update_coordinate_keys(&mut rng, &mut msk, &mut mpk, new_partition_set)?; - // // refresh the user key - // refresh(&msk, &mut usk, true)?; - // // user key kept old access to partition 1 - // assert!(!usk.coordinate_keys.flat_iter().any(|x| { - // x == ( - // &partition_1, - // old_msk.coordinate_keys.get_latest(&partition_1).unwrap(), - // ) - // })); - // assert!(usk.coordinate_keys.flat_iter().any(|x| { - // x == ( - // &partition_2, - // msk.coordinate_keys.get_latest(&partition_2).unwrap(), - // ) - // })); - // // user key kept the old hybrid key for partition 3 - // assert!(!usk.coordinate_keys.flat_iter().any(|x| { - // x == ( - // &partition_3, - // old_msk.coordinate_keys.get_latest(&partition_3).unwrap(), - // ) - // })); - - // // add new key for partition 2 - // rekey( - // &mut rng, - // &mut msk, - // &mut mpk, - // HashSet::from([partition_2.clone()]), - // )?; - // // refresh the user key - // refresh(&msk, &mut usk, true)?; - // let usk_subkeys: Vec<_> = usk - // .coordinate_keys - // .flat_iter() - // .filter(|(part, _)| *part == &partition_2) - // .map(|(_, subkey)| subkey) - // .collect(); - // let msk_subkeys: Vec<_> = msk - // .coordinate_keys - // .get(&partition_2) - // .unwrap() - // .iter() - // .collect(); - // assert_eq!(usk_subkeys.len(), 2); - // assert_eq!(usk_subkeys, msk_subkeys); - - // Ok(()) - // } - - // #[test] - // fn test_user_key_kmac() -> Result<(), Error> { - // let partition_1 = Coordinate(b"1".to_vec()); - // let partition_2 = Coordinate(b"2".to_vec()); - // // partition list - // let partitions_set = HashMap::from([ - // ( - // partition_1.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ( - // partition_2.clone(), - // (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - // ), - // ]); - // // secure random number generator - // let mut rng = CsRng::from_entropy(); - // // setup scheme - // let (msk, _) = setup(&mut rng, partitions_set); - // // create a user key with access to partition 1 and 2 - // let mut usk = keygen(&mut rng, &msk, &HashSet::from([partition_1, partition_2]))?; - - // assert!(verify_usk(&msk, &usk).is_ok()); - // let bytes = usk.serialize()?; - // let usk_ = UserSecretKey::deserialize(&bytes)?; - // assert!(verify_usk(&msk, &usk_).is_ok()); - - // usk.coordinate_keys.create_chain_with_single_value( - // Coordinate(b"3".to_vec()), - // (None, R25519PrivateKey::new(&mut rng)), - // ); - // // KMAC verify will fail after modifying the user key - // assert!(verify_usk(&msk, &usk).is_err()); - - // Ok(()) - // } -} diff --git a/src/core/serialization.rs b/src/core/serialization.rs deleted file mode 100644 index 5457772c..00000000 --- a/src/core/serialization.rs +++ /dev/null @@ -1,538 +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, R25519PrivateKey, R25519PublicKey, RandomFixedSizeCBytes, SymmetricKey, -}; -use pqc_kyber::{KYBER_INDCPA_PUBLICKEYBYTES, KYBER_INDCPA_SECRETKEYBYTES}; - -use super::{KMAC_KEY_LENGTH, KMAC_SIG_LENGTH, TAG_LENGTH}; -use crate::{ - abe_policy::Coordinate, - core::{ - Encapsulation, MasterPublicKey, MasterSecretKey, SeedEncapsulation, UserSecretKey, - SEED_LENGTH, - }, - data_struct::{RevisionMap, RevisionVec}, - CleartextHeader, EncryptedHeader, Error, -}; - -/// Returns the byte length of a serialized option -macro_rules! serialize_len_option { - ($option:expr, $value:ident, $method:expr) => {{ - let mut length = 1; - if let Some($value) = &$option { - length += $method; - } - length - }}; -} - -/// Serialize an optional value as a LEB128-encoded unsigned integer followed by -/// the serialization of the contained value if any. -macro_rules! serialize_option { - ($serializer:expr, $n:expr, $option:expr, $value:ident, $method:expr) => {{ - if let Some($value) = &$option { - $n += $serializer.write_leb128_u64(1)?; - $n += $method?; - } else { - $n += $serializer.write_leb128_u64(0)?; - } - }}; -} - -/// Deserialize an optional value from a LEB128-encoded unsigned integer -/// followed by the deserialization of the contained value if any. -macro_rules! deserialize_option { - ($deserializer:expr, $method:expr) => {{ - let is_some = $deserializer.read_leb128_u64()?; - if is_some == 1 { - Some($method) - } else { - None - } - }}; -} - -impl Serializable for MasterPublicKey { - type Error = Error; - - fn length(&self) -> usize { - let mut length = 2 * R25519PublicKey::LENGTH - // subkeys serialization - + to_leb128_len(self.coordinate_keys.len()) - + self.coordinate_keys.len() * R25519PublicKey::LENGTH; - for (partition, (pk_i, _)) in &self.coordinate_keys { - length += to_leb128_len(partition.len()) + partition.len(); - length += serialize_len_option!(pk_i, _value, KYBER_INDCPA_PUBLICKEYBYTES); - } - length - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(&self.g1.to_bytes())?; - n += ser.write_array(&self.g2.to_bytes())?; - n += ser.write_leb128_u64(self.coordinate_keys.len() as u64)?; - for (partition, (pk_i, h_i)) in &self.coordinate_keys { - n += ser.write_vec(partition)?; - serialize_option!(ser, n, pk_i, value, ser.write_array(value)); - n += ser.write_array(&h_i.to_bytes())?; - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let g1 = R25519PublicKey::try_from_bytes(de.read_array::<{ R25519PublicKey::LENGTH }>()?)?; - let g2 = R25519PublicKey::try_from_bytes(de.read_array::<{ R25519PublicKey::LENGTH }>()?)?; - let n_partitions = ::try_from(de.read_leb128_u64()?)?; - let mut subkeys = HashMap::with_capacity(n_partitions); - for _ in 0..n_partitions { - let partition = Coordinate::from(de.read_vec()?); - let pk_i = deserialize_option!(de, KyberPublicKey(de.read_array()?)); - let h_i = - R25519PublicKey::try_from_bytes(de.read_array::<{ R25519PublicKey::LENGTH }>()?)?; - subkeys.insert(partition, (pk_i, h_i)); - } - Ok(Self { - g1, - g2, - coordinate_keys: subkeys, - }) - } -} - -impl Serializable for MasterSecretKey { - type Error = Error; - - fn length(&self) -> usize { - let mut length = 3 * R25519PrivateKey::LENGTH - + self.signing_key.as_ref().map_or_else(|| 0, |key| key.len()) - // subkeys serialization - + to_leb128_len(self.coordinate_keypairs.len()) - + self.coordinate_keypairs.count_elements() * R25519PrivateKey::LENGTH; - for (partition, chain) in &self.coordinate_keypairs.map { - length += to_leb128_len(partition.len()) + partition.len(); - length += to_leb128_len(chain.len()); - for (sk_i, _) in chain { - let x = serialize_len_option!(sk_i, _value, KYBER_INDCPA_SECRETKEYBYTES); - length += x; - } - } - length - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(&self.s1.to_bytes())?; - n += ser.write_array(&self.s2.to_bytes())?; - n += ser.write_array(&self.s.to_bytes())?; - n += ser.write_leb128_u64(self.coordinate_keypairs.len() as u64)?; - for (partition, chain) in &self.coordinate_keypairs.map { - n += ser.write_vec(partition)?; - n += ser.write_leb128_u64(chain.len() as u64)?; - for (sk_i, x_i) in chain { - serialize_option!(ser, n, sk_i, value, ser.write_array(value)); - n += ser.write_array(&x_i.to_bytes())?; - } - } - if let Some(kmac_key) = &self.signing_key { - n += ser.write_array(kmac_key)?; - } - - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let s1 = - R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; - let s2 = - R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; - let s = R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; - - let n_partitions = ::try_from(de.read_leb128_u64()?)?; - let mut subkeys = RevisionMap::with_capacity(n_partitions); - for _ in 0..n_partitions { - let partition = Coordinate::from(de.read_vec()?); - let n_keys = ::try_from(de.read_leb128_u64()?)?; - let chain: Result, Self::Error> = (0..n_keys) - .map(|_| { - let sk_i = deserialize_option!(de, KyberSecretKey(de.read_array()?)); - let x_i = de.read_array::<{ R25519PrivateKey::LENGTH }>()?; - Ok((sk_i, R25519PrivateKey::try_from_bytes(x_i)?)) - }) - .collect(); - subkeys.map.insert(partition, chain?); - } - - let kmac_key = match de.read_array::<{ KMAC_KEY_LENGTH }>() { - Ok(key_bytes) => Some(SymmetricKey::try_from_bytes(key_bytes)?), - Err(_) => None, - }; - - Ok(Self { - s, - s1, - s2, - coordinate_keypairs: subkeys, - signing_key: kmac_key, - }) - } -} - -impl Serializable for UserSecretKey { - type Error = Error; - - fn length(&self) -> usize { - let mut length = 2 * R25519PrivateKey::LENGTH - + self.msk_signature.as_ref().map_or_else(|| 0, |kmac| kmac.len()) - // subkeys serialization - + to_leb128_len(self.coordinate_keys.len()) - + self.coordinate_keys.count_elements() * R25519PrivateKey::LENGTH; - for (partition, chain) in self.coordinate_keys.iter() { - length += to_leb128_len(partition.len()) + partition.len(); - length += to_leb128_len(chain.len()); - for (sk_i, _) in chain { - length += serialize_len_option!(sk_i, _value, KYBER_INDCPA_SECRETKEYBYTES); - } - } - length - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(&self.a.to_bytes())?; - n += ser.write_array(&self.b.to_bytes())?; - n += ser.write_leb128_u64(self.coordinate_keys.len() as u64)?; - for (partition, chain) in self.coordinate_keys.iter() { - // write chain partition - n += ser.write_vec(partition)?; - // iterate through all subkeys in the chain - n += ser.write_leb128_u64(chain.len() as u64)?; - for (sk_i, x_i) in chain { - serialize_option!(ser, n, sk_i, value, ser.write_array(value)); - n += ser.write_array(&x_i.to_bytes())?; - } - } - if let Some(kmac) = &self.msk_signature { - n += ser.write_array(kmac)?; - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let a = R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; - let b = R25519PrivateKey::try_from_bytes(de.read_array::<{ R25519PrivateKey::LENGTH }>()?)?; - let n_partitions = ::try_from(de.read_leb128_u64()?)?; - let mut subkeys = RevisionVec::with_capacity(n_partitions); - for _ in 0..n_partitions { - let partition = Coordinate::from(de.read_vec()?); - // read all keys forming a chain and inserting them all at once. - let n_keys = ::try_from(de.read_leb128_u64()?)?; - let new_chain: Result, _> = (0..n_keys) - .map(|_| { - let sk_i = deserialize_option!(de, KyberSecretKey(de.read_array()?)); - let x_i = de.read_array::<{ R25519PrivateKey::LENGTH }>()?; - Ok::<_, Self::Error>((sk_i, R25519PrivateKey::try_from_bytes(x_i)?)) - }) - .collect(); - subkeys.insert_new_chain(partition, new_chain?); - } - let kmac = de.read_array::<{ KMAC_SIG_LENGTH }>().ok(); - - Ok(Self { - a, - b, - coordinate_keys: RefCell::new(subkeys), - msk_signature: kmac, - }) - } -} - -impl Serializable for SeedEncapsulation { - type Error = Error; - - fn length(&self) -> usize { - match self { - Self::Classic(e_i) => 1 + e_i.len(), - Self::Hybridized(epq_i) => 1 + epq_i.len(), - } - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = 0; - match self { - Self::Classic(e_i) => { - n += ser.write_leb128_u64(0)?; - n += ser.write_array(&**e_i)?; - } - Self::Hybridized(epq_i) => { - n += ser.write_leb128_u64(1)?; - n += ser.write_array(&**epq_i)?; - } - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let is_hybridized = de.read_leb128_u64()?; - if is_hybridized == 1 { - Ok(Self::Hybridized(Box::new(de.read_array()?))) - } else { - Ok(Self::Classic(Box::new(de.read_array()?))) - } - } -} - -impl Serializable for Encapsulation { - type Error = Error; - - fn length(&self) -> usize { - let mut length = 2 * R25519PublicKey::LENGTH + TAG_LENGTH + to_leb128_len(self.encs.len()); - for key_encasulation in &self.encs { - length += key_encasulation.length(); - } - length - } - - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(&self.c1.to_bytes())?; - n += ser.write_array(&self.c2.to_bytes())?; - n += ser.write_array(&self.tag)?; - n += ser.write_leb128_u64(self.encs.len() as u64)?; - for key_encapsulation in &self.encs { - n += ser.write(key_encapsulation)?; - } - Ok(n) - } - - fn read(de: &mut Deserializer) -> Result { - let c1 = R25519PublicKey::try_from_bytes(de.read_array::<{ R25519PublicKey::LENGTH }>()?)?; - let c2 = R25519PublicKey::try_from_bytes(de.read_array::<{ R25519PublicKey::LENGTH }>()?)?; - let tag = de.read_array()?; - let n_partitions = ::try_from(de.read_leb128_u64()?)?; - let mut encs = HashSet::with_capacity(n_partitions); - for _ in 0..n_partitions { - let key_encapsulation = de.read()?; - encs.insert(key_encapsulation); - } - Ok(Self { c1, c2, tag, encs }) - } -} - -impl Serializable for EncryptedHeader { - type Error = Error; - - fn length(&self) -> usize { - self.encapsulation.length() - + to_leb128_len( - self.encrypted_metadata - .as_ref() - .map(std::vec::Vec::len) - .unwrap_or_default(), - ) - + self - .encrypted_metadata - .as_ref() - .map(std::vec::Vec::len) - .unwrap_or_default() - } - - /// Tries to serialize the encrypted header. - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = self.encapsulation.write(ser)?; - match &self.encrypted_metadata { - Some(bytes) => n += ser.write_vec(bytes)?, - None => n += ser.write_vec(&[])?, - } - Ok(n) - } - - /// Tries to deserialize the encrypted header. - fn read(de: &mut Deserializer) -> Result { - let encapsulation = de.read::()?; - let ciphertext = de.read_vec()?; - let encrypted_metadata = if ciphertext.is_empty() { - None - } else { - Some(ciphertext) - }; - Ok(Self { - encapsulation, - encrypted_metadata, - }) - } -} - -impl Serializable for CleartextHeader { - type Error = Error; - - fn length(&self) -> usize { - SEED_LENGTH - + to_leb128_len( - self.metadata - .as_ref() - .map(std::vec::Vec::len) - .unwrap_or_default(), - ) - + self - .metadata - .as_ref() - .map(std::vec::Vec::len) - .unwrap_or_default() - } - - /// Tries to serialize the cleartext header. - fn write(&self, ser: &mut Serializer) -> Result { - let mut n = ser.write_array(self.symmetric_key.as_bytes())?; - match &self.metadata { - Some(bytes) => n += ser.write_vec(bytes)?, - None => n += ser.write_vec(&[])?, - } - Ok(n) - } - - /// Tries to deserialize the cleartext header. - fn read(de: &mut Deserializer) -> Result { - let symmetric_key = SymmetricKey::try_from_bytes(de.read_array::()?)?; - let metadata = de.read_vec()?; - let metadata = if metadata.is_empty() { - None - } else { - Some(metadata) - }; - Ok(Self { - symmetric_key, - metadata, - }) - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; - - use super::*; - use crate::{ - abe_policy::{AttributeStatus, EncryptionHint}, - core::primitives::{encaps, keygen, setup}, - }; - - #[test] - fn test_serialization() -> Result<(), Error> { - // Setup - let admin_partition = Coordinate(b"admin".to_vec()); - let dev_partition = Coordinate(b"dev".to_vec()); - let partitions_set = HashMap::from([ - ( - admin_partition.clone(), - (EncryptionHint::Hybridized, AttributeStatus::EncryptDecrypt), - ), - ( - dev_partition.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - ), - ]); - let user_set = HashSet::from([admin_partition.clone(), dev_partition.clone()]); - let target_set = HashSet::from([admin_partition, dev_partition]); - let mut rng = CsRng::from_entropy(); - - let (msk, mpk) = setup(&mut rng, partitions_set); - - // Check Covercrypt `MasterSecretKey` serialization. - let bytes = msk.serialize()?; - assert_eq!(bytes.len(), msk.length(), "Wrong master secret key length"); - let msk_ = MasterSecretKey::deserialize(&bytes)?; - assert_eq!(msk, msk_, "Wrong `MasterSecretKey` deserialization."); - assert!( - msk_.signing_key.is_some(), - "Wrong `MasterSecretKey` deserialization." - ); - assert_eq!( - msk.signing_key, msk_.signing_key, - "Wrong `MasterSecretKey` deserialization." - ); - - // Check Covercrypt `PublicKey` serialization. - let bytes = mpk.serialize()?; - assert_eq!(bytes.len(), mpk.length(), "Wrong master public key length"); - let mpk_ = MasterPublicKey::deserialize(&bytes)?; - assert_eq!(mpk, mpk_, "Wrong `PublicKey` derserialization."); - - // Check Covercrypt `UserSecretKey` serialization. - let usk = keygen(&mut rng, &msk, &user_set)?; - let bytes = usk.serialize()?; - assert_eq!(bytes.len(), usk.length(), "Wrong user secret key size"); - let usk_ = UserSecretKey::deserialize(&bytes)?; - assert_eq!(usk.a, usk_.a, "Wrong `UserSecretKey` deserialization."); - assert_eq!(usk.b, usk_.b, "Wrong `UserSecretKey` deserialization."); - assert_eq!( - usk.msk_signature, usk_.msk_signature, - "Wrong `UserSecretKey` deserialization." - ); - assert_eq!(usk, usk_, "Wrong `UserSecretKey` deserialization."); - - // Check Covercrypt `Encapsulation` serialization. - let (_, encapsulation) = encaps(&mut rng, &mpk, &target_set)?; - let bytes = encapsulation.serialize()?; - assert_eq!( - bytes.len(), - encapsulation.length(), - "Wrong encapsulation size" - ); - let encapsulation_ = Encapsulation::deserialize(&bytes)?; - assert_eq!( - encapsulation, encapsulation_, - "Wrong `Encapsulation` derserialization." - ); - - // Setup Covercrypt. - #[cfg(feature = "test_utils")] - { - use crate::{abe_policy::AccessPolicy, test_utils::policy, Covercrypt}; - - let cc = Covercrypt::default(); - let policy = policy()?; - let user_policy = AccessPolicy::from_boolean_expression( - "Department::MKG && Security Level::Top Secret", - )?; - let encryption_policy = AccessPolicy::from_boolean_expression( - "Department::MKG && Security Level::High Secret", - )?; - let (msk, mpk) = cc.generate_master_keys(&policy)?; - let usk = cc.generate_user_secret_key(&msk, &user_policy, &policy)?; - - // Check `EncryptedHeader` serialization. - let (_secret_key, encrypted_header) = - EncryptedHeader::generate(&cc, &policy, &mpk, &encryption_policy, None, None)?; - let bytes = encrypted_header.serialize()?; - assert_eq!( - bytes.len(), - encrypted_header.length(), - "Wrong encapsulation size." - ); - let encrypted_header_ = EncryptedHeader::deserialize(&bytes)?; - assert_eq!( - encrypted_header, encrypted_header_, - "Wrong `EncryptedHeader` derserialization." - ); - - // Check `CleartextHeader` serialization. - let cleartext_header = encrypted_header.decrypt(&cc, &usk, None)?; - let bytes = cleartext_header.serialize()?; - assert_eq!( - bytes.len(), - cleartext_header.length(), - "Wrong cleartext header size." - ); - let cleartext_header_ = CleartextHeader::deserialize(&bytes)?; - assert_eq!( - cleartext_header, cleartext_header_, - "Wrong `CleartextHeader` derserialization." - ); - } - - Ok(()) - } -} diff --git a/src/core/serialization/mod.rs b/src/core/serialization/mod.rs new file mode 100644 index 00000000..2be66d14 --- /dev/null +++ b/src/core/serialization/mod.rs @@ -0,0 +1,815 @@ +//! 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, RandomFixedSizeCBytes, SymmetricKey, +}; + +use super::{ + pke::{ + self, + elgamal::{EcPoint, Scalar}, + postquantum::{self, PublicKey}, + }, + CoordinateKeypair, CoordinatePublicKey, CoordinateSecretKey, TracingPublicKey, + TracingSecretKey, UserId, KMAC_KEY_LENGTH, KMAC_SIG_LENGTH, TAG_LENGTH, +}; +use crate::{ + abe_policy::Coordinate, + core::{ + Encapsulation, MasterPublicKey, MasterSecretKey, SeedEncapsulation, UserSecretKey, + SEED_LENGTH, + }, + data_struct::{RevisionMap, RevisionVec}, + CleartextHeader, EncryptedHeader, 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 += ser.write_array(&pk.to_bytes())?; + } + 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 = EcPoint::try_from_bytes(de.read_array::<{ EcPoint::LENGTH }>()?)?; + tracers.push_back(tracer); + } + Ok(Self(tracers)) + } +} + +impl Serializable for CoordinatePublicKey { + type Error = Error; + + fn length(&self) -> usize { + match self { + CoordinatePublicKey::Hybridized { .. } => { + 1 + SEED_LENGTH + pke::postquantum::PublicKey::LENGTH + } + CoordinatePublicKey::Classic { .. } => 1 + SEED_LENGTH, + } + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + CoordinatePublicKey::Hybridized { + postquantum_pk, + elgamal_pk, + } => { + let mut n = ser.write_leb128_u64(1)?; + n += ser.write_array(postquantum_pk)?; + n += ser.write_array(&elgamal_pk.to_bytes())?; + Ok(n) + } + CoordinatePublicKey::Classic { elgamal_pk } => { + let mut n = ser.write_leb128_u64(0)?; + n += ser.write_array(&elgamal_pk.to_bytes())?; + Ok(n) + } + } + } + + fn read(de: &mut Deserializer) -> Result { + let is_hybridized = de.read_leb128_u64()?; + if 1 == is_hybridized { + Ok(Self::Hybridized { + postquantum_pk: de.read::()?, + elgamal_pk: de.read::()?, + }) + } else if 0 == is_hybridized { + Ok(Self::Classic { + elgamal_pk: de.read::()?, + }) + } else { + Err(Error::ConversionFailed(format!( + "invalid hybridization flag {is_hybridized}" + ))) + } + } +} + +impl Serializable for MasterPublicKey { + type Error = Error; + + fn length(&self) -> usize { + self.h.length() + + self.tpk.length() + + to_leb128_len(self.coordinate_keys.len()) + + self + .coordinate_keys + .iter() + .map(|(coordinate, pk)| coordinate.length() + pk.length()) + .sum::() + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = ser.write_array(&self.h.to_bytes())?; + n += ser.write(&self.tpk)?; + n += ser.write_leb128_u64(self.coordinate_keys.len() as u64)?; + for (coordinate, pk) in &self.coordinate_keys { + n += ser.write(coordinate)?; + n += ser.write(pk)?; + } + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let h = de.read::()?; + 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); + } + Ok(Self { + h, + tpk, + coordinate_keys, + }) + } +} + +impl Serializable for TracingSecretKey { + type Error = Error; + + fn length(&self) -> usize { + to_leb128_len(self.users.len()) + + self.users.iter().map(Serializable::length).sum::() + + to_leb128_len(self.tracers.len()) + + self.tracers.iter().map(Serializable::length).sum::() + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = ser.write_leb128_u64(self.users.len() as u64)?; + for id in &self.users { + n += ser.write(id)?; + } + n += ser.write_leb128_u64(self.tracers.len() as u64)?; + for tracer in &self.tracers { + n += ser.write(tracer)?; + } + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + 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); + } + let n_tracers = ::try_from(de.read_leb128_u64()?)?; + let mut tracers = LinkedList::new(); + for _ in 0..n_tracers { + let t = de.read()?; + tracers.push_back(t); + } + Ok(Self { tracers, users }) + } +} + +impl Serializable for CoordinateKeypair { + type Error = Error; + + fn length(&self) -> usize { + self.elgamal_keypair.length() + + 1 + + self + .postquantum_keypair + .as_ref() + .map(Serializable::length) + .unwrap_or_default() + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = ser.write(&self.elgamal_keypair)?; + if let Some(keypair) = &self.postquantum_keypair { + n += ser.write_leb128_u64(1)?; + n += ser.write(keypair)?; + } else { + n += ser.write_leb128_u64(0)?; + } + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let elgamal_keypair = de.read()?; + let is_hybridized = de.read_leb128_u64()?; + if 1 == is_hybridized { + let postquantum_keypair = de.read()?; + Ok(Self { + elgamal_keypair, + postquantum_keypair: Some(postquantum_keypair), + }) + } else if 0 == is_hybridized { + Ok(Self { + elgamal_keypair, + postquantum_keypair: None, + }) + } else { + Err(Error::ConversionFailed(format!( + "invalid hybridization flag {is_hybridized}" + ))) + } + } +} + +impl Serializable for MasterSecretKey { + type Error = Error; + + fn length(&self) -> usize { + self.s.length() + + self.tsk.length() + + to_leb128_len(self.coordinate_keypairs.len()) + + self + .coordinate_keypairs + .iter() + .map(|(coordinate, chain)| { + coordinate.length() + + to_leb128_len(chain.len()) + + chain.iter().map(Serializable::length).sum::() + }) + .sum::() + + self.signing_key.as_ref().map_or_else(|| 0, |key| key.len()) + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = ser.write(&self.s)?; + n += ser.write(&self.tsk)?; + n += ser.write_leb128_u64(self.coordinate_keypairs.len() as u64)?; + for (coordinate, chain) in &self.coordinate_keypairs.map { + n += ser.write(coordinate)?; + n += ser.write_leb128_u64(to_leb128_len(chain.len()) as u64)?; + for sk in chain { + n += ser.write(sk)?; + } + } + if let Some(kmac_key) = &self.signing_key { + n += ser.write_array(kmac_key)?; + } + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let s = Scalar::try_from_bytes(de.read_array::<{ Scalar::LENGTH }>()?)?; + let tsk = de.read::()?; + let n_coordinates = ::try_from(de.read_leb128_u64()?)?; + println!("n_coordinates: {n_coordinates}"); + let mut coordinate_keypairs = RevisionMap::with_capacity(n_coordinates); + for i in 0..n_coordinates { + println!("reading coordinate {i}"); + let coordinate = de.read()?; + let n_keys = ::try_from(de.read_leb128_u64()?)?; + println!("n_keys {n_keys}"); + let chain = (0..n_keys) + .map(|_| de.read::()) + .collect::, _>>()?; + coordinate_keypairs.map.insert(coordinate, chain); + } + + println!("HEY"); + + let signing_key = if de.value().len() < KMAC_KEY_LENGTH { + None + } else { + Some(SymmetricKey::try_from_bytes( + de.read_array::()?, + )?) + }; + + println!("OH"); + + Ok(Self { + s, + tsk, + coordinate_keypairs, + signing_key, + }) + } +} + +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 CoordinateSecretKey { + type Error = Error; + + fn length(&self) -> usize { + 1 + match self { + CoordinateSecretKey::Hybridized { + postquantum_sk, + elgamal_sk, + } => elgamal_sk.length() + postquantum_sk.length(), + CoordinateSecretKey::Classic { elgamal_sk } => elgamal_sk.length(), + } + } + + fn write(&self, ser: &mut Serializer) -> Result { + match self { + CoordinateSecretKey::Hybridized { + postquantum_sk, + elgamal_sk, + } => { + let mut n = ser.write_leb128_u64(1)?; + n += ser.write(elgamal_sk)?; + n += ser.write(postquantum_sk)?; + Ok(n) + } + CoordinateSecretKey::Classic { elgamal_sk } => { + let mut n = ser.write_leb128_u64(0)?; + n += ser.write(elgamal_sk)?; + Ok(n) + } + } + } + + fn read(de: &mut Deserializer) -> Result { + let is_hybridized = de.read_leb128_u64()?; + if 1 == is_hybridized { + let elgamal_sk = de.read()?; + let postquantum_sk = de.read()?; + Ok(Self::Hybridized { + postquantum_sk, + elgamal_sk, + }) + } else if 0 == is_hybridized { + Ok(Self::Classic { + elgamal_sk: de.read()?, + }) + } 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.coordinate_keys.len()) + + self + .coordinate_keys + .iter() + .map(|(coordinate, chain)| { + coordinate.length() + + to_leb128_len(chain.len()) + + chain.iter().map(|sk| sk.length()).sum::() + }) + .sum::() + + self + .msk_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.coordinate_keys.len() as u64)?; + for (coordinate, chain) in self.coordinate_keys.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.msk_signature { + n += ser.write_array(kmac)?; + } + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let id = de.read::()?; + 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() < KMAC_SIG_LENGTH { + None + } else { + Some(de.read_array::()?) + }; + Ok(Self { + id, + coordinate_keys, + msk_signature, + }) + } +} + +impl Serializable for SeedEncapsulation { + type Error = Error; + + fn length(&self) -> usize { + 1 + match self { + Self::Classic(enc) => enc.len(), + Self::Hybridized(enc) => enc.length(), + } + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = 0; + match self { + Self::Classic(enc) => { + n += ser.write_leb128_u64(0)?; + n += ser.write_array(enc)?; + } + Self::Hybridized(enc) => { + n += ser.write_leb128_u64(1)?; + n += ser.write(enc)?; + } + } + Ok(n) + } + + fn read(de: &mut Deserializer) -> Result { + let is_hybridized = de.read_leb128_u64()?; + if is_hybridized == 1 { + de.read::().map(Self::Hybridized) + } else { + de.read_array::() + .map(Self::Classic) + .map_err(Self::Error::from) + } + } +} + +impl Serializable for Encapsulation { + type Error = Error; + + fn length(&self) -> usize { + TAG_LENGTH + + to_leb128_len(self.traps.len()) + + self.traps.iter().map(Serializable::length).sum::() + + to_leb128_len(self.coordinate_encapsulations.len()) + + self + .coordinate_encapsulations + .iter() + .map(Serializable::length) + .sum::() + } + + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = ser.write_array(&self.tag)?; + n += ser.write_leb128_u64(self.traps.len() as u64)?; + for trap in &self.traps { + n += ser.write(trap)?; + } + n += ser.write_leb128_u64(self.coordinate_encapsulations.len() as u64)?; + for enc in &self.coordinate_encapsulations { + n += ser.write(enc)?; + } + 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 n_encapsulations = ::try_from(de.read_leb128_u64()?)?; + let mut coordinate_encapsulations = HashSet::with_capacity(n_encapsulations); + for _ in 0..n_encapsulations { + let enc = de.read::()?; + coordinate_encapsulations.insert(enc); + } + Ok(Self { + tag, + traps, + coordinate_encapsulations, + }) + } +} + +impl Serializable for EncryptedHeader { + type Error = Error; + + fn length(&self) -> usize { + self.encapsulation.length() + + if let Some(metadata) = &self.encrypted_metadata { + to_leb128_len(to_leb128_len(metadata.len()) + metadata.len()) + } else { + 0 + } + } + + /// Tries to serialize the encrypted header. + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = self.encapsulation.write(ser)?; + match &self.encrypted_metadata { + Some(bytes) => n += ser.write_vec(bytes)?, + None => n += ser.write_vec(&[])?, + } + Ok(n) + } + + /// Tries to deserialize the encrypted header. + fn read(de: &mut Deserializer) -> Result { + let encapsulation = de.read::()?; + let ciphertext = de.read_vec()?; + let encrypted_metadata = if ciphertext.is_empty() { + None + } else { + Some(ciphertext) + }; + Ok(Self { + encapsulation, + encrypted_metadata, + }) + } +} + +impl Serializable for CleartextHeader { + type Error = Error; + + fn length(&self) -> usize { + SEED_LENGTH + + to_leb128_len( + self.metadata + .as_ref() + .map(std::vec::Vec::len) + .unwrap_or_default(), + ) + + self + .metadata + .as_ref() + .map(std::vec::Vec::len) + .unwrap_or_default() + } + + /// Tries to serialize the cleartext header. + fn write(&self, ser: &mut Serializer) -> Result { + let mut n = ser.write_array(self.symmetric_key.as_bytes())?; + match &self.metadata { + Some(bytes) => n += ser.write_vec(bytes)?, + None => n += ser.write_vec(&[])?, + } + Ok(n) + } + + /// Tries to deserialize the cleartext header. + fn read(de: &mut Deserializer) -> Result { + let symmetric_key = SymmetricKey::try_from_bytes(de.read_array::()?)?; + let metadata = de.read_vec()?; + let metadata = if metadata.is_empty() { + None + } else { + Some(metadata) + }; + Ok(Self { + symmetric_key, + metadata, + }) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; + + use super::*; + use crate::{ + abe_policy::{AttributeStatus, EncryptionHint}, + core::{ + primitives::{encaps, mpk_keygen, setup, update_coordinate_keys, usk_keygen}, + MIN_TRACING_LEVEL, + }, + }; + + #[test] + fn test_coordinate_keypair() { + let mut rng = CsRng::from_entropy(); + let s = Scalar::new(&mut rng); + let h = EcPoint::from(&s); + + { + let ckp = CoordinateKeypair::random(&mut rng, &h, true); + let bytes = ckp.serialize().unwrap(); + assert_eq!(bytes.len(), ckp.length()); + let ckp_ = CoordinateKeypair::deserialize(&bytes).unwrap(); + assert_eq!(ckp, ckp_); + } + + { + let ckp = CoordinateKeypair::random(&mut rng, &h, false); + let bytes = ckp.serialize().unwrap(); + assert_eq!(bytes.len(), ckp.length()); + let ckp_ = CoordinateKeypair::deserialize(&bytes).unwrap(); + assert_eq!(ckp, ckp_); + } + } + + #[test] + fn test_coordinate_pk() { + let mut rng = CsRng::from_entropy(); + + { + let elgamal_pk = EcPoint::from(&Scalar::new(&mut rng)); + let cpk = CoordinatePublicKey::Classic { elgamal_pk }; + let bytes = cpk.serialize().unwrap(); + assert_eq!(bytes.len(), cpk.length()); + let cpk_ = CoordinatePublicKey::deserialize(&bytes).unwrap(); + assert_eq!(cpk, cpk_); + } + + { + let elgamal_pk = EcPoint::from(&Scalar::new(&mut rng)); + let postquantum_pk = postquantum::keygen(&mut rng).1; + let cpk = CoordinatePublicKey::Hybridized { + postquantum_pk, + elgamal_pk, + }; + + let bytes = cpk.serialize().unwrap(); + assert_eq!(bytes.len(), cpk.length()); + let cpk_ = CoordinatePublicKey::deserialize(&bytes).unwrap(); + assert_eq!(cpk, cpk_); + } + } + + #[test] + fn test_tracing_keys() { + let mut rng = CsRng::from_entropy(); + let mut tsk = TracingSecretKey::default(); + for _ in 0..MIN_TRACING_LEVEL + 2 { + tsk.increase_tracing(&mut rng); + } + + { + let bytes = tsk.serialize().unwrap(); + assert_eq!(bytes.len(), tsk.length()); + let tsk_ = TracingSecretKey::deserialize(&bytes).unwrap(); + assert_eq!(tsk, tsk_); + } + } + + #[test] + fn test_serialization() { + let mut rng = CsRng::from_entropy(); + let coordinate_1 = Coordinate::random(&mut rng); + let coordinate_2 = Coordinate::random(&mut rng); + let coordinate_3 = Coordinate::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::Classic, 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(&mut rng, MIN_TRACING_LEVEL + 2).unwrap(); + update_coordinate_keys(&mut rng, &mut msk, universe).unwrap(); + let mpk = mpk_keygen(&msk).unwrap(); + + // Check Covercrypt `MasterSecretKey` serialization. + { + let bytes = msk.serialize().unwrap(); + assert_eq!(bytes.len(), msk.length(), "Wrong master secret key length"); + let msk_ = MasterSecretKey::deserialize(&bytes).unwrap(); + assert_eq!(msk, msk_, "Wrong `MasterSecretKey` deserialization."); + } + + // Check Covercrypt `PublicKey` serialization. + { + let bytes = mpk.serialize().unwrap(); + assert_eq!(bytes.len(), mpk.length(), "Wrong master public key length"); + let mpk_ = MasterPublicKey::deserialize(&bytes).unwrap(); + assert_eq!(mpk, mpk_, "Wrong `PublicKey` derserialization."); + } + + // Check Covercrypt `UserSecretKey` serialization. + { + let usk = usk_keygen(&mut rng, &mut msk, user_set).unwrap(); + let bytes = usk.serialize().unwrap(); + assert_eq!(bytes.len(), usk.length(), "Wrong user secret key size"); + let usk_ = UserSecretKey::deserialize(&bytes).unwrap(); + assert_eq!(usk, usk_, "Wrong `UserSecretKey` deserialization."); + } + + // Check Covercrypt `Encapsulation` serialization. + { + let (_, encapsulation) = encaps(&mut rng, &mpk, &target_set).unwrap(); + let bytes = encapsulation.serialize().unwrap(); + assert_eq!( + bytes.len(), + encapsulation.length(), + "Wrong encapsulation size" + ); + let encapsulation_ = Encapsulation::deserialize(&bytes).unwrap(); + assert_eq!( + encapsulation, encapsulation_, + "Wrong `Encapsulation` serialization." + ); + } + + // // Setup Covercrypt. + // { + // use crate::{abe_policy::AccessPolicy, test_utils::policy, Covercrypt}; + + // let cc = Covercrypt::default(); + // let policy = policy()?; + // let user_policy = AccessPolicy::from_boolean_expression( + // "Department::MKG && Security Level::Top Secret", + // )?; + // let encryption_policy = AccessPolicy::from_boolean_expression( + // "Department::MKG && Security Level::High Secret", + // )?; + // let (msk, mpk) = cc.generate_master_keys(&policy)?; + // let usk = cc.generate_user_secret_key(&msk, &user_policy, &policy)?; + + // // Check `EncryptedHeader` serialization. + // let (_secret_key, encrypted_header) = + // EncryptedHeader::generate(&cc, &policy, &mpk, &encryption_policy, None, None)?; + // let bytes = encrypted_header.serialize()?; + // assert_eq!( + // bytes.len(), + // encrypted_header.length(), + // "Wrong encapsulation size." + // ); + // let encrypted_header_ = EncryptedHeader::deserialize(&bytes)?; + // assert_eq!( + // encrypted_header, encrypted_header_, + // "Wrong `EncryptedHeader` derserialization." + // ); + + // // Check `CleartextHeader` serialization. + // let cleartext_header = encrypted_header.decrypt(&cc, &usk, None)?; + // let bytes = cleartext_header.serialize()?; + // assert_eq!( + // bytes.len(), + // cleartext_header.length(), + // "Wrong cleartext header size." + // ); + // let cleartext_header_ = CleartextHeader::deserialize(&bytes)?; + // assert_eq!( + // cleartext_header, cleartext_header_, + // "Wrong `CleartextHeader` derserialization." + // ); + // } + } +} diff --git a/src/core/tests.rs b/src/core/tests.rs index 870f8c2c..4417623a 100644 --- a/src/core/tests.rs +++ b/src/core/tests.rs @@ -3,13 +3,8 @@ use std::collections::{HashMap, HashSet}; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; use crate::{ - abe_policy::{ - AccessPolicy, Attribute, AttributeStatus, Coordinate, DimensionBuilder, EncryptionHint, - Policy, - }, - core::primitives::{decaps, encaps, mpk_keygen, update_coordinate_keys}, - test_utils::policy, - Covercrypt, Error, + abe_policy::{AttributeStatus, Coordinate, EncryptionHint}, + core::primitives::{decaps, encaps, mpk_keygen, refresh, rekey, update_coordinate_keys}, }; use super::{ @@ -18,39 +13,64 @@ use super::{ }; /// This test asserts that it is possible to encapsulate a key for a given -/// coordinate and that several users which key is associated with this +/// coordinate and that different users which key is associated with this /// coordinate can open the resulting encapsulation. #[test] -fn test_broadcast() { +fn test_encapsulation() { let mut rng = CsRng::from_entropy(); - let mut msk = setup(&mut rng, MIN_TRACING_LEVEL).unwrap(); - - let coordinate = Coordinate::random(&mut rng); + let other_coordinate = Coordinate::random(&mut rng); + let target_coordinate = Coordinate::random(&mut rng); + let mut msk = setup(&mut rng, MIN_TRACING_LEVEL).unwrap(); update_coordinate_keys( &mut rng, &mut msk, - HashMap::from_iter([( - coordinate.clone(), - (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), - )]), + HashMap::from_iter([ + ( + other_coordinate.clone(), + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + ), + ( + target_coordinate.clone(), + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + ), + ]), ) .unwrap(); - let mpk = mpk_keygen(&msk).unwrap(); - let (key, enc) = encaps(&mut rng, &mpk, &HashSet::from_iter([coordinate.clone()])).unwrap(); + let (key, enc) = encaps( + &mut rng, + &mpk, + &HashSet::from_iter([target_coordinate.clone()]), + ) + .unwrap(); assert_eq!(enc.coordinate_encapsulations.len(), 1); for _ in 0..3 { - let usk = usk_keygen(&mut rng, &mut msk, HashSet::from_iter([coordinate.clone()])).unwrap(); + let usk = usk_keygen( + &mut rng, + &mut msk, + HashSet::from_iter([target_coordinate.clone()]), + ) + .unwrap(); assert_eq!(usk.coordinate_keys.len(), 1); assert_eq!(Some(&key), decaps(&usk, &enc).unwrap().as_ref()); } + + let usk = usk_keygen( + &mut rng, + &mut msk, + HashSet::from_iter([other_coordinate.clone()]), + ) + .unwrap(); + assert_eq!(usk.coordinate_keys.len(), 1); + assert_eq!(None, decaps(&usk, &enc).unwrap().as_ref()); } /// This test verifies that the correct number of keys is added/removed upon -/// updating the MSK. +/// updating the MSK. It also check that the correct number of coordinate keys +/// are given to the MPK, and removed upon deprecation. #[test] fn test_update() { let mut rng = CsRng::from_entropy(); @@ -105,97 +125,123 @@ fn test_update() { assert_eq!(mpk.coordinate_keys.len(), 5); } +/// This test asserts that re-keyed coordinates allow creating encapsulations +/// using the new keys: old USK cannot open the new ones and new USK cannot open +/// the old ones. #[test] -fn test_master_rekey() -> Result<(), Error> { - let d1 = DimensionBuilder::new( - "D1", - vec![ - ("A", EncryptionHint::Classic), - ("B", EncryptionHint::Classic), - ], - false, - ); - let d2 = DimensionBuilder::new( - "D2", - vec![ - ("A", EncryptionHint::Classic), - ("B", EncryptionHint::Classic), - ], - false, - ); - let mut policy = Policy::new(); - policy.add_dimension(d1)?; - policy.add_dimension(d2)?; - - let cover_crypt = Covercrypt::default(); - let (mut msk, _) = cover_crypt.setup()?; - let _ = cover_crypt.update_master_keys(&policy, &mut msk); - - // There is one key per coordinate. - let mut n_keys = (2 + 1) * (2 + 1); - assert_eq!(msk.coordinate_keypairs.count_elements(), n_keys); - - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D1", "A")); - cover_crypt.rekey(&rekey_access_policy, &policy, &mut msk)?; - n_keys += 2; - assert_eq!(msk.coordinate_keypairs.count_elements(), n_keys); - - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D1", "B")); - cover_crypt.rekey(&rekey_access_policy, &policy, &mut msk)?; - n_keys += 2; - assert_eq!(msk.coordinate_keypairs.count_elements(), n_keys); - - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D2", "A")); - cover_crypt.rekey(&rekey_access_policy, &policy, &mut msk)?; - n_keys += 2; - assert_eq!(msk.coordinate_keypairs.count_elements(), n_keys); - - Ok(()) -} - -#[test] -fn test_refresh_user_key() -> Result<(), Error> { - let policy = policy()?; - let cover_crypt = Covercrypt::default(); - let (mut msk, _) = cover_crypt.setup()?; - let _ = cover_crypt.update_master_keys(&policy, &mut msk); +fn test_rekey() { + let mut rng = CsRng::from_entropy(); + let coordinate_1 = Coordinate::random(&mut rng); + let coordinate_2 = Coordinate::random(&mut rng); + let subspace_1 = HashSet::from_iter([coordinate_1.clone()]); + let subspace_2 = HashSet::from_iter([coordinate_2.clone()]); + let universe = HashSet::from_iter([coordinate_1.clone(), coordinate_2.clone()]); - let ap = AccessPolicy::parse("Department::MKG && Security Level::High Secret")?; - let mut usk = cover_crypt.generate_user_secret_key(&mut msk, &ap, &policy)?; - let original_usk = usk.clone(); + let mut msk = setup(&mut rng, MIN_TRACING_LEVEL).unwrap(); + update_coordinate_keys( + &mut rng, + &mut msk, + HashMap::from_iter([ + ( + coordinate_1.clone(), + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + ), + ( + coordinate_2.clone(), + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + ), + ]), + ) + .unwrap(); + let mpk = mpk_keygen(&msk).unwrap(); + let mut usk_1 = usk_keygen(&mut rng, &mut msk, subspace_1.clone()).unwrap(); + let mut usk_2 = usk_keygen(&mut rng, &mut msk, subspace_2.clone()).unwrap(); - // Re-key the access policy associated to the user key. - cover_crypt.rekey(&ap, &policy, &mut msk)?; + let (old_key_1, old_enc_1) = encaps(&mut rng, &mpk, &subspace_1).unwrap(); + let (old_key_2, old_enc_2) = encaps(&mut rng, &mpk, &subspace_2).unwrap(); - cover_crypt.refresh_usk(&mut usk, &mut msk, true)?; + // Old USK can open encapsulations associated with their coordinate. assert_eq!( - usk.coordinate_keys.count_elements(), - 2 * original_usk.coordinate_keys.count_elements() + Some(&old_key_1), + decaps(&usk_1, &old_enc_1).unwrap().as_ref() ); - for x_i in original_usk.coordinate_keys.flat_iter() { - assert!(usk.coordinate_keys.flat_iter().any(|x| x == x_i)); + assert_eq!(None, decaps(&usk_1, &old_enc_2).unwrap()); + assert_eq!(Some(old_key_2), decaps(&usk_2, &old_enc_2).unwrap()); + assert_eq!(None, decaps(&usk_2, &old_enc_1).unwrap()); + + // Re-key all space coordinates. + rekey(&mut rng, &mut msk, universe).unwrap(); + let mpk = mpk_keygen(&msk).unwrap(); + + let (new_key_1, new_enc_1) = encaps(&mut rng, &mpk, &subspace_1).unwrap(); + let (new_key_2, new_enc_2) = encaps(&mut rng, &mpk, &subspace_2).unwrap(); + + // Old USK cannot open new encapsulations. + assert_eq!(None, decaps(&usk_1, &new_enc_1).unwrap()); + assert_eq!(None, decaps(&usk_1, &new_enc_2).unwrap()); + assert_eq!(None, decaps(&usk_2, &new_enc_2).unwrap()); + assert_eq!(None, decaps(&usk_2, &new_enc_1).unwrap()); + + // Refresh USK. + // Only the first one keeps its old rights. + refresh(&mut rng, &mut msk, &mut usk_1, true).unwrap(); + refresh(&mut rng, &mut msk, &mut usk_2, false).unwrap(); + + // Refreshed USK can open the new encapsulation. + assert_eq!(Some(new_key_1), decaps(&usk_1, &new_enc_1).unwrap()); + assert_eq!(None, decaps(&usk_1, &new_enc_2).unwrap()); + assert_eq!(Some(new_key_2), decaps(&usk_2, &new_enc_2).unwrap()); + assert_eq!(None, decaps(&usk_2, &new_enc_1).unwrap()); + + // Only USK 1 can still open the old encapsulation. + assert_eq!(Some(old_key_1), decaps(&usk_1, &old_enc_1).unwrap()); + assert_eq!(None, decaps(&usk_1, &old_enc_2).unwrap()); + assert_eq!(None, decaps(&usk_2, &old_enc_2).unwrap()); + assert_eq!(None, decaps(&usk_2, &old_enc_1).unwrap()); +} + +/// This test asserts that forged USK cannot be refreshed. +#[test] +fn test_integrity_check() { + let mut rng = CsRng::from_entropy(); + let coordinate_1 = Coordinate::random(&mut rng); + let coordinate_2 = Coordinate::random(&mut rng); + let subspace_1 = HashSet::from_iter([coordinate_1.clone()]); + let subspace_2 = HashSet::from_iter([coordinate_2.clone()]); + + let mut msk = setup(&mut rng, MIN_TRACING_LEVEL).unwrap(); + update_coordinate_keys( + &mut rng, + &mut msk, + HashMap::from_iter([ + ( + coordinate_1.clone(), + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + ), + ( + coordinate_2.clone(), + (EncryptionHint::Classic, AttributeStatus::EncryptDecrypt), + ), + ]), + ) + .unwrap(); + let usk_1 = usk_keygen(&mut rng, &mut msk, subspace_1.clone()).unwrap(); + let usk_2 = usk_keygen(&mut rng, &mut msk, subspace_2.clone()).unwrap(); + + // Here we are trying to get access to both USK1 and USK2 rights. + let mut old_forged_usk = usk_1.clone(); + for (key, chain) in usk_2.coordinate_keys.iter() { + old_forged_usk + .coordinate_keys + .insert_new_chain(key.clone(), chain.clone()); } - // refresh the user key but do NOT preserve access to old partitions - cover_crypt.refresh_usk(&mut usk, &mut msk, false)?; - // the user should still have access to the same number of partitions assert_eq!( - usk.coordinate_keys.count_elements(), - original_usk.coordinate_keys.count_elements() - ); - for x_i in original_usk.coordinate_keys.flat_iter() { - assert!(!usk.coordinate_keys.flat_iter().any(|x| x == x_i)); - } - - // try to modify the user key and refresh - let part = Coordinate::from(vec![1, 6]); - usk.coordinate_keys.create_chain_with_single_value( - part.clone(), - msk.coordinate_keypairs - .get_latest(&part) - .unwrap() - .secret_key(), + old_forged_usk.coordinate_keys.count_elements(), + usk_1.coordinate_keys.count_elements() + usk_2.coordinate_keys.count_elements() ); - assert!(cover_crypt.refresh_usk(&mut usk, &mut msk, false).is_err()); - Ok(()) + // The forged key refresh is rejected: no modification is performed on it. + let mut new_forged_usk = old_forged_usk.clone(); + assert!(refresh(&mut rng, &mut msk, &mut new_forged_usk, true).is_err()); + assert_eq!(new_forged_usk, old_forged_usk); } diff --git a/src/core/tests.rs~ b/src/core/tests.rs~ deleted file mode 100644 index 3def70ff..00000000 --- a/src/core/tests.rs~ +++ /dev/null @@ -1,116 +0,0 @@ -#[test] -fn test_update_master_keys() -> Result<(), Error> { - let policy = policy()?; - let cover_crypt = Covercrypt::default(); - let (mut msk, _) = cover_crypt.setup()?; - let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; - - // same number of subkeys in public and secret key - assert_eq!(mpk.subkeys.len(), 30); - assert_eq!(msk.subkeys.count_elements(), 30); - - // rekey all partitions which include `Department::FIN` - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("Department", "FIN")); - cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; - // public key contains only the last subkeys - assert_eq!(mpk.subkeys.len(), 30); - // secret key stores the 2 old subkeys - assert_eq!(msk.subkeys.count_elements(), 32); - - // remove older subkeys for `Department::FIN` - cover_crypt.prune_master_secret_key(&rekey_access_policy, &policy, &mut msk)?; - // we only keep the last subkeys in the secret key - assert_eq!(msk.subkeys.count_elements(), 30); - - Ok(()) -} - -#[test] -fn test_master_rekey() -> Result<(), Error> { - let d1 = DimensionBuilder::new( - "D1", - vec![ - ("A", EncryptionHint::Classic), - ("B", EncryptionHint::Classic), - ], - false, - ); - let d2 = DimensionBuilder::new( - "D2", - vec![ - ("A", EncryptionHint::Classic), - ("B", EncryptionHint::Classic), - ], - false, - ); - let mut policy = Policy::new(); - policy.add_dimension(d1)?; - policy.add_dimension(d2)?; - - let cover_crypt = Covercrypt::default(); - let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - - // There is one key per coordinate. - let mut n_keys = (2 + 1) * (2 + 1); - assert_eq!(msk.subkeys.count_elements(), n_keys); - - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D1", "A")); - cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; - n_keys += 2; - assert_eq!(msk.subkeys.count_elements(), n_keys); - - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D1", "B")); - cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; - n_keys += 2; - assert_eq!(msk.subkeys.count_elements(), n_keys); - - let rekey_access_policy = AccessPolicy::Attr(Attribute::new("D2", "A")); - cover_crypt.rekey_master_keys(&rekey_access_policy, &policy, &mut msk, &mut mpk)?; - n_keys += 2; - assert_eq!(msk.subkeys.count_elements(), n_keys); - - Ok(()) -} - -#[test] -fn test_refresh_user_key() -> Result<(), Error> { - let policy = policy()?; - let cover_crypt = Covercrypt::default(); - let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - - let ap = AccessPolicy::parse("Department::MKG && Security Level::High Secret")?; - let mut usk = cover_crypt.generate_user_secret_key(&msk, &ap, &policy)?; - let original_usk = UserSecretKey::deserialize(usk.serialize()?.as_slice())?; - - // Re-key the access policy associated to the user key. - cover_crypt.rekey_master_keys(&ap, &policy, &mut msk, &mut mpk)?; - - cover_crypt.refresh_usk(&mut usk, &msk, true)?; - assert_eq!( - usk.subkeys.count_elements(), - 2 * original_usk.subkeys.count_elements() - ); - for x_i in original_usk.subkeys.flat_iter() { - assert!(usk.subkeys.flat_iter().any(|x| x == x_i)); - } - // refresh the user key but do NOT preserve access to old partitions - cover_crypt.refresh_usk(&mut usk, &msk, false)?; - // the user should still have access to the same number of partitions - assert_eq!( - usk.subkeys.count_elements(), - original_usk.subkeys.count_elements() - ); - for x_i in original_usk.subkeys.flat_iter() { - assert!(!usk.subkeys.flat_iter().any(|x| x == x_i)); - } - - // try to modify the user key and refresh - let part = Coordinate::from(vec![1, 6]); - usk.subkeys.create_chain_with_single_value( - part.clone(), - msk.subkeys.get_latest(&part).unwrap().clone(), - ); - assert!(cover_crypt.refresh_usk(&mut usk, &msk, false).is_err()); - - Ok(()) -} diff --git a/src/data_struct/revision_map.rs b/src/data_struct/revision_map.rs index 099cb3e7..2c50b671 100644 --- a/src/data_struct/revision_map.rs +++ b/src/data_struct/revision_map.rs @@ -132,8 +132,8 @@ where } /// Iterates through all key/value couples in arbitrary order. - pub fn iter(&self) -> impl Iterator)> { - self.map.iter().map(|(k, v)| (k, v.iter())) + pub fn iter(&self) -> impl Iterator)> { + self.map.iter() } /// Iterates through all revisions of a given key starting with the more diff --git a/src/lib.rs b/src/lib.rs index 1ddabb35..269986ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,9 +29,7 @@ mod error; pub mod abe_policy; pub mod core; pub mod data_struct; -#[cfg(any(test, feature = "test_utils"))] pub mod test_utils; - pub use error::Error; pub use self::core::{ diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index e07b26af..ec00b4a3 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -3,8 +3,9 @@ use crate::{ Error, }; -// // pub mod non_regression; +// pub mod non_regression; +/// Creates the test policy. pub fn policy() -> Result { let sec_level = DimensionBuilder::new( "Security Level", @@ -33,372 +34,319 @@ pub fn policy() -> Result { Ok(policy) } -// #[cfg(test)] -// mod tests { -// use cosmian_crypto_core::bytes_ser_de::Serializable; - -// use super::*; -// use crate::{ -// abe_policy::{AccessPolicy, Attribute, Coordinate, LegacyPolicy}, -// Covercrypt, EncryptedHeader, -// }; - -// use crate::UserSecretKey; - -// #[test] -// fn write_policy() { -// let _policy = policy().unwrap(); -// std::fs::write("target/policy.json", serde_json::to_vec(&_policy).unwrap()).unwrap(); -// } - -// /// Read policy from a file. Assert `LegacyPolicy` is convertible into a -// /// `Policy`. -// #[test] -// fn read_policy() { -// // Can read a `Policy` V2 -// let policy_v2_str = include_bytes!("./tests_data/policy_v2.json"); -// Policy::try_from(policy_v2_str.as_slice()).unwrap(); - -// // Can read a `Policy` V1 -// let policy_v1_str = include_bytes!("./tests_data/policy_v1.json"); -// Policy::try_from(policy_v1_str.as_slice()).unwrap(); - -// // Can read a `LegacyPolicy` -// let legacy_policy_str = include_bytes!("./tests_data/legacy_policy.json"); -// serde_json::from_slice::(legacy_policy_str).unwrap(); - -// // Can read `LegacyPolicy` as `Policy` -// Policy::try_from(legacy_policy_str.as_slice()).unwrap(); -// } - -// #[test] -// fn test_add_attribute() -> Result<(), Error> { -// let mut policy = policy()?; -// let cover_crypt = Covercrypt::default(); -// let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - -// let partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); -// let partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); -// assert_eq!(partitions_msk.len(), partitions_mpk.len()); -// for p in &partitions_msk { -// assert!(partitions_mpk.contains(p)); -// } - -// // -// // User secret key -// let decryption_policy = AccessPolicy::parse("Security Level::Low Secret")?; -// let mut low_secret_usk = -// cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; - -// // add sales department -// policy.add_attribute( -// Attribute::new("Department", "Sales"), -// EncryptionHint::Classic, -// )?; -// // update the master keys -// cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; -// let new_partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); -// let new_partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); -// assert_eq!(new_partitions_msk.len(), new_partitions_mpk.len()); -// for p in &new_partitions_msk { -// assert!(new_partitions_mpk.contains(p)); -// } -// assert_eq!(new_partitions_msk.len(), partitions_msk.len() + 6); - -// // -// // Encrypt -// let secret_sales_ap = -// AccessPolicy::parse("Security Level::Low Secret && Department::Sales")?; -// let (_, encrypted_header) = -// EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &secret_sales_ap, None, None)?; - -// // User cannot decrypt new message without refreshing its key -// assert!(encrypted_header -// .decrypt(&cover_crypt, &low_secret_usk, None) -// .is_err()); - -// cover_crypt.refresh_usk(&mut low_secret_usk, &msk, false)?; - -// // TODO: fix this behavior? -// assert!(encrypted_header -// .decrypt(&cover_crypt, &low_secret_usk, None) -// .is_err()); - -// Ok(()) -// } - -// #[test] -// fn test_delete_attribute() -> Result<(), Error> { -// let mut policy = policy()?; -// let cover_crypt = Covercrypt::default(); -// let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - -// let partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); -// let partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); -// assert_eq!(partitions_msk.len(), partitions_mpk.len()); -// for p in &partitions_msk { -// assert!(partitions_mpk.contains(p)); -// } - -// // -// // New user secret key -// let decryption_policy = AccessPolicy::parse( -// "Security Level::Top Secret && (Department::FIN || Department::HR)", -// )?; -// let mut top_secret_fin_usk = -// cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; - -// // -// // Encrypt -// let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; -// let (_, encrypted_header) = -// EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; - -// // remove the FIN department -// policy.remove_attribute(&Attribute::new("Department", "FIN"))?; - -// // update the master keys -// cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; -// let new_partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); -// let new_partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); -// assert_eq!(new_partitions_msk.len(), new_partitions_mpk.len()); -// for p in &new_partitions_msk { -// assert!(new_partitions_mpk.contains(p)); -// } -// // 5 is the size of the security level dimension -// assert_eq!(new_partitions_msk.len(), partitions_msk.len() - 6); - -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// // refresh the user key and preserve access to old partitions -// let _new_decryption_policy = -// AccessPolicy::parse("Security Level::Top Secret && Department::HR")?; - -// // refreshing the user key will remove access to removed partitions even if we -// // keep old rotations -// cover_crypt.refresh_usk(&mut top_secret_fin_usk, &msk, true)?; -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_err()); - -// Ok(()) -// } - -// #[test] -// fn test_deactivate_attribute() -> Result<(), Error> { -// let mut policy = policy()?; -// let cover_crypt = Covercrypt::default(); -// let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - -// let partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); -// let partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); -// assert_eq!(partitions_msk.len(), partitions_mpk.len()); - -// // -// // New user secret key -// let decryption_policy = AccessPolicy::parse( -// "Security Level::Top Secret && (Department::FIN || Department::HR)", -// )?; -// let mut top_secret_fin_usk = -// cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; - -// // -// // Encrypt -// let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; -// let (_, encrypted_header) = -// EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; - -// // remove the FIN department -// policy.disable_attribute(&Attribute::new("Department", "FIN"))?; - -// // update the master keys -// cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; -// let new_partitions_msk: Vec = msk.subkeys.keys().cloned().collect(); -// let new_partitions_mpk: Vec = mpk.subkeys.keys().cloned().collect(); -// // the disabled partition have been removed from mpk -// assert_eq!(new_partitions_msk.len() - 6, new_partitions_mpk.len()); -// // msk has not changed -// assert_eq!(new_partitions_msk.len(), partitions_msk.len()); - -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// // Can not encrypt using deactivated attribute -// let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; - -// assert!( -// EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None) -// .is_err() -// ); - -// // refresh the user key and preserve access to old partitions -// cover_crypt.refresh_usk(&mut top_secret_fin_usk, &msk, true)?; -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// // refresh the user key and remove access to old partitions should still work -// cover_crypt.refresh_usk(&mut top_secret_fin_usk, &msk, false)?; -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// // -// // Rotating the disabled attribute should only change the msk -// let rekey_ap = AccessPolicy::Attr(Attribute::new("Department", "FIN")); -// cover_crypt.rekey_master_keys(&rekey_ap, &policy, &mut msk, &mut mpk)?; -// assert_eq!(msk.subkeys.count_elements() - 8, mpk.subkeys.len()); - -// Ok(()) -// } - -// #[test] -// fn test_rename_attribute() -> Result<(), Error> { -// let mut policy = policy()?; -// let cover_crypt = Covercrypt::default(); -// let (mut msk, mut mpk) = cover_crypt.generate_master_keys(&policy)?; - -// // -// // New user secret key -// let decryption_policy = -// AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; -// let mut top_secret_fin_usk = -// cover_crypt.generate_user_secret_key(&msk, &decryption_policy, &policy)?; - -// // -// // Encrypt -// let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; -// let (_, encrypted_header) = -// EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; - -// // remove the FIN department -// policy.rename_attribute(&Attribute::new("Department", "FIN"), "Finance".to_string())?; - -// // update the master keys -// cover_crypt.update_master_keys(&policy, &mut msk, &mut mpk)?; - -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// // refresh the user key and preserve access to old partitions -// let _new_decryption_policy = -// AccessPolicy::parse("Security Level::Top Secret && Department::Finance")?; -// cover_crypt.refresh_usk(&mut top_secret_fin_usk, &msk, false)?; -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// Ok(()) -// } - -// #[test] -// fn encrypt_decrypt_sym_key() -> Result<(), Error> { -// let policy = policy()?; -// let access_policy = AccessPolicy::parse( -// "(Department::MKG || Department::FIN) && Security Level::Top Secret", -// ) -// .unwrap(); -// let cover_crypt = Covercrypt::default(); -// let (msk, mpk) = cover_crypt.generate_master_keys(&policy)?; -// let (sym_key, encrypted_key) = cover_crypt.encaps( -// &policy, -// &mpk, -// AccessPolicy::parse("Department::MKG && Security Level::Top Secret")?, -// )?; -// let usk = cover_crypt.generate_user_secret_key(&msk, &access_policy, &policy)?; -// let recovered_key = cover_crypt.decaps(&usk, &encrypted_key)?; -// assert_eq!(sym_key, recovered_key, "Wrong decryption of the key!"); -// Ok(()) -// } - -// #[test] -// fn test_single_attribute_in_access_policy() -> Result<(), Error> { -// // -// // Declare policy -// let policy = policy()?; - -// // -// // Setup Covercrypt -// let cover_crypt = Covercrypt::default(); -// let (msk, _master_public_key) = cover_crypt.generate_master_keys(&policy)?; - -// // -// // New user secret key -// let _user_key = cover_crypt.generate_user_secret_key( -// &msk, -// &AccessPolicy::parse("Security Level::Top Secret")?, -// &policy, -// )?; - -// Ok(()) -// } - -// #[test] -// fn test_rotate_then_encrypt() -> Result<(), Error> { -// // -// // Declare policy -// let policy = policy()?; -// let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret")?; - -// // -// // Setup Covercrypt -// let cover_crypt = Covercrypt::default(); -// let (mut msk, mut master_public_key) = cover_crypt.generate_master_keys(&policy)?; - -// // -// // New user secret key -// let mut top_secret_fin_usk = cover_crypt.generate_user_secret_key( -// &msk, -// &AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?, -// &policy, -// )?; - -// // -// // Encrypt -// let (_, encrypted_header) = EncryptedHeader::generate( -// &cover_crypt, -// &policy, -// &master_public_key, -// &top_secret_ap, -// None, -// None, -// )?; - -// let _plaintext_header = -// encrypted_header.decrypt(&cover_crypt, &top_secret_fin_usk, None)?; - -// // -// // Rotate argument (must update master keys) -// let rekey_ap = AccessPolicy::Attr(Attribute::from(("Security Level", "Top Secret"))); -// cover_crypt.rekey_master_keys(&rekey_ap, &policy, &mut msk, &mut master_public_key)?; - -// // -// // Encrypt with new attribute -// let (_, encrypted_header) = EncryptedHeader::generate( -// &cover_crypt, -// &policy, -// &master_public_key, -// &top_secret_ap, -// None, -// None, -// )?; - -// // Decryption fails without refreshing the user key -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_err()); - -// cover_crypt.refresh_usk(&mut top_secret_fin_usk, &msk, false)?; - -// // The refreshed key can decrypt the header -// assert!(encrypted_header -// .decrypt(&cover_crypt, &top_secret_fin_usk, None) -// .is_ok()); - -// Ok(()) -// } -// } +#[cfg(test)] +mod tests { + + use super::*; + use crate::{ + abe_policy::{AccessPolicy, Attribute, LegacyPolicy}, + Covercrypt, EncryptedHeader, + }; + + #[test] + fn write_policy() { + let _policy = policy().unwrap(); + std::fs::write("target/policy.json", serde_json::to_vec(&_policy).unwrap()).unwrap(); + } + + /// Read policy from a file. Assert `LegacyPolicy` is convertible into a + /// `Policy`. + #[test] + fn read_policy() { + // Can read a `Policy` V2 + let policy_v2_str = include_bytes!("./tests_data/policy_v2.json"); + Policy::try_from(policy_v2_str.as_slice()).unwrap(); + + // Can read a `Policy` V1 + let policy_v1_str = include_bytes!("./tests_data/policy_v1.json"); + Policy::try_from(policy_v1_str.as_slice()).unwrap(); + + // Can read a `LegacyPolicy` + let legacy_policy_str = include_bytes!("./tests_data/legacy_policy.json"); + serde_json::from_slice::(legacy_policy_str).unwrap(); + + // Can read `LegacyPolicy` as `Policy` + Policy::try_from(legacy_policy_str.as_slice()).unwrap(); + } + + #[test] + fn test_add_attribute() -> Result<(), Error> { + let mut policy = policy()?; + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let _ = cover_crypt.update_master_keys(&policy, &mut msk)?; + + let decryption_policy = AccessPolicy::parse("Security Level::Low Secret")?; + let mut low_secret_usk = + cover_crypt.generate_user_secret_key(&mut msk, &decryption_policy, &policy)?; + + policy.add_attribute( + Attribute::new("Department", "Sales"), + EncryptionHint::Classic, + )?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + + let secret_sales_ap = + AccessPolicy::parse("Security Level::Low Secret && Department::Sales")?; + let (_, encrypted_header) = + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &secret_sales_ap, None, None)?; + + // User cannot decrypt new message without refreshing its key + assert!(encrypted_header + .decrypt(&cover_crypt, &low_secret_usk, None) + .unwrap() + .is_none()); + + cover_crypt.refresh_usk(&mut low_secret_usk, &mut msk, false)?; + + assert!(encrypted_header + .decrypt(&cover_crypt, &low_secret_usk, None) + .unwrap() + .is_none()); + + Ok(()) + } + + #[test] + fn test_delete_attribute() -> Result<(), Error> { + let mut policy = policy()?; + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + + // New user secret key + let decryption_policy = AccessPolicy::parse( + "Security Level::Top Secret && (Department::FIN || Department::HR)", + )?; + let mut top_secret_fin_usk = + cover_crypt.generate_user_secret_key(&mut msk, &decryption_policy, &policy)?; + + // Encrypt + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; + let (_, encrypted_header) = + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; + + // remove the FIN department + policy.remove_attribute(&Attribute::new("Department", "FIN"))?; + + // update the master keys + let _ = cover_crypt.update_master_keys(&policy, &mut msk)?; + + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + // refresh the user key and preserve access to old partitions + let _new_decryption_policy = + AccessPolicy::parse("Security Level::Top Secret && Department::HR")?; + + // refreshing the user key will remove access to removed partitions even if we + // keep old rotations + cover_crypt.refresh_usk(&mut top_secret_fin_usk, &mut msk, true)?; + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_none()); + + Ok(()) + } + + #[test] + fn test_deactivate_attribute() -> Result<(), Error> { + let mut policy = policy()?; + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + + // + // New user secret key + let decryption_policy = AccessPolicy::parse( + "Security Level::Top Secret && (Department::FIN || Department::HR)", + )?; + let mut top_secret_fin_usk = + cover_crypt.generate_user_secret_key(&mut msk, &decryption_policy, &policy)?; + + // + // Encrypt + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; + let (_, encrypted_header) = + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; + + // remove the FIN department + policy.disable_attribute(&Attribute::new("Department", "FIN"))?; + + // update the master keys + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + // Can not encrypt using deactivated attribute + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; + + assert!( + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None) + .is_err() + ); + + // refresh the user key and preserve access to old partitions + cover_crypt.refresh_usk(&mut top_secret_fin_usk, &mut msk, true)?; + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + // refresh the user key and remove access to old partitions should still work + cover_crypt.refresh_usk(&mut top_secret_fin_usk, &mut msk, false)?; + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + Ok(()) + } + + #[test] + fn test_rename_attribute() -> Result<(), Error> { + let mut policy = policy()?; + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + + // New user secret key + let decryption_policy = + AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; + let mut top_secret_fin_usk = + cover_crypt.generate_user_secret_key(&mut msk, &decryption_policy, &policy)?; + + // Encrypt + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?; + let (_, encrypted_header) = + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; + + // remove the FIN department + policy.rename_attribute(&Attribute::new("Department", "FIN"), "Finance".to_string())?; + + // update the master keys + let _ = cover_crypt.update_master_keys(&policy, &mut msk)?; + + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + // refresh the user key and preserve access to old partitions + let _new_decryption_policy = + AccessPolicy::parse("Security Level::Top Secret && Department::Finance")?; + cover_crypt.refresh_usk(&mut top_secret_fin_usk, &mut msk, false)?; + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + Ok(()) + } + + #[test] + fn encrypt_decrypt_sym_key() -> Result<(), Error> { + let policy = policy()?; + let access_policy = AccessPolicy::parse( + "(Department::MKG || Department::FIN) && Security Level::Top Secret", + ) + .unwrap(); + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + let (sym_key, encrypted_key) = cover_crypt.encaps( + &policy, + &mpk, + AccessPolicy::parse("Department::MKG && Security Level::Top Secret")?, + )?; + let usk = cover_crypt.generate_user_secret_key(&mut msk, &access_policy, &policy)?; + let recovered_key = cover_crypt.decaps(&usk, &encrypted_key)?; + assert_eq!(Some(sym_key), recovered_key, "Wrong decryption of the key!"); + Ok(()) + } + + #[test] + fn test_single_attribute_in_access_policy() -> Result<(), Error> { + // + // Declare policy + let policy = policy()?; + + // + // Setup Covercrypt + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let _ = cover_crypt.update_master_keys(&policy, &mut msk)?; + + // + // New user secret key + let _user_key = cover_crypt.generate_user_secret_key( + &mut msk, + &AccessPolicy::parse("Security Level::Top Secret")?, + &policy, + )?; + + Ok(()) + } + + #[test] + fn test_rotate_then_encrypt() -> Result<(), Error> { + // + // Declare policy + let policy = policy()?; + let top_secret_ap = AccessPolicy::parse("Security Level::Top Secret")?; + + // + // Setup Covercrypt + let cover_crypt = Covercrypt::default(); + let (mut msk, _) = cover_crypt.setup()?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; + + // + // New user secret key + let mut top_secret_fin_usk = cover_crypt.generate_user_secret_key( + &mut msk, + &AccessPolicy::parse("Security Level::Top Secret && Department::FIN")?, + &policy, + )?; + + // + // Encrypt + let (_, encrypted_header) = + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; + + let _plaintext_header = + encrypted_header.decrypt(&cover_crypt, &top_secret_fin_usk, None)?; + + // + // Rotate argument (must update master keys) + let rekey_ap = AccessPolicy::Attr(Attribute::from(("Security Level", "Top Secret"))); + let mpk = cover_crypt.rekey(&rekey_ap, &policy, &mut msk)?; + + // + // Encrypt with new attribute + let (_, encrypted_header) = + EncryptedHeader::generate(&cover_crypt, &policy, &mpk, &top_secret_ap, None, None)?; + + // Decryption fails without refreshing the user key + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_none()); + + cover_crypt.refresh_usk(&mut top_secret_fin_usk, &mut msk, false)?; + + // The refreshed key can decrypt the header + assert!(encrypted_header + .decrypt(&cover_crypt, &top_secret_fin_usk, None) + .unwrap() + .is_some()); + + Ok(()) + } +} diff --git a/src/test_utils/non_regression.rs b/src/test_utils/non_regression.rs index cbe682e3..dfb273d3 100644 --- a/src/test_utils/non_regression.rs +++ b/src/test_utils/non_regression.rs @@ -53,8 +53,14 @@ impl EncryptionTestVector { let ciphertext = de.finalize(); let cover_crypt = Covercrypt::default(); - let plaintext_header = - encrypted_header.decrypt(&cover_crypt, &user_key, authentication_data)?; + let plaintext_header = encrypted_header + .decrypt(&cover_crypt, &user_key, authentication_data)? + .ok_or_else(|| { + Error::OperationNotPermitted( + "insufficient rights to open encapsulation".to_string(), + ) + })?; + assert_eq!(plaintext_header.metadata, header_metadata); let plaintext = cover_crypt.dem_decrypt( &plaintext_header.symmetric_key, @@ -116,7 +122,11 @@ struct UserSecretKeyTestVector { } impl UserSecretKeyTestVector { - pub fn new(msk: &MasterSecretKey, policy: &Policy, access_policy: &str) -> Result { + pub fn new( + msk: &mut MasterSecretKey, + policy: &Policy, + access_policy: &str, + ) -> Result { let config: GeneralPurposeConfig = GeneralPurposeConfig::default(); let transcoder: GeneralPurpose = GeneralPurpose::new(&STANDARD, config); Ok(Self { @@ -152,13 +162,13 @@ impl NonRegressionTestVector { // Policy settings // let policy = policy()?; - //policy.rotate(&Attribute::new("Department", "FIN"))?; // // Covercrypt setup // let cover_crypt = Covercrypt::default(); - let (msk, mpk) = cover_crypt.generate_master_keys(&policy)?; + let (mut msk, _) = cover_crypt.setup()?; + let mpk = cover_crypt.update_master_keys(&policy, &mut msk)?; // // Encryption header metadata @@ -172,17 +182,17 @@ impl NonRegressionTestVector { // // Create user decryption keys top_secret_mkg_fin_key: UserSecretKeyTestVector::new( - &msk, + &mut msk, &policy, "(Department::MKG || Department:: FIN) && Security Level::Top Secret", )?, medium_secret_mkg_key: UserSecretKeyTestVector::new( - &msk, + &mut msk, &policy, "Security Level::Medium Secret && Department::MKG", )?, top_secret_fin_key: UserSecretKeyTestVector::new( - &msk, + &mut msk, &policy, "Security Level::Top Secret && Department::FIN", )?, @@ -270,6 +280,10 @@ mod tests { ) .unwrap(); + let reg_vector: NonRegressionTestVector = + serde_json::from_str(include_str!("../../target/non_regression_vector.json")).unwrap(); + reg_vector.verify(); + Ok(()) } diff --git a/src/test_utils/tests_data/non_regression_vector.json b/src/test_utils/tests_data/non_regression_vector.json index 6e7800c1..32e49cc1 100644 --- a/src/test_utils/tests_data/non_regression_vector.json +++ b/src/test_utils/tests_data/non_regression_vector.json @@ -1 +1 @@ -{"public_key":"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","master_secret_key":"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","policy":"eyJ2ZXJzaW9uIjoiVjIiLCJsYXN0X2F0dHJpYnV0ZV92YWx1ZSI6OSwiZGltZW5zaW9ucyI6eyJTZWN1cml0eSBMZXZlbCI6eyJPcmRlcmVkIjp7IlByb3RlY3RlZCI6eyJpZCI6MSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIkxvdyBTZWNyZXQiOnsiaWQiOjIsImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMiLCJ3cml0ZV9zdGF0dXMiOiJFbmNyeXB0RGVjcnlwdCJ9LCJNZWRpdW0gU2VjcmV0Ijp7ImlkIjozLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIiwid3JpdGVfc3RhdHVzIjoiRW5jcnlwdERlY3J5cHQifSwiSGlnaCBTZWNyZXQiOnsiaWQiOjQsImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMiLCJ3cml0ZV9zdGF0dXMiOiJFbmNyeXB0RGVjcnlwdCJ9LCJUb3AgU2VjcmV0Ijp7ImlkIjo1LCJlbmNyeXB0aW9uX2hpbnQiOiJIeWJyaWRpemVkIiwid3JpdGVfc3RhdHVzIjoiRW5jcnlwdERlY3J5cHQifX19LCJEZXBhcnRtZW50Ijp7IlVub3JkZXJlZCI6eyJIUiI6eyJpZCI6NywiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIkZJTiI6eyJpZCI6OSwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIlImRCI6eyJpZCI6NiwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIk1LRyI6eyJpZCI6OCwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In19fX19","top_secret_mkg_fin_key":{"access_policy":"(Department::MKG || Department:: FIN) && Security Level::Top Secret","key":"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"},"medium_secret_mkg_key":{"access_policy":"Security Level::Medium Secret && Department::MKG","key":"z9nKF/RUO+yNxy8K8xLoUaBEydPA80vWJYvvATzuJAbcXgRvrwK2lVQKtC6L9bjevuK5qH7VOYpvZicqQ/IMAwMCAwgBAAaB03lHVTOpPkvsurGnfR539MbIhJ7LG9xXXV2BpJsHAgIIAQBf4h/GduakIi7c8uwHSRQ1GxUl0mhJ6TKdJdbFMcHWDAIBCAEAhdLt9TzDAmkTta9jsifdxF5VMSviczsz3ngYeoER9wVH5v1340JqJ6yBjCcswWDvCPm9TfYC2iOsN9dKXi+fyw=="},"top_secret_fin_key":{"access_policy":"Security Level::Top Secret && Department::FIN","key":"Zaa+BfghLwcXWfU8+W/M9m1Xy7EC6XACghrXiVbdDwV7YbDl6JWOlSTCRf25rQVTDlX+w5BjUH6S7bYYbh+eAwUCAwkBAKju46AscQ2x2IdTacrARB0k3kxdXu4kBd4hYoI6348CAgQJAQDoByFvzhiT/5cnl5/ieDTNU3uaKRF6UQkNRbBUI0MQDQIBCQEAyymfLiZFqkHyXQVxMdChHJpzzARD+DYJPVvmBb/6WQgCAgkBAAOMD6CjzVdDmY/YsO//EQh6Ce3LBDTAZX99Q2Ywv/wFAgUJAQEqjL/z+mkvSn64+4O8JnOut0M9BGpkuQxB4gB7KGnyGbUsgMdiV3E2uBCGY8KlilI9JcdKxYUr2F1NtaUyE1mJ4Y2Woz4LHJGj9HY2u5b6iFIa1q4voWbY9jPYtVYN2x/JtZn8AoLh1M0Ix2SWVRMPw5nd1i1N25A1Sq/JSzpe+mSkEahQzGwTxpJWbFmVJD4cekVEtD/xh6o28hkHo8kGAUsHjJ/LlLalVn4+xryZprorYa9lmTqHqC9cVToiXGVRW5wdzKAZ91RDl7SGFaqumm8N1BeLc3g5R0pAqWAvBxRDd2PaBmljY0bUmjATYgXpuFxD5bxHtz66e6YDInojiK5DJliSOWcLwbhrICPtnAd4VIV1YJjmVQ4VJYVgU4gOxK5VG4YpjLMr/G2C03U7I2ZzU3E2SKjKC7p2QcWQiDHhyBMNOYRyBm//N6Iy6pgQ+ZMV6wEU/I8NNlNm/LFH6abRaC314AAVK1jkBMzjR74aIb1406ATVrep+qRKuT12wkLADGKapUipMXUWNYepEJE2EAsYskkKbH+76xTW9aXv86Ti2WiYTLaFoFbdOlpnhTu4k0hOoQMb5Ca2MqnxvGPEsV7gwnBI6KNwN6lmcy+r13iD4cP0yLjxNR0t9kJcEAPWyFjMFmrXYXPugGQik71XlUwQnB9Zhm7J6lqyFEzmEFHhIZ8BRyVRZrlCNkuGh5aW50C5Gy6fF8IjRkCRp6IuN0Tg5Yj62GEqCoE/7JB4e5iTyEP9lT97Ek2Jlj0QW46BnLqRV3ont7pmKiLLM6AZkzMoRrs58idp92PhVBn+UJvTZoQbyW5ZxpBOd8d/wXcBpimvhWJq+wuEulTTdgi6iRnPG2LuO8KKoGmYvCMiK0BsrBkIkxqyzLr6Jq60Ob665a0y6QN+ZpolKTidY7jf21z3wKUC/BdKQ0mKVktKVFYItzn92MgYZbLI0kMg1atC5lc0+VhhZBlGixiItxCOknGY4LNmxVOC04AqssGVyScgy5aVgR/NFJyYRTJHyaKWUUsEgVRiMRi5KcFkQ0NVGZhfu472dT24UpkBUUBVE2W/oackuE8/AmO/sr7fIaT993VdQKIP5zhZEL0QAwL4gsnLWxrTxSSiujg4qG3FkEeNY8xK1G5//I+lxWrGYikJrF4g2YI7GM6s9Z8kMhVVfHRZpVfLOqJF5JiBAForoXeJCcg0ZVkNCCQNEnJyOM+RQgNsxoN27KlJeDBkqzWayKw4iqfMlH8bWl3idCZh4kwZDIwWOcsXaToyMR5L1X6RaakvYCTHKCMlhqkQJynF0ra47GsdbMNBMAZZl5pYI2vSpTDB3KsnKETeOhsMl2yD4yO+PKUngKaWBVpymUOolgsoyZvtkEyRUXUXtTAatmV2SIjIjIB7mKpbB11XoC6PgBlUQnCg+GTVSQqK92sXEy1p07ZMd0hi1x64NLEioHgbuwz3iw42eqHkp6vWIJFjRnSw9ZXPImGn4T4zG0NuzDqqqZvKtGQ1bKzvZj+77AczMl4MWmncF6uOIHZTSW8669mo315HjIYc4YEtgcX1UyCDkFEoe1alxS6FDBvwYYj6LGE2p0qw30QqLp39LK61jUHrC+IM9vwhl1zs"},"low_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"8pmS0RSF03XWSGmnBJ365ajdj7UAebtwJiBtMSvvoDr0ELS3w+a+tkO83MCa3QzyInuijsUZarBrwsEcmSPfbSaIbS7xLyz9scSRpnS0op0BACLnxqX+uAcZHSDOOgWBAzq2VTdnFOXuIz5GFi2jnNTJIK6A6LBStd+eYBma6IfsxqE+5EKcLL/LX0VyB3AD8h3cxWfWBggbFnVwddCwhvKRlx2WUcTFo1ReqUNS4ZsiqVzt9ImGe1mHKSa65J2UMyDNZQpNeA==","header_metadata":"AAAAAQ==","authentication_data":""},"top_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Top Secret","plaintext":"dG9wX3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"xOgSNb8ClY/EySXmXSFA1GbspILfqGu+HnHTRzhjXRkadlMTugfbEYpWSr93OwTZIvWKgaynYrgnk5eYz+zuZQPf2RuuC7sHSRXPm24VCmABAfYSbj6zyaLE2eideKcmc2zAyqaKRCosbaGL8a/NLOrCDGw1WbT78b7P6KY7Xe3agxnGZ4AF7f/d4dp1JGPPwZ+woDD8W7SbKQExd5KK3lGvkDG3EPGEyzBxm5GMR/sRAjytDDF4Lit+yf3Qwheikgq/GUIXKSO7itTFNbKjLDU7vZd/ctlpjsvrjykqu+B4fU4Qozn2YjHwO/2dkozZo3mLfh1zAP02+HP/+CGZLXenQMLn1BWJHQruYr0+6nrCxXjsF9RCfKn067vYYDFLCTlQWWkPWwjhxWxD4Yqs2K2nYEM0QVlgMeX29XbcbskOrCOww8wJWqKfFRQRxIbAKSL84kyyfojgrEAYghXt8ZrJiiuIfhLbyNu2olJXzYwhZxEISZ4wTqSjJSwz4zi77qLdhqoCGhadkCq/7S3OVcnnFL1IbduVvKcIh8ZXBKUvPX6EWEevlku2bVcGNf3xt3qRl2n3osJnOye5JkYXyz8Op2sb7HBphi/r1vq886uBNRpRT1F4St+d8j8kkfXy0fk6trc+pOOuIpnhxR/CAqO+w4LOdxCwjSh12tmh34EVa6iMgGIgOL+7hZl2VA6lb/Kp/OX5u8xdE9TnlSQBlmI4yQxmtI5sfXEHRioyhQcYfTljRyaNtllc6FIWms4Xm02tHIcZW0exXupcxaqI0iTe7DySWqJGQQAoZXlSdh+jVVA7i2YTD9z0mTGrts7hh3c0OHx3pbus3P0SVG76/GsvW+g/K7QBE64QJl7LN2U5q858QXu5nkrAczrCE9PDufOrNrUT7yNDslFj5xl1XcfXx6k9rjczeY5sUKuA8lw9gKwtABsFeZNrDeMwsmvwmOuZmA1QD6K/4NnF4Oe07bOmPi41v3IQJN946Z6U0dpdnYp6cqFkTxVqhSoWVMxZuMYbSYjsmPKs8M1K/Jo8WO/vMxx9G3Ww3IBy5QNcwDM3GnqpV4ivW9o38y06ySH99XcAEb0kj9eVcJt+y721EeUZPDgH5OE/jE6KNgrXY1lP5m6NnFuS9M0cpbSulS2IA+GQ6OU+n9huUHA4Yd2ncoC5TDG2H0IGBOYPu8awKgB/YlCJZDrR13fvOv0/VctKNiha/lnToznBMOaHxAWKh6YXbgtasy3xkNIdAU67jWrOx3pai9v1JDi/QG/8u3vI4N3q2y9ZSCsrFhQSUrgA7GgtqXhv/8XGzd0zd5f4iuAdRUz5VJttIdI+sk+wPK01YZ6NEbbadkK5lQuRQIboASeUIGkjgU5kFoUcG8C+bcFBuzP2cqzYNQL4aGE/JCMvOjuVUz+R2+SxQl/oFT6gfcAAyVXmsJ3BG3akc4IfamaepWVnbcHo/pi7RusSpwHWFAPtXTVN5tH0p7IU51hjn/Pq+7bBDjpkXyg1l9Zd+w27T2dMOR8XMpFf09JjEnMNhE3va9HA8BW7kgzowRLuhkurIJLXUC784NlFWLJvofDTsAdlxqV7SPn0UGfX/B7mUsAygW3+NnV2Of4U91UuAkQ0mgi6Yjomz9BCGvbYEmCvtKtgnoTawQPGQopx0u/Dr2x9Wm3iFA==","header_metadata":"AAAAAQ==","authentication_data":"AAAAAg=="},"low_secret_fin_test_vector":{"encryption_policy":"Department::FIN && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9maW5fcGxhaW50ZXh0","ciphertext":"Dn5Sj1av5Dd7OcJEWO4gDtrlwy7jI3ZvgyUE0MWg12L+PHXHn5hERx7nIU3X61kn2rpJswFXki7MKiQkSrVJJEH9TW/l38eh4T3EMgnd6LoBAGQ5MGZYj50tLDxS8/U94pKNmsW/c6DLPI9GD6B1jLsfAMi7b7Qhgz3dSJdPIQkE72WmcgwuKyMd9r+SSrOBGUxiq5n3cxGLccxB5yyeE0qpusk6ZXk=","header_metadata":"","authentication_data":""}} \ No newline at end of file +{"public_key":"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","master_secret_key":"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","policy":"eyJ2ZXJzaW9uIjoiVjIiLCJsYXN0X2F0dHJpYnV0ZV92YWx1ZSI6OSwiZGltZW5zaW9ucyI6eyJEZXBhcnRtZW50Ijp7IlVub3JkZXJlZCI6eyJGSU4iOnsiaWQiOjksImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMiLCJ3cml0ZV9zdGF0dXMiOiJFbmNyeXB0RGVjcnlwdCJ9LCJIUiI6eyJpZCI6NywiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIlImRCI6eyJpZCI6NiwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIk1LRyI6eyJpZCI6OCwiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In19fSwiU2VjdXJpdHkgTGV2ZWwiOnsiT3JkZXJlZCI6eyJQcm90ZWN0ZWQiOnsiaWQiOjEsImVuY3J5cHRpb25faGludCI6IkNsYXNzaWMiLCJ3cml0ZV9zdGF0dXMiOiJFbmNyeXB0RGVjcnlwdCJ9LCJMb3cgU2VjcmV0Ijp7ImlkIjoyLCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIiwid3JpdGVfc3RhdHVzIjoiRW5jcnlwdERlY3J5cHQifSwiTWVkaXVtIFNlY3JldCI6eyJpZCI6MywiZW5jcnlwdGlvbl9oaW50IjoiQ2xhc3NpYyIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In0sIkhpZ2ggU2VjcmV0Ijp7ImlkIjo0LCJlbmNyeXB0aW9uX2hpbnQiOiJDbGFzc2ljIiwid3JpdGVfc3RhdHVzIjoiRW5jcnlwdERlY3J5cHQifSwiVG9wIFNlY3JldCI6eyJpZCI6NSwiZW5jcnlwdGlvbl9oaW50IjoiSHlicmlkaXplZCIsIndyaXRlX3N0YXR1cyI6IkVuY3J5cHREZWNyeXB0In19fX19","top_secret_mkg_fin_key":{"access_policy":"(Department::MKG || Department:: FIN) && Security Level::Top Secret","key":"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"},"medium_secret_mkg_key":{"access_policy":"Security Level::Medium Secret && Department::MKG","key":"AjdEcs1aGUj7ww24PJ2Sw66K+bWM2HeBt+c5HlCay9YB/WCawaWvR5Jjouq/dDQOJCNmkI4S1MA/ewINO9pK/wEIAQIBACJhiIeQSFM2C0ZJUKPwbg5NrWMgZuWD1FmIq35qNnkEAQMBAF4fvi+p7ZyhypW/GOmV7WniQGdJGX9pOhSV+j6fzsEPAgIIAQCydu0xyNgQW2uk2+bUE+KCIq/BTs2BcYNluZDFf6GHCQEBAQAwn+gc2NdsO6VAzrazWcuE631INP+Ycc1LQsRJWyn4BwEIAQC7x699f3Z+fSDB3x+6X5S77swCb0UAH8qtJh4KwOmZDQIBCAEAQW79zLZ7BsEPTJ1wsrp3u5lEEmI1A/BpimU7DlsLowIAAQB3EKxiSJyPZiWtEBgDm22KTy8FYtv978s+yBwC4djtBQIDCAEA6c+V0X26RRR4QZb5WQ7yRXAfyIASWeH72pgpTeyjLQZ2VhcSfQ1olIKMH2+YkyqlRIRvhjKulYTc+0L+EPGloA=="},"top_secret_fin_key":{"access_policy":"Security Level::Top Secret && Department::FIN","key":"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"},"low_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"WrgxP7WHX7AHss9AAiK1VwK0C8/T8CICpLVC6wbhcPkIIK6v1etqfRUzElNBS6NLQTIUShGb/L801q01rhP1Np/nRPfD4pO0dGCePpChJ9BBAQCuy7LYmXjAx2bE1Pp9oGcoxV/11vp3yx9Pq5DGpnuyqiD1mu2uzLRljtJxpxVi1IEpUTSmLc7XtlU4zlokIEsi4bnH0oXUP8dkFfCRhVVE5LM4ziwr3tukeSZcI9oZCfC2Tq0ViuWDTToN/e2wRifXLnUw3HU=","header_metadata":"AAAAAQ==","authentication_data":""},"top_secret_mkg_test_vector":{"encryption_policy":"Department::MKG && Security Level::Top Secret","plaintext":"dG9wX3NlY3JldF9ta2dfcGxhaW50ZXh0","ciphertext":"NZ9uXS2Rv69TIZGbZG/YrQLepP0L5QHVXlF3tmUVt6Ha2eWEZ581LgApdaqIhVHUTr7rz4M8bOixg4zI6lzgfKowcxdeQ37mBQO1s85HsxguAQGQk23atuUsbiPJ714ztD3EUhSe0b3nM+mjvT+rBFkUYUc5rWGIpx5hZoJREFn8HR4omYWc7F2FrDcfy8sZRTjE5WnyLpv2rWD4Cd1985Yv5KVlNyrdJXneBps6CRATFnaaCioPsUxBzLp3u/2tBE36jB8t2EoMEMtOrZH4qHDnsQ+Ibkc55sddStaHDalTVAUGRhxfhJczjTYrbaGCziNWQh28e24cbtnM3EfRsCD8kHT4kj6mA/h7hOWRb+HEgq+SnfGf0G8Th/1wdhdAzqQuBXAgXf5tbSZ51xW4iR3PIpIf1jBoDzRHRafIG3zZlVU/2OWKUSixfZoNOcW6rp6oMUKZC9rNlc6ynmSK8uovEjz1ijAIfpn/Tu3k+B/ROZeHI0tiJV8RCrGWlr8STp0sk57YyjFo8D/oWA3ow7zoC7jyMOs0YP2KjtUnZyEjDwALXvmiWx/KWaggfI/JrUhWYBlHKM07SU1PLYIEt0hYV7hHoKYmP0AkpvW/EHtLlKnxt6EJmlvI8TJui37huI3pPC5Mkh+sy0JPtWXJoIO4Aqkhv9Fgz0Voim3Nwx1dpaoy1HZDQBhV3ketlIpo5a3gjqNgCfrvNAgjJO3qXvaOzR/RDYSg8W89nmzVUHbed+wcsljj2RqE/DOHucu3OKepSlZlXLvOwlb/hVnPM+rg2eQEQT6a2rvbeLUfxr/OHvwJzk687S36pkoMx999+jCVyDU5IIM+MzfEB9zGT9dHnUTy6wqP8gd68m+jCyB/fXz2dflWT8GL2SroQ9pS3eh1JD6vnkqdzc3c9Xav/thkXFgaT3oIru/a75mtnQWEIWtl+V5lekMUcdsBZ0cDyr4ZSPrTbbAOOP582BLI+Md6rZTTX3oNoBZeUNT30I0uabbh+yV/73QiInMZpETTQ2W6s9xxzsD9LGYCBUNXhZyNC5/MGvkunjjZqoFSc81EBKbo0+kGhF5v13wgO4oYy7Z/WIgAwNqch08ld5mpAyvKt6lHzaukQ8y+JA+xJmrDLKmG7qFACkl3YTjnkb6uujiuY0991+haqhB54wzwtbf6iQi2GU5dS1mg4Dfxv9mjRhz9nCG08aDRG/ADueEFwxlVKbrfOOwcQnsynNpWNM62Qji4vsS9nPo1oVTswe5yohefQuJd57S9L92EG74kqUrFihR0Yrn9V/oyO3Q1B5ipFD4bqC372arAkdUhiNy5RtgiYr9oSxt7stlMTkSabybhjuP6bfPwrsCozzWGdSbz2sZyQax0WVis4VQUB5NyMqM8LbXgKTSFrWDnANIgtcH7RVlAcIKUmuEwgsUkTUJED31i2J6Xv7S2DQvYvIHlLASbN/Zpe2mSwqT6oF/Bu6+Tbt/k5oYYPf8oy7dHoCfzdleoua5Ffdlg3bOXr/m9+X4swZV7ZnSPptyIwnOpmG6tvlebckJsLS+DzwvPViigkCDR1ApSmDge4OXVxBJxgO8yVBVLOL2fcif0zal4GoELDsOIbCAWQL6rRsngL4/afao5/Qis1du+YC8oScJ6ESeGYlyGRBltkjAltow55Tb2i48J7X8=","header_metadata":"AAAAAQ==","authentication_data":"AAAAAg=="},"low_secret_fin_test_vector":{"encryption_policy":"Department::FIN && Security Level::Low Secret","plaintext":"bG93X3NlY3JldF9maW5fcGxhaW50ZXh0","ciphertext":"e5YhmxxxFEwrw/ykRDbPzwJI1lA0cTZBG8beDSM7d8uCiDqk2vuzLsCLRQfsCeU8b7T0i1kDgJpHyFp+VAhOr/yREc1QrnxYlLuRSnjrSkA8AQBGimlz89tIVYzSoOsJWm5hc3qnMlJq2hwOWEVf+v9Q8gAiCQMhfAu9YlGd3I5/55jE6Ppw0GFxFmStOOQMTLi/cUUwnfA1hl5sWxdgsnwNXe9bRk1n","header_metadata":"","authentication_data":""}} \ No newline at end of file