🎉 import from cosmian_server

This commit is contained in:
Sébastien Lambert 2022-04-07 14:52:22 +02:00
commit 1df1f8fb7e
126 changed files with 36345 additions and 0 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
**/target/
**/.cargo_check/
.git/
.gitignore

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
target/
.cargo_check/
*nix
*.swp
TODO

4871
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[workspace]
members = [
"kms_common",
"kms_client",
"kms_server",
]

45
Dockerfile Normal file
View file

@ -0,0 +1,45 @@
FROM ubuntu:21.10 as builder
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /root
RUN apt-get update \
&& apt-get install --no-install-recommends -qq -y \
curl \
build-essential \
libssl-dev \
ca-certificates \
libclang-dev \
libsodium-dev \
pkg-config \
&& apt-get -y -q upgrade \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "nightly-x86_64-unknown-linux-gnu"
COPY . /root/kms
WORKDIR /root/kms
RUN /root/.cargo/bin/cargo build --release && \
strip /root/kms/target/release/cosmian_kms_server
#
# KMS Server
#
FROM ubuntu:21.10 as kms
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install --no-install-recommends -qq -y \
ca-certificates \
libssl-dev \
libsodium-dev \
&& apt-get -y -q upgrade \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /root/kms/target/release/cosmian_kms_server /usr/bin/cosmian_kms_server
ENTRYPOINT ["cosmian_kms_server"]

66
Makefile.toml Normal file
View file

@ -0,0 +1,66 @@
[config]
default_to_workspace = false
[tasks.ci-format]
description = "Run Rustfmt on base code"
category = "dev"
script = "cargo format"
[tasks.ci-clippy-all]
description = "Run Clippy on base code"
category = "dev"
script = "cargo clippy-all"
[tasks.ci-build]
description = "Build all"
category = "dev"
script = '''
#!/bin/bash
set -ex
cargo build --all-targets --all-features
'''
[tasks.ci-rust-tests]
description = "Run Rust tests"
category = "dev"
script = '''
#!/bin/bash
set -ex
cargo test --workspace --all-features -- --nocapture
cargo test --bins -- --nocapture
'''
[tasks.ci]
dependencies = [
"ci-format",
"ci-clippy-all",
"ci-build",
"ci-rust-tests",
]
#
# Local tests
#
[tasks.rust-tests]
env = { TEST_DB = "kms", TEST_USER = "kms", TEST_PASSWORD = "kms", TEST_HOST_AUTH_METHOD = "trust", TEST_HOST = "localhost" }
script.pre = '''
#!/bin/bash
set -ex
sudo docker stop postgre || true
sudo docker stop mariadb || true
sudo docker run -d --rm --network=host --name postgre -e POSTGRES_DB=$TEST_DB -e POSTGRES_USER=$TEST_USER -e POSTGRES_PASSWORD=$TEST_PASSWORD postgres:latest
sudo docker run -d --rm --network=host --name mariadb -e MYSQL_DATABASE=$TEST_DB -e MYSQL_ROOT_PASSWORD=$TEST_PASSWORD mariadb:latest
'''
script.main = '''
#!/bin/bash
set -ex
export KMS_POSTGRES_URL="postgres://$TEST_USER:$TEST_PASSWORD@$TEST_HOST/$TEST_DB"
export KMS_MYSQL_URL="mysql://root:$TEST_PASSWORD@$TEST_HOST/$TEST_DB"
cargo test --workspace --all-features -- --nocapture
cargo test --bins -- --nocapture
'''
script.post = '''
#!/bin/bash
sudo docker stop postgre || true
sudo docker stop mariadb || true
'''

4
kms_cli/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target/
TODO
TODO.md
*.swp

4240
kms_cli/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

30
kms_cli/Cargo.toml Normal file
View file

@ -0,0 +1,30 @@
[package]
name = "kms_cli"
version = "0.1.0"
edition = "2021"
description = "CLI used to manage the Cosmian KMS."
[[bin]]
name = "kms-cli"
path = "src/main.rs"
[dependencies]
abe_gpsw = { version = "0.6.4", features = ["interfaces"] }
clap = { version = "3.1.8", features = ["derive"] }
cosmian_kms_common = { path = "../kms/kms_common" }
cosmian_kms_client = { path = "../kms/kms_client" }
cosmian_rust_lib = { path = "../kms/rust_lib" }
eyre = "0.6"
futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
[dev-dependencies]
assert_cmd = "2.0"
cosmian_kms_server = { path = "../kms/kms_server", features = ["dev"] }
file_diff = "1.0.0"
predicates = "2.1"
regex = "1.5.5"
reqwest = { version = "0.11", features = ["json"] }

63
kms_cli/README.md Normal file
View file

@ -0,0 +1,63 @@
# KMS CLI
Cosmian has designed a command line to use the KMS in order to manage keys, encrypt or decrypt data.
This CLI supports several crypto-systems listed below:
- [X] Attribute-Based Encryption
- [ ] To be continued....
## Compiling
```
cargo run
```
## Usage
First of all, you need to specify the `kms.json` with the `kms_server_url` and your `kms_access_token` such as:
```json
{
"kms_server_url": "http://127.0.0.1:9998",
"kms_access_token": "MY_TOKEN"
}
```
Then:
```
KMS_CLI_CONF=kms.json kms_cli --help
```
### Attribute Based Encryption
You can perform the following ABE operations by taking advantage of the KMS.
__On master keys__
- `init` Generate a new master key pair
- `rotate` Rotate an attribute
__On user keys__
- `new` Generate a decrypt key for a new user
- `revoke` Revoke a user decryption key
- `destroy` Remove the user decryption key from the kms
__On user data__
- `encrypt` Encrypt data using the public key
- `decrypt` Decrypt data using the user decryption key
For more details, run:
```
kms_cli abe --help
```
## Testing
```
cargo test
```
A kms server is started by the test. Make sure, you don't start another one by yourself.

4
kms_cli/kms.json Normal file
View file

@ -0,0 +1,4 @@
{
"kms_server_url": "http://127.0.0.1:9998",
"kms_access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRWdk5xTEtoUHhUSGdhYUNGRGRoSSJ9.eyJnaXZlbl9uYW1lIjoiTGFldGl0aWEiLCJmYW1pbHlfbmFtZSI6Ikxhbmdsb2lzIiwibmlja25hbWUiOiJsYWV0aXRpYS5sYW5nbG9pcyIsIm5hbWUiOiJMYWV0aXRpYSBMYW5nbG9pcyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp5UEJsSnpqRzNuMWZLLXNyS0ptdUVkYklUX29QRmhVbTd2T2dVWD1zOTYtYyIsImxvY2FsZSI6ImZyIiwidXBkYXRlZF9hdCI6IjIwMjEtMTItMjFUMDk6MjE6NDkuMDgxWiIsImVtYWlsIjoibGFldGl0aWEubGFuZ2xvaXNAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9kZXYtMW1ic2JtaW4udXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4NTgwMDU3NDAwMjkxNDc5ODQyIiwiYXVkIjoiYUZqSzJvTnkwR1RnNWphV3JNYkJBZzV0bjRIV3VJN1ciLCJpYXQiOjE2NDAwNzg1MTQsImV4cCI6MTY0MDExNDUxNCwibm9uY2UiOiJha0poV2xoMlRsTm1lRTVtVFc0NFJHSk5VVEl5WW14aVJUTnVRblV1VEVwa2RrTnFVa2R5WkdoWFdnPT0ifQ.Q4tCzvJTNxmDhIYOJbjsqupdQkWg29Ny0B8njEfSrLVXNaRMFE99eSXedCBaXSMBnZ9GuCV2Z1MAZL8ZjTxqPP_VYCnc2QufG1k1XZg--6Q48pPdpUBXu2Ny1eatwiDrRvgQfUHkiM8thUAOb4bXxGLrtQKlO_ePOehDbEOjfd11aVm3pwyVqj1v6Ki1D5QJsOHtkkpLMinmmyGDtmdHH2YXseZNHGUY7PWZ6DelpJaxI48W5FNDY4b0sJlzaJqdIcoOX7EeP1pfFoHVeZAo5mWyuDev2OaPYKeqpga4PjqHcFT0m1rQoWQHmfGr3EkA3w8NXmKnZmEbQcLLgcCATw"
}

13
kms_cli/policy.json Normal file
View file

@ -0,0 +1,13 @@
{
"policy": {
"level": {
"hierarchical": true,
"attributes": ["confidential", "secret", "top-secret"]
},
"department": {
"hierarchical": false,
"attributes": ["finance", "marketing", "operations"]
}
},
"max-rotations": 100
}

View file

@ -0,0 +1,78 @@
use std::{fs::File, io::prelude::*, path::PathBuf};
use clap::StructOpt;
use cosmian_kms_client::KmipRestClient;
use cosmian_rust_lib::crypto::abe::kmip_requests::build_decryption_request;
use eyre::Context;
/// Decrypts a file identified by its name and
/// given a user decryption key stored in the KMS.
#[derive(StructOpt, Debug)]
pub struct DecryptAction {
/// The file to decrypt
#[clap(required = true, name = "FILE", parse(from_os_str))]
input_file: PathBuf,
/// The optional output directory to output the file to
#[clap(required = false, short, long, default_value = ".")]
output_directory: PathBuf,
/// The optional resource_uid. It's an extra encryption parameter to increase the security level
#[clap(required = false, short, long, default_value = "")]
resource_uid: String,
/// The user decryption key unique identifier stored in the KMS
#[clap(required = true, long = "user-key-id", short = 'u')]
user_key_id: String,
}
impl DecryptAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Read the file to decrypt
let filename = self
.input_file
.file_name()
.ok_or_else(|| eyre::eyre!("Could not get the name of the file to decrypt"))?;
let mut f = File::open(&self.input_file)
.with_context(|| "Can't read the file to decrypt".to_string())?;
let mut data = Vec::new();
f.read_to_end(&mut data)
.with_context(|| "Fail to read the file to decrypt")?;
// Create the kmip query
let decrypt_request = build_decryption_request(
&self.user_key_id,
self.resource_uid.as_bytes().to_vec(),
data,
);
// Query the KMS with your kmip data and get the key pair ids
let decrypt_response = client_connector
.decrypt(decrypt_request)
.await
.with_context(|| "Can't execute the query on the kms server")?;
data = decrypt_response
.data
.ok_or_else(|| eyre::eyre!("The plain data are empty"))?;
let mut decrypted_file = self.output_directory.join(filename);
decrypted_file.set_extension("plain");
// Write the decrypted file
let mut buffer =
File::create(&decrypted_file).with_context(|| "Fail to write the plain file")?;
buffer
.write_all(&data)
.with_context(|| "Fail to write the plain file")?;
println!("The decryption has been properly done.");
println!(
"The decrypted file can be found at {}",
&decrypted_file
.to_str()
.ok_or_else(|| eyre::eyre!("Could not display the name of the plain file"))?
);
Ok(())
}
}

View file

@ -0,0 +1,93 @@
use std::{fs::File, io::prelude::*, path::PathBuf};
use abe_gpsw::interfaces::policy::Attribute;
use clap::StructOpt;
use cosmian_kms_client::KmipRestClient;
use cosmian_rust_lib::crypto::abe::kmip_requests::build_hybrid_encryption_request;
use eyre::Context;
/// Encrypts a file with the given policy attributes
/// and the public key stored in the KMS.
#[derive(StructOpt, Debug)]
pub struct EncryptAction {
/// The file to encrypt
#[structopt(required = true, name = "FILE", parse(from_os_str))]
input_file: PathBuf,
/// The policy attributes to encrypt the file with
/// Example: `-a department::marketing -a level::confidential`
#[structopt(required = true, short, long)]
attributes: Vec<String>,
/// The optional output directory to output the encrypted file
#[structopt(required = false, short, long, default_value = ".")]
output_directory: PathBuf,
/// The optional resource_uid. It's an extra encryption parameter to increase the security level
#[structopt(required = false, short, long, default_value = "")]
resource_uid: String,
/// The public key unique identifier stored in the KMS
#[structopt(required = true, long = "public-key-id", short = 'p')]
public_key_id: String,
}
impl EncryptAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Parse the attributes
let attributes = self
.attributes
.iter()
.map(|s| Attribute::try_from(s.as_str()).map_err(Into::into))
.collect::<eyre::Result<Vec<Attribute>>>()?;
// Read the file to encrypt
let filename = self
.input_file
.file_name()
.ok_or_else(|| eyre::eyre!("Could not get the name of the file to encrypt"))?;
let mut f = File::open(&self.input_file)
.with_context(|| "Can't read the file to encrypt".to_string())?;
let mut data = Vec::new();
f.read_to_end(&mut data)
.with_context(|| "Fail to read the file to encrypt")?;
// Create the kmip query
let encrypt_request = build_hybrid_encryption_request(
&self.public_key_id,
attributes,
self.resource_uid.as_bytes().to_vec(),
data,
)?;
// Query the KMS with your kmip data and get the key pair ids
let encrypt_response = client_connector
.encrypt(encrypt_request)
.await
.with_context(|| "Can't execute the query on the kms server")?;
data = encrypt_response
.data
.ok_or_else(|| eyre::eyre!("The encrypted data are empty"))?;
let mut encrypted_file = self.output_directory.join(filename);
encrypted_file.set_extension("enc");
// Write the encrypted file
let mut buffer =
File::create(&encrypted_file).with_context(|| "Fail to write the encrypted file")?;
buffer
.write_all(&data)
.with_context(|| "Fail to write the encrypted file")?;
println!("The encryption has been properly done.");
println!(
"The encrypted file can be found at {}",
&encrypted_file
.to_str()
.ok_or_else(|| eyre::eyre!("Could not display the name of encrypted file"))?
);
Ok(())
}
}

View file

@ -0,0 +1,43 @@
use clap::StructOpt;
use cosmian_kms_client::KmipRestClient;
use super::{
decrypt::DecryptAction,
encrypt::EncryptAction,
keys::{
DestroyUserKeyAction, NewMasterKeyPairAction, NewUserKeyAction, RevokeUserKeyAction,
RotateAttributeAction,
},
};
/// Uses Attribute-Based encryption.
#[derive(StructOpt, Debug)]
pub enum AbeAction {
Init(NewMasterKeyPairAction),
Rotate(RotateAttributeAction),
New(NewUserKeyAction),
Revoke(RevokeUserKeyAction),
Destroy(DestroyUserKeyAction),
Encrypt(EncryptAction),
Decrypt(DecryptAction),
}
impl AbeAction {
pub async fn process(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
match self {
AbeAction::Init(action) => action.run(client_connector).await?,
AbeAction::Rotate(action) => action.run(client_connector).await?,
AbeAction::New(action) => action.run(client_connector).await?,
// For the time being, Revoke an user decryption key is not possible. We dismiss the action in the cli.
// Uncomment the followings to activate that command.
AbeAction::Revoke(_) => eyre::bail!("Revokation is not supported yet"), // action.run(client_connector).await?,
AbeAction::Destroy(action) => action.run(client_connector).await?,
AbeAction::Encrypt(action) => action.run(client_connector).await?,
AbeAction::Decrypt(action) => action.run(client_connector).await?,
};
Ok(())
}
}

View file

@ -0,0 +1,212 @@
use std::path::PathBuf;
use abe_gpsw::core::policy::{AccessPolicy, Attribute};
use clap::StructOpt;
use cosmian_kms_client::{kmip::kmip_types::RevocationReason, KmipRestClient};
use cosmian_rust_lib::crypto::abe::kmip_requests::{
build_create_master_keypair_request, build_create_user_decryption_private_key_request,
build_destroy_key_request, build_rekey_keypair_request,
build_revoke_user_decryption_key_request,
};
use eyre::Context;
/// Create a new ABE master access key pair for a given policy.
/// The master public key is used to encrypt the files and can be safely shared.
/// The master secret key is used to generate user decryption keys and must be
/// kept confidential.
/// Both of them are stored inside the KMS.
/// This command returns a couple of ID refering to this new key pair.
#[derive(StructOpt, Debug)]
pub struct NewMasterKeyPairAction {
/// The policy filename. The policy is expressed as a JSON object
/// describing the Policy axes and attributes. See the documentation for
/// details.
#[structopt(
required = false,
long = "policy",
short = 'p',
parse(from_os_str),
default_value = "policy.json"
)]
policy_file: PathBuf,
}
impl NewMasterKeyPairAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Parse the json policy file
let policy = super::policy::policy_from_file(&self.policy_file)?;
// Create the kmip query
let create_key_pair = build_create_master_keypair_request(&policy)?;
// Query the KMS with your kmip data and get the key pair ids
let create_key_pair_response = client_connector
.create_key_pair(create_key_pair)
.await
.with_context(|| "Can't connect to the kms server")?;
let private_key_unique_identifier = &create_key_pair_response.private_key_unique_identifier;
let public_key_unique_identifier = &create_key_pair_response.public_key_unique_identifier;
println!("The master key pair has been properly generated.");
println!("Store the followings securely for any further uses:\n");
println!(" Private key unique identifier: {private_key_unique_identifier}");
println!("\n Public key unique identifier: {public_key_unique_identifier}");
Ok(())
}
}
/// Generate a new user decryption key given an Access Policy expressed
/// as a boolean expression. The user decryption key can decrypt files with
/// attributes matching its access policy (i.e. the access policy is true).
#[derive(StructOpt, Debug)]
pub struct NewUserKeyAction {
/// The private master key unique identifier stored in the KMS
#[structopt(required = true, long = "secret-key-id", short = 's')]
secret_key_id: String,
/// The access policy is a boolean expression combining policy attributes.
/// Example: `(department::marketing | department::finance) & level::secret`
#[structopt(required = true)]
access_policy: String,
}
impl NewUserKeyAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Parse self.access_policy
let policy = AccessPolicy::from_boolean_expression(&self.access_policy)
.with_context(|| "Bad access policy definition")?;
// Create the kmip query
let create_user_key =
build_create_user_decryption_private_key_request(&policy, &self.secret_key_id)?;
// Query the KMS with your kmip data
let create_response = client_connector
.create(create_user_key)
.await
.with_context(|| "Can't execute the query on the kms server")?;
let user_key_unique_identifier = &create_response.unique_identifier;
println!("The decryption user key has been properly generated.");
println!("Store the followings securely for any further uses:");
println!("\n Decryption user key unique identifier: {user_key_unique_identifier}");
Ok(())
}
}
/// Revoke a user decryption key.
#[derive(StructOpt, Debug)]
pub struct RevokeUserKeyAction {
/// The user decryption key unique identifier stored in the KMS
/// to revoke
#[structopt(required = true, long = "user-key-id", short = 'u')]
user_key_id: String,
/// The reason of this revocation
#[structopt(required = true, long = "revocation-reason", short = 'r')]
revocation_reason: String,
/*
/// Compromission date if it occurs
#[structopt(long = "compromission-date", short = "d")]
compromise_occurrence_date: Option<String>,
*/
}
impl RevokeUserKeyAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Create the kmip query
let revoke_query = build_revoke_user_decryption_key_request(
&self.user_key_id,
RevocationReason::TextString(self.revocation_reason.to_owned()),
)?;
// Query the KMS with your kmip data
let revoke_response = client_connector
.revoke(revoke_query)
.await
.with_context(|| "Can't execute the query on the kms server")?;
if self.user_key_id == revoke_response.unique_identifier {
println!("The decryption user key has been properly revoked.");
Ok(())
} else {
eyre::bail!("Something went wrong when revoking the user key.")
}
}
}
/// Rotate an attribute and update the master public key file.
/// New files encrypted with the rotated attribute
/// cannot be decrypted by user decryption keys until they have been re-keyed.
#[derive(StructOpt, Debug)]
pub struct RotateAttributeAction {
/// The private master key unique identifier stored in the KMS
#[structopt(required = true, long = "secret-key-id", short = 's')]
secret_key_id: String,
/// The policy attributes to rotate.
/// Example: `-a department::marketing -a level::confidential`
#[structopt(required = true, short, long)]
attributes: Vec<String>,
}
impl RotateAttributeAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Parse the attributes
let attributes = self
.attributes
.iter()
.map(|s| Attribute::try_from(s.as_str()).map_err(Into::into))
.collect::<eyre::Result<Vec<Attribute>>>()?;
// Create the kmip query
let rotate_query = build_rekey_keypair_request(&self.secret_key_id, attributes)?;
// Query the KMS with your kmip data
let rotate_response = client_connector
.rekey_keypair(rotate_query)
.await
.with_context(|| "Can't execute the query on the kms server")?;
if self.secret_key_id == rotate_response.private_key_unique_identifier {
println!("The master key pair has been properly rotated.");
Ok(())
} else {
eyre::bail!("Something went wrong when rotating the user key.")
}
}
}
/// Destroy the decryption key for a given user.
#[derive(StructOpt, Debug)]
pub struct DestroyUserKeyAction {
/// The user decryption key unique identifier stored in the KMS
/// to destroy
#[structopt(required = true, long = "user-key-id", short = 'u')]
user_key_id: String,
}
impl DestroyUserKeyAction {
pub async fn run(&self, client_connector: &KmipRestClient) -> eyre::Result<()> {
// Create the kmip query
let destroy_query = build_destroy_key_request(&self.user_key_id)?;
// Query the KMS with your kmip data
let destroy_response = client_connector
.destroy(destroy_query)
.await
.with_context(|| "Can't execute the query on the kms server")?;
if self.user_key_id == destroy_response.unique_identifier {
println!("The decryption user key has been properly destroyed.");
Ok(())
} else {
eyre::bail!("Something went wrong when destroying the user key.")
}
}
}

View file

@ -0,0 +1,6 @@
mod decrypt;
mod encrypt;
mod keys;
mod policy;
pub mod entrypoint;

View file

@ -0,0 +1,84 @@
use std::{collections::HashMap, fs::File, io::BufReader, path::Path};
use abe_gpsw::core::policy::Policy;
use eyre::Context;
use serde::{Deserialize, Serialize};
/// Example of a json:
/// {
/// "policy": {
/// "level": {
/// "hierarchical": true,
/// "attributes": ["confidential","secret","top-secret"]
/// },
/// "department": {
/// "hierarchical": false,
/// "attributes": ["finance","marketing","operations"]
/// }
/// },
/// "max-rotations": 100
/// }
#[derive(Serialize, Deserialize)]
struct InputPolicy {
#[serde(alias = "max-rotations")]
max_rotations: usize,
#[serde(alias = "policy")]
policy_axis: HashMap<String, InputPolicyAxis>,
}
#[derive(Serialize, Deserialize)]
struct InputPolicyAxis {
hierarchical: bool,
attributes: Vec<String>,
}
pub fn policy_from_file(json_filename: &impl AsRef<Path>) -> eyre::Result<Policy> {
let file =
File::open(json_filename).with_context(|| "Can't read the policy json file".to_string())?;
// Read the json
let raw_policy: InputPolicy = serde_json::from_reader(BufReader::new(file))
.with_context(|| "Policy JSON malformed".to_string())?;
// Build the policy
let mut policy = Policy::new(raw_policy.max_rotations);
// Build the policy axis
for (name, axis) in &raw_policy.policy_axis {
let v: Vec<&str> = axis.attributes.iter().map(|x| x.as_ref()).collect();
policy = policy
.add_axis(name, &v, axis.hierarchical)
.with_context(|| format!("Can't initialize the policy axis {}", &name))?;
}
Ok(policy)
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::policy_from_file;
#[test]
pub fn test_policy_from_file() {
//file not found
let result = policy_from_file(&PathBuf::from("not_exist"));
assert_eq!(
result.err().unwrap().to_string(),
"Can't read the policy json file"
);
// malformed json
let result = policy_from_file(&PathBuf::from("test_data/policy.bad"));
assert_eq!(result.err().unwrap().to_string(), "Policy JSON malformed");
// duplicate policies
let result = policy_from_file(&PathBuf::from("test_data/policy.bad2"));
assert_eq!(
result.err().unwrap().to_string(),
"Can't initialize the policy axis level"
);
}
}

View file

@ -0,0 +1 @@
pub mod abe;

75
kms_cli/src/config.rs Normal file
View file

@ -0,0 +1,75 @@
use std::{env, fs::File, io::BufReader};
use cosmian_kms_client::KmipRestClient;
use eyre::Context;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct CliConf {
kms_server_url: String,
kms_access_token: String,
}
/// Define the configuration of the CLI reading a json
///
/// {
/// "kms_server_url": "http://127.0.0.1:9998",
/// "kms_access_token": "AA...AAA"
/// }
///
pub const KMS_CLI_CONF_ENV: &str = "KMS_CLI_CONF";
impl CliConf {
pub fn load() -> eyre::Result<KmipRestClient> {
let cli_conf_filename =
env::var(KMS_CLI_CONF_ENV).with_context(|| "Can't find KMS_CLI_CONF env variable")?;
let file = File::open(&cli_conf_filename).with_context(|| {
format!(
"Can't read {} set in the KMS_CLI_CONF env variable",
&cli_conf_filename
)
})?;
let conf: CliConf = serde_json::from_reader(BufReader::new(file))
.with_context(|| format!("Config JSON malformed in {}", &cli_conf_filename))?;
// Create a client to query the KMS
let kms_connector =
KmipRestClient::instantiate(&conf.kms_server_url, &conf.kms_access_token)
.with_context(|| {
format!(
"Can't build the query to connect to the kms server {}",
&conf.kms_server_url
)
})?;
Ok(kms_connector)
}
}
#[cfg(test)]
mod tests {
use std::env;
use super::{CliConf, KMS_CLI_CONF_ENV};
#[test]
pub fn test_load() {
env::set_var(KMS_CLI_CONF_ENV, "test_data/kms.json");
assert!(CliConf::load().is_ok());
env::set_var(KMS_CLI_CONF_ENV, "not_exist.json");
assert_eq!(
CliConf::load().err().unwrap().to_string(),
"Can't read not_exist.json set in the KMS_CLI_CONF env variable"
);
env::set_var(KMS_CLI_CONF_ENV, "test_data/kms.bad");
assert_eq!(
CliConf::load().err().unwrap().to_string(),
"Config JSON malformed in test_data/kms.bad",
);
}
}

5
kms_cli/src/lib.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod actions;
pub mod config;
#[cfg(test)]
mod tests;

27
kms_cli/src/main.rs Normal file
View file

@ -0,0 +1,27 @@
use clap::StructOpt;
use kms_cli::actions::abe::entrypoint::AbeAction;
#[derive(StructOpt, Debug)]
#[structopt(
name = "kms-cli",
version = "0.1",
about = "The Cosmian KMS command line"
)]
enum CliCommands {
#[clap(subcommand)]
Abe(AbeAction),
}
use kms_cli::config::CliConf;
#[tokio::main]
async fn main() -> eyre::Result<()> {
let conf = CliConf::load()?;
let opts = CliCommands::parse();
match opts {
CliCommands::Abe(action) => action.process(&conf).await?,
};
Ok(())
}

View file

@ -0,0 +1,641 @@
use std::{fs, path::Path, process::Command};
use assert_cmd::prelude::*;
use file_diff::diff;
use predicates::prelude::*;
use regex::{Regex, RegexBuilder};
use crate::{
config::KMS_CLI_CONF_ENV,
tests::{
test_utils::{init, ONCE},
PROG_NAME,
},
};
const SUB_COMMAND: &str = "abe";
/// Extract the key_uid (prefixed by a pattern) from a text
fn extract_uid<'a>(text: &'a str, pattern: &'a str) -> Option<&'a str> {
let formatted = format!(r"^ {}: (?P<uid>[a-z0-9-]+)$", pattern);
let uid_regex: Regex = RegexBuilder::new(formatted.as_str())
.multi_line(true)
.build()
.unwrap();
uid_regex
.captures(text)
.and_then(|cap| cap.name("uid").map(|uid| uid.as_str()))
}
/// Extract the private key from a text.
fn extract_private_key(text: &str) -> Option<&str> {
extract_uid(text, "Private key unique identifier")
}
/// Extract the public key from a text.
fn extract_public_key(text: &str) -> Option<&str> {
extract_uid(text, "Public key unique identifier")
}
/// Extract the decryption user key from a text.
fn extract_user_key(text: &str) -> Option<&str> {
extract_uid(text, "Decryption user key unique identifier")
}
#[tokio::test]
pub async fn test_init() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("Private key unique identifier:"))
.stdout(predicate::str::contains("Public key unique identifier:"));
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND)
.args(vec!["init", "--policy", "test_data/policy.json"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("Private key unique identifier:"))
.stdout(predicate::str::contains("Public key unique identifier:"));
Ok(())
}
#[tokio::test]
pub async fn test_init_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND)
.args(vec!["init", "--policy", "test_data/notfound.json"]);
cmd.assert().failure().stderr(predicate::str::contains(
"Error: Can't read the policy json file",
));
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND)
.args(vec!["init", "--policy", "test_data/policy.bad"]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Error: Policy JSON malformed"));
Ok(())
}
#[test]
pub fn test_bad_conf() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env_clear();
cmd.arg(SUB_COMMAND).args(vec!["--help"]);
cmd.assert().failure().stderr(predicate::str::contains(
"Error: Can't find KMS_CLI_CONF env variable",
));
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "notfound.json");
cmd.arg(SUB_COMMAND).args(vec!["--help"]);
cmd.assert().failure().stderr(predicate::str::contains(
"Error: Can't read notfound.json set in the KMS_CLI_CONF env variable",
));
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.bad");
cmd.arg(SUB_COMMAND).args(vec!["--help"]);
cmd.assert().failure().stderr(predicate::str::contains(
"Error: Config JSON malformed in test_data/kms.bad",
));
Ok(())
}
#[tokio::test]
pub async fn test_new() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"(department::marketing || department::finance) && level::secret",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
cmd.assert().success().stdout(predicate::str::contains(
"Decryption user key unique identifier:",
));
Ok(())
}
#[tokio::test]
pub async fn test_new_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
// bad attributes
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"department::marketing || level::secret2",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("secret2 is missing in axis level"));
// bad keys
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"department::marketing || level::secret",
"--secret-key-id",
"bad_key",
]);
cmd.assert().failure().stderr(predicate::str::contains(
"Object with uid: bad_key not found",
));
Ok(())
}
#[tokio::test]
pub async fn test_revoke() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"(department::marketing || department::finance) && level::secret",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"revoke",
"--revocation-reason",
"for test",
"-u",
extract_user_key(stdout).unwrap(),
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Revokation is not supported yet"));
//TODO: Uncomment that when revokation will be supported
// cmd.assert().success();
Ok(())
}
#[tokio::test]
pub async fn test_revoke_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
// not exist
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"revoke",
"--revocation-reason",
"for test",
"-u",
"none",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Revokation is not supported yet"));
Ok(())
}
#[tokio::test]
pub async fn test_destroy() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"(department::marketing || department::finance) && level::secret",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND)
.args(vec!["destroy", "-u", extract_user_key(stdout).unwrap()]);
cmd.assert().success();
Ok(())
}
#[tokio::test]
pub async fn test_destroy_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
// not exist
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["destroy", "-u", "none"]);
cmd.assert().success(); // for now this command does not fail
Ok(())
}
#[tokio::test]
pub async fn test_rotate() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"rotate",
"-a",
"department::marketing",
"-a",
"department::finance",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
cmd.assert().success().stdout(predicate::str::contains(
"The master key pair has been properly rotated.",
));
Ok(())
}
#[tokio::test]
pub async fn test_rotate_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
// bad attributes
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"rotate",
"-a",
"level::secret2",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
cmd.assert().failure().stderr(predicate::str::contains(
"attribute not found: level::secret2",
));
// bad keys
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"rotate",
"-a",
"department::marketing",
"--secret-key-id",
"bad_key",
]);
cmd.assert().failure().stderr(predicate::str::contains(
"Object with uid: bad_key not found",
));
Ok(())
}
#[tokio::test]
pub async fn test_encrypt_decrypt() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
fs::remove_file("/tmp/plain.enc").ok();
fs::remove_file("/tmp/plain.plain").ok();
assert!(!Path::new("/tmp/plain.enc").exists());
assert!(!Path::new("/tmp/plain.plain").exists());
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"encrypt",
"-a",
"department::marketing",
"-a",
"level::confidential",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-p",
extract_public_key(stdout).unwrap(),
"test_data/plain.txt",
]);
cmd.assert().success().stdout(predicate::str::contains(
"The encrypted file can be found at /tmp/plain.enc",
));
assert!(Path::new("/tmp/plain.enc").exists());
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"(department::marketing || department::finance) && level::secret",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"decrypt",
"--resource-uid",
"myid",
"-o",
"/tmp",
"-u",
extract_user_key(stdout).unwrap(),
"/tmp/plain.enc",
]);
cmd.assert().success().stdout(predicate::str::contains(
"The decrypted file can be found at /tmp/plain.plain",
));
assert!(Path::new("/tmp/plain.plain").exists());
assert!(diff("/tmp/plain.plain", "test_data/plain.txt"));
fs::remove_file("/tmp/plain.enc").unwrap();
fs::remove_file("/tmp/plain.plain").unwrap();
Ok(())
}
#[tokio::test]
pub async fn test_encrypt_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
// plain text not exist
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"encrypt",
"-a",
"department::marketing",
"-a",
"level::confidential",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-p",
extract_public_key(stdout).unwrap(),
"notexist",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Can't read the file to encrypt"));
// attributes are malformed
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"encrypt",
"-a",
"departmentmarketing",
"-a",
"level::confidential",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-p",
extract_public_key(stdout).unwrap(),
"notexist",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("invalid attribute: separator "));
// attributes are wellformed but not exist
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"encrypt",
"-a",
"department::marketing2",
"-a",
"level::confidential",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-p",
extract_public_key(stdout).unwrap(),
"test_data/plain.txt",
]);
cmd.assert().failure().stderr(predicate::str::contains(
"attribute not found: department::marketing2",
));
// the key is wrong
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"encrypt",
"-a",
"department::marketing2",
"-a",
"level::confidential",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-p",
"trash",
"test_data/plain.txt",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Object with uid: trash not found"));
// the output target is wrong (no right)
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"encrypt",
"-a",
"department::marketing",
"-a",
"level::confidential",
"-o",
"/noexist",
"--resource-uid",
"myid",
"-p",
extract_public_key(stdout).unwrap(),
"test_data/plain.txt",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Fail to write the encrypted file"));
Ok(())
}
#[tokio::test]
pub async fn test_decrypt_error() -> Result<(), Box<dyn std::error::Error>> {
ONCE.get_or_init(init).await;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec!["init"]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"new",
"(department::marketing || department::finance) && level::secret",
"--secret-key-id",
extract_private_key(stdout).unwrap(),
]);
let success = cmd.assert().success();
let output = success.get_output();
let stdout: &str = std::str::from_utf8(&output.stdout)?;
// encrypted text not exist
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"decrypt",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-u",
extract_user_key(stdout).unwrap(),
"notexist",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Can't read the file to decrypt"));
// the key is wrong
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"decrypt",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-u",
"trash",
"test_data/plain.txt",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Object with uid: trash not found"));
// the encrpyted file is wrong
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(KMS_CLI_CONF_ENV, "test_data/kms.json");
cmd.arg(SUB_COMMAND).args(vec![
"decrypt",
"-o",
"/tmp",
"--resource-uid",
"myid",
"-u",
extract_user_key(stdout).unwrap(),
"test_data/plain.txt",
]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Bad or corrupted encrypted files"));
Ok(())
}

View file

@ -0,0 +1 @@
mod integration_tests;

5
kms_cli/src/tests/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod abe;
pub(crate) mod test_utils;
const PROG_NAME: &str = "kms-cli";

View file

@ -0,0 +1,66 @@
use std::thread;
use cosmian_kms_server::{
config::{init_config, Config},
start_server,
};
use reqwest::ClientBuilder;
use tokio::sync::OnceCell;
/// We use that to avoid to try to start N servers (one per test)
/// Otherwise we got: "Address already in use (os error 98)"
/// for N-1 tests.
pub static ONCE: OnceCell<()> = OnceCell::const_new();
#[tokio::main]
pub async fn start_test_server() {
start_server().await.unwrap();
}
/// Start a server for testing
pub async fn init() {
// Configure the serveur
let config = Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
port: 9998,
postgres_url: "".to_string(),
root_dir: "/tmp".to_string(),
hostname: "0.0.0.0".to_string(),
mysql_url: "".to_string(),
};
init_config(&config).await.unwrap();
// Start the server on a independent thread
thread::spawn(start_test_server);
// Depending on the running environnement, the server could take a bit of times to start
// We try to query it with a dummy request until be sure it is started.
let mut retry = true;
let mut timeout = 5;
let mut waiting = 1;
while retry {
let result = ClientBuilder::new()
.build()
.unwrap()
.post("http://127.0.0.1:9998/kmip/2_1")
.json("{}")
.send()
.await;
if result.is_err() {
timeout -= 1;
retry = timeout >= 0;
if retry {
println!("The server is not up yet, retrying in {}s...", waiting);
thread::sleep(std::time::Duration::from_secs(waiting));
waiting *= 2;
} else {
println!("The server is still not up, stop trying");
panic!("Can't start the kms server to run tests");
}
} else {
println!("The server is up!");
retry = false
}
}
}

View file

@ -0,0 +1,4 @@
{
"kms_access_token":
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRWdk5xTEtoUHhUSGdhYUNGRGRoSSJ9.eyJnaXZlbl9uYW1lIjoiTGFldGl0aWEiLCJmYW1pbHlfbmFtZSI6Ikxhbmdsb2lzIiwibmlja25hbWUiOiJsYWV0aXRpYS5sYW5nbG9pcyIsIm5hbWUiOiJMYWV0aXRpYSBMYW5nbG9pcyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp5UEJsSnpqRzNuMWZLLXNyS0ptdUVkYklUX29QRmhVbTd2T2dVWD1zOTYtYyIsImxvY2FsZSI6ImZyIiwidXBkYXRlZF9hdCI6IjIwMjEtMTItMjFUMDk6MjE6NDkuMDgxWiIsImVtYWlsIjoibGFldGl0aWEubGFuZ2xvaXNAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9kZXYtMW1ic2JtaW4udXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4NTgwMDU3NDAwMjkxNDc5ODQyIiwiYXVkIjoiYUZqSzJvTnkwR1RnNWphV3JNYkJBZzV0bjRIV3VJN1ciLCJpYXQiOjE2NDAwNzg1MTQsImV4cCI6MTY0MDExNDUxNCwibm9uY2UiOiJha0poV2xoMlRsTm1lRTVtVFc0NFJHSk5VVEl5WW14aVJUTnVRblV1VEVwa2RrTnFVa2R5WkdoWFdnPT0ifQ.Q4tCzvJTNxmDhIYOJbjsqupdQkWg29Ny0B8njEfSrLVXNaRMFE99eSXedCBaXSMBnZ9GuCV2Z1MAZL8ZjTxqPP_VYCnc2QufG1k1XZg--6Q48pPdpUBXu2Ny1eatwiDrRvgQfUHkiM8thUAOb4bXxGLrtQKlO_ePOehDbEOjfd11aVm3pwyVqj1v6Ki1D5QJsOHtkkpLMinmmyGDtmdHH2YXseZNHGUY7PWZ6DelpJaxI48W5FNDY4b0sJlzaJqdIcoOX7EeP1pfFoHVeZAo5mWyuDev2OaPYKeqpga4PjqHcFT0m1rQoWQHmfGr3EkA3w8NXmKnZmEbQcLLgcCATw"
}

View file

@ -0,0 +1,4 @@
{
"kms_server_url": "http://127.0.0.1:9998",
"kms_access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRWdk5xTEtoUHhUSGdhYUNGRGRoSSJ9.eyJnaXZlbl9uYW1lIjoiTGFldGl0aWEiLCJmYW1pbHlfbmFtZSI6Ikxhbmdsb2lzIiwibmlja25hbWUiOiJsYWV0aXRpYS5sYW5nbG9pcyIsIm5hbWUiOiJMYWV0aXRpYSBMYW5nbG9pcyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp5UEJsSnpqRzNuMWZLLXNyS0ptdUVkYklUX29QRmhVbTd2T2dVWD1zOTYtYyIsImxvY2FsZSI6ImZyIiwidXBkYXRlZF9hdCI6IjIwMjEtMTItMjFUMDk6MjE6NDkuMDgxWiIsImVtYWlsIjoibGFldGl0aWEubGFuZ2xvaXNAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9kZXYtMW1ic2JtaW4udXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4NTgwMDU3NDAwMjkxNDc5ODQyIiwiYXVkIjoiYUZqSzJvTnkwR1RnNWphV3JNYkJBZzV0bjRIV3VJN1ciLCJpYXQiOjE2NDAwNzg1MTQsImV4cCI6MTY0MDExNDUxNCwibm9uY2UiOiJha0poV2xoMlRsTm1lRTVtVFc0NFJHSk5VVEl5WW14aVJUTnVRblV1VEVwa2RrTnFVa2R5WkdoWFdnPT0ifQ.Q4tCzvJTNxmDhIYOJbjsqupdQkWg29Ny0B8njEfSrLVXNaRMFE99eSXedCBaXSMBnZ9GuCV2Z1MAZL8ZjTxqPP_VYCnc2QufG1k1XZg--6Q48pPdpUBXu2Ny1eatwiDrRvgQfUHkiM8thUAOb4bXxGLrtQKlO_ePOehDbEOjfd11aVm3pwyVqj1v6Ki1D5QJsOHtkkpLMinmmyGDtmdHH2YXseZNHGUY7PWZ6DelpJaxI48W5FNDY4b0sJlzaJqdIcoOX7EeP1pfFoHVeZAo5mWyuDev2OaPYKeqpga4PjqHcFT0m1rQoWQHmfGr3EkA3w8NXmKnZmEbQcLLgcCATw"
}

View file

@ -0,0 +1 @@
I'm a plain text

View file

@ -0,0 +1,12 @@
{
"policy": {
"level": {
"attributes": ["confidential", "secret", "top-secret"]
},
"department": {
"hierarchical": false,
"attributes": ["finance", "marketing", "operations"]
}
},
"max-rotations": 100
}

View file

@ -0,0 +1,13 @@
{
"policy": {
"level": {
"hierarchical": true,
"attributes": ["confidential", "secret", "secret"]
},
"department": {
"hierarchical": false,
"attributes": ["finance", "marketing", "operations"]
}
},
"max-rotations": 100
}

View file

@ -0,0 +1,13 @@
{
"policy": {
"level": {
"hierarchical": true,
"attributes": ["confidential", "secret", "top-secret"]
},
"department": {
"hierarchical": false,
"attributes": ["finance", "marketing", "operations"]
}
},
"max-rotations": 100
}

2
kms_client/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target/
.cargo_check/

12
kms_client/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"cSpell.words": [
"keypair",
"kmip",
"mcfe",
"newtype",
"PKCS",
"serializers",
"thiserror",
"Ttlv"
]
}

16
kms_client/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
authors = ["Bruno Grieder <bruno.grieder@cosmian.com>"]
edition = "2021"
name = "cosmian_kms_client"
version = "0.1.0"
[dependencies]
cosmian_kms_common = { path = "../kms_common" }
hex = { version = "0.4", features = ["serde"] }
http = "0.2"
reqwest = { version = "0.11.10", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
tracing = "0.1"

4
kms_client/README.md Normal file
View file

@ -0,0 +1,4 @@
# KMS Client
The `kms_client` forges and sends post query to the `kms_server` given already prepared kmip data.
For preparing these kmip data, please refer to the [rust_lib](http://gitlab.cosmian.com/core/cosmian_server/-/tree/develop/microservices/rust_lib).

53
kms_client/src/error.rs Normal file
View file

@ -0,0 +1,53 @@
use cosmian_kms_common::{
error::KmsCommonError,
kmip::{kmip_operations::ErrorReason, ttlv::error::TtlvError},
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum KmsClientError {
#[error("TTLV Error: {0}")]
TtlvError(String),
#[error("REST Request Failed: {0}")]
RequestFailed(String),
#[error("REST Response Failed: {0}")]
ResponseFailed(String),
#[error("Unexpected Error: {0}")]
UnexpectedError(String),
#[error("Invalid KMIP value: {0}: {1}")]
InvalidKmipValue(ErrorReason, String),
#[error("Invalid KMIP Object: {0}: {1}")]
InvalidKmipObject(ErrorReason, String),
#[error("Kmip Not Supported: {0}: {1}")]
KmipNotSupported(ErrorReason, String),
#[error("Not Supported: {0}")]
NotSupported(String),
#[error("{0}: {1}")]
KmipError(ErrorReason, String),
}
impl From<TtlvError> for KmsClientError {
fn from(e: TtlvError) -> Self {
KmsClientError::TtlvError(e.to_string())
}
}
impl From<KmsCommonError> for KmsClientError {
fn from(e: KmsCommonError) -> Self {
match e {
KmsCommonError::InvalidKmipValue(r, s) => KmsClientError::InvalidKmipValue(r, s),
KmsCommonError::InvalidKmipObject(r, s) => KmsClientError::InvalidKmipObject(r, s),
KmsCommonError::KmipNotSupported(r, s) => KmsClientError::KmipNotSupported(r, s),
KmsCommonError::NotSupported(s) => KmsClientError::NotSupported(s),
KmsCommonError::KmipError(r, s) => KmsClientError::KmipError(r, s),
}
}
}

392
kms_client/src/lib.rs Normal file
View file

@ -0,0 +1,392 @@
#![allow(clippy::upper_case_acronyms)]
//required to detect generic type in Serializer
#![feature(min_specialization)]
#![allow(dead_code)]
pub mod error;
use std::time::Duration;
// re-export the kmip module as kmip
pub use cosmian_kms_common::kmip;
use cosmian_kms_common::kmip::{
kmip_operations::{
Create, CreateKeyPair, CreateKeyPairResponse, CreateResponse, Decrypt, DecryptResponse,
Destroy, DestroyResponse, Encrypt, EncryptResponse, Get, GetAttributes,
GetAttributesResponse, GetResponse, Import, ImportResponse, Locate, LocateResponse,
ReKeyKeyPair, ReKeyKeyPairResponse, Revoke, RevokeResponse,
},
ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLV},
};
use error::KmsClientError;
use http::{HeaderMap, HeaderValue};
use reqwest::{Client, ClientBuilder};
use serde::{Deserialize, Serialize};
/// A struct implementing some of the 50+ operations a KMIP client should implement:
/// https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=kmip
pub struct KmipRestClient {
server_url: String,
client: Client,
}
impl KmipRestClient {
/// This operation requests the server to generate a new symmetric key or
/// generate Secret Data as a Managed Cryptographic Object.
/// The request contains information about the type of object being created,
/// and some of the attributes to be assigned to the object (e.g.,
/// Cryptographic Algorithm, Cryptographic Length, etc.).
///
/// The response contains the Unique Identifier of the created object.
/// The server SHALL
/// copy the Unique Identifier returned by this operation into the ID
/// Placeholder variable.
pub async fn create(&self, request: Create) -> Result<CreateResponse, KmsClientError> {
self.post_ttlv::<Create, CreateResponse>(&request).await
}
/// This operation requests the server to generate a new public/private key
/// pair and register the two corresponding new Managed Cryptographic Object
/// The request contains attributes to be assigned to the objects (e.g.,
/// Cryptographic Algorithm, Cryptographic Length, etc.). Attributes MAY
/// be specified for both keys at the same time by specifying a Common
/// Attributes object in the request. Attributes not common to both keys
/// (e.g., Name, Cryptographic Usage Mask) MAY be specified
/// using the Private Key Attributes and Public Key Attributes objects in
/// the request, which take precedence over the Common Attributes object.
/// For the Private Key, the server SHALL create a Link attribute of Link
/// Type Public Key pointing to the Public Key. For the Public Key, the
/// server SHALL create a Link attribute of Link Type Private Key pointing
/// to the Private Key. The response contains the Unique Identifiers of
/// both created objects. The ID Placeholder value SHALL be set to the
/// Unique Identifier of the Private Key
pub async fn create_key_pair(
&self,
request: CreateKeyPair,
) -> Result<CreateKeyPairResponse, KmsClientError> {
self.post_ttlv::<CreateKeyPair, CreateKeyPairResponse>(&request)
.await
}
/// This operation requests the server to perform a decryption operation on
/// the provided data using a Managed Cryptographic Object as the key
/// for the decryption operation.
///
/// The request contains information about the cryptographic parameters
/// (mode and padding method), the data to be decrypted, and the
/// IV/Counter/Nonce to use. The cryptographic parameters MAY be omitted
/// from the request as they can be specified as associated attributes of
/// the Managed Cryptographic Object.
///
/// The initialization vector/counter/nonce MAY also be omitted from the
/// request if the algorithm does not use an IV/Counter/Nonce.
///
/// The response contains the Unique Identifier of the Managed Cryptographic
/// Object used as the key and the result of the decryption operation.
///
/// The success or failure of the operation is indicated by the Result
/// Status (and if failure the Result Reason) in the response header.
pub async fn decrypt(&self, request: Decrypt) -> Result<DecryptResponse, KmsClientError> {
self.post_ttlv::<Decrypt, DecryptResponse>(&request).await
}
/// This operation is used to indicate to the server that the key material
/// for the specified Managed Object SHALL be destroyed or rendered
/// inaccessible. The meta-data for the key material SHALL be retained by
/// the server. Objects SHALL only be destroyed if they are in either
/// Pre-Active or Deactivated state.
pub async fn destroy(&self, request: Destroy) -> Result<DestroyResponse, KmsClientError> {
self.post_ttlv::<Destroy, DestroyResponse>(&request).await
}
/// This operation requests the server to perform an encryption operation on
/// the provided data using a Managed Cryptographic Object as the key
/// for the encryption operation. The request contains information about
/// the cryptographic parameters (mode and padding method), the
/// data to be encrypted, and the IV/Counter/Nonce to use. The cryptographic
/// parameters MAY be omitted from the request as they can be specified
/// as associated attributes of the Managed Cryptographic Object.
/// The IV/Counter/Nonce MAY also be omitted from the request if the
/// cryptographic parameters indicate that the server shall generate a
/// Random IV on behalf of the client or the encryption algorithm does not
/// need an IV/Counter/Nonce. The server does not store or otherwise
/// manage the IV/Counter/Nonce. If the Managed Cryptographic Object
/// referenced has a Usage Limits attribute then the server SHALL obtain
/// an allocation from the current Usage Limits value prior to performing
/// the encryption operation. If the allocation is unable to be obtained
/// the operation SHALL return with a result status of Operation Failed
/// and result reason of Permission Denied.
///
/// The response contains the Unique Identifier of the Managed Cryptographic
/// Object used as the key and the result of the encryption operation.
/// The success or failure of the operation is indicated by the Result
/// Status (and if failure the Result Reason) in the response header.
pub async fn encrypt(&self, request: Encrypt) -> Result<EncryptResponse, KmsClientError> {
self.post_ttlv::<Encrypt, EncryptResponse>(&request).await
}
/// This operation requests that the server returns the Managed Object
/// specified by its Unique Identifier. Only a single object is
/// returned. The response contains the Unique Identifier of the object,
/// along with the object itself, which MAY be wrapped using a wrapping
/// key as specified in the request. The following key format
/// capabilities SHALL be assumed by the client; restrictions apply when the
/// client requests the server to return an object in a particular
/// format: • If a client registered a key in a given format, the server
/// SHALL be able to return the key during the Get operation in the same
/// format that was used when the key was registered. • Any other format
/// conversion MAY be supported by the server. If Key Format Type is
/// specified to be PKCS#12 then the response payload shall be a PKCS#12
/// container as specified by [RFC7292]. The Unique Identifier shall be
/// either that of a private key or certificate to be included in the
/// response. The container shall be protected using the Secret Data object
/// specified via the private key or certificates PKCS#12 Password
/// Link. The current certificate chain shall also be included
/// as determined by using the private keys Public Key link to get the
/// corresponding public key (where relevant), and then using that
/// public keys PKCS#12 Certificate Link to get the base certificate, and
/// then using each certificates Ce
pub async fn get(&self, request: Get) -> Result<GetResponse, KmsClientError> {
self.post_ttlv::<Get, GetResponse>(&request).await
}
/// This operation requests one or more attributes associated with a Managed
/// Object. The object is specified by its Unique Identifier, and the
/// attributes are specified by their name in the request. If a specified
/// attribute has multiple instances, then all instances are returned. If a
/// specified attribute does not exist (i.e., has no value), then it
/// SHALL NOT be present in the returned response. If none of the requested
/// attributes exist, then the response SHALL consist only of the Unique
/// Identifier. The same Attribute Reference SHALL NOT be present more
/// than once in a request. If no Attribute Reference is provided, the
/// server SHALL return all attributes.
pub async fn get_attributes(
&self,
request: GetAttributes,
) -> Result<GetAttributesResponse, KmsClientError> {
self.post_ttlv::<GetAttributes, GetAttributesResponse>(&request)
.await
}
/// This operation requests the server to Import a Managed Object specified
/// by its Unique Identifier. The request specifies the object being
/// imported and all the attributes to be assigned to the object. The
/// attribute rules for each attribute for “Initially set by” and “When
/// implicitly set” SHALL NOT be enforced as all attributes MUST be set
/// to the supplied values rather than any server generated values.
/// The response contains the Unique Identifier provided in the request or
/// assigned by the server. The server SHALL copy the Unique Identifier
/// returned by this operations into the ID Placeholder variable.
pub async fn import(&self, request: Import) -> Result<ImportResponse, KmsClientError> {
self.post_ttlv::<Import, ImportResponse>(&request).await
}
/// This operation requests that the server search for one or more Managed
/// Objects, depending on the attributes specified in the request. All
/// attributes are allowed to be used. The request MAY contain a Maximum
/// Items field, which specifies the maximum number of objects to be
/// returned. If the Maximum Items field is omitted, then the server MAY
/// return all objects matched, or MAY impose an internal maximum limit due
/// to resource limitations.
///
/// The request MAY contain an Offset Items field, which specifies the
/// number of objects to skip that satisfy the identification criteria
/// specified in the request. An Offset Items field of 0 is the same as
/// omitting the Offset Items field. If both Offset Items and Maximum Items
/// are specified in the request, the server skips Offset Items objects and
/// returns up to Maximum Items objects.
///
/// If more than one object satisfies the identification criteria specified
/// in the request, then the response MAY contain Unique Identifiers for
/// multiple Managed Objects. Responses containing Unique Identifiers for
/// multiple objects SHALL be returned in descending order of object
/// creation (most recently created object first). Returned objects SHALL
/// match all of the attributes in the request. If no objects match, then an
/// empty response payload is returned. If no attribute is specified in the
/// request, any object SHALL be deemed to match the Locate request. The
/// response MAY include Located Items which is the count of all objects
/// that satisfy the identification criteria.
///
/// The server returns a list of Unique Identifiers of the found objects,
/// which then MAY be retrieved using the Get operation. If the objects are
/// archived, then the Recover and Get operations are REQUIRED to be used to
/// obtain those objects. If a single Unique Identifier is returned to the
/// client, then the server SHALL copy the Unique Identifier returned by
/// this operation into the ID Placeholder variable. If the Locate
/// operation matches more than one object, and the Maximum Items value is
/// omitted in the request, or is set to a value larger than one, then the
/// server SHALL empty the ID Placeholder, causing any subsequent operations
/// that are batched with the Locate, and which do not specify a Unique
/// Identifier explicitly, to fail. This ensures that these batched
/// operations SHALL proceed only if a single object is returned by Locate.
///
/// The Date attributes in the Locate request (e.g., Initial Date,
/// Activation Date, etc.) are used to specify a time or a time range for
/// the search. If a single instance of a given Date attribute is used in
/// the request (e.g., the Activation Date), then objects with the same Date
/// attribute are considered to be matching candidate objects. If two
/// instances of the same Date attribute are used (i.e., with two different
/// values specifying a range), then objects for which the Date attribute is
/// inside or at a limit of the range are considered to be matching
/// candidate objects. If a Date attribute is set to its largest possible
/// value, then it is equivalent to an undefined attribute. The KMIP Usage
/// Guide [KMIP-UG] provides examples.
///
/// When the Cryptographic Usage Mask attribute is specified in the request,
/// candidate objects are compared against this field via an operation that
/// consists of a logical AND of the requested mask with the mask in the
/// candidate object, and then a comparison of the resulting value with the
/// requested mask. For example, if the request contains a mask value of
/// 10001100010000, and a candidate object mask contains 10000100010000,
/// then the logical AND of the two masks is 10000100010000, which is
/// compared against the mask value in the request (10001100010000) and the
/// match fails. This means that a matching candidate object has all of the
/// bits set in its mask that are set in the requested mask, but MAY have
/// additional bits set.
///
/// When the Usage Limits attribute is specified in the request, matching
/// candidate objects SHALL have a Usage Limits Count and Usage Limits Total
/// equal to or larger than the values specified in the request.
///
/// When an attribute that is defined as a structure is specified, all of
/// the structure fields are not REQUIRED to be specified. For instance, for
/// the Link attribute, if the Linked Object Identifier value is specified
/// without the Link Type value, then matching candidate objects have the
/// Linked Object Identifier as specified, irrespective of their Link Type.
///
/// When the Object Group attribute and the Object Group Member flag are
/// specified in the request, and the value specified for Object Group
/// Member is Group Member Fresh, matching candidate objects SHALL be
/// fresh objects from the object group. If there are no more fresh objects
/// in the group, the server MAY choose to generate a new object on-the-fly,
/// based on server policy. If the value specified for Object Group Member
/// is Group Member Default, the server locates the default object as
/// defined by server policy.
///
/// The Storage Status Mask field is used to indicate whether on-line
/// objects (not archived or destroyed), archived objects, destroyed objects
/// or any combination of the above are to be searched.The server SHALL NOT
/// return unique identifiers for objects that are destroyed unless the
/// Storage Status Mask field includes the Destroyed Storage indicator. The
/// server SHALL NOT return unique identifiers for objects that are archived
/// unless the Storage Status Mask field includes the Archived Storage
/// indicator.
pub async fn locate(&self, request: Locate) -> Result<LocateResponse, KmsClientError> {
self.post_ttlv::<Locate, LocateResponse>(&request).await
}
// This request is used to generate a replacement key pair for an existing
// public/private key pair. It is analogous to the Create Key Pair operation,
// except that attributes of the replacement key pair are copied from the
// existing key pair, with the exception of the attributes listed in Re-key Key
// Pair Attribute Requirements tor.
//
// As the replacement of the key pair takes over the name attribute for the
// existing public/private key pair, Re-key Key Pair SHOULD only be performed
// once on a given key pair.
//
// For both the existing public key and private key, the server SHALL create a
// Link attribute of Link Type Replacement Key pointing to the replacement
// public and private key, respectively. For both the replacement public and
// private key, the server SHALL create a Link attribute of Link Type Replaced
// Key pointing to the existing public and private key, respectively.
//
// The server SHALL copy the Private Key Unique Identifier of the replacement
// private key returned by this operation into the ID Placeholder variable.
//
// An Offset MAY be used to indicate the difference between the Initial Date and
// the Activation Date of the replacement key pair. If no Offset is specified,
// the Activation Date and Deactivation Date values are copied from the existing
// key pair. If Offset is set and dates exist for the existing key pair, then
// the dates of the replacement key pair SHALL be set based on the dates of the
// existing key pair as follows
pub async fn rekey_keypair(
&self,
request: ReKeyKeyPair,
) -> Result<ReKeyKeyPairResponse, KmsClientError> {
self.post_ttlv::<ReKeyKeyPair, ReKeyKeyPairResponse>(&request)
.await
}
/// This operation requests the server to revoke a Managed Cryptographic
/// Object or an Opaque Object. The request contains a reason for the
/// revocation (e.g., “key compromise”, “cessation of operation”, etc.). The
/// operation has one of two effects. If the revocation reason is “key
/// compromise” or “CA compromise”, then the object is placed into the
/// “compromised” state; the Date is set to the current date and time; and
/// the Compromise Occurrence Date is set to the value (if provided) in the
/// Revoke request and if a value is not provided in the Revoke request then
/// Compromise Occurrence Date SHOULD be set to the Initial Date for the
/// object. If the revocation reason is neither “key compromise” nor “CA
/// compromise”, the object is placed into the “deactivated” state, and the
/// Deactivation Date is set to the current date and time.
pub async fn revoke(&self, request: Revoke) -> Result<RevokeResponse, KmsClientError> {
self.post_ttlv::<Revoke, RevokeResponse>(&request).await
}
}
impl KmipRestClient {
/// Instantiate a new KMIP REST Client
#[allow(dead_code)]
pub fn instantiate(
server_url: &str,
bearer_token: &str,
) -> Result<KmipRestClient, KmsClientError> {
let server_url = match server_url.strip_suffix('/') {
Some(s) => s.to_string(),
None => server_url.to_string(),
} + "/kmip/2_1";
let mut headers = HeaderMap::new();
headers.insert(
"Authorization",
HeaderValue::from_str(format!("Bearer {}", bearer_token).as_str())
.map_err(|e| KmsClientError::UnexpectedError(e.to_string()))?,
);
headers.insert("Connection", HeaderValue::from_static("keep-alive"));
Ok(KmipRestClient {
client: ClientBuilder::new()
.connect_timeout(Duration::from_secs(5))
.tcp_keepalive(Duration::from_secs(30))
.default_headers(headers)
.build()
.map_err(|e| KmsClientError::UnexpectedError(e.to_string()))?,
server_url,
})
}
pub async fn post_ttlv<O, R>(&self, kmip_request: &O) -> Result<R, KmsClientError>
where
O: Serialize,
R: serde::de::DeserializeOwned + Sized + 'static,
{
let response = self
.client
.post(&self.server_url)
.json(&to_ttlv(kmip_request)?)
.send()
.await
.map_err(|e| KmsClientError::RequestFailed(e.to_string()))?;
let status_code = response.status();
if status_code.is_success() {
let ttlv = response
.json::<TTLV>()
.await
.map_err(|e| KmsClientError::TtlvError(e.to_string()))?;
return from_ttlv(&ttlv).map_err(|e| KmsClientError::ResponseFailed(e.to_string()))
}
// process error
let p = response
.text()
.await
.map_err(|e| KmsClientError::RequestFailed(e.to_string()))?;
Err(KmsClientError::RequestFailed(p))
}
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ErrorPayload {
pub error: String,
pub messages: Option<Vec<String>>,
}

6
kms_common/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"cSpell.words": [
"Deserialization",
"Kmip"
]
}

2137
kms_common/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

26
kms_common/Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "cosmian_kms_common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitflags = "1.2"
chrono = "0.4"
hex = { version = "0.4", features = ["serde"] }
num-bigint = { version = "0.4", features = ["rand", "serde"] }
paperclip = { git = "https://github.com/wafflespeanut/paperclip.git", rev = "07f6f1a", features = [
"actix4",
"chrono",
"actix-multipart",
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = "0.24"
strum_macros = "0.24"
thiserror = "1.0"
tracing = "0.1"
tracing-log = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-tree = "0.2"

8
kms_common/README.md Normal file
View file

@ -0,0 +1,8 @@
# KMS common
The `kms_commmon` library implements the kmip standard such as operations, objects, types, etc.
It also implements the ttlv serialization format.
This kmip data are then used by the `kms_client` to query the `kms_server` and then by the `kms_server` to respond.
For specific Cosmian crypto-systems, you can use the [rust_lib](http://gitlab.cosmian.com/core/cosmian_server/-/tree/develop/microservices/rust_lib) to generate kmip data with an abstraction level.

37
kms_common/src/error.rs Normal file
View file

@ -0,0 +1,37 @@
use thiserror::Error;
use crate::kmip::{kmip_operations::ErrorReason, ttlv::error::TtlvError};
#[derive(Error, Debug)]
pub enum KmsCommonError {
#[error("Invalid KMIP value: {0}: {1}")]
InvalidKmipValue(ErrorReason, String),
#[error("Invalid KMIP Object: {0}: {1}")]
InvalidKmipObject(ErrorReason, String),
#[error("Kmip Not Supported: {0}: {1}")]
KmipNotSupported(ErrorReason, String),
#[error("Not Supported: {0}")]
NotSupported(String),
#[error("{0}: {1}")]
KmipError(ErrorReason, String),
}
impl KmsCommonError {
#[must_use]
pub fn reason(&self, reason: ErrorReason) -> Self {
match self {
KmsCommonError::KmipError(_r, e) => KmsCommonError::KmipError(reason, e.clone()),
e => KmsCommonError::KmipError(reason, e.to_string()),
}
}
}
impl From<TtlvError> for KmsCommonError {
fn from(e: TtlvError) -> Self {
KmsCommonError::KmipError(ErrorReason::Codec_Error, e.to_string())
}
}

View file

@ -0,0 +1,36 @@
use serde::{Deserialize, Serialize};
use crate::kmip::kmip_types::UniqueIdentifier;
// Response for success
#[derive(Deserialize, Serialize, Debug)] // Debug is required by ok_json()
pub struct ResponseSuccess {
pub success: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Access {
/// Determines the object being requested. If omitted, then the ID
/// Placeholder value is used by the server as the Unique Identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
/// User identifier, beneficiary of the access
pub userid: String,
/// Operation type for the access
pub operation_type: ObjectOperationTypes,
}
/// Operation types that can get or create objects
/// These operations use `retrieve` or `get` methods.
#[derive(PartialEq, Serialize, Deserialize, Debug, Copy, Clone)]
pub enum ObjectOperationTypes {
Create,
Get,
Encrypt,
Decrypt,
Import,
Revoke,
Locate,
Rekey,
Destroy,
}

View file

@ -0,0 +1,528 @@
use std::clone::Clone;
use num_bigint::BigUint;
use paperclip::actix::Apiv2Schema;
use serde::{ser::SerializeStruct, Deserialize, Serialize};
use tracing::trace;
use crate::{
error::KmsCommonError,
kmip::{
kmip_key_utils::WrappedSymmetricKey,
kmip_operations::ErrorReason,
kmip_types::{
Attributes, CryptographicAlgorithm, EncodingOption, EncryptionKeyInformation,
KeyCompressionType, KeyFormatType, MacSignatureKeyInformation, RecommendedCurve,
WrappingMethod,
},
},
};
/// A Key Block object is a structure used to encapsulate all of the information
/// that is closely associated with a cryptographic key.
/// Section 3 of KMIP Reference 2.1
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct KeyBlock {
pub key_format_type: KeyFormatType,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_compression_type: Option<KeyCompressionType>,
// may be a KeyValue serialized struct - see specs
pub key_value: KeyValue,
pub cryptographic_algorithm: CryptographicAlgorithm,
pub cryptographic_length: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_wrapping_data: Option<KeyWrappingData>,
}
impl KeyBlock {
pub fn to_vec(&self) -> Result<Vec<u8>, KmsCommonError> {
let (key_material, _) = self.key_value.plaintext().ok_or_else(|| {
KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Attribute_Value,
"invalid Plain Text".to_string(),
)
})?;
match key_material {
KeyMaterial::TransparentSymmetricKey { key } => Ok(key.clone()),
other => {
return Err(KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Message,
format!("Invalid key material type for a symmetric key: {:?}", other),
))
}
}
}
/// Extract the Key bytes from the given `KeyBlock`
pub fn key_bytes(&self) -> Result<Vec<u8>, KmsCommonError> {
match &self.key_value {
KeyValue::PlainText { key_material, .. } => {
let key = match key_material {
KeyMaterial::ByteString(v) => Ok(v.clone()),
KeyMaterial::TransparentSymmetricKey { key } => Ok(key.clone()),
other => Err(KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Data_Type,
format!("The key has an invalid key material: {:?}", other),
)),
};
key
}
KeyValue::Wrapped(wrapped) => Ok(wrapped.clone()),
}
}
/// Extract the Key bytes from the given `KeyBlock`
pub fn key_bytes_and_attributes(
&self,
uid: &str,
) -> Result<(Vec<u8>, Option<Attributes>), KmsCommonError> {
match &self.key_value {
KeyValue::PlainText {
key_material,
attributes,
} => {
let key = match key_material {
KeyMaterial::TransparentSymmetricKey { key } => Ok(key.clone()),
KeyMaterial::ByteString(v) => Ok(v.clone()),
other => Err(KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Data_Type,
format!(
"The key at uid: {} has an invalid key material: {:?}",
uid, other
),
)),
};
let attributes = attributes.clone();
Ok((key?, attributes))
}
KeyValue::Wrapped(wrapped) => Ok((wrapped.clone(), None)),
}
}
/// Extract `counter_iv_nonce` value from `KeyBlock`
///
/// # Arguments
///
/// * `self`: the KMIP key block
///
/// # Returns
///
/// * an optional byte vector
///
#[must_use]
pub fn counter_iv_nonce(&self) -> Option<&Vec<u8>> {
match &self.key_wrapping_data {
Some(KeyWrappingData {
iv_counter_nonce, ..
}) => iv_counter_nonce.as_ref(),
None => None,
}
}
/// Convert a raw-wrapped symmetric key into a KMIP-`KeyBlock` struct.
/// Remark/Warning:
/// The key attributes are serialized into the `KeyValue` in order to be located later
///
/// # Arguments
///
/// * `wrapped_key`: key byte-array
/// * `iv_counter_nonce`: iv counter nonce
/// * `key_format_type`: KMIP `KeyFormatType`
/// * `wrapped_key_attributes`: the KMIP `Attributes
///
/// # Returns
///
/// * `key_block`: the new `KeyBlock` structure
///
/// # Errors
///
/// * serializing can fail
pub fn to_wrapped_key_block(
wrapped_key: &[u8],
iv_counter_nonce: Option<Vec<u8>>,
key_format_type: KeyFormatType,
wrapped_key_attributes: &Attributes,
) -> Result<KeyBlock, KmsCommonError> {
trace!("array_to_wrapped_key_block: {}", wrapped_key.len());
let key_value = serde_json::to_vec(&WrappedSymmetricKey {
attributes: wrapped_key_attributes.clone(),
wrapped_symmetric_key: wrapped_key.to_vec(),
})
.map_err(|e| {
KmsCommonError::InvalidKmipObject(ErrorReason::Invalid_Attribute_Value, e.to_string())
})?;
let key_wrapping_data = KeyWrappingData {
wrapping_method: WrappingMethod::Encrypt,
iv_counter_nonce,
..KeyWrappingData::default()
};
Ok(KeyBlock {
cryptographic_algorithm: CryptographicAlgorithm::AES,
key_format_type,
key_compression_type: None,
key_value: KeyValue::Wrapped(key_value),
cryptographic_length: wrapped_key.len() as i32,
key_wrapping_data: Some(key_wrapping_data),
})
}
}
/// The Key Value is used only inside a Key Block and is either a Byte String or
/// a:
///
/// • The Key Value structure contains the key material, either as a byte string
/// or as a Transparent Key structure, and OPTIONAL attribute information that
/// is associated and encapsulated with the key material. This attribute
/// information differs from the attributes associated with Managed Objects, and
/// is obtained via the Get Attributes operation, only by the fact that it is
/// encapsulated with (and possibly wrapped with) the key material itself.
///
/// • The Key Value Byte String is either the wrapped TTLV-encoded Key Value
/// structure, or the wrapped un-encoded value of the Byte String Key Material
/// field.
#[derive(Deserialize, Clone, Debug, PartialEq)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum KeyValue {
PlainText {
// may be a specialized key material
#[serde(rename = "KeyMaterial")]
key_material: KeyMaterial,
#[serde(rename = "Attributes", skip_serializing_if = "Option::is_none")]
attributes: Option<Attributes>,
},
Wrapped(Vec<u8>),
}
impl KeyValue {
pub fn attributes(&self) -> Result<&Attributes, KmsCommonError> {
match self {
KeyValue::PlainText { attributes, .. } => attributes.as_ref().ok_or_else(|| {
KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Attribute_Value,
"key is missing its attributes".to_string(),
)
}),
KeyValue::Wrapped(_) => Err(KmsCommonError::KmipNotSupported(
ErrorReason::Feature_Not_Supported,
"key is wrapped and this is not yet supported".to_string(),
)),
}
}
#[must_use]
pub fn plaintext(&self) -> Option<(&KeyMaterial, &Option<Attributes>)> {
match self {
KeyValue::PlainText {
key_material,
attributes,
} => Some((key_material, attributes)),
_ => None,
}
}
pub fn raw_bytes(&self) -> Result<Vec<u8>, KmsCommonError> {
match &self {
KeyValue::PlainText { key_material, .. } => {
let key = match key_material {
KeyMaterial::ByteString(v) => Ok(v.clone()),
other => Err(KmsCommonError::KmipNotSupported(
ErrorReason::Invalid_Data_Type,
format!("The key has an invalid key material: {:?}", other),
)),
};
key
}
KeyValue::Wrapped(wrapped) => Ok(wrapped.clone()),
}
}
}
impl Serialize for KeyValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
KeyValue::PlainText {
key_material,
attributes,
} => {
let mut st = serializer.serialize_struct("PlainText", 2)?;
st.serialize_field("KeyMaterial", key_material)?;
st.serialize_field("Attributes", attributes)?;
st.end()
}
KeyValue::Wrapped(bytes) => serializer.serialize_bytes(bytes),
}
}
}
/// The Key Block MAY also supply OPTIONAL information about a cryptographic key
/// wrapping mechanism used to wrap the Key Value. This consists of a Key
/// Wrapping Data structure. It is only used inside a Key Block.
/// This structure contains fields for:
///
/// Value Description
///
/// Wrapping Method Indicates the method used to wrap the Key Value.
///
/// Encryption Key Information Contains the Unique Identifier value of the
/// encryption key and associated cryptographic parameters.
///
/// MAC/Signature Key Information
/// Contains the Unique Identifier value of the MAC/signature key and
/// associated cryptographic parameters.
///
/// MAC/Signature Contains a MAC or signature of the Key Value
///
/// IV/Counter/Nonce If REQUIRED by the wrapping method.
///
/// Encoding Option Specifies the encoding of the Key Material within the Key
/// Value structure of the Key Block that has been wrapped. If No Encoding is
/// specified, then the Key Value structure SHALL NOT contain any
/// attributes.
///
/// If wrapping is used, then the whole Key Value structure is wrapped unless
/// otherwise specified by the Wrapping Method. The algorithms used for wrapping
/// are given by the Cryptographic Algorithm attributes of the encryption key
/// and/or MAC/signature key; the block-cipher mode, padding method, and hashing
/// algorithm used for wrapping are given by the Cryptographic Parameters in the
/// Encryption Key Information and/or MAC/Signature Key Information, or, if not
/// present, from the Cryptographic Parameters attribute of the respective
/// key(s). Either the Encryption Key Information or the MAC/Signature Key
/// Information (or both) in the Key Wrapping Data structure SHALL be specified.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Apiv2Schema, Default)]
#[serde(rename_all = "PascalCase")]
pub struct KeyWrappingData {
pub wrapping_method: WrappingMethod,
#[serde(skip_serializing_if = "Option::is_none")]
pub encryption_key_information: Option<EncryptionKeyInformation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mac_or_signature_key_information: Option<MacSignatureKeyInformation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mac_or_signature: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none", rename = "IVCounterNonce")]
pub iv_counter_nonce: Option<Vec<u8>>,
/// Specifies the encoding of the Key Value Byte String. If not present, the
/// wrapped Key Value structure SHALL be TTLV encoded.
#[serde(skip_serializing_if = "Option::is_none")]
pub encoding_option: Option<EncodingOption>,
}
// KeyMaterial has variants that do not appear in the TTLV
// Typically, for a Transparent Symmetric key it will look like
// this
// ```
// TTLV {
// tag: "KeyMaterial".to_string(),
// value: TTLValue::Structure(vec![TTLV {
// tag: "Key".to_string(),
// value: TTLValue::ByteString(key_value.to_vec()),
// }]),
// }
// ```
// So we use the `untagged` which unfortunately breaks pascalCase
#[derive(Deserialize, Clone, Debug, PartialEq, Apiv2Schema)]
#[serde(untagged)]
#[openapi(empty)]
pub enum KeyMaterial {
ByteString(Vec<u8>),
#[serde(rename_all = "PascalCase")]
TransparentDHPrivateKey {
p: BigUint,
#[serde(skip_serializing_if = "Option::is_none")]
q: Option<BigUint>,
g: BigUint,
#[serde(skip_serializing_if = "Option::is_none")]
j: Option<BigUint>,
x: BigUint,
},
#[serde(rename_all = "PascalCase")]
TransparentDHPublicKey {
p: BigUint,
#[serde(skip_serializing_if = "Option::is_none")]
q: Option<BigUint>,
g: BigUint,
#[serde(skip_serializing_if = "Option::is_none")]
j: Option<BigUint>,
y: BigUint,
},
//TODO can be confused by the Deserializer with the TransparentDHPrivateKey
#[serde(rename_all = "PascalCase")]
TransparentDSAPrivateKey {
p: BigUint,
q: BigUint,
g: BigUint,
x: BigUint,
},
//TODO can be confused by the Deserializer with the TransparentDHPublicKey
#[serde(rename_all = "PascalCase")]
TransparentDSAPublicKey {
p: BigUint,
q: BigUint,
g: BigUint,
y: BigUint,
},
#[serde(rename_all = "PascalCase")]
TransparentSymmetricKey {
key: Vec<u8>,
},
#[serde(rename_all = "PascalCase")]
TransparentRSAPrivateKey {
modulus: BigUint,
#[serde(skip_serializing_if = "Option::is_none")]
private_exponent: Option<BigUint>,
#[serde(skip_serializing_if = "Option::is_none")]
public_exponent: Option<BigUint>,
#[serde(skip_serializing_if = "Option::is_none")]
p: Option<BigUint>,
#[serde(skip_serializing_if = "Option::is_none")]
q: Option<BigUint>,
#[serde(skip_serializing_if = "Option::is_none")]
prime_exponent_p: Option<BigUint>,
#[serde(skip_serializing_if = "Option::is_none")]
prime_exponent_q: Option<BigUint>,
#[serde(skip_serializing_if = "Option::is_none")]
crt_coefficient: Option<BigUint>,
},
#[serde(rename_all = "PascalCase")]
TransparentRSAPublicKey {
modulus: BigUint,
public_exponent: BigUint,
},
#[serde(rename_all = "PascalCase")]
TransparentECPrivateKey {
recommended_curve: RecommendedCurve,
// big int in big endian format
d: BigUint,
},
#[serde(rename_all = "PascalCase")]
TransparentECPublicKey {
recommended_curve: RecommendedCurve,
q_string: Vec<u8>,
},
}
// Unfortunately, default serialization does not play well
// for ByteString, so we have to do it by had. Deserialization is OK though
impl Serialize for KeyMaterial {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
KeyMaterial::ByteString(bytes) => serializer.serialize_bytes(bytes),
KeyMaterial::TransparentSymmetricKey { key } => {
let mut st = serializer.serialize_struct("KeyMaterial", 1)?;
st.serialize_field("Key", key)?;
st.end()
}
KeyMaterial::TransparentDHPrivateKey { p, q, g, j, x } => {
let mut st = serializer.serialize_struct("KeyMaterial", 5)?;
st.serialize_field("P", p)?;
if let Some(q) = q {
st.serialize_field("Q", q)?
};
st.serialize_field("G", g)?;
if let Some(j) = j {
st.serialize_field("J", j)?
};
st.serialize_field("X", x)?;
st.end()
}
KeyMaterial::TransparentDHPublicKey { p, q, g, j, y } => {
let mut st = serializer.serialize_struct("KeyMaterial", 5)?;
st.serialize_field("P", p)?;
if let Some(q) = q {
st.serialize_field("Q", q)?
};
st.serialize_field("G", g)?;
if let Some(j) = j {
st.serialize_field("J", j)?
};
st.serialize_field("Y", y)?;
st.end()
}
KeyMaterial::TransparentDSAPrivateKey { p, q, g, x } => {
let mut st = serializer.serialize_struct("KeyMaterial", 4)?;
st.serialize_field("P", p)?;
st.serialize_field("Q", q)?;
st.serialize_field("G", g)?;
st.serialize_field("X", x)?;
st.end()
}
KeyMaterial::TransparentDSAPublicKey { p, q, g, y } => {
let mut st = serializer.serialize_struct("KeyMaterial", 4)?;
st.serialize_field("P", p)?;
st.serialize_field("Q", q)?;
st.serialize_field("G", g)?;
st.serialize_field("Y", y)?;
st.end()
}
KeyMaterial::TransparentRSAPrivateKey {
modulus,
private_exponent,
public_exponent,
p,
q,
prime_exponent_p,
prime_exponent_q,
crt_coefficient,
} => {
let mut st = serializer.serialize_struct("KeyMaterial", 8)?;
st.serialize_field("Modulus", modulus)?;
if let Some(private_exponent) = private_exponent {
st.serialize_field("PrivateExponent", private_exponent)?
};
if let Some(public_exponent) = public_exponent {
st.serialize_field("PublicExponent", public_exponent)?
};
if let Some(p) = p {
st.serialize_field("P", p)?
};
if let Some(q) = q {
st.serialize_field("Q", q)?
};
if let Some(prime_exponent_p) = prime_exponent_p {
st.serialize_field("PrimeExponentP", prime_exponent_p)?
};
if let Some(prime_exponent_q) = prime_exponent_q {
st.serialize_field("PrimeExponentQ", prime_exponent_q)?
};
if let Some(crt_coefficient) = crt_coefficient {
st.serialize_field("CrtCoefficient", crt_coefficient)?
};
st.end()
}
KeyMaterial::TransparentRSAPublicKey {
modulus,
public_exponent,
} => {
let mut st = serializer.serialize_struct("KeyMaterial", 2)?;
st.serialize_field("Modulus", modulus)?;
st.serialize_field("PublicExponent", public_exponent)?;
st.end()
}
KeyMaterial::TransparentECPrivateKey {
recommended_curve,
d,
} => {
let mut st = serializer.serialize_struct("KeyMaterial", 1)?;
st.serialize_field("RecommendedCurve", recommended_curve)?;
st.serialize_field("D", d)?;
st.end()
}
KeyMaterial::TransparentECPublicKey {
recommended_curve,
q_string,
} => {
let mut st = serializer.serialize_struct("KeyMaterial", 1)?;
st.serialize_field("RecommendedCurve", recommended_curve)?;
st.serialize_field("QString", q_string)?;
st.end()
}
}
}
}

View file

@ -0,0 +1,39 @@
use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
use crate::{
error::KmsCommonError,
kmip::{kmip_operations::ErrorReason, kmip_types::Attributes},
};
#[derive(Serialize, Deserialize)]
pub struct WrappedSymmetricKey {
pub(crate) attributes: Attributes,
pub(crate) wrapped_symmetric_key: Vec<u8>,
}
impl WrappedSymmetricKey {
#[must_use]
pub fn attributes(&self) -> Attributes {
self.attributes.clone()
}
#[must_use]
pub fn wrapped_symmetric_key(&self) -> Vec<u8> {
self.wrapped_symmetric_key.clone()
}
}
impl TryFrom<&Vec<u8>> for WrappedSymmetricKey {
type Error = KmsCommonError;
fn try_from(wrapped_key_bytes: &Vec<u8>) -> Result<Self, KmsCommonError> {
serde_json::from_slice(wrapped_key_bytes).map_err(|_e| {
KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Attribute_Value,
"failed deserializing to an WrappedSymmetricKey".to_string(),
)
})
}
}

View file

@ -0,0 +1,282 @@
use std::convert::{TryFrom, TryInto};
use num_bigint::BigUint;
use paperclip::actix::Apiv2Schema;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};
use super::kmip_types::Attributes;
use crate::{
error::KmsCommonError,
kmip::{
kmip_data_structures::KeyBlock,
kmip_operations::ErrorReason,
kmip_types::{
CertificateRequestType, CertificateType, OpaqueDataType, SecretDataType, SplitKeyMethod,
},
},
};
/// Object Types
/// Section 2 of KMIP Reference 2.1
///
/// A KMIP Object. The top level structure
/// Serialization is carried out internally tagged :
/// https://serde.rs/enum-representations.html#internally-tagged
/// This is likely not KMIP compliant therefore
/// some JSON gimmicks may be required in and out
///
/// Deserialization is untagged and the `ObjectType` is not at
/// an adjacent level in the structure. Correction code needs to be
/// run post-serialization: for instance `PrivateKey` and `SymmetricKey`
/// share the same internal structure a `SingleKeyBlock`; since `PrivateKey`
/// appears first, a `SymmetricKey` will be deserialized as a `PrivateKey`
///
/// Order matters: `SecretData` will be deserialized as a `PrivateKey` if it
/// appears after despite the presence of `secret_data_type`
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Display, Apiv2Schema)]
#[serde(untagged)]
#[openapi(empty)]
pub enum Object {
#[serde(rename_all = "PascalCase")]
Certificate {
certificate_type: CertificateType,
certificate_value: Vec<u8>,
},
#[serde(rename_all = "PascalCase")]
CertificateRequest {
certificate_request_type: CertificateRequestType,
certificate_request_value: Vec<u8>,
},
/// A Managed Object that the key management server is possibly not able to
/// interpret. The context information for this object MAY be stored and
/// retrieved using Custom Attributes. An Opaque Object MAY be a Managed
/// Cryptographic Object depending on the client context of usage and as
/// such is treated in the same manner as a Managed Cryptographic Object
/// for handling of attributes.
OpaqueObject {
opaque_data_type: OpaqueDataType,
opaque_data_value: Vec<u8>,
},
/// A Managed Cryptographic Object that is a text-based representation of a
/// PGP key. The Key Block field, indicated below, will contain the
/// ASCII-armored export of a PGP key in the format as specified in RFC
/// 4880. It MAY contain only a public key block, or both a public and
/// private key block. Two different versions of PGP keys, version 3 and
/// version 4, MAY be stored in this Managed Cryptographic Object.
/// kmip-spec-v2.1-cs01 07 May 2020 Standards Track Work Product
/// Copyright © OASIS Open 2020. All Rights Reserved. Page 20 of 240
/// KMIP implementers SHOULD treat the Key Block field as an opaque
/// blob. PGP-aware KMIP clients SHOULD take on the responsibility
/// of decomposing the Key Block into other Managed Cryptographic
/// Objects (Public Keys, Private Keys, etc.).
PGPKey {
#[serde(rename = "PGPKeyVersion")]
pgp_key_version: u32,
#[serde(rename = "KeyBlock")]
key_block: KeyBlock,
},
#[serde(rename_all = "PascalCase")]
SecretData {
secret_data_type: SecretDataType,
key_block: KeyBlock,
},
/// A Managed Cryptographic Object that is a Split Key. A split key is a
/// secret, usually a symmetric key or a private key that has been split
/// into a number of parts, each of which MAY then be distributed to
/// several key holders, for additional security. The Split Key Parts
/// field indicates the total number of parts, and the
/// kmip-spec-v2.1-cs01 07 May 2020 Standards Track Work Product
/// Copyright © OASIS Open 2020. All Rights Reserved. Page 21 of 240
/// Split Key Threshold field indicates the minimum number of parts
/// needed to reconstruct the entire key. The Key Part Identifier
/// indicates which key part is contained in the cryptographic
/// object, and SHALL be at least 1 and SHALL be less than or equal to Split
/// Key Parts.
#[serde(rename_all = "PascalCase")]
SplitKey {
split_key_parts: u32,
key_part_identifier: u32,
split_key_threshold: u32,
split_key_method: SplitKeyMethod,
/// REQUIRED only if Split Key Method is Polynomial Sharing Prime Field.
#[serde(skip_serializing_if = "Option::is_none")]
prime_field_size: Option<BigUint>,
key_block: KeyBlock,
},
PrivateKey {
#[serde(rename = "KeyBlock")]
key_block: KeyBlock,
},
PublicKey {
#[serde(rename = "KeyBlock")]
key_block: KeyBlock,
},
SymmetricKey {
#[serde(rename = "KeyBlock")]
key_block: KeyBlock,
},
}
impl Object {
/// Returns the corresponding `ObjectType` for that object
#[must_use]
pub fn object_type(&self) -> ObjectType {
match self {
Object::Certificate { .. } => ObjectType::Certificate,
Object::CertificateRequest { .. } => ObjectType::CertificateRequest,
Object::OpaqueObject { .. } => ObjectType::OpaqueObject,
Object::PGPKey { .. } => ObjectType::PGPKey,
Object::PrivateKey { .. } => ObjectType::PrivateKey,
Object::PublicKey { .. } => ObjectType::PublicKey,
Object::SecretData { .. } => ObjectType::SecretData,
Object::SplitKey { .. } => ObjectType::SplitKey,
Object::SymmetricKey { .. } => ObjectType::SymmetricKey,
}
}
/// Returns the `KeyBlock` of that object if any,
/// an error otherwise
pub fn key_block(&self) -> Result<&KeyBlock, KmsCommonError> {
Ok(match self {
Object::PublicKey { key_block }
| Object::PrivateKey { key_block }
| Object::SecretData { key_block, .. }
| Object::PGPKey { key_block, .. }
| Object::SymmetricKey { key_block }
| Object::SplitKey { key_block, .. } => key_block,
_ => {
return Err(KmsCommonError::InvalidKmipObject(
ErrorReason::Invalid_Object_Type,
"This object does not have a key block".to_string(),
))
}
})
}
/// Returns the `Attributes` of that object if any,
/// an error otherwise
pub fn attributes(&self) -> Result<Option<&Attributes>, KmsCommonError> {
let key_block = self.key_block()?;
match &key_block.key_value {
super::kmip_data_structures::KeyValue::PlainText {
key_material: _,
attributes,
} => Ok(attributes.as_ref()),
super::kmip_data_structures::KeyValue::Wrapped(_) => {
Err(KmsCommonError::InvalidKmipObject(
ErrorReason::Invalid_Object_Type,
"This object is wrapped and the attributes cannot be accessed".to_string(),
))
}
}
}
/// Returns the `Attributes` of that object if any,
/// an error otherwise
pub fn attributes_mut(&mut self) -> Result<Option<&mut Attributes>, KmsCommonError> {
let key_block = self.key_block_mut()?;
match &mut key_block.key_value {
super::kmip_data_structures::KeyValue::PlainText {
key_material: _,
attributes,
} => Ok(attributes.as_mut()),
super::kmip_data_structures::KeyValue::Wrapped(_) => {
Err(KmsCommonError::InvalidKmipObject(
ErrorReason::Invalid_Object_Type,
"This object is wrapped and the attributes cannot be accessed".to_string(),
))
}
}
}
/// Returns the `KeyBlock` of that object if any,
/// an error otherwise
pub fn key_block_mut(&mut self) -> Result<&mut KeyBlock, KmsCommonError> {
Ok(match self {
Object::PublicKey { key_block }
| Object::PrivateKey { key_block }
| Object::SecretData { key_block, .. }
| Object::PGPKey { key_block, .. }
| Object::SymmetricKey { key_block }
| Object::SplitKey { key_block, .. } => key_block,
_ => {
return Err(KmsCommonError::InvalidKmipObject(
ErrorReason::Invalid_Object_Type,
"This object does not have a key block".to_string(),
))
}
})
}
/// Deserialization is untagged and the `ObjectType` is not at
/// an adjacent level in the structure. Correction code needs to be
/// run post-serialization
/// see `Object` for details
#[must_use]
pub fn post_fix(object_type: ObjectType, object: Object) -> Object {
match object_type {
ObjectType::SymmetricKey => match object {
Object::PrivateKey { key_block } => Object::SymmetricKey { key_block },
Object::PublicKey { key_block } => Object::SymmetricKey { key_block },
_ => object,
},
ObjectType::PublicKey => match object {
Object::SymmetricKey { key_block } => Object::PublicKey { key_block },
Object::PrivateKey { key_block } => Object::PublicKey { key_block },
_ => object,
},
ObjectType::PrivateKey => match object {
Object::SymmetricKey { key_block } => Object::PrivateKey { key_block },
Object::PublicKey { key_block } => Object::PrivateKey { key_block },
_ => object,
},
_ => object,
}
}
}
impl TryFrom<&[u8]> for Object {
type Error = KmsCommonError;
fn try_from(object_bytes: &[u8]) -> Result<Self, KmsCommonError> {
serde_json::from_slice(object_bytes).map_err(|_e| {
KmsCommonError::InvalidKmipValue(
ErrorReason::Invalid_Attribute_Value,
"failed deserializing to an Object".to_string(),
)
})
}
}
impl TryInto<Vec<u8>> for Object {
type Error = KmsCommonError;
fn try_into(self) -> Result<Vec<u8>, KmsCommonError> {
serde_json::to_vec(&self).map_err(|_e| {
KmsCommonError::InvalidKmipObject(
ErrorReason::Invalid_Attribute_Value,
"failed serializing Object to bytes".to_string(),
)
})
}
}
/// The type of a KMIP Objects
#[allow(non_camel_case_types)]
#[allow(clippy::enum_clike_unportable_variant)]
#[derive(
Serialize, Deserialize, Copy, Clone, Debug, PartialEq, EnumString, Display, Apiv2Schema,
)]
#[serde(rename_all = "PascalCase")]
pub enum ObjectType {
Certificate = 0x0000_0001,
SymmetricKey = 0x0000_0002,
PublicKey = 0x0000_0003,
PrivateKey = 0x0000_0004,
SplitKey = 0x0000_0005,
SecretData = 0x0000_0007,
OpaqueObject = 0x0000_0008,
PGPKey = 0x0000_0009,
CertificateRequest = 0x0000_000A,
}

View file

@ -0,0 +1,841 @@
use std::fmt;
use paperclip::actix::Apiv2Schema;
use serde::{
de::{self, MapAccess, Visitor},
Deserialize, Serialize,
};
use strum_macros::Display;
use super::{
kmip_data_structures::KeyWrappingData,
kmip_objects::{Object, ObjectType},
kmip_types::{
AttributeReference, Attributes, CryptographicParameters, KeyCompressionType, KeyFormatType,
KeyWrapType, ObjectGroupMember, ProtectionStorageMasks, RevocationReason,
StorageStatusMask, UniqueIdentifier,
},
};
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Display, Debug)]
pub enum ErrorReason {
Item_Not_Found = 0x0000_0001,
Response_Too_Large = 0x0000_0002,
Authentication_Not_Successful = 0x0000_0003,
Invalid_Message = 0x0000_0004,
Operation_Not_Supported = 0x0000_0005,
Missing_Data = 0x0000_0006,
Invalid_Field = 0x0000_0007,
Feature_Not_Supported = 0x0000_0008,
Operation_Canceled_By_Requester = 0x0000_0009,
Cryptographic_Failure = 0x0000_000A,
Permission_Denied = 0x0000_000C,
Object_Archived = 0x0000_000D,
Application_Namespace_Not_Supported = 0x0000_000F,
Key_Format_Type_Not_Supported = 0x0000_0010,
Key_Compression_Type_Not_Supported = 0x0000_0011,
Encoding_Option_Error = 0x0000_0012,
Key_Value_Not_Present = 0x0000_0013,
Attestation_Required = 0x0000_0014,
Attestation_Failed = 0x0000_0015,
Sensitive = 0x0000_0016,
Not_Extractable = 0x0000_0017,
Object_Already_Exists = 0x0000_0018,
Invalid_Ticket = 0x0000_0019,
Usage_Limit_Exceeded = 0x0000_001A,
Numeric_Range = 0x0000_001B,
Invalid_Data_Type = 0x0000_001C,
Read_Only_Attribute = 0x0000_001D,
Multi_Valued_Attribute = 0x0000_001E,
Unsupported_Attribute = 0x0000_001F,
Attribute_Instance_Not_Found = 0x0000_0020,
Attribute_Not_Found = 0x0000_0021,
Attribute_Read_Only = 0x0000_0022,
Attribute_Single_Valued = 0x0000_0023,
Bad_Cryptographic_Parameters = 0x0000_0024,
Bad_Password = 0x0000_0025,
Codec_Error = 0x0000_0026,
Illegal_Object_Type = 0x0000_0028,
Incompatible_Cryptographic_Usage_Mask = 0x0000_0029,
Internal_Server_Error = 0x0000_002A,
Invalid_Asynchronous_Correlation_Value = 0x0000_002B,
Invalid_Attribute = 0x0000_002C,
Invalid_Attribute_Value = 0x0000_002D,
Invalid_Correlation_Value = 0x0000_002E,
Invalid_CSR = 0x0000_002F,
Invalid_Object_Type = 0x0000_0030,
Key_Wrap_Type_Not_Supported = 0x0000_0032,
Missing_Initialization_Vector = 0x0000_0034,
Non_Unique_Name_Attribute = 0x0000_0035,
Object_Destroyed = 0x0000_0036,
Object_Not_Found = 0x0000_0037,
Not_Authorised = 0x0000_0039,
Server_Limit_Exceeded = 0x0000_003A,
Unknown_Enumeration = 0x0000_003B,
Unknown_Message_Extension = 0x0000_003C,
Unknown_Tag = 0x0000_003D,
Unsupported_Cryptographic_Parameters = 0x0000_003E,
Unsupported_Protocol_Version = 0x0000_003F,
Wrapping_Object_Archived = 0x0000_0040,
Wrapping_Object_Destroyed = 0x0000_0041,
Wrapping_Object_Not_Found = 0x0000_0042,
Wrong_Key_Lifecycle_State = 0x0000_0043,
Protection_Storage_Unavailable = 0x0000_0044,
PKCS_11_Codec_Error = 0x0000_0045,
PKCS_11_Invalid_Function = 0x0000_0046,
PKCS_11_Invalid_Interface = 0x0000_0047,
Private_Protection_Storage_Unavailable = 0x0000_0048,
Public_Protection_Storage_Unavailable = 0x0000_0049,
General_Failure = 0x0000_0100,
}
/// This operation requests the server to Import a Managed Object specified by
/// its Unique Identifier. The request specifies the object being imported and
/// all the attributes to be assigned to the object. The attribute rules for
/// each attribute for “Initially set by” and “When implicitly set” SHALL NOT be
/// enforced as all attributes MUST be set to the supplied values rather than
/// any server generated values.
///
/// The response contains the Unique Identifier provided in the request or
/// assigned by the server. The server SHALL copy the Unique Identifier returned
/// by this operations into the ID Placeholder variable. https://docs.oasis-open.org/kmip/kmip-spec/v2.1/os/kmip-spec-v2.1-os.html#_Toc57115657
#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct Import {
/// The Unique Identifier of the object to be imported
pub unique_identifier: UniqueIdentifier,
/// Determines the type of object being imported.
pub object_type: ObjectType,
/// A Boolean. If specified and true then any existing object with the same
/// Unique Identifier SHALL be replaced by this operation. If absent or
/// false and an object exists with the same Unique Identifier then an error
/// SHALL be returned.
#[serde(skip_serializing_if = "Option::is_none")]
pub replace_existing: Option<bool>,
/// If Not Wrapped then the server SHALL unwrap the object before storing
/// it, and return an error if the wrapping key is not available.
/// Otherwise the server SHALL store the object as provided.
#[serde(skip_serializing_if = "Option::is_none")]
pub key_wrap_type: Option<KeyWrapType>,
/// Specifies object attributes to be associated with the new object.
pub attributes: Attributes,
/// The object being imported. The object and attributes MAY be wrapped.
pub object: Object,
}
/// Deserialization needs to be handwritten because the
/// included `Object` may be incorrectly deserialized to a `PrivateKey`
/// when it is a `PublicKey` (as it is "untagged" - see `postfix()`).
/// It is a lot of code for a simple post fix but well...
impl<'de> Deserialize<'de> for Import {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier)] //, rename_all = "snake_case"
enum Field {
UniqueIdentifier,
ObjectType,
ReplaceExisting,
KeyWrapType,
Attributes,
Object,
}
struct ImportVisitor;
impl<'de> Visitor<'de> for ImportVisitor {
type Value = Import;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Import")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut unique_identifier: Option<UniqueIdentifier> = None;
let mut object_type: Option<ObjectType> = None;
let mut replace_existing: Option<bool> = None;
let mut key_wrap_type: Option<KeyWrapType> = None;
let mut attributes: Option<Attributes> = None;
let mut object: Option<Object> = None;
while let Some(key) = map.next_key()? {
match key {
Field::UniqueIdentifier => {
if unique_identifier.is_some() {
return Err(de::Error::duplicate_field("unique_identifier"))
}
unique_identifier = Some(map.next_value()?);
}
Field::ObjectType => {
if object_type.is_some() {
return Err(de::Error::duplicate_field("object_type"))
}
object_type = Some(map.next_value()?);
}
Field::ReplaceExisting => {
if replace_existing.is_some() {
return Err(de::Error::duplicate_field("replace_existing"))
}
replace_existing = Some(map.next_value()?);
}
Field::KeyWrapType => {
if key_wrap_type.is_some() {
return Err(de::Error::duplicate_field("key_wrap_type"))
}
key_wrap_type = Some(map.next_value()?);
}
Field::Attributes => {
if attributes.is_some() {
return Err(de::Error::duplicate_field("attributes"))
}
attributes = Some(map.next_value()?);
}
Field::Object => {
if object.is_some() {
return Err(de::Error::duplicate_field("object"))
}
object = Some(map.next_value()?);
}
}
}
let unique_identifier = unique_identifier
.ok_or_else(|| de::Error::missing_field("unique_identifier"))?;
let object_type =
object_type.ok_or_else(|| de::Error::missing_field("object_type"))?;
let attributes =
attributes.ok_or_else(|| de::Error::missing_field("attributes"))?;
let object = object.ok_or_else(|| de::Error::missing_field("object"))?;
// all this code .... to be able to insert that line....
let object = Object::post_fix(object_type, object);
Ok(Import {
unique_identifier,
object_type,
replace_existing,
key_wrap_type,
attributes,
object,
})
}
}
const FIELDS: &[&str] = &[
"unique_identifier",
"object_type",
"replace_existing",
"key_wrap_type",
"attributes",
"object",
];
deserializer.deserialize_struct("Import", FIELDS, ImportVisitor)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct ImportResponse {
/// The Unique Identifier of the newly imported object.
pub unique_identifier: UniqueIdentifier,
}
/// This operation requests the server to generate a new symmetric key or
/// generate Secret Data as aManaged Cryptographic Object. The request contains
/// information about the type of object being created, and some of the
/// attributes to be assigned to the object (e.g., Cryptographic Algorithm,
/// Cryptographic Length, etc.). The response contains the Unique Identifier of
/// the created object. The server SHALL copy the Unique Identifier returned by
/// this operation into the ID Placeholder variable.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Create {
/// Determines the type of object to be created.
pub object_type: ObjectType,
/// Specifies desired attributes to be associated with the new object.
pub attributes: Attributes,
/// Specifies all permissible Protection Storage Mask selections for the new
/// object
#[serde(skip_serializing_if = "Option::is_none")]
pub protection_storage_masks: Option<ProtectionStorageMasks>,
}
impl paperclip::v2::schema::Apiv2Schema for Create {
const DESCRIPTION: &'static str = "Create request Apiv2Schema";
const NAME: Option<&'static str> = None;
const REQUIRED: bool = true;
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct CreateResponse {
/// Type of object created.
pub object_type: ObjectType,
/// The Unique Identifier of the newly created object.
pub unique_identifier: UniqueIdentifier,
}
/// This operation requests the server to generate a new public/private key pair
/// and register the two corresponding new Managed Cryptographic Object
/// The request contains attributes to be assigned to the objects (e.g.,
/// Cryptographic Algorithm, Cryptographic Length, etc.). Attributes MAY be
/// specified for both keys at the same time by specifying a Common Attributes
/// object in the request. Attributes not common to both keys (e.g., Name,
/// Cryptographic Usage Mask) MAY be specified using the Private Key Attributes
/// and Public Key Attributes objects in the request, which take precedence over
/// the Common Attributes object. For the Private Key, the server SHALL create a
/// Link attribute of Link Type Public Key pointing to the Public Key.
/// For the Public Key, the server SHALL create a Link attribute of Link Type
/// Private Key pointing to the Private Key. The response contains the Unique
/// Identifiers of both created objects. The ID Placeholder value SHALL be set
/// to the Unique Identifier of the Private Key
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "PascalCase")]
pub struct CreateKeyPair {
/// Specifies desired attributes to be associated with the new object that
/// apply to both the Private and Public Key Objects
#[serde(skip_serializing_if = "Option::is_none")]
pub common_attributes: Option<Attributes>,
/// Specifies the attributes to be associated with the new object that apply
/// to the Private Key Object.
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key_attributes: Option<Attributes>,
/// Specifies the attributes to be associated with the new object that apply
/// to the Public Key Object.
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key_attributes: Option<Attributes>,
/// Specifies all ProtectionStorage Mask selections that are permissible for
/// the new Private Key and Public Key objects.
#[serde(skip_serializing_if = "Option::is_none")]
pub common_protection_storage_masks: Option<ProtectionStorageMasks>,
/// Specifies all ProtectionStorage Mask selections that are permissible for
/// the new Private Key object.
#[serde(skip_serializing_if = "Option::is_none")]
pub private_protection_storage_masks: Option<ProtectionStorageMasks>,
/// Specifies all ProtectionStorage Mask selections that are permissible for
/// the new PublicKey object.
#[serde(skip_serializing_if = "Option::is_none")]
pub public_protection_storage_masks: Option<ProtectionStorageMasks>,
}
impl paperclip::v2::schema::Apiv2Schema for CreateKeyPair {
const DESCRIPTION: &'static str = "Create key pair request Apiv2Schema";
const NAME: Option<&'static str> = None;
const REQUIRED: bool = true;
}
#[derive(Serialize, Deserialize, Apiv2Schema, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct CreateKeyPairResponse {
/// The Unique Identifier of the newly created private key object.
pub private_key_unique_identifier: UniqueIdentifier,
/// The Unique Identifier of the newly created public key object.
pub public_key_unique_identifier: UniqueIdentifier,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema, Default)]
#[serde(rename_all = "PascalCase")]
pub struct Get {
/// Determines the object being requested. If omitted, then the ID
/// Placeholder value is used by the server as the Unique Identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
/// Determines the key format type to be returned.
#[serde(skip_serializing_if = "Option::is_none")]
pub key_format_type: Option<KeyFormatType>,
/// Determines the Key Wrap Type of the returned key value.
#[serde(skip_serializing_if = "Option::is_none")]
pub key_wrap_type: Option<KeyWrapType>,
/// Determines the compression method for elliptic curve public keys.
#[serde(skip_serializing_if = "Option::is_none")]
pub key_compression_type: Option<KeyCompressionType>,
/// Specifies keys and other information for wrapping the returned object.
#[serde(skip_serializing_if = "Option::is_none")]
pub key_wrapping_data: Option<KeyWrappingData>,
}
impl From<String> for Get {
// Create a GetRequest for an object to be returned "as is"
fn from(uid: String) -> Self {
Get {
unique_identifier: Some(uid),
key_format_type: None,
key_wrap_type: None,
key_compression_type: None,
key_wrapping_data: None,
}
}
}
impl From<&String> for Get {
// Create a GetRequest for an object to be returned "as is"
fn from(uid: &String) -> Self {
Self::from(uid.clone())
}
}
impl From<&str> for Get {
// Create a GetRequest for an object to be returned "as is"
fn from(uid: &str) -> Self {
Self::from(uid.to_owned())
}
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct GetResponse {
pub object_type: ObjectType,
pub unique_identifier: UniqueIdentifier,
pub object: Object,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct GetAttributes {
/// Determines the object whose attributes
/// are being requested. If omitted, then
/// the ID Placeholder value is used by the
/// server as the Unique Identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
/// Specifies an attribute associated with
/// the object.
#[serde(skip_serializing_if = "Option::is_none")]
pub attribute_references: Option<Vec<AttributeReference>>,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct GetAttributesResponse {
/// The Unique Identifier of the object
pub unique_identifier: UniqueIdentifier,
/// Attributes
pub attributes: Attributes,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema, Default)]
#[serde(rename_all = "PascalCase")]
pub struct Encrypt {
/// The Unique Identifier of the Managed
/// Cryptographic Object that is the key to
/// use for the encryption operation. If
/// omitted, then the ID Placeholder value
/// SHALL be used by the server as the
/// Unique Identifier
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
/// The Cryptographic Parameters (Block
/// Cipher Mode, Padding Method,
/// RandomIV) corresponding to the
/// particular encryption method
/// requested.
/// If there are no Cryptographic
/// Parameters associated with the
/// Managed Cryptographic Object and
/// the algorithm requires parameters then
/// the operation SHALL return with a
/// Result Status of Operation Failed.
#[serde(skip_serializing_if = "Option::is_none")]
pub cryptographic_parameters: Option<CryptographicParameters>,
/// The data to be encrypted
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<u8>>,
/// The initialization vector, counter or
/// nonce to be used (where appropriate).
#[serde(skip_serializing_if = "Option::is_none")]
pub iv_counter_nonce: Option<Vec<u8>>,
/// Specifies the existing stream or by-
/// parts cryptographic operation (as
/// returned from a previous call to this
/// operation)
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_value: Option<Vec<u8>>,
/// Initial operation as Boolean
#[serde(skip_serializing_if = "Option::is_none")]
pub init_indicator: Option<bool>,
/// Final operation as Boolean
#[serde(skip_serializing_if = "Option::is_none")]
pub final_indicator: Option<bool>,
/// Any additional data to be authenticated via the Authenticated Encryption
/// Tag. If supplied in multi-part encryption,
/// this data MUST be supplied on the initial Encrypt request
#[serde(skip_serializing_if = "Option::is_none")]
pub authenticated_encryption_additional_data: Option<Vec<u8>>,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct EncryptResponse {
/// The Unique Identifier of the Managed
/// Cryptographic Object that was the key
/// used for the encryption operation.
pub unique_identifier: UniqueIdentifier,
/// The encrypted data (as a Byte String).
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<u8>>,
/// The value used if the Cryptographic
/// Parameters specified Random IV and
/// the IV/Counter/Nonce value was not
/// provided in the request and the
/// algorithm requires the provision of an
/// IV/Counter/Nonce.
#[serde(skip_serializing_if = "Option::is_none")]
pub iv_counter_nonce: Option<Vec<u8>>,
/// Specifies the stream or by-parts value
/// to be provided in subsequent calls to
/// this operation for performing
/// cryptographic operations.
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_value: Option<Vec<u8>>,
/// Specifies the tag that will be needed to
/// authenticate the decrypted data (and
/// any “additional data”). Only returned on
/// completion of the encryption of the last
/// of the plaintext by an authenticated
/// encryption cipher.
#[serde(skip_serializing_if = "Option::is_none")]
pub authenticated_encryption_tag: Option<Vec<u8>>,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema, Default)]
#[serde(rename_all = "PascalCase")]
pub struct Decrypt {
/// The Unique Identifier of the Managed
/// Cryptographic Object that is the key to
/// use for the decryption operation. If
/// omitted, then the ID Placeholder value
/// SHALL be used by the server as the
/// Unique Identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
/// The Cryptographic Parameters (Block
/// Cipher Mode, Padding Method)
/// corresponding to the particular
/// decryption method requested.
/// If there are no Cryptographic
/// Parameters associated with the
/// Managed Cryptographic Object and
/// the algorithm requires parameters then
/// the operation SHALL return with a
/// Result Status of Operation Failed.
#[serde(skip_serializing_if = "Option::is_none")]
pub cryptographic_parameters: Option<CryptographicParameters>,
/// The data to be decrypted.
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<u8>>,
/// The initialization vector, counter or
/// nonce to be used (where appropriate)
#[serde(skip_serializing_if = "Option::is_none")]
pub iv_counter_nonce: Option<Vec<u8>>,
/// Initial operation as Boolean
#[serde(skip_serializing_if = "Option::is_none")]
pub init_indicator: Option<bool>,
/// Final operation as Boolean
#[serde(skip_serializing_if = "Option::is_none")]
pub final_indicator: Option<bool>,
/// Additional data to be authenticated via
/// the Authenticated Encryption Tag. If
/// supplied in multi-part decryption, this
/// data MUST be supplied on the initial
/// Decrypt request
#[serde(skip_serializing_if = "Option::is_none")]
pub authenticated_encryption_additional_data: Option<Vec<u8>>,
/// Specifies the tag that will be needed to
/// authenticate the decrypted data and
/// the additional authenticated data. If
/// supplied in multi-part decryption, this
/// data MUST be supplied on the initial
/// Decrypt request
#[serde(skip_serializing_if = "Option::is_none")]
pub authenticated_encryption_tag: Option<Vec<u8>>,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct DecryptResponse {
/// The Unique Identifier of the Managed
/// Cryptographic Object that was the key
/// used for the decryption operation.
pub unique_identifier: UniqueIdentifier,
/// The decrypted data
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<u8>>,
/// Specifies the stream or by-parts value
/// to be provided in subsequent calls to
/// this operation for performing
/// cryptographic operations.
#[serde(skip_serializing_if = "Option::is_none")]
pub correlation_value: Option<Vec<u8>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Locate {
/// An Integer object that indicates the maximum number of object
/// identifiers the server MAY return.
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum_items: Option<i32>,
/// An Integer object that indicates the number of object identifiers to
/// skip that satisfy the identification criteria specified in the request.
#[serde(skip_serializing_if = "Option::is_none")]
pub offset_items: Option<i32>,
/// An Integer object (used as a bit mask) that indicates whether only
/// on-line objects, only archived objects, destroyed objects or any
/// combination of these, are to be searched. If omitted, then only on-line
/// objects SHALL be returned.
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_status_mask: Option<StorageStatusMask>,
/// An Enumeration object that indicates the object group member type.
#[serde(skip_serializing_if = "Option::is_none")]
pub object_group_member: Option<ObjectGroupMember>,
/// Specifies an attribute and its value(s) that are REQUIRED to match those
/// in a candidate object (according to the matching rules defined above).
pub attributes: Attributes,
}
/// This operation requests that the server search for one or more Managed
/// Objects, depending on the attributes specified in the request. All attributes
/// are allowed to be used. The request MAY contain a Maximum Items field, which
/// specifies the maximum number of objects to be returned. If the Maximum Items
/// field is omitted, then the server MAY return all objects matched, or MAY
/// impose an internal maximum limit due to resource limitations.
///
/// The request MAY contain an Offset Items field, which specifies the number of
/// objects to skip that satisfy the identification criteria specified in the
/// request. An Offset Items field of 0 is the same as omitting the Offset Items
/// field. If both Offset Items and Maximum Items are specified in the request,
/// the server skips Offset Items objects and returns up to Maximum Items
/// objects.
///
/// If more than one object satisfies the identification criteria specified in
/// the request, then the response MAY contain Unique Identifiers for multiple
/// Managed Objects. Responses containing Unique Identifiers for multiple objects
/// SHALL be returned in descending order of object creation (most recently
/// created object first). Returned objects SHALL match all of the attributes in
/// the request. If no objects match, then an empty response payload is returned.
/// If no attribute is specified in the request, any object SHALL be deemed to
/// match the Locate request. The response MAY include Located Items which is the
/// count of all objects that satisfy the identification criteria.
///
/// The server returns a list of Unique Identifiers of the found objects, which
/// then MAY be retrieved using the Get operation. If the objects are archived,
/// then the Recover and Get operations are REQUIRED to be used to obtain those
/// objects. If a single Unique Identifier is returned to the client, then the
/// server SHALL copy the Unique Identifier returned by this operation into the
/// ID Placeholder variable. If the Locate operation matches more than one
/// object, and the Maximum Items value is omitted in the request, or is set to a
/// value larger than one, then the server SHALL empty the ID Placeholder,
/// causing any subsequent operations that are batched with the Locate, and which
/// do not specify a Unique Identifier explicitly, to fail. This ensures that
/// these batched operations SHALL proceed only if a single object is returned by
/// Locate.
///
/// The Date attributes in the Locate request (e.g., Initial Date, Activation
/// Date, etc.) are used to specify a time or a time range for the search. If a
/// single instance of a given Date attribute is used in the request (e.g., the
/// Activation Date), then objects with the same Date attribute are considered to
/// be matching candidate objects. If two instances of the same Date attribute
/// are used (i.e., with two different values specifying a range), then objects
/// for which the Date attribute is inside or at a limit of the range are
/// considered to be matching candidate objects. If a Date attribute is set to
/// its largest possible value, then it is equivalent to an undefined attribute.
///
/// When the Cryptographic Usage Mask attribute is specified in the request,
/// candidate objects are compared against this field via an operation that
/// consists of a logical AND of the requested mask with the mask in the
/// candidate object, and then a comparison of the resulting value with the
/// requested mask. For example, if the request contains a mask value of
/// 10001100010000, and a candidate object mask contains 10000100010000, then the
/// logical AND of the two masks is 10000100010000, which is compared against the
/// mask value in the request (10001100010000) and the match fails. This means
/// that a matching candidate object has all of the bits set in its mask that are
/// set in the requested mask, but MAY have additional bits set.
///
/// When the Usage Limits attribute is specified in the request, matching
/// candidate objects SHALL have a Usage Limits Count and Usage Limits Total
/// equal to or larger than the values specified in the request.
///
/// When an attribute that is defined as a structure is specified, all of the
/// structure fields are not REQUIRED to be specified. For instance, for the Link
/// attribute, if the Linked Object Identifier value is specified without the
/// Link Type value, then matching candidate objects have the Linked Object
/// Identifier as specified, irrespective of their Link Type.
///
/// When the Object Group attribute and the Object Group Member flag are
/// specified in the request, and the value specified for Object Group Member is
/// Group Member Fresh, matching candidate objects SHALL be fresh objects from
/// the object group. If there are no more fresh objects in the group, the server
/// MAY choose to generate a new object on-the-fly, based on server policy. If
/// the value specified for Object Group Member is Group Member Default, the
/// server locates the default object as defined by server policy.
///
/// The Storage Status Mask field is used to indicate whether on-line objects
/// (not archived or destroyed), archived objects, destroyed objects or any
/// combination of the above are to be searched.The server SHALL NOT return
/// unique identifiers for objects that are destroyed unless the Storage Status
/// Mask field includes the Destroyed Storage indicator. The server SHALL NOT
/// return unique identifiers for objects that are archived unless the Storage
/// Status Mask field includes the Archived Storage indicator.
impl Locate {
#[must_use]
pub fn new(object_type: ObjectType) -> Locate {
Locate {
maximum_items: None,
offset_items: None,
storage_status_mask: None,
object_group_member: None,
attributes: Attributes::new(object_type),
}
}
}
impl paperclip::v2::schema::Apiv2Schema for Locate {
const DESCRIPTION: &'static str = "Locate Request Apiv2Schema";
const NAME: Option<&'static str> = None;
const REQUIRED: bool = true;
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
pub struct LocateResponse {
/// An Integer object that indicates the number of object identifiers that
/// satisfy the identification criteria specified in the request. A server
/// MAY elect to omit this value from the Response if it is unable or
/// unwilling to determine the total count of matched items.
// A server MAY elect to return the Located Items value even if Offset Items is not present in
// the Request.
#[serde(skip_serializing_if = "Option::is_none", rename = "LocatedItems")]
pub located_items: Option<i32>,
/// The Unique Identifier of the located objects.
#[serde(skip_serializing_if = "Option::is_none", rename = "UniqueIdentifier")]
pub unique_identifiers: Option<Vec<UniqueIdentifier>>,
}
/// This operation requests the server to revoke a Managed Cryptographic Object
/// or an Opaque Object. The request contains a reason for the revocation (e.g.,
/// “key compromise”, “cessation of operation”, etc.). The operation has one of
/// two effects.
///
/// If the revocation reason is “key compromise” or “CA compromise”,
/// then the object is placed into the “compromised” state; the Date is set to
/// the current date and time; and the Compromise Occurrence Date is set to the
/// value (if provided) in the Revoke request and if a value is not provided in
/// the Revoke request then Compromise Occurrence Date SHOULD be set to the
/// Initial Date for the object.
///
/// If the revocation reason is neither “key
/// compromise” nor “CA compromise”, the object is placed into the “deactivated”
/// state, and the Deactivation Date is set to the current date and time.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Revoke {
/// Determines the object being revoked. If omitted, then the ID Placeholder
/// value is used by the server as the Unique Identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
/// Specifies the reason for revocation.
pub revocation_reason: RevocationReason,
/// SHOULD be specified if the Revocation Reason is 'key compromise' or CA
/// compromise and SHALL NOT be specified for other Revocation Reason
/// enumerations.
#[serde(skip_serializing_if = "Option::is_none")]
pub compromise_occurrence_date: Option<u64>, // epoch millis
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct RevokeResponse {
/// The Unique Identifier of the object.
pub unique_identifier: UniqueIdentifier,
}
/// This request is used to generate a replacement key pair for an existing
/// public/private key pair. It is analogous to the Create Key Pair operation,
/// except that attributes of the replacement key pair are copied from the
/// existing key pair, with the exception of the attributes listed in Re-key Key
/// Pair Attribute Requirements tor.
///
/// As the replacement of the key pair takes over the name attribute for the
/// existing public/private key pair, Re-key Key Pair SHOULD only be performed
/// once on a given key pair.
///
/// For both the existing public key and private key, the server SHALL create a
/// Link attribute of Link Type Replacement Key pointing to the replacement
/// public and private key, respectively. For both the replacement public and
/// private key, the server SHALL create a Link attribute of Link Type Replaced
/// Key pointing to the existing public and private key, respectively.
///
/// The server SHALL copy the Private Key Unique Identifier of the replacement
/// private key returned by this operation into the ID Placeholder variable.
///
/// An Offset MAY be used to indicate the difference between the Initial Date and
/// the Activation Date of the replacement key pair. If no Offset is specified,
/// the Activation Date and Deactivation Date values are copied from the existing
/// key pair. If Offset is set and dates exist for the existing key pair, then
/// the dates of the replacement key pair SHALL be set based on the dates of the
/// existing key pair as follows
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
pub struct ReKeyKeyPair {
// Determines the existing Asymmetric key pair to be re-keyed. If omitted, then the ID
// Placeholder is substituted by the server.
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key_unique_identifier: Option<UniqueIdentifier>,
// An Interval object indicating the difference between the Initial Date and the Activation
// Date of the replacement key pair to be created.
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<i32>,
// Specifies desired attributes that apply to both the Private and Public Key Objects.
#[serde(skip_serializing_if = "Option::is_none")]
pub common_attributes: Option<Attributes>,
// Specifies attributes that apply to the Private Key Object.
#[serde(skip_serializing_if = "Option::is_none")]
pub private_key_attributes: Option<Attributes>,
// Specifies attributes that apply to the Public Key Object.
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key_attributes: Option<Attributes>,
// Specifies all Protection Storage Mask selections that are permissible for the new Private
// Key and new Public Key objects
#[serde(skip_serializing_if = "Option::is_none")]
pub common_protection_storage_masks: Option<ProtectionStorageMasks>,
// Specifies all Protection
// Storage Mask selections that are permissible for the new Private Key object.
#[serde(skip_serializing_if = "Option::is_none")]
pub private_protection_storage_masks: Option<ProtectionStorageMasks>,
// Specifies all Protection
// Storage Mask selections that are permissible for the new Public Key object.
#[serde(skip_serializing_if = "Option::is_none")]
pub public_protection_storage_masks: Option<ProtectionStorageMasks>,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct ReKeyKeyPairResponse {
// The Unique Identifier of the newly created replacement Private Key object.
pub private_key_unique_identifier: UniqueIdentifier,
// The Unique Identifier of the newly created replacement Public Key object.}
pub public_key_unique_identifier: UniqueIdentifier,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Destroy {
/// Determines the object being destroyed. If omitted, then the ID
/// Placeholder value is used by the server as the Unique Identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_identifier: Option<UniqueIdentifier>,
}
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
#[serde(rename_all = "PascalCase")]
pub struct DestroyResponse {
/// The Unique Identifier of the object.
pub unique_identifier: UniqueIdentifier,
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
pub mod access;
pub mod kmip_data_structures;
pub mod kmip_key_utils;
pub mod kmip_objects;
pub mod kmip_operations;
pub mod kmip_types;
pub mod ttlv;

View file

@ -0,0 +1,868 @@
use std::convert::TryInto;
use serde::{
de::{self, DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor},
Deserialize,
};
use tracing::log::trace;
use crate::kmip::ttlv::{error::TtlvError, TTLVEnumeration, TTLValue, TTLV};
type Result<T> = std::result::Result<T, TtlvError>;
/// The current input being deserialized
#[derive(Debug, PartialEq)]
enum Deserializing {
StructureTag,
StructureValue,
ByteString,
BigInt,
}
#[derive(Debug)]
enum Inputs<'de> {
Structure(Vec<&'de TTLV>),
Bytes(&'de [u8]),
BigInt(Vec<u32>),
}
pub struct TtlvDeserializer<'de> {
/// whether a tag or a value is being deserialized with thi serializer
deserializing: Deserializing,
/// the index+1 of the TTLV being processed
/// 0 is reserved so that visitors can increase then process
index: usize,
/// the inputs being deserialized, index gives the current input
inputs: Inputs<'de>,
}
impl<'de> TtlvDeserializer<'de> {
#[must_use]
pub fn from_ttlv(root: &'de TTLV) -> Self {
TtlvDeserializer {
deserializing: Deserializing::StructureValue,
inputs: Inputs::Structure(vec![root]),
index: 1,
}
}
fn get_structure(&self) -> Result<&[&'de TTLV]> {
match &self.inputs {
Inputs::Structure(children) => Ok(children),
_ => Err(TtlvError::custom(format!(
"Unable to get TTLV Structure children. Currently deserializing {:#?}",
&self.deserializing
))),
}
}
fn get_bytes(&self) -> Result<&'de [u8]> {
match &self.inputs {
Inputs::Bytes(bytes) => Ok(*bytes),
_ => Err(TtlvError::custom(format!(
"Unable to get ByteString bytes. Currently deserializing {:#?}",
&self.deserializing
))),
}
}
fn get_bigint(&self) -> Result<&[u32]> {
match &self.inputs {
Inputs::BigInt(array) => Ok(array),
_ => Err(TtlvError::custom(format!(
"Unable to get BigInt array. Currently deserializing {:#?}",
&self.deserializing
))),
}
}
}
pub fn from_ttlv<'a, T>(s: &'a TTLV) -> Result<T>
where
T: Deserialize<'a>,
{
let mut deserializer = TtlvDeserializer::from_ttlv(s);
T::deserialize(&mut deserializer)
}
impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> {
type Error = TtlvError;
// Look at the input data to decide what Serde data model type to
// deserialize as. Not all data formats are able to support this operation.
// Formats that support `deserialize_any` are known as self-describing.
// #[instrument(skip(self, visitor))]
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
trace!(
"deserialize_any {:?}: {:?} -> {:?}",
self.deserializing,
self.index,
&self.inputs
);
if self.deserializing == Deserializing::ByteString {
return visitor.visit_u8(self.get_bytes()?[self.index - 1])
}
if self.deserializing == Deserializing::BigInt {
return visitor.visit_u32(self.get_bigint()?[self.index - 1])
}
if self.deserializing == Deserializing::StructureTag {
return visitor.visit_borrowed_str(&self.get_structure()?[self.index - 1].tag)
}
// deserializing the value of the child of a Structure
let child = &self.get_structure()?[self.index - 1];
let child_tag = &child.tag;
let child_value = &child.value;
trace!("deserialize_any child value {:?}", child_value);
match child_value {
TTLValue::Structure(elements) => {
if elements.is_empty() || child_tag == &elements[0].tag {
// in TTLV when the elements tags are identical to the parent tag,
// it is a sequence
visitor.visit_seq(TtlvDeserializer {
deserializing: Deserializing::StructureValue,
inputs: Inputs::Structure(elements.iter().collect::<Vec<&TTLV>>()), // can probably do better
// start at 0 because the Visit Map is going to increment first
index: 0,
})
} else {
visitor.visit_map(TtlvDeserializer {
deserializing: Deserializing::StructureValue,
inputs: Inputs::Structure(elements.iter().collect::<Vec<&TTLV>>()), // can probably do better
// start at 0 because the Visit Map is going to increment first
index: 0,
})
}
}
TTLValue::Integer(i) => visitor.visit_i32(*i),
TTLValue::BitMask(e) => visitor.visit_u32(*e),
TTLValue::LongInteger(li) => visitor.visit_i64(*li),
TTLValue::Enumeration(e) => match e {
TTLVEnumeration::Integer(i) => visitor.visit_i32(*i),
TTLVEnumeration::Name(n) => visitor.visit_str(n),
},
TTLValue::ByteString(b) => visitor.visit_seq(TtlvDeserializer {
deserializing: Deserializing::ByteString,
inputs: Inputs::Bytes(b),
// start at 0 because the Visit Map is going to increment first
index: 0,
}),
TTLValue::BigInteger(e) => visitor.visit_seq(TtlvDeserializer {
deserializing: Deserializing::BigInt,
inputs: Inputs::BigInt(e.to_u32_digits()),
// start at 0 because the Visit Map is going to increment first
index: 0,
}),
TTLValue::TextString(s) => visitor.visit_str(s),
TTLValue::Boolean(b) => visitor.visit_bool(*b),
TTLValue::DateTime(dt) => visitor.visit_str(&dt.to_rfc3339()),
TTLValue::Interval(i) => visitor.visit_u32(*i),
TTLValue::DateTimeExtended(dte) => visitor.visit_str(&dte.to_rfc3339()),
}
}
// #[instrument(skip(self, visitor))]
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// this can only happen for `Deserializing::Value`
let child = &self.get_structure()?[self.index - 1].value;
visitor.visit_bool(match child {
TTLValue::Boolean(b) => *b,
x => return Err(TtlvError::custom(format!("Invalid type for bool: {:?}", x))),
})
}
// #[instrument(skip(self, _visitor))]
fn deserialize_i8<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize an i8 value. Not supported in KMIP:".to_string(),
))
}
// #[instrument(skip(self, _visitor))]
fn deserialize_i16<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize an i16 value. Not supported in KMIP:".to_string(),
))
}
// #[instrument(skip(self, visitor))]
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// this can only happen for `Deserializing::Value`
let child = &self.get_structure()?[self.index - 1].value;
visitor.visit_i32(match child {
TTLValue::Integer(v) => *v,
x => return Err(TtlvError::custom(format!("Invalid type for i32: {:?}", x))),
})
}
// #[instrument(skip(self, visitor))]
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// this can only happen for `Deserializing::Value`
let child = &self.get_structure()?[self.index - 1].value;
visitor.visit_i64(match child {
TTLValue::LongInteger(v) => *v,
x => return Err(TtlvError::custom(format!("Invalid type for i64: {:?}", x))),
})
}
// #[instrument(skip(self, visitor))]
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match &self.deserializing {
Deserializing::ByteString => {
let u = &self.get_bytes()?[self.index - 1];
visitor.visit_u8(*u)
}
x => Err(TtlvError::custom(format!(
"deserialize_u8. Unexpected {:?}",
x
))),
}
}
// #[instrument(skip(self, _visitor))]
fn deserialize_u16<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize a u16 value. Not supported in KMIP:".to_string(),
))
}
// #[instrument(skip(self, visitor))]
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match &self.deserializing {
Deserializing::BigInt => {
let u = &self.get_bigint()?[self.index - 1];
visitor.visit_u32(*u)
}
Deserializing::StructureValue => {
let child = &self.get_structure()?[self.index - 1].value;
visitor.visit_u32(match child {
TTLValue::Integer(v) => (*v).try_into().map_err(|_e| {
TtlvError::custom(format!("Invalid type for u32: {:?}", v))
})?,
TTLValue::BitMask(v) => *v,
x => return Err(TtlvError::custom(format!("Invalid type for u32: {:?}", x))),
})
}
x => Err(TtlvError::custom(format!(
"deserialize_str. Unexpected {:?}",
x
))),
}
}
// #[instrument(skip(self, visitor))]
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// this can only happen for `Deserializing::Value`
let child = &self.get_structure()?[self.index - 1].value;
visitor.visit_u64(match child {
TTLValue::LongInteger(v) => (*v)
.try_into()
.map_err(|_e| TtlvError::custom(format!("Invalid type for u64: {:?}", v)))?,
x => return Err(TtlvError::custom(format!("Invalid type for u64: {:?}", x))),
})
}
// #[instrument(skip(self, _visitor))]
fn deserialize_f32<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize a f32 value. Not supported in KMIP:".to_string(),
))
}
// #[instrument(skip(self, visitor))]
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// this can only happen for `Deserializing::Value`
let child = &self.get_structure()?[self.index - 1].value;
visitor.visit_f64(match child {
TTLValue::Integer(v) => f64::from(*v),
x => return Err(TtlvError::custom(format!("Invalid type for f64: {:?}", x))),
})
}
// #[instrument(skip(self, _visitor))]
fn deserialize_char<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize a char value. Not supported in KMIP:".to_string(),
))
}
// Refer to the "Understanding deserializer lifetimes" page for information
// about the three deserialization flavors of strings in Serde.
// #[instrument(skip(self, visitor))]
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match &self.deserializing {
Deserializing::StructureTag => {
let tag = &self.get_structure()?[self.index - 1].tag;
visitor.visit_borrowed_str(tag)
}
Deserializing::StructureValue => {
let value = &self.get_structure()?[self.index - 1].value;
match value {
TTLValue::TextString(v) => visitor.visit_borrowed_str(v),
TTLValue::Enumeration(v) => match v {
TTLVEnumeration::Integer(i) => Err(TtlvError::custom(format!(
"deserialize_str. Unexpected integer in enumeration: {:?}",
i
))),
TTLVEnumeration::Name(n) => visitor.visit_borrowed_str(n),
},
x => Err(TtlvError::custom(format!(
"deserialize_str. Invalid type for string: {:?}",
x
))),
}
}
x => Err(TtlvError::custom(format!(
"deserialize_str. Unexpected {:?}",
x
))),
}
}
// #[instrument(skip(self, visitor))]
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}
// #[instrument(skip(self, _visitor))]
fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize a byte array (not in a ByteString). Not supported in KMIP:"
.to_string(),
))
}
// #[instrument(skip(self, _visitor))]
fn deserialize_byte_buf<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize a byte array (not in a ByteString). Not supported in KMIP:"
.to_string(),
))
}
// #[instrument(skip(self, visitor))]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// No none in KMIP
visitor.visit_some(self)
}
// In Serde, unit means an anonymous value containing no data.
// #[instrument(skip(self, _visitor))]
fn deserialize_unit<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize unit. Not supported in KMIP:".to_string(),
))
}
// Unit struct means a named value containing no data.
// #[instrument(skip(self, _visitor))]
fn deserialize_unit_struct<V>(self, _name: &'static str, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
Err(TtlvError::custom(
"Unable to deserialize unit struct. Not supported in KMIP:".to_string(),
))
}
// As is done here, serializers are encouraged to treat newtype structs as
// insignificant wrappers around the data they contain. That means not
// parsing anything other than the contained value.
// #[instrument(skip(self, visitor))]
fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
// Deserialization of compound types like sequences and maps happens by
// passing the visitor an "Access" object that gives it the ability to
// iterate through the data contained in the sequence.
// #[instrument(skip(self, visitor))]
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match self.deserializing {
Deserializing::StructureTag => Err(TtlvError::custom(
"deserialize_seq. A seq should not be deserialized when deserializing a tag"
.to_string(),
)),
Deserializing::StructureValue => {
let value = &self.get_structure()?[self.index - 1].value;
match value {
TTLValue::ByteString(array) =>
// go down one level by deserializing the inner structure
{
visitor.visit_seq(TtlvDeserializer {
deserializing: Deserializing::ByteString,
inputs: Inputs::Bytes(array),
// start at 0 because the Visit Map is going to increment first
index: 0,
})
}
TTLValue::BigInteger(big_int) =>
// go down one level by deserializing the inner structure
{
visitor.visit_seq(TtlvDeserializer {
deserializing: Deserializing::BigInt,
inputs: Inputs::BigInt(big_int.to_u32_digits()),
index: 0,
})
}
TTLValue::Structure(array) => {
// go down one level by deserializing the inner structure
visitor.visit_seq(TtlvDeserializer {
deserializing: Deserializing::StructureValue,
inputs: Inputs::Structure(array.iter().collect::<Vec<&TTLV>>()), // can probably do better
// start at 0 because the Visit Map is going to increment first
index: 0,
})
}
x => Err(TtlvError::custom(format!(
"deserialize_seq. Invalid type for value: {:?}",
x
))),
}
}
Deserializing::ByteString => {
// already at the ByteString level, visit it
visitor.visit_seq(self)
}
Deserializing::BigInt => {
// already at the BigInt level, visit it
visitor.visit_seq(self)
}
}
}
// Tuples look just like sequences
// #[instrument(skip(self, visitor))]
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
// Tuple structs look just like sequences
// #[instrument(skip(self, visitor))]
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_seq(visitor)
}
// #[instrument(skip(self, visitor))]
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match &self.deserializing {
Deserializing::StructureValue => {
let value = &self.get_structure()?[self.index - 1].value;
match value {
TTLValue::Structure(array) => {
// go down one level by deserializing the inner structure
visitor.visit_map(TtlvDeserializer {
deserializing: Deserializing::StructureValue,
inputs: Inputs::Structure(array.iter().collect::<Vec<&TTLV>>()), // can probably do better
// start at 0 because the Visit Map is going to increment first
index: 0,
})
}
x => Err(TtlvError::custom(format!(
"deserialize_map. Invalid type for value: {:?}",
x
))),
}
}
x => Err(TtlvError::custom(format!(
"deserialize_map. A map should not be deserialized when deserializing a {:?}",
x
))),
}
}
// Structs look just like maps in KMIP.
//
// Notice the `fields` parameter - a "struct" in the Serde data model means
// that the `Deserialize` implementation is required to know what the fields
// are before even looking at the input data. Any key-value pairing in which
// the fields cannot be known ahead of time is probably a map.
// #[instrument(skip(self, visitor))]
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_map(visitor)
}
// #[instrument(skip(self, visitor))]
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
match &self.deserializing {
Deserializing::StructureTag => Err(TtlvError::custom(
"deserialize_enum. An enum should not be deserialized when deserializing a tag"
.to_string(),
)),
Deserializing::StructureValue => {
let value = &self.get_structure()?[self.index - 1].value;
match value {
TTLValue::Enumeration(_e) => visitor.visit_enum(EnumWalker::new(self)),
// TTLValue::Structure(_s) => visitor.visit_enum(EnumWalker::new(self)),
x => Err(TtlvError::custom(format!(
"deserialize_enum. Invalid type for value: {:?}",
x
))),
}
}
x => Err(TtlvError::custom(format!(
"deserialize_enum. An enum should not be deserialized when deserializing a {:?}",
x
))),
}
}
// An identifier in Serde is the type that identifies a field of a struct or
// the variant of an enum. In TTLV, struct fields and enum variants are
// represented as strings
// #[instrument(skip(self, visitor))]
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match &self.deserializing {
Deserializing::StructureTag => {
//go ahead deserialize the tag
self.deserialize_str(visitor)
}
Deserializing::StructureValue => {
let value = &self.get_structure()?[self.index - 1].value;
match value {
TTLValue::Enumeration(v) => match v {
TTLVEnumeration::Integer(_i) => self.deserialize_i32(visitor),
TTLVEnumeration::Name(_n) => self.deserialize_str(visitor),
},
x => Err(TtlvError::custom(format!(
"deserialize_identifier. Invalid type for value: {:?}",
x
))),
}
}
x => Err(TtlvError::custom(format!(
"deserialize_identifier. An identifier should not be deserialized when \
deserializing a {:?}",
x
))),
}
}
// Like `deserialize_any` but indicates to the `Deserializer` that it makes
// no difference which `Visitor` method is called because the data is
// ignored.
//
// Some deserializers are able to implement this more efficiently than
// `deserialize_any`, for example by rapidly skipping over matched
// delimiters without paying close attention to the data in between.
//
// Some formats are not able to implement this at all. Formats that can
// implement `deserialize_any` and `deserialize_ignored_any` are known as
// self-describing.
// #[instrument(skip(self, visitor))]
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_any(visitor)
}
}
// MapAccess is called when deserializing a struct
// The calls to `next_value` are driven by the visitor
// and it is up to this Access to synchronize and advance its counter
// over the struct fields (`self.index`) in this case
impl<'de, 'a> MapAccess<'de> for TtlvDeserializer<'de> {
type Error = TtlvError;
// #[instrument(skip(self, seed))]
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: DeserializeSeed<'de>,
{
match &self.deserializing {
Deserializing::StructureTag => Err(TtlvError::custom(
"next_key_seed. An next key seed should not be deserialized when deserializing a \
Tag"
.to_string(),
)),
Deserializing::StructureValue => {
// Check if there are no more elements.
self.index += 1;
let children = &self.get_structure()?;
if self.index > children.len() {
return Ok(None)
}
trace!(
"next_key_seed : {:#?}",
&self.get_structure()?[self.index - 1].tag
);
// tell the deserializer that it now going to deserialize the `tag` value
// of the next key
self.deserializing = Deserializing::StructureTag;
// Deserialize a map key.
// This will trigger a call `deserialize_identifier()`
// on the injected Deserializer (`self` in this case)
seed.deserialize(self).map(Some)
}
x => Err(TtlvError::custom(format!(
"next_key_seed. An next key seed should not be deserialized when deserializing a \
{:?}",
x
))),
}
}
// #[instrument(skip(self, seed))]
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
where
V: DeserializeSeed<'de>,
{
match &self.deserializing {
Deserializing::StructureTag => {
// go ahead deserialize the value (index was advanced by a call to
// next_key_seed) ). This will trigger a call to the
// corresponding `deserialize_xxx()` method on the injected
// Deserializer (`self` in this case)
self.deserializing = Deserializing::StructureValue;
seed.deserialize(self)
}
Deserializing::StructureValue => Err(TtlvError::custom(
"next_value_seed. A next value seed should not be deserialized when already \
deserializing a Value"
.to_string(),
)),
x => Err(TtlvError::custom(format!(
"next_value_seed. A next value seed should not be deserialized when deserializing \
a {:?}",
x
))),
}
}
}
// `SeqAccess` is provided to the `Visitor` to give it the ability to iterate
// through elements of the sequence.
impl<'de, 'a> SeqAccess<'de> for TtlvDeserializer<'de> {
type Error = TtlvError;
// #[instrument(skip(self, seed))]
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: DeserializeSeed<'de>,
{
match &self.deserializing {
Deserializing::StructureValue => {
// Check if there are no more elements.
self.index += 1;
let children = &self.get_structure()?;
if self.index > children.len() {
return Ok(None)
}
// tell the deserializer that it is going to deserialize the value
self.deserializing = Deserializing::StructureValue;
// go ahead deserialize it (index was advanced by a call to next_key_seed) ).
// This will trigger a call to the corresponding `deserialize_xxx()`
// method on the injected Deserializer (`self` in this case)
seed.deserialize(self).map(Some)
}
Deserializing::ByteString => {
// Check if there are no more elements.
self.index += 1;
let children = &self.get_bytes()?;
if self.index > children.len() {
return Ok(None)
}
// tell the deserializer that it is going to deserialize the bytes
self.deserializing = Deserializing::ByteString;
// go ahead deserialize it (index was advanced by a call to next_key_seed) ).
// This will trigger a call to the corresponding `deserialize_xxx()`
// method on the injected Deserializer (`self` in this case)
seed.deserialize(self).map(Some)
}
Deserializing::BigInt => {
// Check if there are no more elements.
self.index += 1;
let children = self.get_bigint()?;
if self.index > children.len() {
return Ok(None)
}
// tell the deserializer that it is going to deserialize the bytes
self.deserializing = Deserializing::BigInt;
// go ahead deserialize it (index was advanced by a call to next_key_seed) ).
// This will trigger a call to the corresponding `deserialize_xxx()`
// method on the injected Deserializer (`self` in this case)
seed.deserialize(self).map(Some)
}
x => Err(TtlvError::custom(format!(
"next_element_seed. A next element seed should not be deserialized when \
deserializing a {:?}",
x
))),
}
}
}
struct EnumWalker<'a, 'de: 'a> {
de: &'a mut TtlvDeserializer<'de>,
}
impl<'a, 'de> EnumWalker<'a, 'de> {
// #[instrument(skip(de))]
fn new(de: &'a mut TtlvDeserializer<'de>) -> Self {
EnumWalker { de }
}
}
// `EnumAccess` is provided to the `Visitor` to give it the ability to determine
// which variant of the enum is supposed to be deserialized.
//
// Note that all enum deserialization methods in Serde refer exclusively to the
// "externally tagged" enum representation.
impl<'de, 'a> EnumAccess<'de> for EnumWalker<'a, 'de> {
type Error = TtlvError;
type Variant = Self;
// #[instrument(skip(self, seed))]
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
where
V: DeserializeSeed<'de>,
{
self.de.deserializing = Deserializing::StructureValue;
let val = seed.deserialize(&mut *self.de)?;
Ok((val, self))
}
}
// `VariantAccess` is provided to the `Visitor` to give it the ability to see
// the content of the single variant that it decided to deserialize.
impl<'de, 'a> VariantAccess<'de> for EnumWalker<'a, 'de> {
type Error = TtlvError;
// If the `Visitor` expected this variant to be a unit variant, the input
// should have been the plain string case handled in `deserialize_enum`.
// #[instrument(skip(self))]
fn unit_variant(self) -> Result<()> {
Ok(())
}
/// `variant` is called to identify which variant to deserialize.
// #[instrument(skip(self, seed))]
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: DeserializeSeed<'de>,
{
seed.deserialize(self.de)
}
// Tuple variants are not in KMIP but, if any,
// deserialize as a sequence of data here.
// #[instrument(skip(self, visitor))]
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
de::Deserializer::deserialize_seq(self.de, visitor)
}
// Struct variants are represented in JSON as `{ NAME: { K: V, ... } }` so
// deserialize the inner map here.
// #[instrument(skip(self, visitor))]
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
de::Deserializer::deserialize_map(self.de, visitor)
}
}

View file

@ -0,0 +1,47 @@
use std::fmt::Display;
use serde::{de, ser};
#[derive(Debug)]
pub struct TtlvError {
pub error: String,
}
impl TtlvError {
#[must_use]
pub fn new(s: &str) -> TtlvError {
TtlvError {
error: s.to_owned(),
}
}
}
impl Display for TtlvError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.error)
}
}
impl std::error::Error for TtlvError {}
impl ser::Error for TtlvError {
fn custom<T>(msg: T) -> Self
where
T: Display,
{
TtlvError {
error: format!("{}", msg),
}
}
}
impl de::Error for TtlvError {
fn custom<T>(msg: T) -> Self
where
T: Display,
{
TtlvError {
error: format!("{}", msg),
}
}
}

View file

@ -0,0 +1,461 @@
pub mod deserializer;
pub mod error;
pub mod serializer;
#[cfg(test)]
mod tests;
use core::fmt;
use std::convert::TryInto;
use chrono::{DateTime, TimeZone, Utc};
use num_bigint::BigUint;
use paperclip::actix::Apiv2Schema;
use serde::{
de::{self, MapAccess, Visitor},
ser::{SerializeStruct, Serializer},
Deserialize, Serialize,
};
pub enum ItemTypeEnumeration {
Structure = 0x01,
Integer = 0x02,
LongInteger = 0x03,
BigInteger = 0x04,
Enumeration = 0x05,
Boolean = 0x06,
TextString = 0x07,
ByteString = 0x08,
DateTime = 0x09,
Interval = 0x0A,
DateTimeExtended = 0x0B,
}
#[derive(PartialEq, Debug, Clone)]
pub enum TTLVEnumeration {
Integer(i32),
Name(String),
}
impl Serialize for TTLVEnumeration {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self {
TTLVEnumeration::Integer(i) => serializer.serialize_i32(*i),
TTLVEnumeration::Name(s) => serializer.serialize_str(s),
}
}
}
impl<'de> Deserialize<'de> for TTLVEnumeration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct TTLVEnumerationVisitor;
impl<'de> Visitor<'de> for TTLVEnumerationVisitor {
type Value = TTLVEnumeration;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct TTLVEnumeration")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(TTLVEnumeration::Integer(v.try_into().map_err(|_e| {
de::Error::custom(format!(
"Unexpected value: {}, expected a 32 bit integer",
v
))
})?))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(TTLVEnumeration::Integer(v.try_into().map_err(|_e| {
de::Error::custom(format!(
"Unexpected value: {}, expected a 32 bit integer",
v
))
})?))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(TTLVEnumeration::Name(v.to_owned()))
}
}
deserializer.deserialize_any(TTLVEnumerationVisitor)
}
}
#[derive(Debug, Clone, Apiv2Schema)]
#[openapi(empty)]
pub enum TTLValue {
Structure(Vec<TTLV>),
Integer(i32),
BitMask(u32),
LongInteger(i64),
BigInteger(BigUint),
Enumeration(TTLVEnumeration),
Boolean(bool),
TextString(String),
ByteString(Vec<u8>),
DateTime(DateTime<Utc>),
Interval(u32),
DateTimeExtended(DateTime<Utc>),
}
impl Default for TTLValue {
fn default() -> Self {
TTLValue::TextString(String::default())
}
}
impl PartialEq for TTLValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Structure(l0), Self::Structure(r0)) => l0 == r0,
(Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
(Self::BitMask(l0), Self::BitMask(r0)) => l0 == r0,
(Self::LongInteger(l0), Self::LongInteger(r0)) => l0 == r0,
(Self::BigInteger(l0), Self::BigInteger(r0)) => l0 == r0,
(Self::Enumeration(l0), Self::Enumeration(r0)) => l0 == r0,
(Self::Boolean(l0), Self::Boolean(r0)) => l0 == r0,
(Self::TextString(l0), Self::TextString(r0)) => l0 == r0,
(Self::ByteString(l0), Self::ByteString(r0)) => l0 == r0,
(Self::DateTime(l0), Self::DateTime(r0)) => l0.timestamp() == r0.timestamp(),
(Self::Interval(l0), Self::Interval(r0)) => l0 == r0,
(Self::DateTimeExtended(l0), Self::DateTimeExtended(r0)) => {
l0.timestamp_nanos() / 1000 == r0.timestamp_nanos() / 1000
}
(_, _) => false,
}
}
}
#[derive(PartialEq, Debug, Clone, Apiv2Schema, Default)]
pub struct TTLV {
pub tag: String,
pub value: TTLValue,
}
impl Serialize for TTLV {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
fn _serialize<S, T>(
serializer: S,
tag: &str,
typ: &str,
value: &T,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
let mut ttlv = serializer.serialize_struct("TTLV", 3)?;
ttlv.serialize_field("tag", tag)?;
ttlv.serialize_field("type", typ)?;
ttlv.serialize_field("value", value)?;
ttlv.end()
}
match &self.value {
TTLValue::Structure(v) => _serialize(serializer, &self.tag, "Structure", v),
TTLValue::Integer(v) => _serialize(serializer, &self.tag, "Integer", v),
TTLValue::BitMask(v) => _serialize(
serializer,
&self.tag,
"Integer",
&("0x".to_string() + &hex::encode_upper(v.to_be_bytes())),
),
TTLValue::LongInteger(v) => _serialize(
serializer,
&self.tag,
"LongInteger",
&("0x".to_string() + &hex::encode_upper(v.to_be_bytes())),
),
TTLValue::BigInteger(v) => {
//TODO Note that Big Integers must be sign extended to
//TODO contain a multiple of 8 bytes, and as per LongInteger, JS numbers only
// support a limited range of values.
_serialize(
serializer,
&self.tag,
"BigInteger",
&("0x".to_string() + &hex::encode_upper(v.to_bytes_be())),
)
}
TTLValue::Enumeration(v) => _serialize(serializer, &self.tag, "Enumeration", v),
TTLValue::Boolean(v) => _serialize(serializer, &self.tag, "Boolean", v),
TTLValue::TextString(v) => _serialize(serializer, &self.tag, "TextString", v),
TTLValue::ByteString(v) => {
_serialize(serializer, &self.tag, "ByteString", &hex::encode_upper(v))
}
TTLValue::DateTime(v) => _serialize(
serializer,
&self.tag,
"DateTime",
&format!("{}", v.format("%+")),
),
TTLValue::Interval(v) => _serialize(serializer, &self.tag, "Interval", v),
TTLValue::DateTimeExtended(v) => _serialize(
serializer,
&self.tag,
"DateTimeExtended",
&("0x".to_string()
+ &hex::encode_upper((v.timestamp_nanos() / 1000).to_be_bytes())),
),
}
}
}
/// Used to deserialize the "Integer" type
enum IntegerOrMask {
Integer(i32),
Mask(u32),
}
impl<'de> Deserialize<'de> for IntegerOrMask {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct IntegerOrMaskVisitor;
impl<'de> Visitor<'de> for IntegerOrMaskVisitor {
type Value = IntegerOrMask;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct IntegerOrMask")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(IntegerOrMask::Integer(v.try_into().map_err(|_e| {
de::Error::custom(format!(
"Unexpected value: {}, expected a 32 bit integer",
v
))
})?))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(IntegerOrMask::Integer(v.try_into().map_err(|_e| {
de::Error::custom(format!(
"Unexpected value: {}, expected a 32 bit integer",
v
))
})?))
}
fn visit_str<E>(self, hex: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if &hex[0..2] != "0x" {
return Err(de::Error::custom(format!(
"Invalid value for Mask: {}",
hex
)))
}
let bytes = hex::decode(&hex[2..])
.map_err(|_e| de::Error::custom(format!("Invalid value for Mask: {}", hex)))?;
let m: u32 =
u32::from_be_bytes(bytes.as_slice().try_into().map_err(|_e| {
de::Error::custom(format!("Invalid value for Mask: {}", hex))
})?);
Ok(IntegerOrMask::Mask(m))
}
}
deserializer.deserialize_any(IntegerOrMaskVisitor)
}
}
impl<'de> Deserialize<'de> for TTLV {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// see https://serde.rs/deserialize-struct.html
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Tag,
Type,
Value,
}
struct TTLVVisitor;
impl<'de> Visitor<'de> for TTLVVisitor {
type Value = TTLV;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct TTLV")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut tag: Option<String> = None;
let mut typ: Option<String> = None;
let mut value: Option<TTLValue> = None;
while let Some(key) = map.next_key()? {
match key {
Field::Tag => {
if tag.is_some() {
return Err(de::Error::duplicate_field("tag"))
}
tag = Some(map.next_value()?);
}
Field::Type => {
if typ.is_some() {
return Err(de::Error::duplicate_field("type"))
}
typ = Some(map.next_value()?);
}
Field::Value => {
if value.is_some() {
return Err(de::Error::duplicate_field("value"))
}
let typ = typ.clone().unwrap_or_else(|| "Structure".to_string());
value = Some(match typ.as_str() {
"Structure" => TTLValue::Structure(map.next_value()?),
"Integer" => {
let im: IntegerOrMask = map.next_value()?;
match im {
IntegerOrMask::Integer(v) => TTLValue::Integer(v),
IntegerOrMask::Mask(m) => TTLValue::BitMask(m),
}
}
"LongInteger" => {
let hex: String = map.next_value()?;
if &hex[0..2] != "0x" {
return Err(de::Error::custom(format!(
"Invalid value for i64 hex String: {}",
hex
)))
}
let bytes = hex::decode(&hex[2..]).map_err(|_e| {
de::Error::custom(format!(
"Invalid value for i64 hex String: {}",
hex
))
})?;
let v: i64 = i64::from_be_bytes(
bytes.as_slice().try_into().map_err(|_e| {
de::Error::custom(format!(
"Invalid value for i64 hex String: {}",
hex
))
})?,
);
TTLValue::LongInteger(v)
}
"BigInteger" => {
let hex: String = map.next_value()?;
if &hex[0..2] != "0x" {
return Err(de::Error::custom(format!(
"Invalid value for Mask: {}",
hex
)))
}
let bytes = hex::decode(&hex[2..]).map_err(|_e| {
de::Error::custom(format!(
"Invalid value for Mask: {}",
hex
))
})?;
let v = BigUint::from_bytes_be(bytes.as_slice());
TTLValue::BigInteger(v)
}
"Enumeration" => {
let e: TTLVEnumeration = map.next_value()?;
TTLValue::Enumeration(e)
}
"Boolean" => {
let b = map.next_value()?;
TTLValue::Boolean(b)
}
"TextString" => {
let s = map.next_value()?;
TTLValue::TextString(s)
}
"ByteString" => {
let hex: String = map.next_value()?;
TTLValue::ByteString(hex::decode(&hex).map_err(|_e| {
de::Error::custom(format!(
"Invalid value for a ByteString: {}",
&hex
))
})?)
}
"DateTime" => {
let d: String = map.next_value()?;
let date = DateTime::parse_from_rfc3339(&d).map_err(|_e| {
de::Error::custom(format!(
"Invalid value for an ISO 8601 date: {}",
d
))
})?;
TTLValue::DateTime(date.into())
}
"Interval" => TTLValue::Interval(map.next_value()?),
"DateTimeExtended" => {
let hex: String = map.next_value()?;
if &hex[0..2] != "0x" {
return Err(de::Error::custom(format!(
"Invalid value for i64 hex String: {}",
hex
)))
}
let bytes = hex::decode(&hex[2..]).map_err(|_e| {
de::Error::custom(format!(
"Invalid value for i64 hex String: {}",
hex
))
})?;
let v: i64 = i64::from_be_bytes(
bytes.as_slice().try_into().map_err(|_e| {
de::Error::custom(format!(
"Invalid value for i64 hex String: {}",
hex
))
})?,
);
let dt = Utc.timestamp_nanos(v * 1000);
TTLValue::DateTimeExtended(dt)
}
t => return Err(de::Error::custom(format!("Unknown type: {}", t))),
});
}
}
}
let tag = tag.ok_or_else(|| de::Error::missing_field("tag"))?;
let value = value.ok_or_else(|| de::Error::missing_field("value"))?;
Ok(TTLV { tag, value })
}
}
const FIELDS: &[&str] = &["tag", "value"];
deserializer.deserialize_struct("TTLV", FIELDS, TTLVVisitor)
}
}

View file

@ -0,0 +1,712 @@
use std::convert::TryInto;
use num_bigint::BigUint;
use serde::{
ser::{self, Error, SerializeSeq},
Serialize,
};
use tracing::{debug, log::trace};
use super::{error::TtlvError, TTLVEnumeration, TTLValue, TTLV};
type Result<T> = std::result::Result<T, TtlvError>;
#[derive(Debug)]
pub struct TTLVSerializer {
parents: Vec<TTLV>,
current: TTLV,
}
/// The public API of the TTLV Serde serializer
pub fn to_ttlv<T>(value: &T) -> Result<TTLV>
where
T: Serialize,
{
let mut serializer = TTLVSerializer {
parents: vec![],
current: TTLV::default(),
};
value.serialize(&mut serializer)?;
Ok(serializer.current)
}
impl<'a> ser::Serializer for &'a mut TTLVSerializer {
// The error type when some error occurs during serialization.
type Error = TtlvError;
// The output type produced by this `Serializer` during successful
// serialization. Most serializers that produce text or binary output should
// set `Ok = ()` and serialize into an `io::Write` or buffer contained
// within the `Serializer` instance, as happens here. Serializers that build
// in-memory data structures may be simplified by using `Ok` to propagate
// the data structure around.
type Ok = ();
type SerializeMap = Self;
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps. In this case no
// additional state is required beyond what is already stored in the
// Serializer struct.
type SerializeSeq = Self;
type SerializeStruct = Self;
type SerializeStructVariant = Self;
type SerializeTuple = Self;
type SerializeTupleStruct = Self;
type SerializeTupleVariant = Self;
// Here we go with the simple methods. The following 12 methods receive one
// of the primitive types of the data model and map it to JSON by appending
// into the output string.
//#[instrument(skip(self))]
fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
self.current.value = TTLValue::Boolean(v);
Ok(())
}
// TTLV does not distinguish between integers of size < 32
//#[instrument(skip(self))]
fn serialize_i8(self, v: i8) -> Result<Self::Ok> {
self.serialize_i32(i32::from(v))
}
//#[instrument(skip(self))]
fn serialize_i16(self, v: i16) -> Result<Self::Ok> {
self.serialize_i32(i32::from(v))
}
//#[instrument(skip(self))]
fn serialize_i32(self, v: i32) -> Result<Self::Ok> {
self.current.value = TTLValue::Integer(v);
Ok(())
}
//#[instrument(skip(self))]
fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
self.current.value = TTLValue::LongInteger(v);
Ok(())
}
//#[instrument(skip(self))]
fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
self.serialize_i32(i32::from(v))
}
//#[instrument(skip(self))]
fn serialize_u16(self, v: u16) -> Result<Self::Ok> {
self.serialize_i32(i32::from(v))
}
// assume this is an integer
//#[instrument(skip(self))]
fn serialize_u32(self, v: u32) -> Result<Self::Ok> {
self.serialize_i32(v.try_into().map_err(|_e| {
TtlvError::custom(format!(
"Unexpected value: {}, expected a 32 bit integer",
v
))
})?)
}
// assume this is a long integer
//#[instrument(skip(self))]
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
self.serialize_i64(v.try_into().map_err(|_e| {
TtlvError::custom(format!(
"Unexpected value: {}, expected a 32 bit integer",
v
))
})?)
}
// assume this is an integer
//#[instrument(skip(self))]
fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
self.serialize_i32(v.round() as i32)
}
// assume this is a Long integer
//#[instrument(skip(self))]
fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
self.serialize_i64(v.round() as i64)
}
// Serialize a char as a single-character string. Other formats may
// represent this differently.
//#[instrument(skip(self))]
fn serialize_char(self, _v: char) -> Result<Self::Ok> {
Err(TtlvError::custom(
"'char' type is unsupported in TTLV".to_string(),
))
}
//#[instrument(skip(self))]
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
self.current.value = TTLValue::TextString(v.to_owned());
Ok(())
}
//#[instrument(skip(self))]
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
self.current.value = TTLValue::ByteString(v.to_owned());
Ok(())
}
/// Copied from https://github.com/NLnetLabs/kmip-ttlv/blob/main/src/ser.rs
/// Serializing `None` values, e.g. `Option::`<TypeName>`::None`, is not
/// supported.
///
/// TTLV doesn't support the notion of a serialized value that indicates the
/// absence of a value.
///
/// ### Using Serde to "skip" a missing value
///
/// The correct way to omit None values is to not attempt to serialize them
/// at all, e.g. using the `#[serde(skip_serializing_if =
/// "Option::is_none")]` Serde derive field attribute. Note that at the time
/// of writing it seems that Serde derive only handles this attribute
/// correctly when used on Rust brace struct field members (which we do
/// not support), or on tuple struct fields (i.e. there must be more than
/// one field). Also, note that not serializing a None struct field
/// value will still result in the struct itself being serialized as
/// a TTLV "Structure" unless you also mark the struct as "transparent"
/// (using the rename attribute like so: `[#serde(rename =
/// "Transparent:0xAABBCC"))]`. Using the attribute on `newtype` structs still
/// causes Serde derive to invoke `serialize_none()` which will result
/// in an unsupported error.
///
/// ### Rationale
///
/// As we have already serialized the item tag to the output by the time we
/// process the `Option` value, serializing nothing here would still
/// result in something having been serialized. We could in theory remove
/// the already serialized bytes from the stream but is not necessarily
/// safe, e.g. if the already serialized bytes were a TTLV Structure "
/// header" (i.e. 0xAABBCC 0x00000001 0x00000000) removing the header might
/// be incorrect if there are other structure items that will be
/// serialized to the stream after this "none". Removing the Structure
/// "header" bytes would also break the current logic which at the end
/// of a structure goes back to the start and replaces the zero length
/// value in the TTLV Structure "header" with the actual length as the bytes
/// to replace would no longer exist.
//#[instrument(skip(self))]
fn serialize_none(self) -> Result<Self::Ok> {
Err(TtlvError::custom(
"'Option.None' is unsupported in TTLV".to_string(),
))
}
// A present optional is represented as just the contained value.
//#[instrument(skip(self, value))]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
// to the serializer, a Vec<u8> and a BigUint look
// like seq. We want to intercept that and make sure they
// are serialized for what they are
enum Detected {
Other,
ByteString(Vec<u8>),
BigInt(BigUint),
}
trait Detect {
fn detect(&self) -> Detected;
}
impl<T> Detect for T {
default fn detect(&self) -> Detected {
Detected::Other
}
}
impl Detect for &Vec<u8> {
fn detect(&self) -> Detected {
trace!("handling a byte string");
Detected::ByteString((*self).clone())
}
}
impl Detect for &BigUint {
fn detect(&self) -> Detected {
debug!("serializing a Big Uint {:?}", self);
Detected::BigInt(self.to_owned().clone())
}
}
match value.detect() {
Detected::Other => value.serialize(self),
Detected::ByteString(byte_string) => {
self.current.value = TTLValue::ByteString(byte_string);
Ok(())
}
Detected::BigInt(big_int) => {
self.current.value = TTLValue::BigInteger(big_int);
Ok(())
}
}
}
// The type of () in Rust
//#[instrument(skip(self))]
fn serialize_unit(self) -> Result<Self::Ok> {
Err(TtlvError::custom(
"'value ()' is unsupported in TTLV".to_string(),
))
}
//#[instrument(skip(self))]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
Err(TtlvError::custom(
"'unit struct' is unsupported in TTLV".to_string(),
))
}
// For example the `E::A` and `E::B` in `enum E { A, B }`
//#[instrument(skip(self))]
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok> {
self.current.value = TTLValue::Enumeration(TTLVEnumeration::Name(variant.to_owned()));
Ok(())
}
// For example struct `Millimeters(u8)`
// Detect Interval(here)
//#[instrument(skip(self, value))]
fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
// requires the specification feature
trait Detect {
fn detect_specific_value(&self, name: &'static str) -> Option<TTLValue>;
}
impl<T> Detect for T {
default fn detect_specific_value(&self, _name: &'static str) -> Option<TTLValue> {
None
}
}
impl Detect for u32 {
fn detect_specific_value(&self, name: &'static str) -> Option<TTLValue> {
if name == "Interval" {
Some(TTLValue::Interval(*self))
} else {
None
}
}
}
if let Some(value) = value.detect_specific_value(name) {
self.current.value = value;
Ok(())
} else {
value.serialize(self)
}
}
// For example the E::N in enum E { N(u8) }.
//#[instrument(skip(self, value))]
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
value: &T,
) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
//#[instrument(skip(self))]
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
trace!("serialize_seq. Value: {:?}", &self.current);
// Push the struct on the parents stack, collecting the name
let tag = self.current.tag.clone();
self.parents.push(TTLV {
tag,
value: TTLValue::Structure(vec![]),
});
self.current = TTLV::default();
Ok(self)
}
// Tuples look just like sequences in JSON. Some formats may be able to
// represent tuples more efficiently by omitting the length, since tuple
// means that the corresponding `Deserialize implementation will know the
// length without needing to look at the serialized data.
//#[instrument(skip(self))]
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
// A named tuple, for example struct Rgb(u8, u8, u8)
//#[instrument(skip(self))]
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.serialize_seq(Some(len))
}
// For example the E::T in enum E { T(u8, u8) }.
//#[instrument(skip(self))]
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
Err(TtlvError::custom(
"'tuple variant' is unsupported in TTLV".to_string(),
))
}
// A variably sized heterogeneous key-value pairing,
// for example BTreeMap<K, V>. When serializing,
// the length may or may not be known before iterating through all the entries.
// When deserializing, the length is determined by looking at the serialized
// data.
//#[instrument(skip(self))]
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
Err(TtlvError::custom(
"'map' is unsupported in TTLV".to_string(),
))
}
// A statically sized heterogeneous key-value pairing
// in which the keys are compile-time constant strings
// and will be known at deserialization time
// without looking at the serialized data,
// for example struct S { r: u8, g: u8, b: u8 }.
//#[instrument(skip(self))]
fn serialize_struct(self, name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
// Push the struct on the parents stack, collecting the name
let tag = if self.current.tag.is_empty() {
// top level struct => get its name
name.to_owned()
} else {
self.current.tag.clone()
};
self.parents.push(TTLV {
tag,
value: TTLValue::Structure(vec![]),
});
self.current = TTLV::default();
Ok(self)
}
// For example the E::S in enum E { S { r: u8, g: u8, b: u8 } }
// same as Struct
//#[instrument(skip(self))]
fn serialize_struct_variant(
self,
name: &'static str,
_variant_index: u32,
_variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant> {
self.serialize_struct(name, len)
}
}
// The following 7 impls deal with the serialization of compound types like
// sequences and maps. Serialization of such types is begun by a Serializer
// method and followed by zero or more calls to serialize individual elements of
// the compound type and one call to end the compound type.
// This impl is SerializeSeq so these methods are called after `serialize_seq`
// is called on the Serializer.
impl<'a> ser::SerializeSeq for &'a mut TTLVSerializer {
// Must match the `Error` type of the serializer.
type Error = TtlvError;
// Must match the `Ok` type of the serializer.
type Ok = ();
// Serialize a single element of the sequence.
//#[instrument(skip(self, value))]
fn serialize_element<T>(&mut self, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
trace!(
"Before serialize seq element {:?} #### {:?}",
self.parents,
self.current
);
value.serialize(&mut **self)?;
// recover the parent
let parent: &mut TTLV = self
.parents
.last_mut()
.ok_or_else(|| TtlvError::custom("'no parent for the element !".to_string()))?;
// give the same tag as tag of the parent
self.current.tag = parent.tag.clone();
// update the parent
match &mut parent.value {
TTLValue::Structure(v) => {
v.push(self.current.clone());
self.current = TTLV::default();
}
v => {
return Err(TtlvError::custom(format!(
"'unexpected value for struct: {:?}",
v
)))
}
}
trace!(
"After serialize seq element {:?} #### {:?}",
self.parents,
self.current
);
Ok(())
}
// Close the sequence.
// //#[instrument(skip(self))]
fn end(self) -> Result<Self::Ok> {
//pop the parent
self.current = match self.parents.pop() {
Some(p) => p,
None => {
return Err(TtlvError::custom(
"'unexpected end of seq: no parent ".to_string(),
))
}
};
trace!(
"After serialize seq end {:?} #### {:?}",
self.parents,
self.current
);
Ok(())
}
}
// Same thing but for tuples.
impl<'a> ser::SerializeTuple for &'a mut TTLVSerializer {
type Error = TtlvError;
type Ok = ();
//#[instrument(skip(self, value))]
fn serialize_element<T>(&mut self, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
<&'a mut TTLVSerializer as SerializeSeq>::serialize_element(self, value)
}
fn end(self) -> Result<Self::Ok> {
<&'a mut TTLVSerializer as SerializeSeq>::end(self)
}
}
// Same thing but for tuple structs.
impl<'a> ser::SerializeTupleStruct for &'a mut TTLVSerializer {
type Error = TtlvError;
type Ok = ();
//#[instrument(skip(self, value))]
fn serialize_field<T>(&mut self, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
<&'a mut TTLVSerializer as SerializeSeq>::serialize_element(self, value)
}
fn end(self) -> Result<Self::Ok> {
<&'a mut TTLVSerializer as SerializeSeq>::end(self)
}
}
// For example the E::T in enum E { T(u8, u8) }
impl<'a> ser::SerializeTupleVariant for &'a mut TTLVSerializer {
type Error = TtlvError;
type Ok = ();
//#[instrument(skip(_value))]
fn serialize_field<T>(&mut self, _value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
Err(TtlvError::custom(
"'tuple variant' fields are unsupported in TTLV".to_string(),
))
//value.serialize(&mut **self)
}
// //#[instrument(skip(self))]
fn end(self) -> Result<Self::Ok> {
Err(TtlvError::custom(
"'tuple variant' is unsupported in TTLV".to_string(),
))
}
}
// A variably sized heterogeneous key-value pairing,
// for example BTreeMap<K, V>. When serializing,
// the length may or may not be known before iterating through all the entries.
// When deserializing, the length is determined by looking at the serialized
// data.
impl<'a> ser::SerializeMap for &'a mut TTLVSerializer {
type Error = TtlvError;
type Ok = ();
//#[instrument(skip(self, _key))]
fn serialize_key<T>(&mut self, _key: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
Err(TtlvError::custom(
"'map' keys are unsupported in TTLV".to_string(),
))
}
// It doesn't make a difference whether the colon is printed at the end of
// `serialize_key` or at the beginning of `serialize_value`. In this case
// the code is a bit simpler having it here.
//#[instrument(skip(_value))]
fn serialize_value<T>(&mut self, _value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
Err(TtlvError::custom(
"'map' values are unsupported in TTLV".to_string(),
))
}
// //#[instrument(skip(self))]
fn end(self) -> Result<Self::Ok> {
Ok(())
// Err(TtlvError::custom(
// "'map' is unsupported in TTLV".to_string(),
// ))
}
}
// Structs are like seqs for their elements
impl<'a> ser::SerializeStruct for &'a mut TTLVSerializer {
type Error = TtlvError;
type Ok = ();
//#[instrument(skip(self, value))]
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
enum Detected {
Other,
ByteString(Vec<u8>),
BigInt(BigUint),
}
trait Detect {
fn detect(&self) -> Detected;
}
impl<T> Detect for T {
default fn detect(&self) -> Detected {
trace!("... value has other type {}", std::any::type_name::<T>());
Detected::Other
}
}
impl Detect for &Vec<u8> {
fn detect(&self) -> Detected {
Detected::ByteString((*self).clone())
}
}
impl Detect for &BigUint {
fn detect(&self) -> Detected {
Detected::BigInt(self.to_owned().clone())
}
}
self.current.tag = key.to_owned();
trace!(
"Before serialize field {:?} #### {:?}",
self.parents,
self.current
);
match value.detect() {
Detected::Other => {
trace!("... detected other for {}", &self.current.tag);
value.serialize(&mut **self)?
}
Detected::ByteString(byte_string) => {
trace!("... detected ByteString for {}", &self.current.tag);
self.current.value = TTLValue::ByteString(byte_string);
}
Detected::BigInt(big_int) => {
trace!("... detected BigInteger for {}", &self.current.tag);
self.current.value = TTLValue::BigInteger(big_int);
}
}
let parent: &mut TTLV = match self.parents.last_mut() {
Some(p) => p,
None => return Err(TtlvError::custom("'no parent for the field !".to_string())),
};
match &mut parent.value {
TTLValue::Structure(v) => {
v.push(self.current.clone());
self.current = TTLV::default();
}
v => {
return Err(TtlvError::custom(format!(
"'unexpected value for struct: {:?}",
v
)))
}
};
trace!(
"After serialize field {:?} #### {:?}",
self.parents,
self.current
);
Ok(())
}
// //#[instrument(skip(self))]
fn end(self) -> Result<Self::Ok> {
//pop the parent
self.current = match self.parents.pop() {
Some(p) => p,
None => {
return Err(TtlvError::custom(
"'unexpected end of struct fields: no parent ".to_string(),
))
}
};
trace!(
"After serialize struct fields end {:?} #### {:?}",
self.parents,
self.current
);
Ok(())
}
}
// For example the `E::S` in `enum E { S { r: u8, g: u8, b: u8 } }`
// same as Struct therefore same as seqs
impl<'a> ser::SerializeStructVariant for &'a mut TTLVSerializer {
type Error = TtlvError;
type Ok = ();
//#[instrument(skip(self, value))]
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<Self::Ok>
where
T: ?Sized + Serialize,
{
<&'a mut TTLVSerializer as ser::SerializeStruct>::serialize_field(self, key, value)
}
// //#[instrument(skip(self))]
fn end(self) -> Result<Self::Ok> {
<&'a mut TTLVSerializer as ser::SerializeStruct>::end(self)
}
}

View file

@ -0,0 +1,55 @@
{
"tag": "Attributes",
"value": [
{
"tag": "Link",
"value": [
{
"tag": "Link",
"value": [
{
"tag": "LinkType",
"type": "Enumeration",
"value": "PrivateKeyLink"
},
{
"tag": "LinkedObjectIdentifier",
"type": "TextString",
"value": "9948"
}
]
}
]
},
{
"tag": "ObjectType",
"type": "Enumeration",
"value": "PublicKey"
},
{
"tag": "VendorAttributes",
"value": [
{
"tag": "VendorAttributes",
"value": [
{
"tag": "VendorIdentification",
"type": "TextString",
"value": "cosmian"
},
{
"tag": "AttributeName",
"type": "TextString",
"value": "abe_policy"
},
{
"tag": "AttributeValue",
"type": "ByteString",
"value": "7B226C6173745F617474726962757465223A312C226D61785F617474726962757465223A312C2273746F7265223A7B225365637572697479204C6576656C223A5B5B226C6576656C2031225D2C66616C73655D7D2C226174747269627574655F746F5F696E74223A7B225365637572697479204C6576656C3A3A6C6576656C2031223A5B315D7D7D"
}
]
}
]
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,129 @@
{
"tag": "Import",
"value": [
{
"tag": "UniqueIdentifier",
"type": "TextString",
"value": "9949"
},
{
"tag": "ObjectType",
"type": "Enumeration",
"value": "PublicKey"
},
{
"tag": "ReplaceExisting",
"type": "Boolean",
"value": false
},
{
"tag": "Attributes",
"value": [
{
"tag": "CryptographicAlgorithm",
"type": "Enumeration",
"value": "ABE"
},
{
"tag": "Link",
"value": []
},
{
"tag": "ObjectType",
"type": "Enumeration",
"value": "PublicKey"
}
]
},
{
"tag": "Object",
"value": [
{
"tag": "KeyBlock",
"value": [
{
"tag": "KeyFormatType",
"type": "Enumeration",
"value": "AbeMasterPublicKey"
},
{
"tag": "KeyValue",
"value": [
{
"tag": "KeyMaterial",
"type": "ByteString",
"value": "00000001AD664F98DDFEB4B05D3AAD066B9D0948C974D15ACB6FA135A6CF49B080DF1CBDE4B4C77E08C9150F309BB25976C7CC2A8163028E15DCD6654D741B337EF0317F00495ACD7C2C60E912C99B586EE37DE69E62F92F277A73501CB26A8D023A161B157C6CFA58031762232FF4273AB1739F052553E42D32129DBEEAEE88209452F936997EFDA88E2ED785944391C40FDFEA101D60F56B2962507E921DD422B4681BF5501EDAA9B4C29AFB643C94126202F27DF2430930897A72F10412CE3E5371720C9909A031B1DCA8E77681293EF587546509E501F8E56E5C372C3460A6F3E5C0537613FC5AF3C2B6FE84BEE19D76B5D30DD46853E7291E585DA1068B1011808573F85A4118C01F190BB53A1CA801C1B13095FE52756026E93422CDBBF3D4247A11AC947745FFFE8EF804657E1400DEABCCFD72399AE1A13867C1D1F405583E51DBE8A8EDB150C20FDF52C457AF0442B2"
},
{
"tag": "Attributes",
"value": [
{
"tag": "Link",
"value": [
{
"tag": "Link",
"value": [
{
"tag": "LinkType",
"type": "Enumeration",
"value": "PrivateKeyLink"
},
{
"tag": "LinkedObjectIdentifier",
"type": "TextString",
"value": "9948"
}
]
}
]
},
{
"tag": "ObjectType",
"type": "Enumeration",
"value": "PublicKey"
},
{
"tag": "VendorAttributes",
"value": [
{
"tag": "VendorAttributes",
"value": [
{
"tag": "VendorIdentification",
"type": "TextString",
"value": "cosmian"
},
{
"tag": "AttributeName",
"type": "TextString",
"value": "abe_policy"
},
{
"tag": "AttributeValue",
"type": "ByteString",
"value": "7B226C6173745F617474726962757465223A312C226D61785F617474726962757465223A312C2273746F7265223A7B225365637572697479204C6576656C223A5B5B226C6576656C2031225D2C66616C73655D7D2C226174747269627574655F746F5F696E74223A7B225365637572697479204C6576656C3A3A6C6576656C2031223A5B315D7D7D"
}
]
}
]
}
]
}
]
},
{
"tag": "CryptographicAlgorithm",
"type": "Enumeration",
"value": "ABE"
},
{
"tag": "CryptographicLength",
"type": "Integer",
"value": 340
}
]
}
]
}
]
}

View file

@ -0,0 +1,661 @@
use std::time::SystemTime;
use chrono::{DateTime, Utc};
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
use crate::{
kmip::{
kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue},
kmip_objects::{Object, ObjectType},
kmip_operations::{Import, ImportResponse},
kmip_types::{Attributes, CryptographicAlgorithm, KeyFormatType},
ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLVEnumeration, TTLValue, TTLV},
},
log_utils::log_init,
};
pub(crate) fn aes_key_material(key_value: &[u8]) -> KeyMaterial {
KeyMaterial::TransparentSymmetricKey {
key: key_value.to_vec(),
}
}
pub(crate) fn aes_key_value(key_value: &[u8]) -> KeyValue {
KeyValue::PlainText {
key_material: aes_key_material(key_value),
attributes: Some(Attributes::new(ObjectType::SymmetricKey)),
}
}
pub(crate) fn aes_key_block(key_value: &[u8]) -> KeyBlock {
KeyBlock {
key_format_type: KeyFormatType::TransparentSymmetricKey,
key_compression_type: None,
key_value: aes_key_value(key_value),
cryptographic_algorithm: CryptographicAlgorithm::AES,
cryptographic_length: 256,
key_wrapping_data: None,
}
}
pub(crate) fn aes_key(key_value: &[u8]) -> Object {
Object::SymmetricKey {
key_block: aes_key_block(key_value),
}
}
pub(crate) fn aes_key_material_ttlv(key_value: &[u8]) -> TTLV {
TTLV {
tag: "KeyMaterial".to_string(),
value: TTLValue::Structure(vec![TTLV {
tag: "Key".to_string(),
value: TTLValue::ByteString(key_value.to_vec()),
}]),
}
}
pub(crate) fn aes_key_value_ttlv(key_value: &[u8]) -> TTLV {
TTLV {
tag: "KeyValue".to_string(),
value: TTLValue::Structure(vec![
aes_key_material_ttlv(key_value),
TTLV {
tag: "Attributes".to_string(),
value: TTLValue::Structure(vec![
TTLV {
tag: "Link".to_string(),
value: TTLValue::Structure(vec![]),
},
TTLV {
tag: "ObjectType".to_string(),
value: TTLValue::Enumeration(TTLVEnumeration::Name(
"SymmetricKey".to_string(),
)),
},
]),
},
]),
}
}
pub(crate) fn aes_key_block_ttlv(key_value: &[u8]) -> TTLV {
TTLV {
tag: "KeyBlock".to_string(),
value: TTLValue::Structure(vec![
TTLV {
tag: "KeyFormatType".to_string(),
value: TTLValue::Enumeration(TTLVEnumeration::Name(
"TransparentSymmetricKey".to_string(),
)),
},
aes_key_value_ttlv(key_value),
TTLV {
tag: "CryptographicAlgorithm".to_string(),
value: TTLValue::Enumeration(TTLVEnumeration::Name("AES".to_string())),
},
TTLV {
tag: "CryptographicLength".to_string(),
value: TTLValue::Integer(256),
},
]),
}
}
pub(crate) fn aes_key_ttlv(key_value: &[u8]) -> TTLV {
TTLV {
tag: "Object".to_string(),
value: TTLValue::Structure(vec![aes_key_block_ttlv(key_value)]),
}
}
#[test]
fn test_enumeration() {
let es = TTLVEnumeration::Name("blah".to_string());
let s = serde_json::to_string_pretty(&es).unwrap();
assert_eq!(es, serde_json::from_str(&s).unwrap());
let i_plus = TTLVEnumeration::Integer(1);
let s = serde_json::to_string_pretty(&i_plus).unwrap();
assert_eq!(i_plus, serde_json::from_str(&s).unwrap());
let i_minus = TTLVEnumeration::Integer(-1);
let s = serde_json::to_string_pretty(&i_minus).unwrap();
assert_eq!(i_minus, serde_json::from_str(&s).unwrap());
}
#[test]
fn test_serialization_deserialization() {
let now: DateTime<Utc> = SystemTime::now().into();
let ttlv = TTLV {
tag: "Test".to_string(),
value: TTLValue::Structure(vec![
TTLV {
tag: "AnInt".to_string(),
value: TTLValue::Integer(42),
},
TTLV {
tag: "ABitMask".to_string(),
value: TTLValue::BitMask(42),
},
TTLV {
tag: "ALongInt".to_string(),
value: TTLValue::LongInteger(-42_i64),
},
TTLV {
tag: "ABigInteger".to_string(),
value: TTLValue::BigInteger(BigUint::from(2_487_678_887_987_987_798_676_u128)),
},
TTLV {
tag: "AnEnumeration_1".to_string(),
value: TTLValue::Enumeration(TTLVEnumeration::Integer(54)),
},
TTLV {
tag: "AnEnumeration_2".to_string(),
value: TTLValue::Enumeration(TTLVEnumeration::Name("blah".to_string())),
},
TTLV {
tag: "ABoolean".to_string(),
value: TTLValue::Boolean(true),
},
TTLV {
tag: "ATextString".to_string(),
value: TTLValue::TextString("blah".to_string()),
},
TTLV {
tag: "AnByteString".to_string(),
value: TTLValue::ByteString(b"hello".to_vec()),
},
TTLV {
tag: "ADateTime".to_string(),
value: TTLValue::DateTime(now),
},
TTLV {
tag: "AnInterval".to_string(),
value: TTLValue::Interval(27),
},
TTLV {
tag: "ADateTimeExtended".to_string(),
value: TTLValue::DateTimeExtended(now),
},
]),
};
let j = serde_json::to_string_pretty(&ttlv).unwrap();
let rec: TTLV = serde_json::from_str(&j).unwrap();
match rec.value {
TTLValue::Structure(s) => match &s[0].value {
TTLValue::Integer(i) => assert_eq!(42, *i),
x => panic!("unexpected 2nd level type : {:?}", x),
},
x => panic!("unexpected type : {:?}", x),
};
}
#[test]
fn test_ser_int() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct Test {
an_int: u32,
}
let test = Test {
an_int: 1,
// seq: vec!["a", "b"],
};
let ttlv = to_ttlv(&test).unwrap();
let expected =
r#"TTLV { tag: "Test", value: Structure([TTLV { tag: "AnInt", value: Integer(1) }]) }"#;
let ttlv_s = format!("{:?}", ttlv);
assert_eq!(ttlv_s, expected);
}
#[test]
fn test_ser_array() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct Test {
seq: Vec<&'static str>,
}
let test = Test {
seq: vec!["a", "b"],
};
let ttlv = to_ttlv(&test).unwrap();
let expected = r#"TTLV { tag: "Test", value: Structure([TTLV { tag: "Seq", value: Structure([TTLV { tag: "Seq", value: TextString("a") }, TTLV { tag: "Seq", value: TextString("b") }]) }]) }"#;
let ttlv_s = format!("{:?}", ttlv);
// println!("{}", serde_json::to_string_pretty(&ttlv).unwrap());
assert_eq!(ttlv_s, expected);
}
#[test]
fn test_ser_big_int() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
struct Test {
big_int: BigUint,
}
let test = Test {
big_int: BigUint::from(0x1111_1111_1222_2222_u128),
};
let ttlv = to_ttlv(&test).unwrap();
// println!("{:#?}", ttlv);
let expected = r#"TTLV {
tag: "Test",
value: Structure(
[
TTLV {
tag: "BigInt",
value: BigInteger(
1229782938265199138,
),
},
],
),
}"#;
let ttlv_s = format!("{:#?}", ttlv);
// println!("{}", serde_json::to_string_pretty(&ttlv).unwrap());
assert_eq!(ttlv_s, expected);
}
#[test]
fn test_ser_aes_key() {
log_init("debug,hyper=info,reqwest=info");
let key_bytes: &[u8] = b"this_is_a_test";
let aes_key = aes_key(key_bytes);
let ttlv = to_ttlv(&aes_key).unwrap();
assert_eq!(aes_key_ttlv(key_bytes), ttlv);
}
#[test]
fn test_des_int() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(rename_all = "PascalCase")]
struct Test {
an_int: i32,
another_int: u32,
}
let ttlv = TTLV {
tag: "Test".to_string(),
value: TTLValue::Structure(vec![
TTLV {
tag: "AnInt".to_string(),
value: TTLValue::Integer(2),
},
TTLV {
tag: "AnotherInt".to_string(),
value: TTLValue::BitMask(4),
},
]),
};
let rec: Test = from_ttlv(&ttlv).unwrap();
assert_eq!(
&Test {
an_int: 2,
another_int: 4
},
&rec
)
}
#[test]
fn test_des_array() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(rename_all = "PascalCase")]
struct Test {
ints: Vec<i32>,
}
let ttlv = TTLV {
tag: "Test".to_string(),
value: TTLValue::Structure(vec![TTLV {
tag: "Ints".to_string(),
value: TTLValue::Structure(vec![
TTLV {
tag: "Ints".to_string(),
value: TTLValue::Integer(2),
},
TTLV {
tag: "Ints".to_string(),
value: TTLValue::Integer(4),
},
]),
}]),
};
let rec: Test = from_ttlv(&ttlv).unwrap();
assert_eq!(&Test { ints: vec![2, 4] }, &rec)
}
#[test]
fn test_des_enum() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(rename_all = "PascalCase")]
struct Test {
crypto_algo: CryptographicAlgorithm,
}
let ttlv = TTLV {
tag: "Test".to_string(),
value: TTLValue::Structure(vec![TTLV {
tag: "CryptoAlgo".to_string(),
value: TTLValue::Enumeration(TTLVEnumeration::Name("AES".to_string())),
}]),
};
let rec: Test = from_ttlv(&ttlv).unwrap();
assert_eq!(
&Test {
crypto_algo: CryptographicAlgorithm::AES
},
&rec
)
}
#[test]
fn test_key_material_vec_deserialization() {
log_init("debug,hyper=info,reqwest=info");
let bytes = vec![
116, 104, 105, 115, 95, 105, 115, 95, 97, 95, 116, 101, 115, 116,
];
let ttlv = TTLV {
tag: "KeyMaterial".to_string(),
value: TTLValue::Structure(vec![TTLV {
tag: "Key".to_string(),
value: TTLValue::ByteString(bytes.clone()),
}]),
};
let km_: KeyMaterial = from_ttlv(&ttlv).unwrap();
let km = KeyMaterial::TransparentSymmetricKey { key: bytes };
assert_eq!(km, km_);
}
#[test]
fn test_key_material_big_int_deserialization() {
log_init("debug,hyper=info,reqwest=info");
let ttlv = TTLV {
tag: "KeyMaterial".to_string(),
value: TTLValue::Structure(vec![
TTLV {
tag: "P".to_string(),
value: TTLValue::BigInteger(BigUint::from(u32::MAX)),
},
TTLV {
tag: "Q".to_string(),
value: TTLValue::BigInteger(BigUint::from(1_u32)),
},
TTLV {
tag: "G".to_string(),
value: TTLValue::BigInteger(BigUint::from(2_u32)),
},
TTLV {
tag: "X".to_string(),
value: TTLValue::BigInteger(BigUint::from(u128::MAX)),
},
]),
};
let km = KeyMaterial::TransparentDHPrivateKey {
p: BigUint::from(u32::MAX),
q: Some(BigUint::from(1_u64)),
g: BigUint::from(2_u32),
j: None,
x: BigUint::from(u128::MAX),
};
let ttlv_ = to_ttlv(&km).unwrap();
assert_eq!(ttlv, ttlv_);
let km_: KeyMaterial = from_ttlv(&ttlv_).unwrap();
assert_eq!(km, km_);
}
#[test]
fn test_big_int_deserialization() {
let km = KeyMaterial::TransparentDHPrivateKey {
p: BigUint::from(u32::MAX),
q: Some(BigUint::from(1_u64)),
g: BigUint::from(2_u32),
j: None,
x: BigUint::from(u128::MAX - 1),
};
let j = serde_json::to_value(&km).unwrap();
let km_: KeyMaterial = serde_json::from_value(j).unwrap();
assert_eq!(km, km_);
}
#[test]
fn test_des_aes_key() {
log_init("debug,hyper=info,reqwest=info");
let key_bytes: &[u8] = b"this_is_a_test";
//
let json = serde_json::to_value(aes_key(key_bytes)).unwrap();
let o: Object = serde_json::from_value(json).unwrap();
// Deserialization cannot make the difference between a SymmetricKey or a
// PrivateKey
assert_eq!(
aes_key(key_bytes),
Object::post_fix(ObjectType::SymmetricKey, o)
);
//
let ttlv = aes_key_ttlv(key_bytes);
let rec: Object = from_ttlv(&ttlv).unwrap();
// Deserialization cannot make the difference between a SymmetricKey or a
// PrivateKey
assert_eq!(
aes_key(key_bytes),
Object::post_fix(ObjectType::SymmetricKey, rec)
)
}
#[test]
fn test_aes_key_block() {
log_init("debug,hyper=info,reqwest=info");
let key_bytes: &[u8] = b"this_is_a_test";
//
let json = serde_json::to_value(aes_key_block(key_bytes)).unwrap();
let kv: KeyBlock = serde_json::from_value(json).unwrap();
assert_eq!(aes_key_block(key_bytes), kv);
//
let ttlv = aes_key_block_ttlv(key_bytes);
let rec: KeyBlock = from_ttlv(&ttlv).unwrap();
assert_eq!(aes_key_block(key_bytes), rec)
}
#[test]
fn test_aes_key_value() {
log_init("debug,hyper=info,reqwest=info");
let key_bytes: &[u8] = b"this_is_a_test";
//
let json = serde_json::to_value(aes_key_value(key_bytes)).unwrap();
let kv: KeyValue = serde_json::from_value(json).unwrap();
assert_eq!(aes_key_value(key_bytes), kv);
//
let ttlv = aes_key_value_ttlv(key_bytes);
let rec: KeyValue = from_ttlv(&ttlv).unwrap();
assert_eq!(aes_key_value(key_bytes), rec);
}
#[test]
fn test_aes_key_material() {
log_init("debug,hyper=info,reqwest=info");
let key_bytes: &[u8] = b"this_is_a_test";
let ttlv = aes_key_material_ttlv(key_bytes);
let rec: KeyMaterial = from_ttlv(&ttlv).unwrap();
assert_eq!(aes_key_material(key_bytes), rec)
}
#[test]
#[allow(clippy::large_enum_variant)]
fn test_some_attributes() {
log_init("debug,hyper=info,reqwest=info");
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(untagged)]
enum Wrapper {
Attr {
#[serde(skip_serializing_if = "Option::is_none", rename = "Attributes")]
attributes: Option<Attributes>,
},
NoAttr {
whatever: i32,
},
}
let value = Wrapper::Attr {
attributes: Some(Attributes::new(ObjectType::SymmetricKey)),
};
let ttlv = to_ttlv(&value).unwrap();
// println!("TTLV: {:#?}", &ttlv);
let json = serde_json::to_value(&ttlv).unwrap();
// println!("JSON: {:#?}", &json);
let ttlv_: TTLV = serde_json::from_value(json).unwrap();
assert_eq!(ttlv, ttlv_);
let rec: Wrapper = from_ttlv(&ttlv_).unwrap();
// println!("REC: {:#?}", &rec);
assert_eq!(value, rec);
}
#[test]
fn test_java_import_request() {
log_init("debug,hyper=info,reqwest=info");
let ir_java = r#"
{
"tag" : "Import",
"value" : [ {
"tag" : "UniqueIdentifier",
"type" : "TextString",
"value" : "unique_identifier"
}, {
"tag" : "ObjectType",
"type" : "Enumeration",
"value" : "SymmetricKey"
}, {
"tag" : "ReplaceExisting",
"type" : "Boolean",
"value" : true
}, {
"tag" : "KeyWrapType",
"type" : "Enumeration",
"value" : "AsRegistered"
}, {
"tag" : "Attributes",
"value" : [ {
"tag" : "Link",
"value" : [ ]
}, {
"tag" : "ObjectType",
"type" : "Enumeration",
"value" : "OpaqueObject"
} ]
}, {
"tag" : "Object",
"value" : [ {
"tag" : "KeyBlock",
"value" : [ {
"tag" : "KeyFormatType",
"type" : "Enumeration",
"value" : "TransparentSymmetricKey"
}, {
"tag" : "KeyValue",
"value" : [ {
"tag" : "KeyMaterial",
"value" : [ {
"tag" : "Key",
"type" : "ByteString",
"value" : "6279746573"
} ]
} ]
}, {
"tag" : "CryptographicAlgorithm",
"type" : "Enumeration",
"value" : "AES"
}, {
"tag" : "CryptographicLength",
"type" : "Integer",
"value" : 256
} ]
} ]
} ]
}
"#;
let ttlv: TTLV = serde_json::from_str(ir_java).unwrap();
let _import_request: Import = from_ttlv(&ttlv).unwrap();
}
#[test]
pub fn test_java_import_request_2() {
log_init("info");
let json = include_str!("./import_abe_public_key_java.json");
let ttlv: TTLV = serde_json::from_str(json).unwrap();
let _import_request: Import = from_ttlv(&ttlv).unwrap();
}
#[test]
fn test_java_import_response() {
log_init("info");
let ir = ImportResponse {
unique_identifier: "blah".to_string(),
};
let json = serde_json::to_string(&to_ttlv(&ir).unwrap()).unwrap();
let ir_ = from_ttlv(&serde_json::from_str::<TTLV>(&json).unwrap()).unwrap();
assert_eq!(ir, ir_);
}
#[test]
fn test_byte_string_key_material() {
log_init("info");
let key_bytes: &[u8] = b"this_is_a_test";
let key_value = KeyValue::PlainText {
key_material: KeyMaterial::ByteString(key_bytes.to_vec()),
attributes: Some(Attributes::new(ObjectType::SymmetricKey)),
};
let ttlv = to_ttlv(&key_value).unwrap();
// println!("{:#?}", &ttlv);
let key_value_: KeyValue = from_ttlv(&ttlv).unwrap();
assert_eq!(key_value, key_value_);
}
#[test]
fn test_aes_key_full() {
log_init("info");
let key_bytes: &[u8] = b"this_is_a_test";
let aes_key = aes_key(key_bytes);
let ttlv = to_ttlv(&aes_key).unwrap();
// println!("{:#?}", &ttlv);
let aes_key_: Object = from_ttlv(&ttlv).unwrap();
assert_eq!(
aes_key,
Object::post_fix(ObjectType::SymmetricKey, aes_key_)
)
}
#[test]
pub fn test_attributes_with_links() {
log_init("info");
let json = include_str!("./attributes_with_links.json");
let ttlv: TTLV = serde_json::from_str(json).unwrap();
let _attributes: Attributes = from_ttlv(&ttlv).unwrap();
}
#[test]
pub fn test_import_correct_object() {
log_init("debug,hyper=info,reqwest=info");
let json = include_str!("./import.json");
let ttlv: TTLV = serde_json::from_str(json).unwrap();
let import: Import = from_ttlv(&ttlv).unwrap();
assert_eq!(ObjectType::PublicKey, import.object_type);
assert_eq!(ObjectType::PublicKey, import.object.object_type());
assert_eq!(
CryptographicAlgorithm::ABE,
import.object.key_block().unwrap().cryptographic_algorithm
);
}

7
kms_common/src/lib.rs Normal file
View file

@ -0,0 +1,7 @@
#![allow(clippy::upper_case_acronyms)]
//required to detect generic type in Serializer
#![feature(min_specialization)]
pub mod error;
pub mod kmip;
pub mod log_utils;

View file

@ -0,0 +1,40 @@
use std::sync::Once;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
static LOG_INIT: Once = Once::new();
/// # Panics
///
/// Will panic if we cannot set global tracing subscriber
pub fn log_init(paths: &str) {
LOG_INIT.call_once(|| {
if let Ok(old) = std::env::var("RUST_LOG") {
std::env::set_var("RUST_LOG", format!("{},{}", old, paths));
} else {
std::env::set_var("RUST_LOG", paths);
}
tracing_setup();
});
}
/// # Panics
///
/// Will panic if we cannot set global subscriber
fn tracing_setup() {
let layer = tracing_tree::HierarchicalLayer::default()
.with_verbose_exit(true)
.with_verbose_entry(true)
.with_targets(true)
.with_thread_names(true)
.with_thread_ids(true)
.with_indent_lines(true);
let (filter, _reload_handle) =
tracing_subscriber::reload::Layer::new(EnvFilter::from_default_env());
let subscriber = tracing_subscriber::Registry::default()
.with(filter)
.with(layer);
tracing::subscriber::set_global_default(subscriber).unwrap();
tracing_log::LogTracer::init().unwrap();
}

2
kms_server/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target/
.cargo_check/

33
kms_server/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,33 @@
{
"cSpell.words": [
"actix",
"canonicalize",
"ciphertext",
"cosmian",
"dmcfe",
"gpsw",
"initialised",
"initialising",
"initialised",
"initialising",
"JWKS",
"keypair",
"Kmip",
"Mcfe",
"Mersenne",
"pgsql",
"sqlx",
"PKCS",
"pgsql",
"sqlx",
"structopt",
"TFHE",
"TTLV",
"upsert",
"Upserted",
"uids",
"upsert",
"Upserted",
"Upserting"
]
}

3268
kms_server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

60
kms_server/Cargo.toml Normal file
View file

@ -0,0 +1,60 @@
[package]
authors = ["Bruno Grieder <bruno.grieder@cosmian.com>"]
edition = "2021"
name = "cosmian_kms_server"
version = "2.0.0"
[features]
dev = []
[dependencies]
abe_gpsw = { version = "0.6.3", features = ["interfaces"] }
# for development
# abe_gpsw = { path = "../../../../abe_gpsw", features = ["interfaces"] }
actix-identity = "0.4"
actix-service = "2.0"
actix-web = { version = "4.0", features = ["openssl"] }
alcoholic_jwt = "1.0"
async-trait = "0.1"
bitflags = "1.2"
clap = { version = "3.1", features = ["derive"] }
chrono = "0.4"
cosmian_crypto_base = { version = "0.5.2", features = ["libsodium"] }
cosmian_kms_common = { path = "../kms_common" }
cosmian_mcfe = { git = "https://github.com/Cosmian/DMCFE.git", branch = "feature/add_mcfe" }
cosmian_rust_lib = { path = "../rust_lib" }
env_logger = "0.9"
# for error conversions only
eyre = "0.6"
futures = "0.3"
hex = { version = "0.4", features = ["serde"] }
# for status codes
http = "0.2"
num-bigint = "0.4"
once_cell = "1.9"
rand_core = "0.6"
rand_hc = "0.3"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha3 = "0.10"
sqlx = { version = "0.5", features = [
"json",
"runtime-actix-native-tls",
"mysql",
"postgres",
"sqlite"
] }
strum = "0.23"
strum_macros = "0.23"
thiserror = "1.0.20"
torus_fhe = { git = "http://gitlab.cosmian.com/other/tfhe.git" }
tracing = "0.1"
twelf = "0.2"
uuid = { version = "0.8", features = ["v4"] }
[dev-dependencies]
actix-rt = "2.6"
actix-http = "3.0"
tempfile = "3.1"
serial_test = "0.6"

43
kms_server/Postgresql.md Normal file
View file

@ -0,0 +1,43 @@
# Postgresql install and set-up
## Ubuntu
Main instructions: [here](https://www.postgresql.org/download/linux/ubuntu/)
```bash
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get -y install postgresql
```
## Create user `kms` and database `kms`
1. Connect to psql under user `postgres`
```
sudo -u postgres psql
```
2. Create user `kms` with password `kms`
```
create user kms with encrypted password 'kms';
```
The password can obviously be set to any other appropriate value
3. Create database `kms` under owner `kms`
```
create database kms owner=kms;
```
4. Connection `POSTGRES_URL`
Assuming a server running on localhost, the connection URL will be
```
POSTGRES_URL=CONNECTION=postgresql://kms:kms@127.0.0.1:5432:kms
```

33
kms_server/README.md Normal file
View file

@ -0,0 +1,33 @@
# KMS Server
## Start with Authentication
Example of how to run for test authentication
```sh
$ KMS_DELEGATED_AUTHORITY_DOMAIN="dev-1mbsbmin.us.auth0.com" cargo run
```
## Running with PostgreSQL
```sh
KMS_POSTGRES_URL=postgresql://kms:kms@127.0.0.1:5432/kms cargo run
```
## Running with MySQL/MariaDB
```sh
KMS_MYSQL_URL=mysql://root:kms@localhost/kms cargo run
```
## Tests
`cargo make` setups PostgreSQL and MariaDB automatically to perform tests.
```console
cargo install cargo-make
cargo make rust-tests
```
## Dev
For developping you can use `--features=dev` to tell the server to not verify the expiration of tokens.

64
kms_server/src/auth.rs Normal file
View file

@ -0,0 +1,64 @@
use alcoholic_jwt::token_kid;
use serde::{Deserialize, Serialize};
use crate::{
config,
error::KmsError,
kms_ensure,
result::{KResult, KResultHelper},
};
#[derive(Debug, Deserialize, Serialize)]
pub struct UserClaim {
pub email: Option<String>,
pub iss: String,
pub sub: String,
pub aud: String,
pub iat: usize,
pub exp: usize,
}
/// Decode a json web token (JWT)
pub(crate) fn decode_jwt_new(authorization_content: &str) -> KResult<UserClaim> {
let bearer: Vec<&str> = authorization_content.splitn(2, ' ').collect();
kms_ensure!(
bearer.len() == 2 && bearer[0] == "Bearer",
"Bad authorization header content (bad bearer)"
);
let token: &str = bearer[1];
kms_ensure!(!token.is_empty(), "token is empty");
tracing::trace!("token {}", &token);
let authority = config::delegated_authority_domain()
.context("no authority: there should be no request to decode a JWT token")?;
let jwks =
config::jwks().context("no authority: there should be no request to decode a JWT token")?;
let validations = vec![
alcoholic_jwt::Validation::Issuer(format!("https://{}/", authority)),
alcoholic_jwt::Validation::SubjectPresent,
#[cfg(all(not(test), not(feature = "dev")))]
alcoholic_jwt::Validation::NotExpired,
/* Validate Audience would imply to keep track of all existing audiences.
* It could be done via Auth0-API-call: https://manage.auth0.com/dashboard/us/dev-1mbsbmin/apis/management/explorer
* using `/api/v2/clients`. Then add to this vector: `Validation::Audience(audience)` */
];
// If a JWKS contains multiple keys, the correct KID first
// needs to be fetched from the token headers.
let kid = token_kid(token)
.map_err(|_| KmsError::Unauthorized("Failed to decode token headers".to_string()))?
.ok_or_else(|| KmsError::Unauthorized("No 'kid' claim present in token".to_string()))?;
let jwk = jwks
.find(&kid)
.ok_or_else(|| KmsError::Unauthorized("Specified key not found in set".to_string()))?;
let valid_jwt = alcoholic_jwt::validate(token, jwk, validations)
.map_err(|err| KmsError::Unauthorized(format!("Cannot validate token: {:?}", err)))?;
let payload = serde_json::from_value(valid_jwt.claims)?;
Ok(payload)
}

181
kms_server/src/config.rs Normal file
View file

@ -0,0 +1,181 @@
use std::path::{Path, PathBuf};
use alcoholic_jwt::JWKS;
use once_cell::sync::OnceCell;
use tracing::{debug, info};
use crate::{kms_bail, kms_error, result::KResult};
static INSTANCE_CONFIG: OnceCell<SharedConfig> = OnceCell::new();
#[twelf::config]
#[derive(Debug)]
pub struct Config {
/// Delegated authority domain coming from auth0
pub delegated_authority_domain: Option<String>,
#[serde(default = "default_postgres_url")]
pub postgres_url: String,
#[serde(default = "default_mysql_url")]
pub mysql_url: String,
#[serde(default = "default_port")]
pub port: u16,
#[serde(default = "default_root_dir")]
pub root_dir: String,
#[serde(default = "default_hostname")]
pub hostname: String,
}
/// A default implementation to use in tests
#[cfg(test)]
impl Default for Config {
fn default() -> Self {
Config {
delegated_authority_domain: std::option_env!("KMS_DELEGATED_AUTHORITY_DOMAIN")
.map(|v| v.to_string()),
postgres_url: "".to_string(),
mysql_url: "".to_string(),
port: 9998,
root_dir: "/tmp".to_string(),
hostname: "0.0.0.0".to_string(),
}
}
}
fn default_postgres_url() -> String {
String::from("")
}
fn default_mysql_url() -> String {
String::from("")
}
fn default_port() -> u16 {
9998_u16
}
fn default_root_dir() -> String {
String::from("/tmp")
}
fn default_hostname() -> String {
String::from("0.0.0.0")
}
#[derive(Clone, Debug)]
pub enum DbParams {
// contains the path to the db file
Sqlite(PathBuf),
// contain the postgres connection URL
Postgres(String),
// contain the mysql connection URL
Mysql(String),
}
#[derive(Clone, Debug)]
pub struct SharedConfig {
pub delegated_authority_domain: Option<String>,
pub jwks: Option<JWKS>,
pub db_params: DbParams,
pub hostname: String,
pub port: u16,
}
pub(crate) fn init(conf: SharedConfig) {
let _ = INSTANCE_CONFIG.set(conf);
}
#[inline(always)]
pub(crate) fn delegated_authority_domain() -> Option<String> {
INSTANCE_CONFIG
.get()
.expect("config must be initialised")
.delegated_authority_domain
.clone()
}
#[inline(always)]
pub(crate) fn jwks() -> Option<JWKS> {
INSTANCE_CONFIG
.get()
.expect("config must be initialised")
.jwks
.clone()
}
#[inline(always)]
pub(crate) fn db_params() -> DbParams {
INSTANCE_CONFIG
.get()
.expect("config must be initialised")
.db_params
.clone()
}
#[inline(always)]
pub(crate) fn hostname() -> String {
INSTANCE_CONFIG
.get()
.expect("config must be initialised")
.hostname
.clone()
}
#[inline(always)]
pub(crate) fn port() -> u16 {
INSTANCE_CONFIG
.get()
.expect("config must be initialised")
.port
}
pub async fn init_config(conf: &Config) -> KResult<()> {
let delegated_authority_domain: Option<String> = conf
.delegated_authority_domain
.to_owned()
.map(|d| d.trim_end_matches('/').to_string());
let jwks = if let Some(dad) = &delegated_authority_domain {
let jwks_uri = format!("https://{dad}/.well-known/jwks.json");
Some(
reqwest::get(jwks_uri)
.await
.map_err(|e| kms_error!("Unable to connect to retrieve JWKS: {:?}", e))?
.json::<JWKS>()
.await
.map_err(|e| kms_error!("Unable to get JWKS as a JSON: {:?}", e))?,
)
} else {
None
};
if !conf.postgres_url.is_empty() && !conf.mysql_url.is_empty() {
kms_bail!("Postgres and MariaDB/MySQL URL are both set, can't decide which one to use");
}
let db_params = if !conf.postgres_url.is_empty() {
DbParams::Postgres(conf.postgres_url.to_owned())
} else if !conf.mysql_url.is_empty() {
DbParams::Mysql(conf.mysql_url.to_owned())
} else {
DbParams::Sqlite(Path::new(&conf.root_dir).canonicalize()?.join("kms.db"))
};
let shared_conf = SharedConfig {
jwks,
delegated_authority_domain,
db_params,
hostname: conf.hostname.to_owned(),
port: conf.port,
};
debug!("shared conf: {shared_conf:#?}");
init(shared_conf);
info!("initialising with configuration: {conf:#?}");
Ok(())
}

174
kms_server/src/error.rs Normal file
View file

@ -0,0 +1,174 @@
use abe_gpsw::error::FormatErr;
use cosmian_kms_common::{
error::KmsCommonError,
kmip::{kmip_operations::ErrorReason, ttlv::error::TtlvError},
};
use cosmian_rust_lib::error::LibError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum KmsError {
#[error("Invalid KMIP value: {0}: {1}")]
InvalidKmipValue(ErrorReason, String),
#[error("Invalid KMIP Object: {0}: {1}")]
InvalidKmipObject(ErrorReason, String),
#[error("Kmip Not Supported: {0}: {1}")]
KmipNotSupported(ErrorReason, String),
#[error("Not Supported: {0}")]
NotSupported(String),
#[error("{0}: {1}")]
KmipError(ErrorReason, String),
#[error("Unexpected Error: {0}")]
UnexpectedError(String),
#[error("Cryptographic Error: {0}: {1}")]
CryptographicError(String, String),
#[error("Database Error: {0}")]
DatabaseError(String),
#[error("Invalid Request: {0}")]
InvalidRequest(String),
#[error("REST Request Failed: {0}")]
RequestFailed(String),
#[error("REST Response Filed: {0}")]
ResponseFailed(String),
#[error("Unexpected server error: {0}")]
ServerError(String),
#[error("Access denied: {0}")]
Unauthorized(String),
}
impl KmsError {
#[must_use]
pub fn reason(&self, reason: ErrorReason) -> Self {
match self {
KmsError::KmipError(_r, e) => KmsError::KmipError(reason, e.clone()),
e => KmsError::KmipError(reason, e.to_string()),
}
}
}
impl From<std::io::Error> for KmsError {
fn from(e: std::io::Error) -> Self {
KmsError::ServerError(e.to_string())
}
}
impl From<eyre::Report> for KmsError {
fn from(e: eyre::Report) -> Self {
KmsError::ServerError(e.to_string())
}
}
impl From<TtlvError> for KmsError {
fn from(e: TtlvError) -> Self {
KmsError::KmipError(ErrorReason::Codec_Error, e.to_string())
}
}
impl From<serde_json::Error> for KmsError {
fn from(e: serde_json::Error) -> Self {
KmsError::ServerError(e.to_string())
}
}
impl From<FormatErr> for KmsError {
fn from(e: FormatErr) -> Self {
KmsError::CryptographicError("ABE".to_owned(), e.to_string())
}
}
impl From<LibError> for KmsError {
fn from(e: LibError) -> Self {
match e {
LibError::TtlvError(s) => KmsError::NotSupported(s),
LibError::RequestFailed(s) => KmsError::RequestFailed(s),
LibError::ResponseFailed(s) => KmsError::ResponseFailed(s),
LibError::UnexpectedError(s) => KmsError::UnexpectedError(s),
LibError::InvalidKmipValue(_, _) => todo!(),
LibError::InvalidKmipObject(_, _) => todo!(),
LibError::CryptographicError(s, r) => KmsError::CryptographicError(s, r),
LibError::InvalidRequest(_) => todo!(),
LibError::Error(s) => KmsError::ServerError(s),
LibError::KmipError(_, _) => todo!(),
}
}
}
impl From<KmsCommonError> for KmsError {
fn from(e: KmsCommonError) -> Self {
match e {
KmsCommonError::InvalidKmipValue(r, s) => KmsError::InvalidKmipValue(r, s),
KmsCommonError::InvalidKmipObject(r, s) => KmsError::InvalidKmipObject(r, s),
KmsCommonError::KmipNotSupported(r, s) => KmsError::KmipNotSupported(r, s),
KmsCommonError::NotSupported(s) => KmsError::NotSupported(s),
KmsCommonError::KmipError(r, s) => KmsError::KmipError(r, s),
}
}
}
impl From<sqlx::Error> for KmsError {
fn from(e: sqlx::Error) -> Self {
KmsError::DatabaseError(e.to_string())
}
}
/// Return early with an error if a condition is not satisfied.
///
/// This macro is equivalent to `if !$cond { return Err(From::from($err)); }`.
#[macro_export]
macro_rules! kms_ensure {
($cond:expr, $msg:literal $(,)?) => {
if !$cond {
return ::core::result::Result::Err($crate::error::KmsError::ServerError($msg.to_owned()));
}
};
($cond:expr, $err:expr $(,)?) => {
if !$cond {
return ::core::result::Result::Err($crate::error::KmsError::ServerError($err.to_string()));
}
};
($cond:expr, $fmt:expr, $($arg:tt)*) => {
if !$cond {
return ::core::result::Result::Err($crate::error::KmsError::ServerError(format!($fmt, $($arg)*)));
}
};
}
/// Construct a server error from a string.
#[macro_export]
macro_rules! kms_error {
($msg:literal $(,)?) => {
$crate::error::KmsError::ServerError($msg.to_owned())
};
($err:expr $(,)?) => ({
$crate::error::KmsError::ServerError($err.to_string())
});
($fmt:expr, $($arg:tt)*) => {
$crate::error::KmsError::ServerError(format!($fmt, $($arg)*))
};
}
/// Return early with an error if a condition is not satisfied.
#[macro_export]
macro_rules! kms_bail {
($msg:literal $(,)?) => {
return ::core::result::Result::Err($crate::error::KmsError::ServerError($msg.to_owned()))
};
($err:expr $(,)?) => {
return ::core::result::Result::Err($crate::error::KmsError::ServerError($err.to_string()))
};
($fmt:expr, $($arg:tt)*) => {
return ::core::result::Result::Err($crate::error::KmsError::ServerError(format!($fmt, $($arg)*)))
};
}

View file

@ -0,0 +1,312 @@
use std::convert::TryFrom;
use abe_gpsw::core::policy::Policy;
use cosmian_kms_common::kmip::{
kmip_key_utils::WrappedSymmetricKey,
kmip_objects::{Object, ObjectType},
kmip_operations::{
Create, CreateKeyPair, ErrorReason, Get, Import, Locate, ReKeyKeyPairResponse,
},
kmip_types::{
Attributes, CryptographicAlgorithm, KeyFormatType, Link, LinkType, LinkedObjectIdentifier,
},
};
use cosmian_rust_lib::{
crypto::abe::{
attributes::{
access_policy_from_attributes, attributes_as_vendor_attribute,
attributes_from_attributes, policy_from_attributes, upsert_policy_in_attributes,
},
user_key::create_user_decryption_key_object,
},
kmip_utils::{
key_bytes_and_attributes_from_key_block, public_key_unique_identifier_from_private_key,
},
KeyPair,
};
use tracing::trace;
use crate::{
error::KmsError,
kmip::kmip_server::server::kmip_server::KmipServer,
kms_error,
result::{KResult, KResultHelper},
};
/// `Re_key` an ABE master Key for the given attributes, which in ABE terms
/// is to "revoke" the list of given attributes by increasing their value
pub(crate) async fn rekey_keypair_abe<K>(
kmip_server: &K,
master_private_key_uid: &str,
attributes: &Attributes,
owner: &str,
) -> KResult<ReKeyKeyPairResponse>
where
K: KmipServer,
{
trace!("Internal rekey key pair ABE");
// Verify the operation is performed for an ABE Master Key
let key_format_type = attributes
.key_format_type
.as_ref()
.ok_or_else(|| {
KmsError::ServerError(
"Unable to rekey an ABE key, the format type is not specified".to_owned(),
)
})
.reason(ErrorReason::Invalid_Attribute_Value)?;
if key_format_type != &KeyFormatType::AbeMasterSecretKey {
return Err(kms_error!(
"ReKey: the format of the key must be an ABE master key"
))
.reason(ErrorReason::Feature_Not_Supported)
}
// Determine the list of policy attributes which will be revoked (i.e. their value increased)
let abe_policy_attributes_to_revoke = attributes_from_attributes(attributes)?;
trace!(
"Revoking attributes: {:?}",
&abe_policy_attributes_to_revoke
);
// Recover the master private key
let private_key = kmip_server
.get(Get::from(master_private_key_uid), owner)
.await?
.object;
// Recover the Master Public Key
let master_public_key_uid = public_key_unique_identifier_from_private_key(&private_key)?;
let public_key = kmip_server
.get(Get::from(master_public_key_uid.clone()), owner)
.await?
.object;
// Recover the policy from the private key
let (master_private_key_bytes, private_key_attributes) =
key_bytes_and_attributes_from_key_block(private_key.key_block()?, master_private_key_uid)?;
let mut private_key_attributes = private_key_attributes
.context("The ABE Master key should have attributes")
.reason(ErrorReason::Attribute_Not_Found)?;
let mut policy = policy_from_attributes(&private_key_attributes)?;
// Increment the Attributes values in the Policy
for attr in &abe_policy_attributes_to_revoke {
policy.rotate(attr)?
}
trace!("The new policy is : {:#?}", &policy);
// Update Master Private Key Policy and re-import the key
upsert_policy_in_attributes(&mut private_key_attributes, &policy)?;
// re_import it
let import_request = Import {
unique_identifier: master_private_key_uid.to_string(),
object_type: ObjectType::PrivateKey,
replace_existing: Some(true),
key_wrap_type: None,
attributes: private_key_attributes,
object: private_key,
};
let _import_response = kmip_server.import(import_request, owner).await?;
// Update Master Public Key Policy and re-import the key
let mut public_key_attributes = public_key.key_block()?.key_value.attributes()?.clone();
public_key_attributes.set_object_type(ObjectType::PublicKey);
upsert_policy_in_attributes(&mut public_key_attributes, &policy)?;
// re_import it
let import_request = Import {
unique_identifier: master_public_key_uid.clone(),
object_type: ObjectType::PublicKey,
replace_existing: Some(true),
key_wrap_type: None,
attributes: public_key_attributes,
object: public_key,
};
let _import_response = kmip_server.import(import_request, owner).await?;
// Search the user decryption keys that need to be refreshed
let search_attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::ABE),
key_format_type: Some(KeyFormatType::AbeUserDecryptionKey),
vendor_attributes: Some(vec![
// abe_master_private_key_id_as_vendor_attribute(master_private_key_uid),
attributes_as_vendor_attribute(abe_policy_attributes_to_revoke)?,
]),
link: vec![Link {
link_type: LinkType::ParentLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(
master_private_key_uid.to_owned(),
),
}],
..Attributes::new(ObjectType::PrivateKey)
};
let locate_request = Locate {
attributes: search_attributes,
..Locate::new(ObjectType::PrivateKey)
};
let locate_response = kmip_server.locate(locate_request, owner).await?;
// Refresh the User Decryption Key that were found
if let Some(unique_identifiers) = &locate_response.unique_identifiers {
trace!(
"Rekeying the following user decryption keys: {:?}",
&unique_identifiers
);
renew_all_user_decryption_keys(
kmip_server,
&master_private_key_bytes,
&policy,
unique_identifiers,
owner,
)
.await?
}
Ok(ReKeyKeyPairResponse {
private_key_unique_identifier: master_private_key_uid.to_string(),
public_key_unique_identifier: master_public_key_uid,
})
}
async fn renew_all_user_decryption_keys<K>(
kmip_server: &K,
master_private_key_bytes: &[u8],
policy: &Policy,
user_decryption_key_unique_identifiers: &[String],
owner: &str,
) -> KResult<()>
where
K: KmipServer,
{
// Renew user decryption key previously found
for user_decryption_key_unique_identifier in user_decryption_key_unique_identifiers {
let get_response = kmip_server
.get(Get::from(user_decryption_key_unique_identifier), owner)
.await?;
let key_block = get_response.object.key_block()?;
// Handle both plaintext and wrapped key
let current_key_attributes = match &key_block.key_wrapping_data {
Some(_) => {
let wrapped_symmetric_key =
WrappedSymmetricKey::try_from(&key_block.key_value.raw_bytes()?)?;
wrapped_symmetric_key.attributes()
}
None => key_block.key_value.attributes()?.clone(),
};
let current_access_policy = access_policy_from_attributes(&current_key_attributes)?;
// Generate a fresh User Decryption Key
let new_user_decryption_key = create_user_decryption_key_object(
master_private_key_bytes,
policy,
&current_access_policy,
Some(&current_key_attributes),
)?;
let import_request = Import {
unique_identifier: get_response.unique_identifier,
object_type: get_response.object_type,
replace_existing: Some(true),
key_wrap_type: None,
attributes: current_key_attributes,
object: new_user_decryption_key,
};
let _import_response = kmip_server.import(import_request, owner).await?;
}
Ok(())
}
/// Create a User Decryption Key in the KMS
///
/// The attributes of the `Create` request must contain the
/// `Access Policy`
pub(crate) async fn create_user_decryption_key<K>(
kmip_server: &K,
create_request: &Create,
owner: &str,
) -> KResult<Object>
where
K: KmipServer,
{
create_user_decryption_key_(kmip_server, &create_request.attributes, owner).await
}
async fn create_user_decryption_key_<K>(
kmip_server: &K,
create_attributes: &Attributes,
owner: &str,
) -> KResult<Object>
where
K: KmipServer,
{
// Recover the access policy
let access_policy = access_policy_from_attributes(create_attributes)?;
// Recover private key
let master_private_key_uid = create_attributes.get_parent_id().context(
"there should be a reference to the master private key in the creation attributes",
)?;
let gr_private_key = kmip_server
.get(Get::from(master_private_key_uid.clone()), owner)
.await?;
let master_private_key = &gr_private_key.object;
let (master_private_key_bytes, master_private_key_attributes) =
key_bytes_and_attributes_from_key_block(
master_private_key.key_block()?,
&master_private_key_uid,
)?;
// recover the current policy from the key attributes
let policy = policy_from_attributes(&master_private_key_attributes.ok_or_else(|| {
KmsError::InvalidKmipObject(
ErrorReason::Attribute_Not_Found,
"the master private key does not have attributes with the Policy".to_string(),
)
})?)?;
trace!("Policy: {:?}", &policy);
create_user_decryption_key_object(
&master_private_key_bytes,
&policy,
&access_policy,
Some(create_attributes),
)
.map_err(Into::into)
}
/// Create a KMIP tuple (`Object::PrivateKey`, `Object::PublicKey`)
pub(crate) async fn create_user_decryption_key_pair<K>(
kmip_server: &K,
create_key_pair_request: &CreateKeyPair,
owner: &str,
) -> KResult<KeyPair>
where
K: KmipServer,
{
// create user decryption key
let private_key_attributes = create_key_pair_request
.private_key_attributes
.as_ref()
.or(create_key_pair_request.common_attributes.as_ref())
.context("Missing private attributes in ABE Create Keypair request")
.reason(ErrorReason::Attribute_Not_Found)?;
let private_key =
create_user_decryption_key_(kmip_server, private_key_attributes, owner).await?;
//Recover Public Key
let public_key_attributes = create_key_pair_request
.public_key_attributes
.as_ref()
.or(create_key_pair_request.common_attributes.as_ref())
.context("Missing public attributes in ABE Create Keypair request")
.reason(ErrorReason::Attribute_Not_Found)?;
let master_public_key_uid = public_key_attributes.get_parent_id().context(
"the master public key id should be available in the public creation attributes",
)?;
let gr_public_key = kmip_server
.get(Get::from(master_public_key_uid), owner)
.await?;
Ok(KeyPair((private_key, gr_public_key.object)))
}

View file

@ -0,0 +1,108 @@
use async_trait::async_trait;
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_objects::{Object, ObjectType},
kmip_types::{StateEnumeration, UniqueIdentifier},
};
use serde::{Deserialize, Serialize};
use crate::{kms_bail, result::KResult};
#[async_trait]
pub(crate) trait Database {
/// Insert the given Object in the database.
///
/// A new UUID will be created if none is supplier.
/// This method will fail if a `uid` is supplied
/// and an object with the same id already exists
async fn create(
&self,
uid: Option<String>,
owner: &str,
object: &Object,
) -> KResult<UniqueIdentifier>;
/// Insert the provided Objects in the database in a transaction
///
/// A new UUID will be created if none is supplier.
/// This method will fail if a `uid` is supplied
/// and an object with the same id already exists
async fn create_objects(
&self,
owner: &str,
objects: &[(Option<String>, Object)],
) -> KResult<Vec<UniqueIdentifier>>;
/// Retrieve an object from the database using `uid` and `owner`.
/// The `query_read_access` allows additional lookup in `read_access` table to see
/// if `owner` is matching `read_access` authorization
async fn retrieve(
&self,
uid: &str,
owner: &str,
query_read_access: ObjectOperationTypes,
) -> KResult<Option<(Object, StateEnumeration)>>;
async fn update_object(&self, uid: &str, owner: &str, object: &Object) -> KResult<()>;
async fn update_state(&self, uid: &str, owner: &str, state: StateEnumeration) -> KResult<()>;
/// upsert (update or create if not exsits)
async fn upsert(
&self,
uid: &str,
owner: &str,
object: &Object,
state: StateEnumeration,
) -> KResult<()>;
async fn delete(&self, uid: &str, owner: &str) -> KResult<()>;
async fn list(&self, owner: &str) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>>;
/// Insert a `userid` to give `operation_type` access right for the object identified
/// by its `uid` and belonging to `owner`
async fn insert_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()>;
/// Delete a `userid` to remove read access right for the object identified
/// by its `uid` and belonging to `owner`
async fn delete_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()>;
/// Test if an object identified by its `uid` is currently owned by `owner`
async fn is_object_owned_by(&self, uid: &str, owner: &str) -> KResult<bool>;
}
/// The Database implemented using `SQLite`
///
/// This class uses a connection should be cloned on each server thread
#[derive(Clone)]
/// When using JSON serialization, the Object is untagged
/// and looses its type information, so we have to keep
/// the `ObjectType`. See `Object` anf `post_fix()` for details
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct DBObject {
pub(crate) object_type: ObjectType,
pub(crate) object: Object,
}
pub fn state_from_string(s: &str) -> KResult<StateEnumeration> {
match s {
"PreActive" => Ok(StateEnumeration::PreActive),
"Active" => Ok(StateEnumeration::Active),
"Deactivated" => Ok(StateEnumeration::Deactivated),
"Compromised" => Ok(StateEnumeration::Compromised),
"Destroyed" => Ok(StateEnumeration::Destroyed),
"Destroyed_Compromised" => Ok(StateEnumeration::Destroyed_Compromised),
x => kms_bail!("invalid state in db: {}", x),
}
}

View file

@ -0,0 +1,7 @@
pub(crate) mod database;
pub(crate) mod server;
pub(crate) type KMSServer = server::KMS;
pub(crate) mod abe;
pub(crate) mod mysql;
pub(crate) mod pgsql;
pub(crate) mod sqlite;

View file

@ -0,0 +1,965 @@
use std::str::FromStr;
use async_trait::async_trait;
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_objects::{self, Object},
kmip_operations::ErrorReason,
kmip_types::{StateEnumeration, UniqueIdentifier},
};
use serde_json::Value;
use sqlx::{
mysql::{MySqlConnectOptions, MySqlPoolOptions, MySqlRow},
ConnectOptions, Executor, MySql, Pool, Row,
};
use tracing::trace;
use uuid::Uuid;
use super::database::{state_from_string, DBObject, Database};
use crate::{
kms_bail,
result::{KResult, KResultHelper},
};
/// The MySQL connector is also compatible to connect a MariaDB
/// see: https://mariadb.com/kb/en/mariadb-vs-mysql-compatibility/
pub(crate) struct Sql {
pool: Pool<MySql>,
}
impl Sql {
pub async fn instantiate(connection_url: &str) -> KResult<Sql> {
let mut options = MySqlConnectOptions::from_str(connection_url)?;
// disable logging of each query
options.disable_statement_logging();
let pool = MySqlPoolOptions::new()
.max_connections(5)
.connect_with(options)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS objects (
id VARCHAR(255) PRIMARY KEY,
object json NOT NULL,
state TEXT,
owner TEXT
)",
)
.execute(&pool)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS read_access (
id TEXT,
userid TEXT,
permissions json NOT NULL,
UNIQUE (id, userid)
)",
)
.execute(&pool)
.await?;
Ok(Sql { pool })
}
#[cfg(test)]
pub async fn clean_database(&self) {
// Erase `objects` table
sqlx::query("TRUNCATE objects")
.execute(&self.pool)
.await
.expect("cannot truncate objects table");
// Erase `read_access` table
sqlx::query("TRUNCATE read_access")
.execute(&self.pool)
.await
.expect("cannot truncate read_access table");
}
#[cfg(test)]
pub async fn perms(&self, uid: &str, userid: &str) -> KResult<Vec<ObjectOperationTypes>> {
fetch_permissions_(uid, userid, &self.pool).await
}
}
async fn create_<'e, E>(
uid: Option<String>,
owner: &str,
object: &kmip_objects::Object,
executor: E,
) -> KResult<UniqueIdentifier>
where
E: Executor<'e, Database = MySql>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
let uid = uid.unwrap_or_else(|| Uuid::new_v4().to_string());
sqlx::query("INSERT INTO objects (id, object, state, owner) VALUES (?, ?, ?, ?)")
.bind(uid.clone())
.bind(json)
.bind(StateEnumeration::Active.to_string())
.bind(owner)
.execute(executor)
.await?;
Ok(uid)
}
async fn retrieve_<'e, E>(
uid: &str,
owner_or_userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<Option<(kmip_objects::Object, StateEnumeration)>>
where
E: Executor<'e, Database = MySql> + Copy,
{
let row: Option<MySqlRow> =
sqlx::query("SELECT object, state FROM objects WHERE id=? AND owner=?")
.bind(uid)
.bind(owner_or_userid)
.fetch_optional(executor)
.await?;
if let Some(row) = row {
let json = row.get::<Value, _>(0);
let db_object: DBObject = serde_json::from_value(json)
.context("failed deserializing the object")
.reason(ErrorReason::Internal_Server_Error)?;
let object = Object::post_fix(db_object.object_type, db_object.object);
let state = state_from_string(&row.get::<String, _>(1))?;
return Ok(Some((object, state)))
}
let row: Option<MySqlRow> = sqlx::query(
"SELECT objects.object, objects.state, read_access.permissions
FROM objects, read_access
WHERE objects.id=? AND read_access.id=? AND read_access.userid=?",
)
.bind(uid)
.bind(uid)
.bind(owner_or_userid)
.fetch_optional(executor)
.await?;
row.map_or(Ok(None), |row| {
let perms_raw = row.get::<Value, _>(2);
let perms: Vec<ObjectOperationTypes> = serde_json::from_value(perms_raw)
.context("failed deserializing the permissions")
.reason(ErrorReason::Internal_Server_Error)?;
// Check this operation is legit to fetch this object
if perms.into_iter().all(|p| p != operation_type) {
kms_bail!("No authorization to perform this operation");
}
let json = row.get::<Value, _>(0);
let db_object: DBObject = serde_json::from_value(json)
.context("failed deserializing the object")
.reason(ErrorReason::Internal_Server_Error)?;
let object = Object::post_fix(db_object.object_type, db_object.object);
let state = state_from_string(&row.get::<String, _>(1))?;
Ok(Some((object, state)))
})
}
async fn update_object_<'e, E>(
uid: &str,
owner: &str,
object: &kmip_objects::Object,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = MySql>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
sqlx::query("UPDATE objects SET object=? WHERE id=? AND owner=?")
.bind(json)
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn update_state_<'e, E>(
uid: &str,
owner: &str,
state: StateEnumeration,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = MySql>,
{
sqlx::query("UPDATE objects SET state=? WHERE id=? AND owner=?")
.bind(state.to_string())
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn delete_<'e, E>(uid: &str, owner: &str, executor: E) -> KResult<()>
where
E: Executor<'e, Database = MySql>,
{
sqlx::query("DELETE FROM objects WHERE id=? AND owner=?")
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn upsert_<'e, E>(
uid: &str,
owner: &str,
object: &kmip_objects::Object,
state: StateEnumeration,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = MySql>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
sqlx::query(
"INSERT INTO objects (id, object, state, owner) VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
object = IF(objects.owner=?, VALUES(object), object),
state = IF(objects.owner=?, VALUES(state), state)",
)
.bind(uid)
.bind(json.clone())
.bind(state.to_string())
.bind(owner)
.bind(owner)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn list_<'e, E>(
executor: E,
owner: &str,
) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>>
where
E: Executor<'e, Database = MySql>,
{
let list = sqlx::query("SELECT id, state FROM objects WHERE owner=?")
.bind(owner)
.fetch_all(executor)
.await?;
let mut ids: Vec<(String, StateEnumeration)> = Vec::with_capacity(list.len());
for row in list {
ids.push((
row.get::<String, _>(0),
state_from_string(&row.get::<String, _>(1))?,
));
}
Ok(ids)
}
async fn fetch_permissions_<'e, E>(
uid: &str,
userid: &str,
executor: E,
) -> KResult<Vec<ObjectOperationTypes>>
where
E: Executor<'e, Database = MySql>,
{
let row: Option<MySqlRow> = sqlx::query(
"SELECT permissions
FROM read_access
WHERE id=? AND userid=?",
)
.bind(uid)
.bind(userid)
.fetch_optional(executor)
.await?;
row.map_or(Ok(vec![]), |row| {
let perms_raw = row.get::<Value, _>(0);
let perms: Vec<ObjectOperationTypes> = serde_json::from_value(perms_raw)
.context("failed deserializing the permissions")
.reason(ErrorReason::Internal_Server_Error)?;
Ok(perms)
})
}
async fn insert_access_<'e, E>(
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = MySql> + Copy,
{
// Retrieve existing permissions if any
let mut perms = fetch_permissions_(uid, userid, executor).await?;
if perms.contains(&operation_type) {
// permission is already setup
return Ok(())
}
perms.push(operation_type);
// Serialize permissions
let json = serde_json::to_value(&perms)
.context("failed serializing the permissions to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
// Upsert the DB
sqlx::query(
"INSERT INTO read_access (id, userid, permissions) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
permissions = IF((id=VALUES(id)) AND (userid=VALUES(userid)), VALUES(permissions), \
permissions)",
)
.bind(uid)
.bind(userid)
.bind(json)
.execute(executor)
.await?;
trace!("Insert read access right in DB: {uid} / {userid}");
Ok(())
}
async fn delete_access_<'e, E>(
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = MySql> + Copy,
{
// Retrieve existing permissions if any
let mut perms = fetch_permissions_(uid, userid, executor).await?;
perms.retain(|p| *p != operation_type);
// No remaining permissions, delete the row
if perms.is_empty() {
sqlx::query("DELETE FROM read_access WHERE id=? AND userid=?")
.bind(uid)
.bind(userid)
.execute(executor)
.await?;
return Ok(())
}
// Serialize permissions
let json = serde_json::to_value(&perms)
.context("failed serializing the permissions to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
// Update the DB
sqlx::query(
"UPDATE read_access SET permissions=?
WHERE id=? AND userid=?",
)
.bind(json)
.bind(uid)
.bind(userid)
.execute(executor)
.await?;
trace!("Deleted in DB: {uid} / {userid}");
Ok(())
}
async fn is_object_owned_by_<'e, E>(uid: &str, owner: &str, executor: E) -> KResult<bool>
where
E: Executor<'e, Database = MySql> + Copy,
{
let row: Option<MySqlRow> = sqlx::query("SELECT 1 FROM objects WHERE id=? AND owner=?")
.bind(uid)
.bind(owner)
.fetch_optional(executor)
.await?;
Ok(row.is_some())
}
#[async_trait]
impl Database for Sql {
async fn create(
&self,
uid: Option<String>,
owner: &str,
object: &kmip_objects::Object,
) -> KResult<UniqueIdentifier> {
create_(uid, owner, object, &self.pool).await
}
async fn create_objects(
&self,
owner: &str,
objects: &[(Option<String>, kmip_objects::Object)],
) -> KResult<Vec<UniqueIdentifier>> {
let mut res: Vec<UniqueIdentifier> = vec![];
let mut tx = self.pool.begin().await?;
for (uid, object) in objects {
match create_(uid.to_owned(), owner, object, &mut tx).await {
Ok(uid) => res.push(uid),
Err(e) => {
tx.rollback().await.context("transaction failed")?;
kms_bail!("creation of objects failed: {}", e);
}
};
}
tx.commit().await?;
Ok(res)
}
async fn retrieve(
&self,
uid: &str,
owner: &str,
operation_type: ObjectOperationTypes,
) -> KResult<Option<(kmip_objects::Object, StateEnumeration)>> {
retrieve_(uid, owner, operation_type, &self.pool).await
}
async fn update_object(
&self,
uid: &str,
owner: &str,
object: &kmip_objects::Object,
) -> KResult<()> {
update_object_(uid, owner, object, &self.pool).await
}
async fn update_state(&self, uid: &str, owner: &str, state: StateEnumeration) -> KResult<()> {
update_state_(uid, owner, state, &self.pool).await
}
async fn upsert(
&self,
uid: &str,
owner: &str,
object: &kmip_objects::Object,
state: StateEnumeration,
) -> KResult<()> {
upsert_(uid, owner, object, state, &self.pool).await
}
async fn delete(&self, uid: &str, owner: &str) -> KResult<()> {
delete_(uid, owner, &self.pool).await
}
async fn list(&self, owner: &str) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>> {
list_(&self.pool, owner).await
}
async fn insert_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()> {
insert_access_(uid, userid, operation_type, &self.pool).await
}
async fn delete_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()> {
delete_access_(uid, userid, operation_type, &self.pool).await
}
async fn is_object_owned_by(&self, uid: &str, owner: &str) -> KResult<bool> {
is_object_owned_by_(uid, owner, &self.pool).await
}
}
// Run these tests using: `cargo make rust-tests`
#[cfg(test)]
mod tests {
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_types::{Link, LinkType, LinkedObjectIdentifier, StateEnumeration},
};
use cosmian_rust_lib::crypto::aes::create_aes_symmetric_key;
use serial_test::serial;
use uuid::Uuid;
use super::Sql;
use crate::{
kmip::kmip_server::database::Database,
kms_bail, kms_error,
result::{KResult, KResultHelper},
};
#[actix_rt::test]
#[serial(mysql)]
pub async fn test_crud() -> KResult<()> {
let mysql_url = std::option_env!("KMS_MYSQL_URL")
.ok_or_else(|| kms_error!("No MySQL database configured"))?;
let mysql = Sql::instantiate(mysql_url).await?;
mysql.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
// test non existent row (with very high probability)
if mysql
.retrieve(
&Uuid::new_v4().to_string(),
owner,
ObjectOperationTypes::Get,
)
.await?
.is_some()
{
kms_bail!("There should be no object");
}
// Insert an object and query it, update it, delete it, query it
let mut symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
let uid_ = mysql
.create(Some(uid.clone()), owner, &symmetric_key)
.await?;
assert_eq!(&uid, &uid_);
match mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
{
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Active, state_);
assert_eq!(&symmetric_key, &obj_);
}
None => kms_bail!("There should be an object"),
}
let mut attributes = symmetric_key
.attributes_mut()?
.context("there should be attributes")?;
attributes.link = vec![Link {
link_type: LinkType::PreviousLink,
linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
}];
mysql.update_object(&uid, owner, &symmetric_key).await?;
match mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
{
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Active, state_);
assert_eq!(
&obj_
.attributes()
.context("there should be attributes")?
.context("there should be attributes")?
.link[0]
.linked_object_identifier,
&LinkedObjectIdentifier::TextString("foo".to_string())
);
}
None => kms_bail!("There should be an object"),
}
mysql
.update_state(&uid, owner, StateEnumeration::Deactivated)
.await?;
match mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
{
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Deactivated, state_);
assert_eq!(&symmetric_key, &obj_);
}
None => kms_bail!("There should be an object"),
}
mysql.delete(&uid, owner).await?;
if mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object should have been deleted");
}
Ok(())
}
#[actix_rt::test]
#[serial(mysql)]
pub async fn test_upsert() -> KResult<()> {
let mysql_url = std::option_env!("KMS_MYSQL_URL")
.ok_or_else(|| kms_error!("No MySQL database configured"))?;
let mysql = Sql::instantiate(mysql_url).await?;
mysql.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
let mut symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
mysql
.upsert(&uid, owner, &symmetric_key, StateEnumeration::Active)
.await?;
match mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
{
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Active, state_);
assert_eq!(&symmetric_key, &obj_);
}
None => kms_bail!("There should be an object"),
}
let mut attributes = symmetric_key
.attributes_mut()?
.context("there should be attributes")?;
attributes.link = vec![Link {
link_type: LinkType::PreviousLink,
linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
}];
mysql
.upsert(&uid, owner, &symmetric_key, StateEnumeration::PreActive)
.await?;
match mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
{
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::PreActive, state_);
assert_eq!(
&obj_
.attributes()
.context("there should be attributes")?
.context("there should be attributes")?
.link[0]
.linked_object_identifier,
&LinkedObjectIdentifier::TextString("foo".to_string())
);
}
None => kms_bail!("There should be an object"),
}
mysql.delete(&uid, owner).await?;
if mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object should have been deleted");
}
Ok(())
}
#[actix_rt::test]
#[serial(mysql)]
pub async fn test_tx_and_list() -> KResult<()> {
let mysql_url = std::option_env!("KMS_MYSQL_URL")
.ok_or_else(|| kms_error!("No MySQL database configured"))?;
let mysql = Sql::instantiate(mysql_url).await?;
mysql.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
let symmetric_key_1 = create_aes_symmetric_key(None)?;
let uid_1 = Uuid::new_v4().to_string();
let symmetric_key_2 = create_aes_symmetric_key(None)?;
let uid_2 = Uuid::new_v4().to_string();
let ids = mysql
.create_objects(
owner,
&[
(Some(uid_1.clone()), symmetric_key_1.clone()),
(Some(uid_2.clone()), symmetric_key_2.clone()),
],
)
.await?;
assert_eq!(&uid_1, &ids[0]);
assert_eq!(&uid_2, &ids[1]);
let list = mysql.list(owner).await?;
match list.iter().find(|(id, _state)| id == &uid_1) {
Some((uid_, state_)) => {
assert_eq!(&uid_1, uid_);
assert_eq!(&StateEnumeration::Active, state_);
}
None => todo!(),
}
match list.iter().find(|(id, _state)| id == &uid_2) {
Some((uid_, state_)) => {
assert_eq!(&uid_2, uid_);
assert_eq!(&StateEnumeration::Active, state_);
}
None => todo!(),
}
mysql.delete(&uid_1, owner).await?;
mysql.delete(&uid_2, owner).await?;
if mysql
.retrieve(&uid_1, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object 1 should have been deleted");
}
if mysql
.retrieve(&uid_2, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object 2 should have been deleted");
}
Ok(())
}
#[actix_rt::test]
#[serial(mysql)]
pub async fn test_owner() -> KResult<()> {
let mysql_url = std::option_env!("KMS_MYSQL_URL")
.ok_or_else(|| kms_error!("No MySQL database configured"))?;
let mysql = Sql::instantiate(mysql_url).await?;
mysql.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
let userid = "foo@example.org";
let userid2 = "bar@example.org";
let invalid_owner = "invalid_owner";
let symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
// test non existent row (with very high probability)
if mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("There should be no object");
}
mysql
.upsert(&uid, owner, &symmetric_key, StateEnumeration::Active)
.await?;
assert!(mysql.is_object_owned_by(&uid, owner).await?);
// Retrieve object with valid owner with `Get` operation type - OK
match mysql
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
{
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Retrieve object with invalid owner with `Get` operation type - ko
if mysql
.retrieve(&uid, invalid_owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("It should not be possible to get this object")
}
// Add authorized `userid` to `read_access` table
mysql
.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
// Retrieve object with authorized `userid` with `Create` operation type - ko
if mysql
.retrieve(&uid, userid, ObjectOperationTypes::Create)
.await
.is_ok()
{
kms_bail!("It should not be possible to get this object with `Create` request")
}
// Retrieve object with authorized `userid` with `Get` operation type - OK
match mysql
.retrieve(&uid, userid, ObjectOperationTypes::Get)
.await?
{
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Add authorized `userid2` to `read_access` table
mysql
.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Try to add same access again - OK
mysql
.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Retrieve object with authorized `userid2` with `Create` operation type - ko
if mysql
.retrieve(&uid, userid2, ObjectOperationTypes::Create)
.await
.is_ok()
{
kms_bail!("It should not be possible to get this object with `Create` request")
}
// Retrieve object with authorized `userid` with `Get` operation type - OK
match mysql
.retrieve(&uid, userid2, ObjectOperationTypes::Get)
.await?
{
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Be sure we can still retrieve object with authorized `userid` with `Get` operation type - OK
match mysql
.retrieve(&uid, userid, ObjectOperationTypes::Get)
.await?
{
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Remove `userid2` authorization
mysql
.delete_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Retrieve object with `userid2` with `Get` operation type - ko
if mysql
.retrieve(&uid, userid2, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("It should not be possible to get this object with `Get` request")
}
Ok(())
}
#[actix_rt::test]
#[serial(mysql)]
pub async fn test_permissions() -> KResult<()> {
let userid = "foo@example.org";
let userid2 = "bar@example.org";
let mysql_url = std::option_env!("KMS_MYSQL_URL")
.ok_or_else(|| kms_error!("No MySQL database configured"))?;
let mysql = Sql::instantiate(mysql_url).await?;
mysql.clean_database().await;
let uid = Uuid::new_v4().to_string();
// simple insert
mysql
.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = mysql.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
// double insert, expect no duplicate
mysql
.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = mysql.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
// insert other operation type
mysql
.insert_access(&uid, userid, ObjectOperationTypes::Encrypt)
.await?;
let perms = mysql.perms(&uid, userid).await?;
assert_eq!(
perms,
vec![ObjectOperationTypes::Get, ObjectOperationTypes::Encrypt]
);
// insert other `userid2`, check it is ok and it didn't change anything for `userid`
mysql
.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
let perms = mysql.perms(&uid, userid2).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
let perms = mysql.perms(&uid, userid).await?;
assert_eq!(
perms,
vec![ObjectOperationTypes::Get, ObjectOperationTypes::Encrypt]
);
// remove `Get` access for `userid`
mysql
.delete_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = mysql.perms(&uid, userid2).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
let perms = mysql.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Encrypt]);
Ok(())
}
}

View file

@ -0,0 +1,939 @@
use std::str::FromStr;
use async_trait::async_trait;
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_objects::{self, Object},
kmip_operations::ErrorReason,
kmip_types::{StateEnumeration, UniqueIdentifier},
};
use serde_json::Value;
use sqlx::{
postgres::{PgConnectOptions, PgPoolOptions, PgRow},
ConnectOptions, Executor, Pool, Postgres, Row,
};
use tracing::trace;
use uuid::Uuid;
use super::database::{state_from_string, DBObject, Database};
use crate::{
kms_bail,
result::{KResult, KResultHelper},
};
pub(crate) struct Pgsql {
pool: Pool<Postgres>,
}
impl Pgsql {
pub async fn instantiate(connection_url: &str) -> KResult<Pgsql> {
let mut options = PgConnectOptions::from_str(connection_url)?;
// disable logging of each query
options.disable_statement_logging();
let pool = PgPoolOptions::new()
.max_connections(5)
.connect_with(options)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS objects (
id TEXT PRIMARY KEY UNIQUE,
object json NOT NULL,
state TEXT,
owner TEXT
)",
)
.execute(&pool)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS read_access (
id TEXT,
userid TEXT,
permissions json NOT NULL,
UNIQUE (id, userid)
)",
)
.execute(&pool)
.await?;
Ok(Pgsql { pool })
}
#[cfg(test)]
pub async fn clean_database(&self) {
// Erase `objects` table
sqlx::query("TRUNCATE objects CASCADE")
.execute(&self.pool)
.await
.expect("cannot truncate objects table");
// Erase `read_access` table
sqlx::query("TRUNCATE read_access CASCADE")
.execute(&self.pool)
.await
.expect("cannot truncate read_access table");
}
#[cfg(test)]
pub async fn perms(&self, uid: &str, userid: &str) -> KResult<Vec<ObjectOperationTypes>> {
fetch_permissions_(uid, userid, &self.pool).await
}
}
async fn create_<'e, E>(
uid: Option<String>,
owner: &str,
object: &kmip_objects::Object,
executor: E,
) -> KResult<UniqueIdentifier>
where
E: Executor<'e, Database = Postgres>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
let uid = uid.unwrap_or_else(|| Uuid::new_v4().to_string());
sqlx::query("INSERT INTO objects (id, object, state, owner) VALUES ($1, $2, $3, $4)")
.bind(uid.clone())
.bind(json)
.bind(StateEnumeration::Active.to_string())
.bind(owner)
.execute(executor)
.await?;
Ok(uid)
}
async fn retrieve_<'e, E>(
uid: &str,
owner_or_userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<Option<(kmip_objects::Object, StateEnumeration)>>
where
E: Executor<'e, Database = Postgres> + Copy,
{
let row: Option<PgRow> =
sqlx::query("SELECT object, state FROM objects WHERE id=$1 AND owner=$2")
.bind(uid)
.bind(owner_or_userid)
.fetch_optional(executor)
.await?;
if let Some(row) = row {
let json = row.get::<Value, _>(0);
let db_object: DBObject = serde_json::from_value(json)
.context("failed deserializing the object")
.reason(ErrorReason::Internal_Server_Error)?;
let object = Object::post_fix(db_object.object_type, db_object.object);
let state = state_from_string(&row.get::<String, _>(1))?;
return Ok(Some((object, state)))
}
let row: Option<PgRow> = sqlx::query(
"SELECT objects.object, objects.state, read_access.permissions
FROM objects, read_access
WHERE objects.id=$1 AND read_access.id=$1 AND read_access.userid=$2",
)
.bind(uid)
.bind(owner_or_userid)
.fetch_optional(executor)
.await?;
row.map_or(Ok(None), |row| {
let perms_raw = row.get::<Value, _>(2);
let perms: Vec<ObjectOperationTypes> = serde_json::from_value(perms_raw)
.context("failed deserializing the permissions")
.reason(ErrorReason::Internal_Server_Error)?;
// Check this operation is legit to fetch this object
if perms.into_iter().all(|p| p != operation_type) {
kms_bail!("No authorization to perform this operation");
}
let json = row.get::<Value, _>(0);
let db_object: DBObject = serde_json::from_value(json)
.context("failed deserializing the object")
.reason(ErrorReason::Internal_Server_Error)?;
let object = Object::post_fix(db_object.object_type, db_object.object);
let state = state_from_string(&row.get::<String, _>(1))?;
Ok(Some((object, state)))
})
}
async fn update_object_<'e, E>(
uid: &str,
owner: &str,
object: &kmip_objects::Object,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Postgres>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
sqlx::query("UPDATE objects SET object=$1 WHERE id=$2 AND owner=$3")
.bind(json)
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn update_state_<'e, E>(
uid: &str,
owner: &str,
state: StateEnumeration,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Postgres>,
{
sqlx::query("UPDATE objects SET state=$1 WHERE id=$2 AND owner=$3")
.bind(state.to_string())
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn delete_<'e, E>(uid: &str, owner: &str, executor: E) -> KResult<()>
where
E: Executor<'e, Database = Postgres>,
{
sqlx::query("DELETE FROM objects WHERE id=$1 AND owner=$2")
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn upsert_<'e, E>(
uid: &str,
owner: &str,
object: &kmip_objects::Object,
state: StateEnumeration,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Postgres>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
sqlx::query(
"INSERT INTO objects (id, object, state, owner) VALUES ($1, $2, $3, $4)
ON CONFLICT(id)
DO UPDATE SET object=$2, state=$3
WHERE objects.owner=$4",
)
.bind(uid)
.bind(json)
.bind(state.to_string())
.bind(owner)
.execute(executor)
.await?;
Ok(())
}
async fn list_<'e, E>(
executor: E,
owner: &str,
) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>>
where
E: Executor<'e, Database = Postgres>,
{
let list = sqlx::query("SELECT id, state FROM objects WHERE owner=$1")
.bind(owner)
.fetch_all(executor)
.await?;
let mut ids: Vec<(String, StateEnumeration)> = Vec::with_capacity(list.len());
for row in list {
ids.push((
row.get::<String, _>(0),
state_from_string(&row.get::<String, _>(1))?,
));
}
Ok(ids)
}
async fn fetch_permissions_<'e, E>(
uid: &str,
userid: &str,
executor: E,
) -> KResult<Vec<ObjectOperationTypes>>
where
E: Executor<'e, Database = Postgres>,
{
let row: Option<PgRow> = sqlx::query(
"SELECT permissions
FROM read_access
WHERE id=$1 AND userid=$2",
)
.bind(uid)
.bind(userid)
.fetch_optional(executor)
.await?;
row.map_or(Ok(vec![]), |row| {
let perms_raw = row.get::<Value, _>(0);
let perms: Vec<ObjectOperationTypes> = serde_json::from_value(perms_raw)
.context("failed deserializing the permissions")
.reason(ErrorReason::Internal_Server_Error)?;
Ok(perms)
})
}
async fn insert_access_<'e, E>(
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Postgres> + Copy,
{
// Retrieve existing permissions if any
let mut perms = fetch_permissions_(uid, userid, executor).await?;
if perms.contains(&operation_type) {
// permission is already setup
return Ok(())
}
perms.push(operation_type);
// Serialize permissions
let json = serde_json::to_value(&perms)
.context("failed serializing the permissions to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
// Upsert the DB
sqlx::query(
"INSERT INTO read_access (id, userid, permissions) VALUES ($1, $2, $3)
ON CONFLICT(id, userid)
DO UPDATE SET permissions=$3
WHERE read_access.id=$1 AND read_access.userid=$2",
)
.bind(uid)
.bind(userid)
.bind(json)
.execute(executor)
.await?;
trace!("Insert read access right in DB: {uid} / {userid}");
Ok(())
}
async fn delete_access_<'e, E>(
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Postgres> + Copy,
{
// Retrieve existing permissions if any
let mut perms = fetch_permissions_(uid, userid, executor).await?;
perms.retain(|p| *p != operation_type);
// No remaining permissions, delete the row
if perms.is_empty() {
sqlx::query("DELETE FROM read_access WHERE id=$1 AND userid=$2")
.bind(uid)
.bind(userid)
.execute(executor)
.await?;
return Ok(())
}
// Serialize permissions
let json = serde_json::to_value(&perms)
.context("failed serializing the permissions to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
// Update the DB
sqlx::query(
"UPDATE read_access SET permissions=$3
WHERE id=$1 AND userid=$2",
)
.bind(uid)
.bind(userid)
.bind(json)
.execute(executor)
.await?;
trace!("Deleted in DB: {uid} / {userid}");
Ok(())
}
async fn is_object_owned_by_<'e, E>(uid: &str, owner: &str, executor: E) -> KResult<bool>
where
E: Executor<'e, Database = Postgres> + Copy,
{
let row: Option<PgRow> = sqlx::query("SELECT 1 FROM objects WHERE id=$1 AND owner=$2")
.bind(uid)
.bind(owner)
.fetch_optional(executor)
.await?;
Ok(row.is_some())
}
#[async_trait]
impl Database for Pgsql {
async fn create(
&self,
uid: Option<String>,
owner: &str,
object: &kmip_objects::Object,
) -> KResult<UniqueIdentifier> {
create_(uid, owner, object, &self.pool).await
}
async fn create_objects(
&self,
owner: &str,
objects: &[(Option<String>, kmip_objects::Object)],
) -> KResult<Vec<UniqueIdentifier>> {
let mut res: Vec<UniqueIdentifier> = vec![];
let mut tx = self.pool.begin().await?;
for (uid, object) in objects {
match create_(uid.to_owned(), owner, object, &mut tx).await {
Ok(uid) => res.push(uid),
Err(e) => {
tx.rollback().await.context("transaction failed")?;
kms_bail!("creation of objects failed: {}", e);
}
};
}
tx.commit().await?;
Ok(res)
}
async fn retrieve(
&self,
uid: &str,
owner: &str,
operation_type: ObjectOperationTypes,
) -> KResult<Option<(kmip_objects::Object, StateEnumeration)>> {
retrieve_(uid, owner, operation_type, &self.pool).await
}
async fn update_object(
&self,
uid: &str,
owner: &str,
object: &kmip_objects::Object,
) -> KResult<()> {
update_object_(uid, owner, object, &self.pool).await
}
async fn update_state(&self, uid: &str, owner: &str, state: StateEnumeration) -> KResult<()> {
update_state_(uid, owner, state, &self.pool).await
}
async fn upsert(
&self,
uid: &str,
owner: &str,
object: &kmip_objects::Object,
state: StateEnumeration,
) -> KResult<()> {
upsert_(uid, owner, object, state, &self.pool).await
}
async fn delete(&self, uid: &str, owner: &str) -> KResult<()> {
delete_(uid, owner, &self.pool).await
}
async fn list(&self, owner: &str) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>> {
list_(&self.pool, owner).await
}
async fn insert_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()> {
insert_access_(uid, userid, operation_type, &self.pool).await
}
async fn delete_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()> {
delete_access_(uid, userid, operation_type, &self.pool).await
}
async fn is_object_owned_by(&self, uid: &str, owner: &str) -> KResult<bool> {
is_object_owned_by_(uid, owner, &self.pool).await
}
}
// Run these tests using:
//
// KMS_POSTGRES_URL=postgresql://kms:kms@127.0.0.1:5432/kms cargo t -- kmip::kmip_server::pgsql::tests --nocapture
//
#[cfg(test)]
mod tests {
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_types::{Link, LinkType, LinkedObjectIdentifier, StateEnumeration},
};
use cosmian_rust_lib::crypto::aes::create_aes_symmetric_key;
use serial_test::serial;
use uuid::Uuid;
use super::Pgsql;
use crate::{
kmip::kmip_server::database::Database,
kms_bail, kms_error,
result::{KResult, KResultHelper},
};
// Run this test using:
//
// KMS_POSTGRES_URL=postgresql://kms:kms@127.0.0.1:5432/kms cargo t -- kmip::kmip_server::pgsql::tests::test_crud --exact --nocapture
//
#[actix_rt::test]
#[serial(pgsql)]
pub async fn test_crud() -> KResult<()> {
let postgres_url = std::option_env!("KMS_POSTGRES_URL")
.ok_or_else(|| kms_error!("No PostgreSQL database configured"))?;
let pg = Pgsql::instantiate(postgres_url).await?;
pg.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
// test non existent row (with very high probability)
if pg
.retrieve(
&Uuid::new_v4().to_string(),
owner,
ObjectOperationTypes::Get,
)
.await?
.is_some()
{
kms_bail!("There should be no object");
}
// Insert an object and query it, update it, delete it, query it
let mut symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
let uid_ = pg.create(Some(uid.clone()), owner, &symmetric_key).await?;
assert_eq!(&uid, &uid_);
match pg.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Active, state_);
assert_eq!(&symmetric_key, &obj_);
}
None => kms_bail!("There should be an object"),
}
let mut attributes = symmetric_key
.attributes_mut()?
.context("there should be attributes")?;
attributes.link = vec![Link {
link_type: LinkType::PreviousLink,
linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
}];
pg.update_object(&uid, owner, &symmetric_key).await?;
match pg.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Active, state_);
assert_eq!(
&obj_
.attributes()
.context("there should be attributes")?
.context("there should be attributes")?
.link[0]
.linked_object_identifier,
&LinkedObjectIdentifier::TextString("foo".to_string())
);
}
None => kms_bail!("There should be an object"),
}
pg.update_state(&uid, owner, StateEnumeration::Deactivated)
.await?;
match pg.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Deactivated, state_);
assert_eq!(&symmetric_key, &obj_);
}
None => kms_bail!("There should be an object"),
}
pg.delete(&uid, owner).await?;
if pg
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object should have been deleted");
}
Ok(())
}
// Run this test using:
//
// KMS_POSTGRES_URL=postgresql://kms:kms@127.0.0.1:5432/kms cargo t -- kmip::kmip_server::pgsql::tests::test_upsert --exact --nocapture
//
#[actix_rt::test]
#[serial(pgsql)]
pub async fn test_upsert() -> KResult<()> {
let postgres_url = std::option_env!("KMS_POSTGRES_URL")
.ok_or_else(|| kms_error!("No PostgreSQL database configured"))?;
let pg = Pgsql::instantiate(postgres_url).await?;
pg.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
let mut symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
pg.upsert(&uid, owner, &symmetric_key, StateEnumeration::Active)
.await?;
match pg.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::Active, state_);
assert_eq!(&symmetric_key, &obj_);
}
None => kms_bail!("There should be an object"),
}
let mut attributes = symmetric_key
.attributes_mut()?
.context("there should be attributes")?;
attributes.link = vec![Link {
link_type: LinkType::PreviousLink,
linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
}];
pg.upsert(&uid, owner, &symmetric_key, StateEnumeration::PreActive)
.await?;
match pg.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj_, state_)) => {
assert_eq!(StateEnumeration::PreActive, state_);
assert_eq!(
&obj_
.attributes()
.context("there should be attributes")?
.context("there should be attributes")?
.link[0]
.linked_object_identifier,
&LinkedObjectIdentifier::TextString("foo".to_string())
);
}
None => kms_bail!("There should be an object"),
}
pg.delete(&uid, owner).await?;
if pg
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object should have been deleted");
}
Ok(())
}
// Run this test using:
//
// KMS_POSTGRES_URL=postgresql://kms:kms@127.0.0.1:5432/kms cargo t -- kmip::kmip_server::pgsql::tests::test_tx_and_list --exact --nocapture
//
#[actix_rt::test]
#[serial(pgsql)]
pub async fn test_tx_and_list() -> KResult<()> {
let postgres_url = std::option_env!("KMS_POSTGRES_URL")
.ok_or_else(|| kms_error!("No PostgreSQL database configured"))?;
let pg = Pgsql::instantiate(postgres_url).await?;
pg.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
let symmetric_key_1 = create_aes_symmetric_key(None)?;
let uid_1 = Uuid::new_v4().to_string();
let symmetric_key_2 = create_aes_symmetric_key(None)?;
let uid_2 = Uuid::new_v4().to_string();
let ids = pg
.create_objects(
owner,
&[
(Some(uid_1.clone()), symmetric_key_1.clone()),
(Some(uid_2.clone()), symmetric_key_2.clone()),
],
)
.await?;
assert_eq!(&uid_1, &ids[0]);
assert_eq!(&uid_2, &ids[1]);
let list = pg.list(owner).await?;
match list.iter().find(|(id, _state)| id == &uid_1) {
Some((uid_, state_)) => {
assert_eq!(&uid_1, uid_);
assert_eq!(&StateEnumeration::Active, state_);
}
None => todo!(),
}
match list.iter().find(|(id, _state)| id == &uid_2) {
Some((uid_, state_)) => {
assert_eq!(&uid_2, uid_);
assert_eq!(&StateEnumeration::Active, state_);
}
None => todo!(),
}
pg.delete(&uid_1, owner).await?;
pg.delete(&uid_2, owner).await?;
if pg
.retrieve(&uid_1, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object 1 should have been deleted");
}
if pg
.retrieve(&uid_2, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("The object 2 should have been deleted");
}
Ok(())
}
// Run this test using:
//
// KMS_POSTGRES_URL=postgresql://kms:kms@127.0.0.1:5432/kms cargo t -- kmip::kmip_server::pgsql::tests::test_owner --exact --nocapture
//
#[actix_rt::test]
#[serial(pgsql)]
pub async fn test_owner() -> KResult<()> {
let postgres_url = std::option_env!("KMS_POSTGRES_URL")
.ok_or_else(|| kms_error!("No PostgreSQL database configured"))?;
let pg = Pgsql::instantiate(postgres_url).await?;
pg.clean_database().await;
let owner = "eyJhbGciOiJSUzI1Ni";
let userid = "foo@example.org";
let userid2 = "bar@example.org";
let invalid_owner = "invalid_owner";
let symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
// test non existent row (with very high probability)
if pg
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("There should be no object");
}
pg.upsert(&uid, owner, &symmetric_key, StateEnumeration::Active)
.await?;
assert!(pg.is_object_owned_by(&uid, owner).await?);
// Retrieve object with valid owner with `Get` operation type - OK
match pg.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Retrieve object with invalid owner with `Get` operation type - ko
if pg
.retrieve(&uid, invalid_owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("It should not be possible to get this object")
}
// Add authorized `userid` to `read_access` table
pg.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
// Retrieve object with authorized `userid` with `Create` operation type - ko
if pg
.retrieve(&uid, userid, ObjectOperationTypes::Create)
.await
.is_ok()
{
kms_bail!("It should not be possible to get this object with `Create` request")
}
// Retrieve object with authorized `userid` with `Get` operation type - OK
match pg.retrieve(&uid, userid, ObjectOperationTypes::Get).await? {
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Add authorized `userid2` to `read_access` table
pg.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Try to add same access again - OK
pg.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Retrieve object with authorized `userid2` with `Create` operation type - ko
if pg
.retrieve(&uid, userid2, ObjectOperationTypes::Create)
.await
.is_ok()
{
kms_bail!("It should not be possible to get this object with `Create` request")
}
// Retrieve object with authorized `userid` with `Get` operation type - OK
match pg
.retrieve(&uid, userid2, ObjectOperationTypes::Get)
.await?
{
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Be sure we can still retrieve object with authorized `userid` with `Get` operation type - OK
match pg.retrieve(&uid, userid, ObjectOperationTypes::Get).await? {
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Remove `userid2` authorization
pg.delete_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Retrieve object with `userid2` with `Get` operation type - ko
if pg
.retrieve(&uid, userid2, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("It should not be possible to get this object with `Get` request")
}
Ok(())
}
#[actix_rt::test]
#[serial(pgsql)]
pub async fn test_permissions() -> KResult<()> {
let userid = "foo@example.org";
let userid2 = "bar@example.org";
let postgres_url = std::option_env!("KMS_POSTGRES_URL")
.ok_or_else(|| kms_error!("No PostgreSQL database configured"))?;
let pg = Pgsql::instantiate(postgres_url).await?;
pg.clean_database().await;
let uid = Uuid::new_v4().to_string();
// simple insert
pg.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = pg.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
// double insert, expect no duplicate
pg.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = pg.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
// insert other operation type
pg.insert_access(&uid, userid, ObjectOperationTypes::Encrypt)
.await?;
let perms = pg.perms(&uid, userid).await?;
assert_eq!(
perms,
vec![ObjectOperationTypes::Get, ObjectOperationTypes::Encrypt]
);
// insert other `userid2`, check it is ok and it didn't change anything for `userid`
pg.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
let perms = pg.perms(&uid, userid2).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
let perms = pg.perms(&uid, userid).await?;
assert_eq!(
perms,
vec![ObjectOperationTypes::Get, ObjectOperationTypes::Encrypt]
);
// remove `Get` access for `userid`
pg.delete_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = pg.perms(&uid, userid2).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
let perms = pg.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Encrypt]);
Ok(())
}
}

View file

@ -0,0 +1,533 @@
//TODO Split this file in multiple implementations under their operations names
use std::convert::TryFrom;
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_key_utils::WrappedSymmetricKey,
kmip_objects::Object,
kmip_operations::{Create, CreateKeyPair, ErrorReason, Get, GetResponse},
kmip_types::{Attributes, CryptographicAlgorithm, KeyFormatType, RecommendedCurve},
};
use cosmian_mcfe::lwe;
use cosmian_rust_lib::{
crypto::{
abe::{
attributes::{access_policy_from_attributes, header_uid_from_attributes},
ciphers::{AbeHybridCipher, AbeHybridDecipher},
locate::compare_abe_attributes,
master_keys::create_master_keypair,
secret_key::wrapped_secret_key,
},
aes::{create_aes_symmetric_key, AesGcmCipher},
curve_25519::{self},
fpe::FpeCipher,
mcfe::{
mcfe_master_key_from_key_block, mcfe_setup_from_attributes,
secret_data_from_lwe_functional_key, secret_key_from_lwe_master_secret_key,
secret_key_from_lwe_secret_key, setup_from_secret_key, DMcfeDeCipher, DMcfeEnCipher,
FunctionalKeyCreateRequest,
},
tfhe::{self, TFHEKeyCreateRequest},
},
DeCipher, EnCipher, KeyPair,
};
use torus_fhe::{trlwe::TRLWEKey, HasGenerator};
use tracing::{debug, trace};
use super::KMS;
#[cfg(feature = "pgsql")]
use crate::kmip::kmip_server::pgsql::Pgsql;
use crate::{
config::{db_params, DbParams},
error::KmsError,
kmip::kmip_server::{
abe::{create_user_decryption_key, create_user_decryption_key_pair},
database::Database,
mysql::Sql,
pgsql::Pgsql,
server::kmip_server::KmipServer,
sqlite::SqlitePool,
},
kms_error,
result::{KResult, KResultHelper},
};
impl KMS {
pub async fn instantiate() -> KResult<KMS> {
let db: Box<dyn Database + Sync + Send> = match db_params() {
DbParams::Sqlite(db_path) => Box::new(SqlitePool::instantiate(&db_path).await?),
DbParams::Postgres(url) => Box::new(Pgsql::instantiate(&url).await?),
DbParams::Mysql(url) => Box::new(Sql::instantiate(&url).await?),
};
Ok(KMS { db })
}
pub async fn encipher(&self, key_uid: &str, owner: &str) -> KResult<Box<dyn EnCipher>> {
let (object, _state) = self
.db
.retrieve(key_uid, owner, ObjectOperationTypes::Encrypt)
.await?
.with_context(|| format!("Object with uid: {key_uid} not found"))
.reason(ErrorReason::Item_Not_Found)?;
match &object {
Object::SymmetricKey { key_block } => {
match &key_block.key_format_type {
KeyFormatType::AbeSymmetricKey => {
Ok(Box::new(AesGcmCipher::instantiate(key_uid, &object)?)
as Box<dyn EnCipher>)
}
KeyFormatType::TransparentSymmetricKey => {
Ok(Box::new(FpeCipher::instantiate(key_uid, &object)?)
as Box<dyn EnCipher>)
}
KeyFormatType::McfeSecretKey => {
// we need to recover the lwe::Setup parameter
Ok(Box::new(DMcfeEnCipher::instantiate(key_uid, &object)?)
as Box<dyn EnCipher>)
}
KeyFormatType::TFHE => {
Ok(Box::new(tfhe::Cipher::instantiate(key_uid, &object)?))
}
other => Err(kms_error!(
"This server does not yet support decryption with keys of format: {}",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
}
}
Object::PublicKey { key_block } => match &key_block.key_format_type {
KeyFormatType::AbeMasterPublicKey => {
Ok(Box::new(AbeHybridCipher::instantiate(key_uid, &object)?)
as Box<dyn EnCipher>)
}
other => Err(kms_error!(
"This server does not yet support decryption with public keys of format: {}",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
},
other => Err(kms_error!(
"This server does not support encryption with keys of type: {}",
other.object_type()
)
.reason(ErrorReason::Operation_Not_Supported)),
}
}
pub(crate) async fn decipher(
&self,
object_uid: &str,
owner: &str,
) -> KResult<Box<dyn DeCipher>> {
let (object, _state) = self
.db
.retrieve(object_uid, owner, ObjectOperationTypes::Decrypt)
.await?
.with_context(|| format!("Object with uid: {object_uid} not found"))
.reason(ErrorReason::Item_Not_Found)?;
match &object {
Object::SecretData {
key_block,
secret_data_type: _,
} => {
match &key_block.key_format_type {
KeyFormatType::McfeFunctionalKey => {
// we need to recover the lwe::Setup parameter
Ok(Box::new(DMcfeDeCipher::instantiate(object_uid, &object)?))
}
other => Err(kms_error!(
"This server does not yet support decryption with keys of format: {}",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
}
}
Object::PrivateKey { key_block } => match &key_block.key_format_type {
KeyFormatType::AbeUserDecryptionKey => Ok(Box::new(
AbeHybridDecipher::instantiate(object_uid, &object)?,
)),
other => Err(kms_error!(
"This server does not yet support decryption with keys of format: {}",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
},
Object::SymmetricKey { key_block } => match &key_block.key_format_type {
KeyFormatType::AbeSymmetricKey => {
Ok(Box::new(AesGcmCipher::instantiate(object_uid, &object)?))
}
KeyFormatType::TransparentSymmetricKey => {
Ok(Box::new(FpeCipher::instantiate(object_uid, &object)?))
}
KeyFormatType::TFHE => {
Ok(Box::new(tfhe::Cipher::instantiate(object_uid, &object)?))
}
other => Err(kms_error!(
"This server does not yet support decryption with keys of format: {}",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
},
other => Err(kms_error!(
"This server does not support decryption with keys of type: {}",
other.object_type()
)
.reason(ErrorReason::Operation_Not_Supported)),
}
}
pub(crate) async fn create_symmetric_key(
&self,
request: &Create,
owner: &str,
) -> KResult<Object> {
let attributes = &request.attributes;
match &attributes.cryptographic_algorithm {
Some(CryptographicAlgorithm::AES) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create a symmetric key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::AbeSymmetricKey) => {
debug!("Creating ABE symmetric key: {:?}", attributes);
// AB encryption requires master public key.
let abe_master_public_key_id = attributes.get_parent_id().context(
"the attributes must contain the reference to the ABE master public key \
ID when creating a linked symmetric key",
)?;
let public_key_response = self
.get(
Get {
unique_identifier: Some(abe_master_public_key_id.to_string()),
..Get::default()
},
owner,
)
.await?;
let access_policy = access_policy_from_attributes(attributes)?;
let abe_header_uid = header_uid_from_attributes(attributes)?;
wrapped_secret_key(&public_key_response, &access_policy, abe_header_uid)
.map_err(Into::into)
}
Some(KeyFormatType::TransparentSymmetricKey) => {
create_aes_symmetric_key(attributes.cryptographic_length.map(|v| v as usize))
.map_err(Into::into)
}
Some(other) => Err(kms_error!(
"Unable to generate an ABE symmetric key for format: {}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(CryptographicAlgorithm::LWE) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create a secret key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::McfeSecretKey) => {
let setup = mcfe_setup_from_attributes(attributes)?;
let sk = lwe::SecretKey::try_from(&setup)?;
secret_key_from_lwe_secret_key(&setup, &sk).map_err(Into::into)
}
Some(KeyFormatType::McfeFksSecretKey) => Err(kms_error!(
"Generation of Functional Key Shares Secret Keys is not yet supported"
))
.reason(ErrorReason::Feature_Not_Supported),
Some(KeyFormatType::McfeMasterSecretKey) => {
let setup = mcfe_setup_from_attributes(attributes)?;
let msk = lwe::MasterSecretKey::try_from(&setup)?;
secret_key_from_lwe_master_secret_key(&setup, msk.as_slice())
.map_err(Into::into)
}
Some(other) => Err(kms_error!(
"Unable to generate an LWE secret key for format: {}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(CryptographicAlgorithm::TFHE) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create a secret key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::TFHE) => {
let request = TFHEKeyCreateRequest::try_from(attributes)?;
let key = match request.pregenerated_key {
None => {
//*** Security Parameter
//
// Vector size
use torus_fhe::typenum::{U1023, U512};
type N = U512;
const N: usize = 512;
//*** LUT Parameters
type D = U1023;
const D: usize = 1023;
match (request.vector_size, request.d) {
(N, D) => TRLWEKey::<N, D>::gen(),
_ => {
return Err(kms_error!(
"cosmian server has no rules to process vector_size \
{request.vector_size}, d {request.d}"
)
.reason(ErrorReason::Invalid_Message))
}
}
}
Some(key) => key,
};
let key_bytes = serde_json::to_vec(&key)?;
Ok(Object::SymmetricKey {
key_block: tfhe::array_to_key_block(
&key_bytes,
attributes.clone(),
KeyFormatType::TFHE,
),
})
}
Some(other) => Err(kms_error!(
"Unable to generate an LWE secret key for format: {:?}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(other) => Err(kms_error!(
"The creation of secret key for algorithm: {:?} is not supported",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
None => Err(kms_error!(
"The cryptographic algorithm must be specified for secret key creation"
))
.reason(ErrorReason::Feature_Not_Supported),
}
}
pub(crate) async fn create_secret_data(
&self,
request: &Create,
owner: &str,
) -> KResult<Object> {
let attributes = &request.attributes;
match &attributes.cryptographic_algorithm {
Some(CryptographicAlgorithm::LWE) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create a secret key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::McfeFunctionalKey) => {
let request = FunctionalKeyCreateRequest::try_from(attributes)?;
let (object, _state) = self
.db
.retrieve(
&request.master_secret_key_uid,
owner,
ObjectOperationTypes::Create,
)
.await?
.with_context(|| {
format!(
"Object with uid: {} and owner: {owner} not found",
request.master_secret_key_uid
)
})
.reason(ErrorReason::Item_Not_Found)?;
if let Object::SymmetricKey { key_block } = &object {
if key_block.key_format_type == KeyFormatType::McfeMasterSecretKey {
let msk = mcfe_master_key_from_key_block(key_block)?;
let setup =
setup_from_secret_key(&request.master_secret_key_uid, key_block)?;
let parameters = lwe::Parameters::instantiate(&setup)?;
let fk = parameters.functional_key(&msk, &request.vectors)?;
secret_data_from_lwe_functional_key(&setup, &fk).map_err(Into::into)
} else {
Err(kms_error!(
"Generation of Functional Key failed. The given uid is not that \
of a Master Secret Key"
))
.reason(ErrorReason::Invalid_Message)
}
} else {
Err(kms_error!(
"Generation of Functional Key failed. The given uid is not that of a \
Master Secret Key"
))
.reason(ErrorReason::Invalid_Object_Type)
}
}
Some(other) => Err(kms_error!(
"Unable to generate an LWE secret key for format: {:?}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(other) => Err(kms_error!(
"The creation of secret data for algorithm: {:?} is not supported",
other
)
.reason(ErrorReason::Operation_Not_Supported)),
None => Err(kms_error!(
"The cryptographic algorithm must be specified for secret data creation"
))
.reason(ErrorReason::Feature_Not_Supported),
}
}
pub(crate) async fn create_private_key(
&self,
create_request: &Create,
owner: &str,
) -> KResult<Object> {
trace!("Internal create private key");
let attributes = &create_request.attributes;
match &attributes.cryptographic_algorithm {
Some(CryptographicAlgorithm::ABE) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create an ABE key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::AbeUserDecryptionKey) => {
trace!("Creating ABE user decryption key");
create_user_decryption_key(self, create_request, owner).await
}
Some(other) => Err(kms_error!(
"Unable to generate an ABE private key for format: {:?}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(other) => Err(kms_error!(
"The creation of a private key for algorithm: {:?} is not supported",
other
))
.reason(ErrorReason::Operation_Not_Supported),
None => Err(kms_error!(
"The cryptographic algorithm must be specified for private key creation"
))
.reason(ErrorReason::Feature_Not_Supported),
}
}
pub(crate) async fn create_key_pair_(
&self,
request: &CreateKeyPair,
owner: &str,
) -> KResult<KeyPair> {
trace!("Internal create key pair");
let attributes = request
.common_attributes
.as_ref()
.or(request.private_key_attributes.as_ref())
.or(request.public_key_attributes.as_ref())
.ok_or_else(|| {
KmsError::ServerError(
"Attributes must be provided in a CreateKeyPair request".to_owned(),
)
})?;
match &attributes.cryptographic_algorithm {
Some(CryptographicAlgorithm::EC) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create a EC key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::ECPrivateKey) => {
let dp = attributes
.cryptographic_domain_parameters
.unwrap_or_default();
match dp.recommended_curve.unwrap_or_default() {
RecommendedCurve::CURVE25519 => {
curve_25519::generate_key_pair().map_err(Into::into)
}
other => Err(kms_error!(
"Generation of Key Pair for curve: {:?}, is not supported",
other
))
.reason(ErrorReason::Operation_Not_Supported),
}
}
Some(other) => Err(kms_error!(
"Unable to generate an DH keypair for format: {}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(CryptographicAlgorithm::ABE) => match attributes.key_format_type {
None => Err(kms_error!(
"Unable to create an ABE key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::AbeMasterSecretKey) => {
create_master_keypair(request).map_err(Into::into)
}
Some(KeyFormatType::AbeUserDecryptionKey) => {
create_user_decryption_key_pair(self, request, owner).await
}
Some(other) => Err(kms_error!(
"Unable to generate an ABE keypair for format: {:?}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(other) => Err(kms_error!(
"The creation of a key pair for algorithm: {:?} is not supported",
other
))
.reason(ErrorReason::Operation_Not_Supported),
None => Err(kms_error!(
"The cryptographic algorithm must be specified for key pair creation"
))
.reason(ErrorReason::Feature_Not_Supported),
}
}
}
pub(crate) fn contains_attributes(
researched_attributes: &Attributes,
kmip_response: &GetResponse,
) -> KResult<bool> {
let key_block = kmip_response.object.key_block()?;
let object_attributes = match &key_block.key_wrapping_data {
Some(_) => {
let wrapped_symmetric_key =
WrappedSymmetricKey::try_from(&key_block.key_value.raw_bytes()?)?;
wrapped_symmetric_key.attributes()
}
None => key_block.key_value.attributes()?.clone(),
};
match &researched_attributes.cryptographic_algorithm {
Some(CryptographicAlgorithm::ABE) => match researched_attributes.key_format_type {
None => Err(kms_error!(
"Unable to locate an ABE key, the format type is not specified"
))
.reason(ErrorReason::Invalid_Message),
Some(KeyFormatType::AbeUserDecryptionKey) => {
compare_abe_attributes(&object_attributes, researched_attributes)
.map_err(Into::into)
}
Some(other) => Err(kms_error!(
"Unable to locate an ABE keypair for format: {:?}",
other
))
.reason(ErrorReason::Invalid_Message),
},
Some(other) => Err(kms_error!(
"The locate of an object for algorithm: {:?} is not yet supported",
other
))
.reason(ErrorReason::Operation_Not_Supported),
None => Err(kms_error!(
"The cryptographic algorithm must be specified for object location"
))
.reason(ErrorReason::Feature_Not_Supported),
}
}

View file

@ -0,0 +1,836 @@
use async_trait::async_trait;
use cosmian_kms_common::kmip::{
access::{Access, ObjectOperationTypes},
kmip_data_structures::KeyValue,
kmip_objects::{Object, ObjectType},
kmip_operations::{
Create, CreateKeyPair, CreateKeyPairResponse, CreateResponse, Decrypt, DecryptResponse,
Destroy, DestroyResponse, Encrypt, EncryptResponse, ErrorReason, Get, GetAttributes,
GetAttributesResponse, GetResponse, Import, ImportResponse, Locate, LocateResponse,
ReKeyKeyPair, ReKeyKeyPairResponse, Revoke, RevokeResponse,
},
kmip_types::{
AttributeReference, Attributes, CryptographicAlgorithm, Link, LinkType,
LinkedObjectIdentifier, RevocationReason, RevocationReasonEnumeration, StateEnumeration,
Tag, UniqueIdentifier,
},
};
use tracing::{debug, trace, warn};
use uuid::Uuid;
use super::KMS;
use crate::{
error::KmsError,
kmip::kmip_server::{abe::rekey_keypair_abe, server::implementation::contains_attributes},
kms_error,
result::{KResult, KResultHelper},
};
#[async_trait]
pub trait KmipServer {
/// This operation requests the server to Import a Managed Object specified
/// by its Unique Identifier. The request specifies the object being
/// imported and all the attributes to be assigned to the object. The
/// attribute rules for each attribute for “Initially set by” and “When
/// implicitly set” SHALL NOT be enforced as all attributes MUST be set
/// to the supplied values rather than any server generated values.
/// The response contains the Unique Identifier provided in the request or
/// assigned by the server. The server SHALL copy the Unique Identifier
/// returned by this operations into the ID Placeholder variable.
async fn import(&self, request: Import, owner: &str) -> KResult<ImportResponse>;
/// This operation requests the server to generate a new symmetric key or
/// generate Secret Data as a Managed Cryptographic Object.
/// The request contains information about the type of object being created,
/// and some of the attributes to be assigned to the object (e.g.,
/// Cryptographic Algorithm, Cryptographic Length, etc.). The response
/// contains the Unique Identifier of the created object. The server SHALL
/// copy the Unique Identifier returned by this operation into the ID
/// Placeholder variable.
async fn create(&self, request: Create, owner: &str) -> KResult<CreateResponse>;
/// This operation requests the server to generate a new public/private key
/// pair and register the two corresponding new Managed Cryptographic Object
/// The request contains attributes to be assigned to the objects (e.g.,
/// Cryptographic Algorithm, Cryptographic Length, etc.). Attributes MAY
/// be specified for both keys at the same time by specifying a Common
/// Attributes object in the request. Attributes not common to both keys
/// (e.g., Name, Cryptographic Usage Mask) MAY be specified
/// using the Private Key Attributes and Public Key Attributes objects in
/// the request, which take precedence over the Common Attributes object.
/// For the Private Key, the server SHALL create a Link attribute of Link
/// Type Public Key pointing to the Public Key. For the Public Key, the
/// server SHALL create a Link attribute of Link Type Private Key pointing
/// to the Private Key. The response contains the Unique Identifiers of
/// both created objects. The ID Placeholder value SHALL be set to the
/// Unique Identifier of the Private Key
async fn create_key_pair(
&self,
request: CreateKeyPair,
owner: &str,
) -> KResult<CreateKeyPairResponse>;
/// This operation requests that the server returns the Managed Object
/// specified by its Unique Identifier. Only a single object is
/// returned. The response contains the Unique Identifier of the object,
/// along with the object itself, which MAY be wrapped using a wrapping
/// key as specified in the request. The following key format
/// capabilities SHALL be assumed by the client; restrictions apply when the
/// client requests the server to return an object in a particular
/// format: • If a client registered a key in a given format, the server
/// SHALL be able to return the key during the Get operation in the same
/// format that was used when the key was registered. • Any other format
/// conversion MAY be supported by the server. If Key Format Type is
/// specified to be PKCS#12 then the response payload shall be a PKCS#12
/// container as specified by [RFC7292]. The Unique Identifier shall be
/// either that of a private key or certificate to be included in the
/// response. The container shall be protected using the Secret Data object
/// specified via the private key or certificates PKCS#12 Password
/// Link. The current certificate chain shall also be included
/// as determined by using the private keys Public Key link to get the
/// corresponding public key (where relevant), and then using that
/// public keys PKCS#12 Certificate Link to get the base certificate, and
/// then using each certificates Ce
async fn get(&self, request: Get, owner: &str) -> KResult<GetResponse>;
/// This operation requests one or more attributes associated with a Managed
/// Object. The object is specified by its Unique Identifier, and the
/// attributes are specified by their name in the request. If a specified
/// attribute has multiple instances, then all instances are returned. If a
/// specified attribute does not exist (i.e., has no value), then it
/// SHALL NOT be present in the returned response. If none of the requested
/// attributes exist, then the response SHALL consist only of the Unique
/// Identifier. The same Attribute Reference SHALL NOT be present more
/// than once in a request. If no Attribute Reference is provided, the
/// server SHALL return all attributes.
async fn get_attributes(
&self,
request: GetAttributes,
owner: &str,
) -> KResult<GetAttributesResponse>;
/// This operation requests the server to perform an encryption operation on
/// the provided data using a Managed Cryptographic Object as the key
/// for the encryption operation.
///
/// The request contains information about the cryptographic parameters
/// (mode and padding method), the data to be encrypted, and the
/// IV/Counter/Nonce to use. The cryptographic parameters MAY be omitted
/// from the request as they can be specified as associated attributes of
/// the Managed Cryptographic Object.
///
/// The IV/Counter/Nonce MAY also be omitted from the request if the
/// cryptographic parameters indicate that the server shall generate a
/// Random IV on behalf of the client or the encryption algorithm does not
/// need an IV/Counter/Nonce. The server does not store or otherwise
/// manage the IV/Counter/Nonce.
///
/// If the Managed Cryptographic Object referenced has a Usage Limits
/// attribute then the server SHALL obtain an allocation from the
/// current Usage Limits value prior to performing the encryption operation.
/// If the allocation is unable to be obtained the operation SHALL
/// return with a result status of Operation Failed and result reason of
/// Permission Denied.
///
/// The response contains the Unique Identifier of the Managed Cryptographic
/// Object used as the key and the result of the encryption operation.
///
/// The success or failure of the operation is indicated by the Result
/// Status (and if failure the Result Reason) in the response header.
async fn encrypt(&self, request: Encrypt, owner: &str) -> KResult<EncryptResponse>;
/// This operation requests the server to perform a decryption operation on
/// the provided data using a Managed Cryptographic Object as the key
/// for the decryption operation.
///
/// The request contains information about the cryptographic parameters
/// (mode and padding method), the data to be decrypted, and the
/// IV/Counter/Nonce to use. The cryptographic parameters MAY be omitted
/// from the request as they can be specified as associated attributes of
/// the Managed Cryptographic Object.
///
/// The initialization vector/counter/nonce MAY also be omitted from the
/// request if the algorithm does not use an IV/Counter/Nonce.
///
/// The response contains the Unique Identifier of the Managed Cryptographic
/// Object used as the key and the result of the decryption operation.
///
/// The success or failure of the operation is indicated by the Result
/// Status (and if failure the Result Reason) in the response header.
async fn decrypt(&self, request: Decrypt, owner: &str) -> KResult<DecryptResponse>;
/// This operation requests that the server search for one or more Managed
/// Objects, depending on the attributes specified in the request. All
/// attributes are allowed to be used. The request MAY contain a Maximum
/// Items field, which specifies the maximum number of objects to be
/// returned. If the Maximum Items field is omitted, then the server MAY
/// return all objects matched, or MAY impose an internal maximum limit due
/// to resource limitations.
///
/// The request MAY contain an Offset Items field, which specifies the
/// number of objects to skip that satisfy the identification criteria
/// specified in the request. An Offset Items field of 0 is the same as
/// omitting the Offset Items field. If both Offset Items and Maximum Items
/// are specified in the request, the server skips Offset Items objects and
/// returns up to Maximum Items objects.
///
/// If more than one object satisfies the identification criteria specified
/// in the request, then the response MAY contain Unique Identifiers for
/// multiple Managed Objects. Responses containing Unique Identifiers for
/// multiple objects SHALL be returned in descending order of object
/// creation (most recently created object first). Returned objects SHALL
/// match all of the attributes in the request. If no objects match, then an
/// empty response payload is returned. If no attribute is specified in the
/// request, any object SHALL be deemed to match the Locate request. The
/// response MAY include Located Items which is the count of all objects
/// that satisfy the identification criteria.
///
/// The server returns a list of Unique Identifiers of the found objects,
/// which then MAY be retrieved using the Get operation. If the objects are
/// archived, then the Recover and Get operations are REQUIRED to be used to
/// obtain those objects. If a single Unique Identifier is returned to the
/// client, then the server SHALL copy the Unique Identifier returned by
/// this operation into the ID Placeholder variable. If the Locate
/// operation matches more than one object, and the Maximum Items value is
/// omitted in the request, or is set to a value larger than one, then the
/// server SHALL empty the ID Placeholder, causing any subsequent operations
/// that are batched with the Locate, and which do not specify a Unique
/// Identifier explicitly, to fail. This ensures that these batched
/// operations SHALL proceed only if a single object is returned by Locate.
///
/// The Date attributes in the Locate request (e.g., Initial Date,
/// Activation Date, etc.) are used to specify a time or a time range for
/// the search. If a single instance of a given Date attribute is used in
/// the request (e.g., the Activation Date), then objects with the same Date
/// attribute are considered to be matching candidate objects. If two
/// instances of the same Date attribute are used (i.e., with two different
/// values specifying a range), then objects for which the Date attribute is
/// inside or at a limit of the range are considered to be matching
/// candidate objects. If a Date attribute is set to its largest possible
/// value, then it is equivalent to an undefined attribute. The KMIP Usage
/// Guide [KMIP-UG] provides examples.
///
/// When the Cryptographic Usage Mask attribute is specified in the request,
/// candidate objects are compared against this field via an operation that
/// consists of a logical AND of the requested mask with the mask in the
/// candidate object, and then a comparison of the resulting value with the
/// requested mask. For example, if the request contains a mask value of
/// 10001100010000, and a candidate object mask contains 10000100010000,
/// then the logical AND of the two masks is 10000100010000, which is
/// compared against the mask value in the request (10001100010000) and the
/// match fails. This means that a matching candidate object has all of the
/// bits set in its mask that are set in the requested mask, but MAY have
/// additional bits set.
///
/// When the Usage Limits attribute is specified in the request, matching
/// candidate objects SHALL have a Usage Limits Count and Usage Limits Total
/// equal to or larger than the values specified in the request.
///
/// When an attribute that is defined as a structure is specified, all of
/// the structure fields are not REQUIRED to be specified. For instance, for
/// the Link attribute, if the Linked Object Identifier value is specified
/// without the Link Type value, then matching candidate objects have the
/// Linked Object Identifier as specified, irrespective of their Link Type.
///
/// When the Object Group attribute and the Object Group Member flag are
/// specified in the request, and the value specified for Object Group
/// Member is Group Member Fresh, matching candidate objects SHALL be
/// fresh objects from the object group. If there are no more fresh objects
/// in the group, the server MAY choose to generate a new object on-the-fly,
/// based on server policy. If the value specified for Object Group Member
/// is Group Member Default, the server locates the default object as
/// defined by server policy.
///
/// The Storage Status Mask field is used to indicate whether on-line
/// objects (not archived or destroyed), archived objects, destroyed objects
/// or any combination of the above are to be searched.The server SHALL NOT
/// return unique identifiers for objects that are destroyed unless the
/// Storage Status Mask field includes the Destroyed Storage indicator. The
/// server SHALL NOT return unique identifiers for objects that are archived
/// unless the Storage Status Mask field includes the Archived Storage
/// indicator.
async fn locate(&self, request: Locate, owner: &str) -> KResult<LocateResponse>;
/// This operation requests the server to revoke a Managed Cryptographic
/// Object or an Opaque Object. The request contains a reason for the
/// revocation (e.g., “key compromise”, “cessation of operation”, etc.). The
/// operation has one of two effects. If the revocation reason is “key
/// compromise” or “CA compromise”, then the object is placed into the
/// “compromised” state; the Date is set to the current date and time; and
/// the Compromise Occurrence Date is set to the value (if provided) in the
/// Revoke request and if a value is not provided in the Revoke request then
/// Compromise Occurrence Date SHOULD be set to the Initial Date for the
/// object. If the revocation reason is neither “key compromise” nor “CA
/// compromise”, the object is placed into the “deactivated” state, and the
/// Deactivation Date is set to the current date and time.
async fn revoke(&self, request: Revoke, owner: &str) -> KResult<RevokeResponse>;
// This request is used to generate a replacement key pair for an existing
// public/private key pair. It is analogous to the Create Key Pair operation,
// except that attributes of the replacement key pair are copied from the
// existing key pair, with the exception of the attributes listed in Re-key Key
// Pair Attribute Requirements tor.
//
// As the replacement of the key pair takes over the name attribute for the
// existing public/private key pair, Re-key Key Pair SHOULD only be performed
// once on a given key pair.
//
// For both the existing public key and private key, the server SHALL create a
// Link attribute of Link Type Replacement Key pointing to the replacement
// public and private key, respectively. For both the replacement public and
// private key, the server SHALL create a Link attribute of Link Type Replaced
// Key pointing to the existing public and private key, respectively.
//
// The server SHALL copy the Private Key Unique Identifier of the replacement
// private key returned by this operation into the ID Placeholder variable.
//
// An Offset MAY be used to indicate the difference between the Initial Date and
// the Activation Date of the replacement key pair. If no Offset is specified,
// the Activation Date and Deactivation Date values are copied from the existing
// key pair. If Offset is set and dates exist for the existing key pair, then
// the dates of the replacement key pair SHALL be set based on the dates of the
// existing key pair as follows
async fn rekey_keypair(
&self,
request: ReKeyKeyPair,
owner: &str,
) -> KResult<ReKeyKeyPairResponse>;
/// This operation is used to indicate to the server that the key material
/// for the specified Managed Object SHALL be destroyed or rendered
/// inaccessible. The meta-data for the key material SHALL be retained by
/// the server. Objects SHALL only be destroyed if they are in either
/// Pre-Active or Deactivated state.
async fn destroy(&self, request: Destroy, owner: &str) -> KResult<DestroyResponse>;
/// Insert an access authorization for a user (identified by `access.userid`)
/// to an object (identified by `access.unique_identifier`)
/// which is owned by `owner` (identified by `access.owner`)
async fn insert_access(&self, access: &Access, owner: &str) -> KResult<()>;
/// Remove an access authorization for a user (identified by `access.userid`)
/// to an object (identified by `access.unique_identifier`)
/// which is owned by `owner` (identified by `access.owner`)
async fn delete_access(&self, access: &Access, owner: &str) -> KResult<()>;
}
/// Implement the KMIP Server Trait and dispatches the actual actions
/// to the implementation module or ciphers for encryption/decryption
#[async_trait]
impl KmipServer for KMS {
async fn import(&self, request: Import, owner: &str) -> KResult<ImportResponse> {
let mut object = request.object;
match &mut object {
Object::PrivateKey { key_block }
| Object::PublicKey { key_block }
| Object::SymmetricKey { key_block } => match &key_block.key_value {
KeyValue::PlainText { key_material, .. } => {
// replace attributes
key_block.key_value = KeyValue::PlainText {
key_material: key_material.clone(),
attributes: Some(request.attributes),
};
}
KeyValue::Wrapped(wrapped_key) => {
key_block.key_value = KeyValue::Wrapped(wrapped_key.clone())
}
},
x => {
//TODO keep attributes as separate column in DB
warn!("Attributes are not yet supported for objects of type : {x}")
}
}
//TODO no support for wrapped stuff for now
let wrapped = match &object {
Object::PrivateKey { key_block } | Object::PublicKey { key_block } => {
key_block.key_wrapping_data.as_ref()
}
_ => None,
};
if wrapped.is_some() {
return Err(kms_error!("This server does not yet support wrapped keys"))
}
let replace_existing = if let Some(v) = request.replace_existing {
v
} else {
false
};
let uid = if replace_existing {
debug!(
"Upserting object of type: {}, with uid: {}",
request.object_type, request.unique_identifier
);
self.db
.upsert(
&request.unique_identifier,
owner,
&object,
StateEnumeration::Active,
)
.await?;
request.unique_identifier
} else {
debug!("Inserting object of type: {}", request.object_type);
let id = if request.unique_identifier.is_empty() {
None
} else {
Some(request.unique_identifier)
};
self.db.create(id, owner, &object).await?
};
Ok(ImportResponse {
unique_identifier: uid,
})
}
async fn create(&self, request: Create, owner: &str) -> KResult<CreateResponse> {
trace!("Create: {}", serde_json::to_string(&request)?);
if request.protection_storage_masks.is_some() {
return Err(KmsError::ServerError(
"This server does not support protection masks".to_owned(),
)
.reason(ErrorReason::Protection_Storage_Unavailable))
}
let object = match &request.object_type {
ObjectType::SymmetricKey => self.create_symmetric_key(&request, owner).await?,
ObjectType::SecretData => self.create_secret_data(&request, owner).await?,
&ObjectType::PrivateKey => self.create_private_key(&request, owner).await?,
_ => {
return Err(kms_error!(
"This server does not yet support creation of: {object_type}"
))
.reason(ErrorReason::Feature_Not_Supported)
}
};
let uid = self.db.create(None, owner, &object).await?;
debug!(
"Created KMS Object of type {:?} with id {uid}",
&object.object_type(),
);
Ok(CreateResponse {
object_type: request.object_type,
unique_identifier: uid,
})
}
async fn create_key_pair(
&self,
request: CreateKeyPair,
owner: &str,
) -> KResult<CreateKeyPairResponse> {
trace!("Create key pair: {}", serde_json::to_string(&request)?);
if request.common_protection_storage_masks.is_some()
|| request.private_protection_storage_masks.is_some()
|| request.public_protection_storage_masks.is_some()
{
return Err(KmsError::ServerError(
"This server does not support protection masks".to_owned(),
)
.reason(ErrorReason::Protection_Storage_Unavailable))
}
let sk_uid = Uuid::new_v4().to_string();
let pk_uid = Uuid::new_v4().to_string();
let (sk, pk) = self.create_key_pair_(&request, owner).await?.0;
// start a transaction
// let mut conn = self.db.get_connection()?;
// let tx = self.db.transaction(&mut conn)?;
// let sk_uid = self.db.create_tx(None, &sk, &tx)?;
// let pk_uid = self.db.create_tx(None, &pk, &tx)?;
trace!("create_key_pair: sk_uid: {sk_uid}, pk_uid: {pk_uid}");
//TODO now that the uid is no more generated
//TODO move the update link code out ard create links before DB inserts
// now update the public key links
let mut pk_key_block = match &pk {
Object::PublicKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Public Key".to_owned(),
))
}
};
let (key_material, attributes) = pk_key_block.key_value.plaintext().ok_or_else(|| {
KmsError::ServerError(
"This should never happen. It must be a plain text key value".to_owned(),
)
.reason(ErrorReason::Internal_Server_Error)
})?;
let mut attr = attributes.clone().ok_or_else(|| {
KmsError::ServerError("This should never happen. There should be attributes".to_owned())
.reason(ErrorReason::Internal_Server_Error)
})?;
attr.link = vec![Link {
link_type: LinkType::PrivateKeyLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(sk_uid.clone()),
}];
pk_key_block.key_value = KeyValue::PlainText {
key_material: key_material.clone(),
attributes: Some(attr),
};
let public_key = Object::PublicKey {
key_block: pk_key_block,
};
// self.db.update_tx(
// &pk_uid,
// &Object::PublicKey {
// key_block: pk_key_block,
// },
// &tx,
// )?;
// now update the private key links
let mut sk_key_block = match &sk {
Object::PrivateKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Private Key".to_owned(),
))
}
};
let (key_material, attributes) = sk_key_block.key_value.plaintext().ok_or_else(|| {
KmsError::ServerError(
"This should never happen. It must be a plain text key value".to_owned(),
)
.reason(ErrorReason::Internal_Server_Error)
})?;
trace!("Create private key link OK");
let mut attr = attributes.clone().ok_or_else(|| {
KmsError::ServerError("This should never happen. There should be attributes".to_owned())
.reason(ErrorReason::Internal_Server_Error)
})?;
attr.link = vec![Link {
link_type: LinkType::PublicKeyLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(pk_uid.clone()),
}];
sk_key_block.key_value = KeyValue::PlainText {
key_material: key_material.clone(),
attributes: Some(attr),
};
let private_key = Object::PrivateKey {
key_block: sk_key_block,
};
self.db
.create_objects(
owner,
&[
(Some(sk_uid.clone()), private_key),
(Some(pk_uid.clone()), public_key),
],
)
.await?;
// debug!("Created key pair: {}/{}", &sk_uid, &pk_uid);
Ok(CreateKeyPairResponse {
private_key_unique_identifier: sk_uid,
public_key_unique_identifier: pk_uid,
})
}
async fn get(&self, request: Get, owner: &str) -> KResult<GetResponse> {
trace!("Get: {}", serde_json::to_string(&request)?);
let uid = request
.unique_identifier
.as_ref()
.context("This KMIP server does not yet support place holder id")?;
trace!("retrieving KMIP Object with id: {uid}");
let (object, _state) = self
.db
.retrieve(uid, owner, ObjectOperationTypes::Get)
.await?
.with_context(|| format!("Object with uid: {uid} not found"))
.reason(ErrorReason::Item_Not_Found)?;
debug!("Retrieved Object: {} with id {uid}", &object.object_type());
Ok(GetResponse {
object_type: object.object_type(),
unique_identifier: uid.clone(),
object,
})
}
async fn get_attributes(
&self,
request: GetAttributes,
owner: &str,
) -> KResult<GetAttributesResponse> {
trace!("Get attributes: {}", serde_json::to_string(&request)?);
let uid = request
.unique_identifier
.as_ref()
.context("This server does not yet support place holder id")?;
trace!("retrieving attributes of KMIP Object with id: {uid}");
let (object, _state) = self
.db
.retrieve(uid, owner, ObjectOperationTypes::Get)
.await?
.with_context(|| format!("Object with uid: {uid} not found"))
.reason(ErrorReason::Item_Not_Found)?;
let object_type = object.object_type();
let attributes = match object {
Object::PrivateKey { key_block }
| Object::PublicKey { key_block }
| Object::PGPKey { key_block, .. }
| Object::SymmetricKey { key_block } => Some(key_block.key_value),
_ => None,
}
.and_then(|kv| match kv {
KeyValue::Wrapped(_) => None, // we do not handle these for now
KeyValue::PlainText { attributes, .. } => attributes,
})
.with_context(|| format!("No attributes found on object with: {uid}"))
.reason(ErrorReason::Attribute_Not_Found)?;
let req_attributes = match &request.attribute_references {
None => {
return Ok(GetAttributesResponse {
unique_identifier: uid.clone(),
attributes,
})
}
Some(attrs) => attrs,
};
let mut res = Attributes::new(object_type);
for requested in req_attributes {
match requested {
AttributeReference::Vendor(req_vdr_attr) => {
if let Some(vdr_attrs) = attributes.vendor_attributes.as_ref() {
let mut list = res.vendor_attributes.as_ref().unwrap_or(&vec![]).clone();
vdr_attrs
.iter()
.filter(|attr| {
attr.vendor_identification == req_vdr_attr.vendor_identification
&& attr.attribute_name == req_vdr_attr.attribute_name
})
.for_each(|vdr_attr| {
list.push(vdr_attr.clone());
});
if !list.is_empty() {
res.vendor_attributes = Some(list);
}
}
}
AttributeReference::Standard(tag) => match tag {
Tag::ActivationDate => {
res.activation_date = attributes.activation_date;
}
Tag::CryptographicAlgorithm => {
res.cryptographic_algorithm = attributes.cryptographic_algorithm;
}
Tag::CryptographicLength => {
res.cryptographic_length = attributes.cryptographic_length;
}
Tag::CryptographicParameters => {
res.cryptographic_parameters = attributes.cryptographic_parameters.clone();
}
Tag::CryptographicUsageMask => {
res.cryptographic_usage_mask = attributes.cryptographic_usage_mask;
}
Tag::KeyFormatType => {
res.key_format_type = attributes.key_format_type;
}
_ => {}
},
}
}
debug!("Retrieved Attributes for object {uid}: {:?}", res);
Ok(GetAttributesResponse {
unique_identifier: uid.clone(),
attributes: res,
})
}
async fn encrypt(&self, request: Encrypt, owner: &str) -> KResult<EncryptResponse> {
// 1 - check correlation //TODO
// 2b - if correlation pull encrypt oracle from cache
// 2a - if no correlation, create encrypt oracle
// 3 - call EncryptOracle.encrypt
trace!("encrypt : {}", serde_json::to_string(&request)?);
let uid = request
.unique_identifier
.as_ref()
.context("This server does not yet support place holder id")?;
self.encipher(uid, owner)
.await?
.encrypt(&request)
.map_err(Into::into)
}
async fn decrypt(&self, request: Decrypt, owner: &str) -> KResult<DecryptResponse> {
trace!("Decrypt: {:?}", &request.unique_identifier);
let uid = request
.unique_identifier
.as_ref()
.context("This server does not yet support place holder id")?;
self.decipher(uid, owner)
.await?
.decrypt(&request)
.map_err(Into::into)
}
async fn locate(&self, request: Locate, owner: &str) -> KResult<LocateResponse> {
let objects = self.db.list(owner).await?;
trace!("Locate database contains {} objects", objects.len());
let mut uids = Vec::<UniqueIdentifier>::new();
for (uid, state) in objects {
if state != StateEnumeration::Active {
debug!("Ignoring object: {uid} in state {state}");
continue
}
let gr = self.get(Get::from(&uid), owner).await?;
if gr.object_type != request.attributes.object_type {
trace!("Skipping object with type: {}", gr.object_type);
continue
}
let attribute_found = contains_attributes(&request.attributes, &gr)?;
if attribute_found {
trace!("Adding object: {} {}", gr.object_type, gr.unique_identifier);
uids.push(gr.unique_identifier);
} else {
trace!(
"Ignoring object: {} {}",
gr.object_type,
gr.unique_identifier
);
}
}
let unique_identifiers = if uids.is_empty() {
None
} else {
Some(uids.clone())
};
let response = LocateResponse {
located_items: Some(uids.len() as i32),
unique_identifiers,
};
Ok(response)
}
async fn revoke(&self, request: Revoke, owner: &str) -> KResult<RevokeResponse> {
//TODO http://gitlab.cosmian.com/core/cosmian_server/-/issues/131 Reasons should be kept
let uid = request.unique_identifier.ok_or_else(|| {
KmsError::ServerError("This server does not yet support placeholder IDs".to_owned())
})?;
let state = match request.revocation_reason {
RevocationReason::Enumeration(e) => match e {
RevocationReasonEnumeration::Unspecified
| RevocationReasonEnumeration::AffiliationChanged
| RevocationReasonEnumeration::Superseded
| RevocationReasonEnumeration::CessationOfOperation
| RevocationReasonEnumeration::PrivilegeWithdrawn => StateEnumeration::Deactivated,
RevocationReasonEnumeration::KeyCompromise
| RevocationReasonEnumeration::CACompromise => {
if request.compromise_occurrence_date.is_none() {
return Err(kms_error!(
"A compromise date must be supplied in case of compromised object"
))
.reason(ErrorReason::Invalid_Message)
}
StateEnumeration::Compromised
}
},
RevocationReason::TextString(_) => StateEnumeration::Deactivated,
};
self.db.update_state(&uid, owner, state).await?;
Ok(RevokeResponse {
unique_identifier: uid,
})
}
async fn rekey_keypair(
&self,
request: ReKeyKeyPair,
owner: &str,
) -> KResult<ReKeyKeyPairResponse> {
trace!("Internal rekey key pair");
let private_key_unique_identifier = request
.private_key_unique_identifier
.as_ref()
.ok_or_else(|| {
kms_error!(
"Rekey keypair: ID place holder is not yet supported an a key ID must be \
supplied"
)
})?;
let attributes = request
.private_key_attributes
.as_ref()
.ok_or_else(|| {
KmsError::ServerError(
"Rekey keypair: the private key attributes must be supplied".to_owned(),
)
})
.reason(ErrorReason::Invalid_Message)?;
match &attributes.cryptographic_algorithm {
Some(CryptographicAlgorithm::ABE) => {
rekey_keypair_abe(self, private_key_unique_identifier, attributes, owner).await
}
Some(other) => Err(kms_error!(
"The rekey of a key pair for algorithm: {:?} is not yet supported",
other
))
.reason(ErrorReason::Operation_Not_Supported),
None => Err(kms_error!(
"The cryptographic algorithm must be specified in the private key attributes for \
key pair creation"
)),
}
}
async fn destroy(&self, request: Destroy, owner: &str) -> KResult<DestroyResponse> {
let uid = request.unique_identifier.ok_or_else(|| {
KmsError::ServerError("This server does not yet support placeholder IDs".to_owned())
})?;
self.db
.update_state(&uid, owner, StateEnumeration::Destroyed)
.await?;
Ok(DestroyResponse {
unique_identifier: uid,
})
}
async fn insert_access(&self, access: &Access, owner: &str) -> KResult<()> {
let uid = access.unique_identifier.as_ref().ok_or_else(|| {
KmsError::ServerError("This server does not yet support placeholder IDs".to_owned())
})?;
// check the object identified by its `uid` is really owned by `owner`
if self.db.is_object_owned_by(uid, owner).await? {
return Err(kms_error!(
"Object with uid `{uid}` is not owned by owner `{owner}`"
))
}
self.db
.insert_access(uid, &access.userid, access.operation_type)
.await?;
Ok(())
}
async fn delete_access(&self, access: &Access, owner: &str) -> KResult<()> {
let uid = access.unique_identifier.as_ref().ok_or_else(|| {
KmsError::ServerError("This server does not yet support placeholder IDs".to_owned())
})?;
// check the object identified by its `uid` is really owned by `owner`
if self.db.is_object_owned_by(uid, owner).await? {
return Err(kms_error!(
"Object with uid `{uid}` is not owned by owner `{owner}`"
))
}
self.db
.delete_access(uid, &access.userid, access.operation_type)
.await?;
Ok(())
}
}

View file

@ -0,0 +1,10 @@
pub(crate) mod implementation;
pub mod kmip_server;
use super::database::Database;
/// A Simple Key Management System that partially implements KMIP 2.1:
/// https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=kmip
pub struct KMS {
db: Box<dyn Database + Sync + Send>,
}

View file

@ -0,0 +1,691 @@
use std::path::Path;
use async_trait::async_trait;
use cosmian_kms_common::kmip::{
access::ObjectOperationTypes,
kmip_objects,
kmip_operations::ErrorReason,
kmip_types::{StateEnumeration, UniqueIdentifier},
};
use sqlx::{
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
ConnectOptions, Executor, Pool, Row, Sqlite,
};
use tracing::{debug, trace};
use uuid::Uuid;
use super::database::{state_from_string, Database};
use crate::{
kmip::kmip_server::database::DBObject,
kms_bail,
result::{KResult, KResultHelper},
};
pub(crate) struct SqlitePool {
pool: Pool<Sqlite>,
}
impl SqlitePool {
/// Instantiate a new `SQLite` database
/// and create the appropriate table(s) if need be
pub async fn instantiate(path: &Path) -> KResult<SqlitePool> {
let mut options = SqliteConnectOptions::new()
.filename(path)
.create_if_missing(true);
// disable logging of each query
options.disable_statement_logging();
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect_with(options)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS objects (
id STRING PRIMARY KEY UNIQUE,
object BLOB,
state STRING,
owner STRING
)",
)
.execute(&pool)
.await?;
sqlx::query(
"CREATE TABLE IF NOT EXISTS read_access (
id STRING,
userid STRING,
permissions BLOB,
UNIQUE(id,userid)
)",
)
.execute(&pool)
.await?;
Ok(SqlitePool { pool })
}
#[cfg(test)]
pub async fn perms(&self, uid: &str, userid: &str) -> KResult<Vec<ObjectOperationTypes>> {
fetch_permissions_(uid, userid, &self.pool).await
}
}
#[async_trait]
impl Database for SqlitePool {
async fn create(
&self,
uid: Option<String>,
owner: &str,
object: &kmip_objects::Object,
) -> KResult<UniqueIdentifier> {
create_(uid, owner, object, &self.pool).await
}
async fn create_objects(
&self,
owner: &str,
objects: &[(Option<String>, kmip_objects::Object)],
) -> KResult<Vec<UniqueIdentifier>> {
let mut res: Vec<UniqueIdentifier> = vec![];
let mut tx = self.pool.begin().await?;
for (uid, object) in objects {
match create_(uid.to_owned(), owner, object, &mut tx).await {
Ok(uid) => res.push(uid),
Err(e) => {
tx.rollback().await.context("transaction failed")?;
kms_bail!("creation of objects failed: {}", e);
}
};
}
tx.commit().await?;
Ok(res)
}
async fn retrieve(
&self,
uid: &str,
owner: &str,
operation_type: ObjectOperationTypes,
) -> KResult<Option<(kmip_objects::Object, StateEnumeration)>> {
retrieve_(uid, owner, operation_type, &self.pool).await
}
async fn update_object(
&self,
uid: &str,
owner: &str,
object: &kmip_objects::Object,
) -> KResult<()> {
update_object_(uid, owner, object, &self.pool).await
}
async fn update_state(&self, uid: &str, owner: &str, state: StateEnumeration) -> KResult<()> {
update_state_(uid, owner, state, &self.pool).await
}
async fn upsert(
&self,
uid: &str,
owner: &str,
object: &kmip_objects::Object,
state: StateEnumeration,
) -> KResult<()> {
upsert_(uid, owner, object, state, &self.pool).await
}
async fn delete(&self, uid: &str, owner: &str) -> KResult<()> {
delete_(uid, owner, &self.pool).await
}
async fn list(&self, owner: &str) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>> {
list_(owner, &self.pool).await
}
async fn insert_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()> {
insert_access_(uid, userid, operation_type, &self.pool).await
}
async fn delete_access(
&self,
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
) -> KResult<()> {
delete_access_(uid, userid, operation_type, &self.pool).await
}
async fn is_object_owned_by(&self, uid: &str, userid: &str) -> KResult<bool> {
is_object_owned_by_(uid, userid, &self.pool).await
}
}
async fn create_<'e, E>(
uid: Option<String>,
owner: &str,
object: &kmip_objects::Object,
executor: E,
) -> KResult<UniqueIdentifier>
where
E: Executor<'e, Database = Sqlite>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
let uid = uid.unwrap_or_else(|| Uuid::new_v4().to_string());
sqlx::query("INSERT INTO objects (id, object, state, owner) VALUES (?1, ?2, ?3, ?4)")
.bind(uid.clone())
.bind(json)
.bind(StateEnumeration::Active.to_string())
.bind(owner)
.execute(executor)
.await?;
trace!("Created in DB: {uid} / {owner}");
Ok(uid)
}
async fn retrieve_<'e, E>(
uid: &str,
owner_or_userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<Option<(kmip_objects::Object, StateEnumeration)>>
where
E: Executor<'e, Database = Sqlite> + Copy,
{
let row: Option<SqliteRow> =
sqlx::query("SELECT object, state FROM objects WHERE id=?1 AND owner=?2")
.bind(uid)
.bind(owner_or_userid)
.fetch_optional(executor)
.await?;
if let Some(row) = row {
let raw = row.get::<Vec<u8>, _>(0);
let db_object: DBObject = serde_json::from_slice(&raw)
.context("failed deserializing the object")
.reason(ErrorReason::Internal_Server_Error)?;
let object = kmip_objects::Object::post_fix(db_object.object_type, db_object.object);
let state = state_from_string(&row.get::<String, _>(1))?;
return Ok(Some((object, state)))
}
let row: Option<SqliteRow> = sqlx::query(
"SELECT objects.object, objects.state, read_access.permissions
FROM objects, read_access
WHERE objects.id=?1 AND read_access.id=?1 AND read_access.userid=?2",
)
.bind(uid)
.bind(owner_or_userid)
.fetch_optional(executor)
.await?;
row.map_or(Ok(None), |row| {
let perms_raw = row.get::<Vec<u8>, _>(2);
let perms: Vec<ObjectOperationTypes> = serde_json::from_slice(&perms_raw)
.context("failed deserializing the permissions")
.reason(ErrorReason::Internal_Server_Error)?;
// Check this operation is legit to fetch this object
if perms.into_iter().all(|p| p != operation_type) {
kms_bail!("No authorization to perform this operation");
}
let raw = row.get::<Vec<u8>, _>(0);
let db_object: DBObject = serde_json::from_slice(&raw)
.context("failed deserializing the object")
.reason(ErrorReason::Internal_Server_Error)?;
let object = kmip_objects::Object::post_fix(db_object.object_type, db_object.object);
let state = state_from_string(&row.get::<String, _>(1))?;
Ok(Some((object, state)))
})
}
async fn update_object_<'e, E>(
uid: &str,
owner: &str,
object: &kmip_objects::Object,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Sqlite>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
sqlx::query("UPDATE objects SET object=?1 WHERE id=?2 AND owner=?3")
.bind(json)
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
trace!("Updated in DB: {uid}");
Ok(())
}
async fn update_state_<'e, E>(
uid: &str,
owner: &str,
state: StateEnumeration,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Sqlite>,
{
sqlx::query("UPDATE objects SET state=?1 WHERE id=?2 AND owner=?3")
.bind(state.to_string())
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
trace!("Updated in DB: {uid}");
Ok(())
}
async fn delete_<'e, E>(uid: &str, owner: &str, executor: E) -> KResult<()>
where
E: Executor<'e, Database = Sqlite>,
{
sqlx::query("DELETE FROM objects WHERE id=?1 AND owner=?2")
.bind(uid)
.bind(owner)
.execute(executor)
.await?;
trace!("Deleted in DB: {uid}");
Ok(())
}
async fn upsert_<'e, E>(
uid: &str,
owner: &str,
object: &kmip_objects::Object,
state: StateEnumeration,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Sqlite>,
{
let json = serde_json::to_value(&DBObject {
object_type: object.object_type(),
object: object.clone(),
})
.context("failed serializing the object to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
sqlx::query(
"INSERT INTO objects (id, object, state, owner) VALUES (?1, ?2, ?3, ?4)
ON CONFLICT(id)
DO UPDATE SET object=?2, state=?3
WHERE objects.owner=?4",
)
.bind(uid)
.bind(json)
.bind(state.to_string())
.bind(owner)
.execute(executor)
.await?;
trace!("Upserted in DB: {uid}");
Ok(())
}
async fn list_<'e, E>(
owner: &str,
executor: E,
) -> KResult<Vec<(UniqueIdentifier, StateEnumeration)>>
where
E: Executor<'e, Database = Sqlite>,
{
let list = sqlx::query("SELECT id, state FROM objects WHERE owner=?1")
.bind(owner)
.fetch_all(executor)
.await?;
let mut ids: Vec<(String, StateEnumeration)> = Vec::with_capacity(list.len());
for row in list {
ids.push((
row.get::<String, _>(0),
state_from_string(&row.get::<String, _>(1))?,
));
}
debug!("Listed {} rows", ids.len());
Ok(ids)
}
async fn fetch_permissions_<'e, E>(
uid: &str,
userid: &str,
executor: E,
) -> KResult<Vec<ObjectOperationTypes>>
where
E: Executor<'e, Database = Sqlite>,
{
let row: Option<SqliteRow> = sqlx::query(
"SELECT permissions
FROM read_access
WHERE id=?1 AND userid=?2",
)
.bind(uid)
.bind(userid)
.fetch_optional(executor)
.await?;
row.map_or(Ok(vec![]), |row| {
let perms_raw = row.get::<Vec<u8>, _>(0);
let perms: Vec<ObjectOperationTypes> = serde_json::from_slice(&perms_raw)
.context("failed deserializing the permissions")
.reason(ErrorReason::Internal_Server_Error)?;
Ok(perms)
})
}
async fn insert_access_<'e, E>(
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Sqlite> + Copy,
{
// Retrieve existing permissions if any
let mut perms = fetch_permissions_(uid, userid, executor).await?;
if perms.contains(&operation_type) {
// permission is already setup
return Ok(())
}
perms.push(operation_type);
// Serialize permissions
let json = serde_json::to_value(&perms)
.context("failed serializing the permissions to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
// Upsert the DB
sqlx::query(
"INSERT INTO read_access (id, userid, permissions) VALUES (?1, ?2, ?3)
ON CONFLICT(id, userid)
DO UPDATE SET permissions=?3
WHERE read_access.id=?1 AND read_access.userid=?2",
)
.bind(uid)
.bind(userid)
.bind(json)
.execute(executor)
.await?;
trace!("Insert read access right in DB: {uid} / {userid}");
Ok(())
}
async fn delete_access_<'e, E>(
uid: &str,
userid: &str,
operation_type: ObjectOperationTypes,
executor: E,
) -> KResult<()>
where
E: Executor<'e, Database = Sqlite> + Copy,
{
// Retrieve existing permissions if any
let mut perms = fetch_permissions_(uid, userid, executor).await?;
perms.retain(|p| *p != operation_type);
// No remaining permissions, delete the row
if perms.is_empty() {
sqlx::query("DELETE FROM read_access WHERE id=?1 AND userid=?2")
.bind(uid)
.bind(userid)
.execute(executor)
.await?;
return Ok(())
}
// Serialize permissions
let json = serde_json::to_value(&perms)
.context("failed serializing the permissions to JSON")
.reason(ErrorReason::Internal_Server_Error)?;
// Update the DB
sqlx::query(
"UPDATE read_access SET permissions=?3
WHERE id=?1 AND userid=?2",
)
.bind(uid)
.bind(userid)
.bind(json)
.execute(executor)
.await?;
trace!("Deleted in DB: {uid} / {userid}");
Ok(())
}
async fn is_object_owned_by_<'e, E>(uid: &str, owner: &str, executor: E) -> KResult<bool>
where
E: Executor<'e, Database = Sqlite> + Copy,
{
let row: Option<SqliteRow> = sqlx::query("SELECT 1 FROM objects WHERE id=?1 AND owner=?2")
.bind(uid)
.bind(owner)
.fetch_optional(executor)
.await?;
Ok(row.is_some())
}
#[cfg(test)]
mod tests {
use cosmian_kms_common::{
kmip::{access::ObjectOperationTypes, kmip_types::StateEnumeration},
log_utils::log_init,
};
use cosmian_rust_lib::crypto::aes::create_aes_symmetric_key;
use tempfile::tempdir;
use uuid::Uuid;
use crate::{
kmip::kmip_server::{database::Database, sqlite::SqlitePool},
kms_bail,
result::KResult,
};
#[actix_rt::test]
pub async fn test_owner() -> KResult<()> {
log_init("info");
let owner = "eyJhbGciOiJSUzI1Ni";
let userid = "foo@example.org";
let userid2 = "bar@example.org";
let invalid_owner = "invalid_owner";
let dir = tempdir()?;
let file_path = dir.path().join("test_sqlite.db");
if file_path.exists() {
std::fs::remove_file(&file_path).unwrap();
}
let db = SqlitePool::instantiate(&file_path).await?;
let symmetric_key = create_aes_symmetric_key(None)?;
let uid = Uuid::new_v4().to_string();
db.upsert(&uid, owner, &symmetric_key, StateEnumeration::Active)
.await?;
assert!(db.is_object_owned_by(&uid, owner).await?);
// Retrieve object with valid owner with `Get` operation type - OK
match db.retrieve(&uid, owner, ObjectOperationTypes::Get).await? {
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Retrieve object with invalid owner with `Get` operation type - ko
if db
.retrieve(&uid, invalid_owner, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("It should not be possible to get this object")
}
// Add authorized `userid` to `read_access` table
db.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
// Retrieve object with authorized `userid` with `Create` operation type - ko
if db
.retrieve(&uid, userid, ObjectOperationTypes::Create)
.await
.is_ok()
{
kms_bail!("It should not be possible to get this object with `Create` request")
}
// Retrieve object with authorized `userid` with `Get` operation type - OK
match db.retrieve(&uid, userid, ObjectOperationTypes::Get).await? {
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Add authorized `userid2` to `read_access` table
db.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Try to add same access again - OK
db.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Retrieve object with authorized `userid2` with `Create` operation type - ko
if db
.retrieve(&uid, userid2, ObjectOperationTypes::Create)
.await
.is_ok()
{
kms_bail!("It should not be possible to get this object with `Create` request")
}
// Retrieve object with authorized `userid` with `Get` operation type - OK
match db
.retrieve(&uid, userid2, ObjectOperationTypes::Get)
.await?
{
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Be sure we can still retrieve object with authorized `userid` with `Get` operation type - OK
match db.retrieve(&uid, userid, ObjectOperationTypes::Get).await? {
Some((obj, state)) => {
assert_eq!(StateEnumeration::Active, state);
assert_eq!(&symmetric_key, &obj);
}
None => kms_bail!("There should be an object"),
}
// Remove `userid2` authorization
db.delete_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
// Retrieve object with `userid2` with `Get` operation type - ko
if db
.retrieve(&uid, userid2, ObjectOperationTypes::Get)
.await?
.is_some()
{
kms_bail!("It should not be possible to get this object with `Get` request")
}
Ok(())
}
#[actix_rt::test]
pub async fn test_permissions() -> KResult<()> {
log_init("info");
let userid = "foo@example.org";
let userid2 = "bar@example.org";
let dir = tempdir()?;
let file_path = dir.path().join("test_sqlite.db");
if file_path.exists() {
std::fs::remove_file(&file_path).unwrap();
}
let db = SqlitePool::instantiate(&file_path).await?;
let uid = Uuid::new_v4().to_string();
// simple insert
db.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = db.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
// double insert, expect no duplicate
db.insert_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = db.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
// insert other operation type
db.insert_access(&uid, userid, ObjectOperationTypes::Encrypt)
.await?;
let perms = db.perms(&uid, userid).await?;
assert_eq!(
perms,
vec![ObjectOperationTypes::Get, ObjectOperationTypes::Encrypt]
);
// insert other `userid2`, check it is ok and it didn't change anything for `userid`
db.insert_access(&uid, userid2, ObjectOperationTypes::Get)
.await?;
let perms = db.perms(&uid, userid2).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
let perms = db.perms(&uid, userid).await?;
assert_eq!(
perms,
vec![ObjectOperationTypes::Get, ObjectOperationTypes::Encrypt]
);
// remove `Get` access for `userid`
db.delete_access(&uid, userid, ObjectOperationTypes::Get)
.await?;
let perms = db.perms(&uid, userid2).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Get]);
let perms = db.perms(&uid, userid).await?;
assert_eq!(perms, vec![ObjectOperationTypes::Encrypt]);
Ok(())
}
}

View file

@ -0,0 +1,64 @@
use std::sync::Arc;
use cosmian_kms_common::{
kmip::{kmip_client::KmipClient, kmip_operations::*},
result::KResult,
};
use crate::kmip::kmip_server::{server::kmip_server::KmipServer, KMSServer};
/// A KMIP client that calls the local KMS server
pub struct Client {
kms_server: Arc<KMSServer>,
}
impl Client {
pub fn new(kms_server: Arc<KMSServer>) -> Client {
Client { kms_server }
}
}
impl KmipClient for Client {
fn import(&self, request: Import) -> KResult<ImportResponse> {
self.kms_server.import(request)
}
fn create(&self, request: &Create) -> KResult<CreateResponse> {
self.kms_server.create(request)
}
fn get(&self, request: &Get) -> KResult<GetResponse> {
self.kms_server.get(request)
}
fn get_attributes(&self, request: &GetAttributes) -> KResult<GetAttributesResponse> {
self.kms_server.get_attributes(request)
}
fn encrypt(&self, request: &Encrypt) -> KResult<EncryptResponse> {
self.kms_server.encrypt(request)
}
fn decrypt(&self, request: &Decrypt) -> KResult<DecryptResponse> {
self.kms_server.decrypt(request)
}
fn create_key_pair(&self, request: &CreateKeyPair) -> KResult<CreateKeyPairResponse> {
self.kms_server.create_key_pair(request)
}
fn locate(&self, request: &Locate) -> KResult<LocateResponse> {
self.kms_server.locate(request)
}
fn revoke(&self, request: Revoke) -> KResult<RevokeResponse> {
self.kms_server.revoke(request)
}
fn rekey_keypair(&self, request: &ReKeyKeyPair) -> KResult<ReKeyKeyPairResponse> {
self.kms_server.rekey_keypair(request)
}
fn destroy(&self, request: &Destroy) -> KResult<DestroyResponse> {
self.kms_server.destroy(request)
}
}

View file

@ -0,0 +1,6 @@
#![allow(clippy::upper_case_acronyms)]
pub mod kmip_server;
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,228 @@
use abe_gpsw::core::policy::{ap, attr, Attribute, Policy};
use cosmian_kms_common::kmip::{
kmip_operations::{
CreateKeyPairResponse, CreateResponse, DecryptResponse, DestroyResponse, EncryptResponse,
ReKeyKeyPairResponse, Revoke, RevokeResponse,
},
kmip_types::RevocationReason,
};
use cosmian_rust_lib::crypto::abe::kmip_requests::{
build_create_master_keypair_request, build_create_user_decryption_private_key_request,
build_decryption_request, build_destroy_key_request, build_hybrid_encryption_request,
build_rekey_keypair_request,
};
use crate::{
config::{init_config, Config},
kmip::tests::test_utils,
result::{KResult, KResultHelper},
};
#[actix_web::test]
async fn integration_tests() -> KResult<()> {
cosmian_kms_common::log_utils::log_init("cosmian_kms_server=trace");
let config = Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
port: 9999,
..Default::default()
};
init_config(&config).await?;
let app = test_utils::test_app().await;
let policy = Policy::new(10)
.add_axis("Department", &["MKG", "FIN", "HR"], false)?
.add_axis("Level", &["Confidential", "Top Secret"], true)?;
// create Key Pair
let create_key_pair = build_create_master_keypair_request(&policy)?;
let create_key_pair_response: CreateKeyPairResponse =
test_utils::post(&app, &create_key_pair).await?;
let private_key_unique_identifier = &create_key_pair_response.private_key_unique_identifier;
let public_key_unique_identifier = &create_key_pair_response.public_key_unique_identifier;
// Encrypt
let resource_uid = "the uid".as_bytes().to_vec();
let data = "Confidential MKG Data".as_bytes();
let policy_attributes = vec![attr("Level", "Confidential"), attr("Department", "MKG")];
let request = build_hybrid_encryption_request(
public_key_unique_identifier,
policy_attributes.clone(),
resource_uid.clone(),
data.to_vec(),
)?;
let encrypt_response: EncryptResponse = test_utils::post(&app, request).await?;
let encrypted_data = encrypt_response
.data
.expect("There should be encrypted data");
// Create a user decryption key
let access_policy =
(ap("Department", "MKG") | ap("Department", "FIN")) & ap("Level", "Top Secret");
let request = build_create_user_decryption_private_key_request(
&access_policy,
private_key_unique_identifier,
)?;
let create_response: CreateResponse = test_utils::post(&app, request).await?;
let user_decryption_key_identifier = &create_response.unique_identifier;
// decrypt
let request = build_decryption_request(
user_decryption_key_identifier,
resource_uid.clone(),
encrypted_data.clone(),
);
let decrypt_response: DecryptResponse = test_utils::post(&app, request).await.unwrap();
assert_eq!(
data,
&decrypt_response
.data
.context("There should be decrypted data")?
);
// revocation
// Encrypt
let resource_uid = "the uid".as_bytes().to_vec();
let data = "Voilà voilà".as_bytes();
let policy_attributes = vec![attr("Level", "Confidential"), attr("Department", "MKG")];
let request = build_hybrid_encryption_request(
public_key_unique_identifier,
policy_attributes.clone(),
resource_uid.clone(),
data.to_vec(),
)?;
let encrypt_response: EncryptResponse = test_utils::post(&app, &request).await?;
let encrypted_data = encrypt_response
.data
.expect("There should be encrypted data");
//
// Create a user decryption key
let access_policy =
(ap("Department", "MKG") | ap("Department", "FIN")) & ap("Level", "Confidential");
let request = build_create_user_decryption_private_key_request(
&access_policy,
private_key_unique_identifier,
)?;
let create_response: CreateResponse = test_utils::post(&app, &request).await?;
let user_decryption_key_identifier_1 = &create_response.unique_identifier;
//
// Create another user decryption key
let access_policy = (ap("Department", "MKG")) & ap("Level", "Confidential");
let request = build_create_user_decryption_private_key_request(
&access_policy,
private_key_unique_identifier,
)?;
let create_response2: CreateResponse = test_utils::post(&app, &request).await?;
let user_decryption_key_identifier_2 = &create_response2.unique_identifier;
// test user1 can decrypt
let request = build_decryption_request(
user_decryption_key_identifier_1,
resource_uid.clone(),
encrypted_data.clone(),
);
let decrypt_response: DecryptResponse = test_utils::post(&app, &request).await?;
assert_eq!(
data,
&decrypt_response
.data
.context("There should be decrypted data")?
);
// test user2 can decrypt
let request = build_decryption_request(
user_decryption_key_identifier_2,
resource_uid.clone(),
encrypted_data.clone(),
);
let decrypt_response: DecryptResponse = test_utils::post(&app, &request).await?;
assert_eq!(
data,
&decrypt_response
.data
.context("There should be decrypted data")?
);
// Revoke key of user 1
let _revoke_response: RevokeResponse = test_utils::post(
&app,
&Revoke {
unique_identifier: Some(user_decryption_key_identifier_1.clone()),
revocation_reason: RevocationReason::TextString("Revocation test".to_owned()),
compromise_occurrence_date: None,
},
)
.await?;
//
// Rekey all key pairs with matching ABE attributes
let abe_policy_attributes = vec![Attribute::from(("Department", "MKG"))];
let request =
build_rekey_keypair_request(private_key_unique_identifier, abe_policy_attributes)?;
let rekey_keypair_response: ReKeyKeyPairResponse =
test_utils::post(&app, &request).await.unwrap();
assert_eq!(
&rekey_keypair_response.private_key_unique_identifier,
private_key_unique_identifier
);
assert_eq!(
&rekey_keypair_response.public_key_unique_identifier,
public_key_unique_identifier
);
// ReEncrypt with same ABE attribute (which has been previously incremented)
let resource_uid = "the uid".as_bytes().to_vec();
let data = "Voilà voilà".as_bytes();
let policy_attributes = vec![attr("Level", "Confidential"), attr("Department", "MKG")];
let request = build_hybrid_encryption_request(
public_key_unique_identifier,
policy_attributes,
resource_uid.clone(),
data.to_vec(),
)?;
let encrypt_response: EncryptResponse = test_utils::post(&app, &request).await?;
let encrypted_data = encrypt_response
.data
.expect("There should be encrypted data");
// Make sure first user decryption key cannot decrypt new encrypted message (message being encrypted with new `MKG` value)
let request = build_decryption_request(
user_decryption_key_identifier_1,
resource_uid.clone(),
encrypted_data.clone(),
);
let post_ttlv_decrypt: KResult<DecryptResponse> = test_utils::post(&app, &request).await;
assert!(post_ttlv_decrypt.is_err());
// decrypt
let request = build_decryption_request(
user_decryption_key_identifier_2,
resource_uid.clone(),
encrypted_data,
);
let decrypt_response: DecryptResponse = test_utils::post(&app, &request).await?;
assert_eq!(
data,
&decrypt_response
.data
.context("There should be decrypted data")?
);
//
// Destroy user decryption key
let request = build_destroy_key_request(user_decryption_key_identifier_1)?;
let destroy_response: DestroyResponse = test_utils::post(&app, &request).await?;
assert_eq!(
user_decryption_key_identifier_1,
&destroy_response.unique_identifier
);
Ok(())
}

View file

@ -0,0 +1,2 @@
mod integration_tests;
mod unit_tests;

View file

@ -0,0 +1,350 @@
use std::sync::Arc;
use abe_gpsw::core::policy::{ap, attr, Policy};
use cosmian_kms_common::kmip::{
kmip_objects::{Object, ObjectType},
kmip_operations::{Get, Import},
kmip_types::{Attributes, CryptographicAlgorithm},
};
use cosmian_rust_lib::crypto::abe::kmip_requests::{
build_create_master_keypair_request, build_create_user_decryption_key_pair_request,
build_create_user_decryption_private_key_request, build_decryption_request,
build_hybrid_encryption_request,
};
use tracing::debug;
use uuid::Uuid;
use crate::{
config::init_config,
error::KmsError,
kmip::kmip_server::{server::kmip_server::KmipServer, KMSServer},
kms_bail,
result::{KResult, KResultHelper},
};
#[actix_rt::test]
async fn test_abe_keys() -> KResult<()> {
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
//
let policy = Policy::new(10)
.add_axis("Department", &["MKG", "FIN", "HR"], false)?
.add_axis("Level", &["confidential", "secret"], true)?;
// create Key Pair
debug!("ABE Create Master Key Pair");
let cr = kms
.create_key_pair(build_create_master_keypair_request(&policy)?, owner)
.await?;
debug!(" -> response {:?}", cr);
let sk_uid = cr.private_key_unique_identifier;
// check the generated id is an UUID
let sk_uid_ = Uuid::parse_str(&sk_uid).map_err(|e| KmsError::UnexpectedError(e.to_string()))?;
assert_eq!(&sk_uid, &sk_uid_.to_string());
// get Private Key
debug!("ABE Get Master Secret Key");
let gr_sk = kms.get(Get::from(sk_uid.as_str()), owner).await?;
assert_eq!(&sk_uid, &gr_sk.unique_identifier);
assert_eq!(ObjectType::PrivateKey, gr_sk.object_type);
// check sk
let object = &gr_sk.object;
let recovered_kms_sk_key_block = match object {
Object::PrivateKey { key_block } => key_block,
_other => {
kms_bail!("The objet at uid: {sk_uid} is not an ABE Master secret key");
}
};
debug!(
" -> ABE kms_sk: {:?}",
recovered_kms_sk_key_block.cryptographic_algorithm
);
assert_eq!(
CryptographicAlgorithm::ABE,
recovered_kms_sk_key_block.cryptographic_algorithm
);
// get Public Key
debug!("ABE Get Master Public Key");
let pk_uid = cr.public_key_unique_identifier;
let gr_pk = kms.get(Get::from(pk_uid.as_str()), owner).await?;
assert_eq!(pk_uid, gr_pk.unique_identifier);
assert_eq!(ObjectType::PublicKey, gr_pk.object_type);
// check pk
let pk = &gr_pk.object;
let recovered_kms_pk_key_block = match pk {
Object::PublicKey { key_block } => key_block,
_other => {
kms_bail!("The objet at uid: {pk_uid} is not an ABE Master secret key");
}
};
debug!(
" -> ABE kms_pk: {:?}",
recovered_kms_pk_key_block.cryptographic_algorithm
);
assert_eq!(
CryptographicAlgorithm::ABE,
recovered_kms_pk_key_block.cryptographic_algorithm
);
// re-import public key - should fail
let request = Import {
unique_identifier: pk_uid.clone(),
object_type: ObjectType::PublicKey,
replace_existing: Some(false),
key_wrap_type: None,
attributes: Attributes::new(ObjectType::PublicKey),
object: pk.clone(),
};
assert!(kms.import(request, owner).await.is_err());
// re-import public key - should succeed
let request = Import {
unique_identifier: pk_uid.clone(),
object_type: ObjectType::PublicKey,
replace_existing: Some(true),
key_wrap_type: None,
attributes: Attributes::new(ObjectType::PublicKey),
object: pk.clone(),
};
let _update_response = kms.import(request, owner).await?;
// User decryption key
let access_policy =
(ap("Department", "MKG") | ap("Department", "FIN")) & ap("Level", "confidential");
// ...via KeyPair
debug!(" .... user key via Keypair");
let request = build_create_user_decryption_key_pair_request(&access_policy, &sk_uid, &pk_uid)?;
let cr = kms.create_key_pair(request, owner).await?;
debug!("Create Response for User Decryption Key {:?}", cr);
let usk_uid = cr.private_key_unique_identifier;
// check the generated id is an UUID
let usk_uid_ =
Uuid::parse_str(&usk_uid).map_err(|e| KmsError::UnexpectedError(e.to_string()))?;
assert_eq!(&usk_uid, &usk_uid_.to_string());
// get object
let gr = kms.get(Get::from(usk_uid.as_str()), owner).await?;
let object = &gr.object;
assert_eq!(&usk_uid, &gr.unique_identifier);
let _recovered_kms_uk_key_block = match object {
Object::PrivateKey { key_block } => key_block,
_other => {
kms_bail!("The objet at uid: {usk_uid} is not an ABE user decryption key");
}
};
// debug!("ABE kms_uk: {:?}", recovered_kms_uk_key_block);
// ...via Private key
debug!(" .... user key via Private Key");
let request = build_create_user_decryption_private_key_request(&access_policy, &sk_uid)?;
let cr = kms.create(request, owner).await?;
debug!("Create Response for User Decryption Key {:?}", cr);
let usk_uid = cr.unique_identifier;
// check the generated id is an UUID
let usk_uid_ =
Uuid::parse_str(&usk_uid).map_err(|e| KmsError::UnexpectedError(e.to_string()))?;
assert_eq!(&usk_uid, &usk_uid_.to_string());
// get object
let gr = kms.get(Get::from(usk_uid.as_str()), owner).await?;
let object = &gr.object;
assert_eq!(&usk_uid, &gr.unique_identifier);
let recovered_kms_uk_key_block = match object {
Object::PrivateKey { key_block } => key_block,
_other => {
kms_bail!("The objet at uid: {usk_uid} is not an ABE user decryption key");
}
};
debug!("ABE kms_uk: {:?}", recovered_kms_uk_key_block);
Ok(())
}
#[test]
pub fn access_policy_serialization() -> KResult<()> {
let access_policy =
(ap("Department", "MKG") | ap("Department", "FIN")) & ap("Level", "confidential");
let _json = serde_json::to_string(&access_policy)?;
// println!("{}", &json);
Ok(())
}
#[actix_rt::test]
async fn test_abe_encrypt_decrypt() -> KResult<()> {
// cosmian_kms_common::log_utils::log_init("debug,cosmian_kms::kmip_server=trace");
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
let nonexistent_owner = "invalid_owner";
//
let policy = Policy::new(10)
.add_axis("Department", &["MKG", "FIN", "HR"], false)?
.add_axis("Level", &["confidential", "secret"], true)?;
// create Key Pair
let ckr = kms
.create_key_pair(build_create_master_keypair_request(&policy)?, owner)
.await?;
let master_private_key_id = &ckr.private_key_unique_identifier;
let master_public_key_id = &ckr.public_key_unique_identifier;
// encrypt a resource MKG + confidential
let confidential_resource_uid = "the uid confidential".as_bytes().to_vec();
let confidential_mkg_data = "Confidential MKG Data".as_bytes();
let confidential_mkg_policy_attributes =
vec![attr("Level", "confidential"), attr("Department", "MKG")];
let er = kms
.encrypt(
build_hybrid_encryption_request(
master_public_key_id,
confidential_mkg_policy_attributes.clone(),
confidential_resource_uid.clone(),
confidential_mkg_data.to_vec(),
)?,
owner,
)
.await?;
assert_eq!(master_public_key_id, &er.unique_identifier);
let confidential_mkg_encrypted_data = er.data.context("There should be encrypted data")?;
// check it doesn't work with invalid tenant
let er = kms
.encrypt(
build_hybrid_encryption_request(
master_public_key_id,
confidential_mkg_policy_attributes,
confidential_resource_uid.clone(),
confidential_mkg_data.to_vec(),
)?,
nonexistent_owner,
)
.await;
assert!(er.is_err());
// encrypt a resource FIN + Secret
let secret_resource_uid = "the uid secret".as_bytes().to_vec();
let secret_fin_data = "Secret FIN data".as_bytes();
let secret_fin_policy_attributes = vec![attr("Level", "secret"), attr("Department", "FIN")];
let er = kms
.encrypt(
build_hybrid_encryption_request(
master_public_key_id,
secret_fin_policy_attributes.clone(),
secret_resource_uid.clone(),
secret_fin_data.to_vec(),
)?,
owner,
)
.await?;
assert_eq!(master_public_key_id, &er.unique_identifier);
let secret_fin_encrypted_data = er.data.context("There should be encrypted data")?;
// check it doesn't work with invalid tenant
let er = kms
.encrypt(
build_hybrid_encryption_request(
master_public_key_id,
secret_fin_policy_attributes,
secret_resource_uid.clone(),
secret_fin_data.to_vec(),
)?,
nonexistent_owner,
)
.await;
assert!(er.is_err());
// Create a user decryption key MKG | FIN + secret
let secret_mkg_fin_access_policy =
(ap("Department", "MKG") | ap("Department", "FIN")) & ap("Level", "secret");
let cr = kms
.create(
build_create_user_decryption_private_key_request(
&secret_mkg_fin_access_policy,
master_private_key_id,
)?,
owner,
)
.await?;
let secret_mkg_fin_user_key = &cr.unique_identifier;
// decrypt resource MKG + confidential
let dr = kms
.decrypt(
build_decryption_request(
secret_mkg_fin_user_key,
confidential_resource_uid.clone(),
confidential_mkg_encrypted_data.clone(),
),
owner,
)
.await?;
assert_eq!(
confidential_mkg_data,
&dr.data.context("There should be decrypted data")?
);
// check it doesn't work with invalid tenant
let dr = kms
.decrypt(
build_decryption_request(
secret_mkg_fin_user_key,
confidential_resource_uid,
confidential_mkg_encrypted_data,
),
nonexistent_owner,
)
.await;
assert!(dr.is_err());
// decrypt resource FIN + Secret
let dr = kms
.decrypt(
build_decryption_request(
secret_mkg_fin_user_key,
secret_resource_uid.clone(),
secret_fin_encrypted_data.clone(),
),
owner,
)
.await?;
assert_eq!(
secret_fin_data,
&dr.data.context("There should be decrypted data")?
);
// check it doesn't work with invalid tenant
let dr = kms
.decrypt(
build_decryption_request(
secret_mkg_fin_user_key,
secret_resource_uid,
secret_fin_encrypted_data,
),
nonexistent_owner,
)
.await;
assert!(dr.is_err());
Ok(())
}

View file

@ -0,0 +1,112 @@
#![allow(dead_code)]
use cosmian_kms_common::kmip::{
kmip_data_structures::KeyMaterial,
kmip_objects::{Object, ObjectType},
kmip_operations::{CreateKeyPair, ErrorReason, Get},
kmip_types::{
Attributes, CryptographicAlgorithm, CryptographicDomainParameters, KeyFormatType,
RecommendedCurve,
},
};
use cosmian_rust_lib::crypto::curve_25519::{self, PUBLIC_KEY_LENGTH, Q_LENGTH_BITS};
use crate::{error::KmsError, kms_error, result::KResult};
/// Build a `CreateKeyPairRequest` for a curve 25519 key pair
pub fn create_key_pair_request() -> CreateKeyPair {
CreateKeyPair {
common_attributes: Some(Attributes {
activation_date: None,
cryptographic_algorithm: Some(CryptographicAlgorithm::EC),
cryptographic_length: Some(Q_LENGTH_BITS),
cryptographic_domain_parameters: Some(CryptographicDomainParameters {
q_length: Some(Q_LENGTH_BITS),
recommended_curve: Some(RecommendedCurve::CURVE25519),
}),
cryptographic_parameters: None,
cryptographic_usage_mask: None,
key_format_type: Some(KeyFormatType::ECPrivateKey),
link: vec![],
object_type: ObjectType::PrivateKey,
vendor_attributes: None,
}),
private_key_attributes: None,
public_key_attributes: None,
common_protection_storage_masks: None,
private_protection_storage_masks: None,
public_protection_storage_masks: None,
}
}
pub fn get_private_key_request(uid: &str) -> Get {
Get {
unique_identifier: Some(uid.to_string()),
key_format_type: Some(KeyFormatType::TransparentECPrivateKey),
key_wrap_type: None,
key_compression_type: None,
key_wrapping_data: None,
}
}
pub fn get_public_key_request(uid: &str) -> Get {
Get {
unique_identifier: Some(uid.to_string()),
key_format_type: Some(KeyFormatType::TransparentECPublicKey),
key_wrap_type: None,
key_compression_type: None,
key_wrapping_data: None,
}
}
/// parse bytes as a curve 25519 public key
pub fn parse_public_key(bytes: &[u8]) -> KResult<Object> {
if bytes.len() != PUBLIC_KEY_LENGTH {
return Err(kms_error!(
"Invalid public key len: {}, it should be: {} bytes",
bytes.len(),
PUBLIC_KEY_LENGTH
)
.reason(ErrorReason::Invalid_Message))
}
Ok(curve_25519::to_curve_25519_256_public_key(bytes))
}
/// parse bytes as a curve 25519 private key
pub fn parse_private_key(bytes: &[u8]) -> KResult<Object> {
if bytes.len() != curve_25519::SECRET_KEY_LENGTH {
return Err(kms_error!(
"Invalid private key len: {}, it should be: {} bytes",
bytes.len(),
curve_25519::SECRET_KEY_LENGTH
)
.reason(ErrorReason::Invalid_Message))
}
Ok(curve_25519::to_curve_25519_256_private_key(bytes))
}
#[allow(non_snake_case)]
pub fn extract_key_bytes(pk: &Object) -> KResult<Vec<u8>> {
let key_block = match pk {
Object::PublicKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Public Key".to_owned(),
))
}
};
let (key_material, _) = key_block.key_value.plaintext().ok_or_else(|| {
KmsError::ServerError("The public key should be a plain text key value".to_owned())
.reason(ErrorReason::Invalid_Object_Type)
})?;
match key_material {
KeyMaterial::TransparentECPublicKey {
recommended_curve: _,
q_string: QString,
} => Ok(QString.clone()),
_ => Err(KmsError::ServerError(
"The provided object is not an Elliptic Curve Public Key".to_owned(),
)
.reason(ErrorReason::Invalid_Object_Type)),
}
}

View file

@ -0,0 +1,2 @@
pub(crate) mod kmip_requests;
mod unit_tests;

View file

@ -0,0 +1,164 @@
use std::sync::Arc;
use cosmian_kms_common::kmip::{
kmip_data_structures::KeyValue,
kmip_objects::{Object, ObjectType},
kmip_operations::Import,
kmip_types::{
Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier,
},
};
use cosmian_rust_lib::crypto::curve_25519;
use crate::{
config::init_config,
error::KmsError,
kmip::{
kmip_server::{server::kmip_server::KmipServer, KMSServer},
tests::curve_25519_tests::{
self,
kmip_requests::{create_key_pair_request, get_private_key_request},
},
},
result::KResult,
};
#[actix_rt::test]
async fn test_curve_25519_key_pair() -> KResult<()> {
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
// request key pair creation
let request = create_key_pair_request();
let response = kms.create_key_pair(request, owner).await?;
// check that the private and public key exist
// check secret key
let sk_response = kms
.get(
get_private_key_request(&response.private_key_unique_identifier),
owner,
)
.await?;
let sk = &sk_response.object;
let sk_key_block = match sk {
Object::PrivateKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Private Key".to_owned(),
))
}
};
assert_eq!(
sk_key_block.cryptographic_algorithm,
CryptographicAlgorithm::EC,
);
assert_eq!(
sk_key_block.cryptographic_length,
curve_25519::Q_LENGTH_BITS,
);
assert_eq!(
sk_key_block.key_format_type,
KeyFormatType::TransparentECPrivateKey
);
//check link to public key
let attributes = match &sk_key_block.key_value {
KeyValue::PlainText { attributes, .. } => attributes,
_ => {
return Err(KmsError::ServerError(
"The private key should be a plain text key value".to_owned(),
))
}
};
let attr = attributes.clone().ok_or_else(|| {
KmsError::ServerError("This should never happen. There should be attributes".to_owned())
})?;
assert_eq!(attr.link.len(), 1);
let link = &attr.link[0];
assert_eq!(link.link_type, LinkType::PublicKeyLink);
assert_eq!(
link.linked_object_identifier,
LinkedObjectIdentifier::TextString(response.public_key_unique_identifier.clone())
);
// check public key
let pk_response = kms
.get(
curve_25519_tests::kmip_requests::get_public_key_request(
&response.public_key_unique_identifier,
),
owner,
)
.await?;
let pk = &pk_response.object;
let pk_key_block = match &pk {
Object::PublicKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Public Key".to_owned(),
))
}
};
assert_eq!(
pk_key_block.cryptographic_algorithm,
CryptographicAlgorithm::EC,
);
assert_eq!(
pk_key_block.cryptographic_length,
curve_25519::Q_LENGTH_BITS,
);
assert_eq!(
pk_key_block.key_format_type,
KeyFormatType::TransparentECPublicKey
);
//check link to secret key
let attributes = match &pk_key_block.key_value {
KeyValue::PlainText { attributes, .. } => attributes,
_ => {
return Err(KmsError::ServerError(
"The public key should be a plain text key value".to_owned(),
))
}
};
let attr = attributes.clone().ok_or_else(|| {
KmsError::ServerError("This should never happen. There should be attributes".to_owned())
})?;
assert_eq!(attr.link.len(), 1);
let link = &attr.link[0];
assert_eq!(link.link_type, LinkType::PrivateKeyLink);
assert_eq!(
link.linked_object_identifier,
LinkedObjectIdentifier::TextString(response.private_key_unique_identifier)
);
// test import of public key
let pk_bytes = curve_25519_tests::kmip_requests::extract_key_bytes(pk)?;
let pk = curve_25519_tests::kmip_requests::parse_public_key(&pk_bytes)?;
let request = Import {
unique_identifier: "".to_string(),
object_type: ObjectType::PublicKey,
replace_existing: None,
key_wrap_type: None,
attributes: Attributes::new(ObjectType::PublicKey),
object: pk,
};
let new_uid = kms.import(request, owner).await?.unique_identifier;
// update
let pk = curve_25519_tests::kmip_requests::parse_public_key(&pk_bytes)?;
let request = Import {
unique_identifier: new_uid.clone(),
object_type: ObjectType::PublicKey,
replace_existing: Some(true),
key_wrap_type: None,
attributes: Attributes::new(ObjectType::PublicKey),
object: pk,
};
let update_response = kms.import(request, owner).await?;
assert_eq!(new_uid, update_response.unique_identifier);
Ok(())
}

View file

@ -0,0 +1,50 @@
use cosmian_kms_common::kmip::kmip_operations::{Decrypt, Encrypt};
use cosmian_rust_lib::crypto::fpe::{AlphabetCharacters, FpeText};
use tracing::debug;
use crate::result::KResult;
/// Build an FPE Encryption Request to encrypt the provided `data`. Serialize
/// the alphabet used in FPE in the `data` to encrypt
pub fn fpe_build_encryption_request(
aes_uid: &str,
tweak: Vec<u8>,
alphabet_characters: AlphabetCharacters,
input: &str,
) -> KResult<Encrypt> {
let alphabet_and_data = FpeText {
alphabet_characters,
input: input.to_string(),
};
let alphabet_and_data_serialized = serde_json::to_vec(&alphabet_and_data)?;
debug!("data serialized: {:?}", alphabet_and_data_serialized);
Ok(Encrypt {
unique_identifier: Some(aes_uid.to_owned()),
cryptographic_parameters: None,
data: Some(alphabet_and_data_serialized),
iv_counter_nonce: Some(tweak),
correlation_value: None,
init_indicator: None,
final_indicator: None,
authenticated_encryption_additional_data: None,
})
}
/// Build an FPE Encryption Request to decrypt the provided `encrypted_data`.
/// This `encrypted_data` contains the alphabet used in FPE in header.
pub fn fpe_build_decryption_request(
aes_uid: &str,
tweak: Vec<u8>,
encrypted_data: Vec<u8>,
) -> Decrypt {
Decrypt {
unique_identifier: Some(aes_uid.to_owned()),
cryptographic_parameters: None,
data: Some(encrypted_data),
iv_counter_nonce: Some(tweak),
init_indicator: None,
final_indicator: None,
authenticated_encryption_additional_data: None,
authenticated_encryption_tag: None,
}
}

View file

@ -0,0 +1,2 @@
mod kmip_requests;
mod unit_tests;

View file

@ -0,0 +1,97 @@
use std::sync::Arc;
use cosmian_crypto_base::entropy::gen_bytes;
use cosmian_kms_common::kmip::{
kmip_objects::ObjectType,
kmip_operations::Create,
kmip_types::{Attributes, CryptographicAlgorithm, KeyFormatType},
};
use cosmian_rust_lib::crypto::fpe::AlphabetCharacters;
use tracing::debug;
use super::kmip_requests::{fpe_build_decryption_request, fpe_build_encryption_request};
use crate::{
config::init_config,
kmip::kmip_server::{server::kmip_server::KmipServer, KMSServer},
result::{KResult, KResultHelper},
};
#[actix_rt::test]
async fn fpe_encryption() -> KResult<()> {
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
let nonexistent_owner = "invalid_owner";
// Generate FPE Tweak (client responsibility)
let mut tweak = vec![0_u8; 64];
gen_bytes(&mut tweak[..])?;
// Create symmetric key
let create_request = Create {
object_type: ObjectType::SymmetricKey,
attributes: Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
key_format_type: Some(KeyFormatType::TransparentSymmetricKey),
..Attributes::new(ObjectType::SymmetricKey)
},
protection_storage_masks: None,
};
let cr = kms.create(create_request, owner).await?;
let aes_uid = &cr.unique_identifier;
debug!("Create response: {:?}", cr);
// FPE encryption with this AES key
let alphabet = AlphabetCharacters::AlphaNumeric;
let data = "data to encrypt";
let er = kms
.encrypt(
fpe_build_encryption_request(aes_uid, tweak.clone(), alphabet, data)?,
owner,
)
.await?;
assert_eq!(aes_uid, &er.unique_identifier);
let encrypted_data = er.data.context("There should be data")?;
debug!("encrypted data: {:?}", encrypted_data);
// assert encryption fails with an invalid owner
let er = kms
.encrypt(
fpe_build_encryption_request(
aes_uid,
tweak.clone(),
AlphabetCharacters::AlphaNumeric,
data,
)?,
nonexistent_owner,
)
.await;
assert!(er.is_err());
// FPE decryption
let dr = kms
.decrypt(
fpe_build_decryption_request(aes_uid, tweak.clone(), encrypted_data.clone()),
owner,
)
.await?;
let cleartext = &dr.data.context("There should be decrypted data")?;
assert_eq!(data.as_bytes(), cleartext);
// assert decryption fails with an invalid owner
let er = kms
.decrypt(
fpe_build_decryption_request(aes_uid, tweak, encrypted_data),
nonexistent_owner,
)
.await;
assert!(er.is_err());
Ok(())
}

View file

@ -0,0 +1,556 @@
use std::{convert::TryFrom, sync::Arc};
use cosmian_kms_common::{
kmip::{
access::ObjectOperationTypes,
kmip_data_structures::{KeyBlock, KeyValue, KeyWrappingData},
kmip_objects::{Object, ObjectType},
kmip_operations::Import,
kmip_types::{
Attributes, CryptographicAlgorithm, CryptographicParameters, KeyFormatType,
KeyWrapType, LinkType, LinkedObjectIdentifier, StateEnumeration, WrappingMethod,
},
},
log_utils::log_init,
};
use cosmian_mcfe::lwe;
use cosmian_rust_lib::crypto::{curve_25519, mcfe::secret_key_from_lwe_secret_key};
use num_bigint::BigUint;
use tempfile::tempdir;
use tracing::trace;
use uuid::Uuid;
use crate::{
config::init_config,
error::KmsError,
kmip::{
kmip_server::{
database::Database, server::kmip_server::KmipServer, sqlite::SqlitePool, KMSServer,
},
tests::curve_25519_tests,
},
result::KResult,
};
#[actix_rt::test]
async fn test_crud() -> KResult<()> {
log_init("info");
let owner = "eyJhbGciOiJSUzI1Ni";
let dir = tempdir()?;
let file_path = dir.path().join("test_sqlite.db");
if file_path.exists() {
std::fs::remove_file(&file_path).unwrap();
}
let lwe_setup = lwe::Setup {
clients: 10,
message_length: 31,
message_bound: BigUint::from(std::u32::MAX),
vectors_bound: BigUint::from(std::u32::MAX),
n0: 1024,
};
let lwe_sk = lwe::SecretKey::try_from(&lwe_setup)?;
let sk = secret_key_from_lwe_secret_key(&lwe_setup, &lwe_sk)?;
//
let db = SqlitePool::instantiate(&file_path).await?;
//
let uid = db.create(None, owner, &sk).await?;
//
let list = db.list(owner).await?;
assert_eq!(1, list.len());
assert_eq!(uid, list[0].0);
//
let req_obj = db.retrieve(&uid, owner, ObjectOperationTypes::Get).await?;
assert!(req_obj.is_some());
let req_sk =
req_obj.ok_or_else(|| KmsError::ServerError("invalid object returned".to_owned()))?;
assert_eq!(&sk, &req_sk.0);
// check attributes
let key_block = match &sk {
Object::SymmetricKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Symmetric Key".to_owned(),
))
}
};
let (key_material, attributes) = key_block
.key_value
.plaintext()
.ok_or_else(|| KmsError::ServerError("invalid Plain Text Key Value created".to_owned()))?;
let attributes = attributes
.as_ref()
.expect("attributes should have been created");
let req_key_block = match &req_sk.0 {
Object::SymmetricKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Symmetric Key".to_owned(),
))
}
};
let req_attr = req_key_block.key_value.attributes()?;
assert_eq!(
&attributes.cryptographic_algorithm.unwrap(),
&req_attr.cryptographic_algorithm.unwrap()
);
assert_eq!(
&attributes.cryptographic_parameters,
&req_attr.cryptographic_parameters
);
//update
let mut updated_key_block = key_block.clone();
let update_attr = Attributes {
cryptographic_parameters: Some(CryptographicParameters {
invocation_field_length: Some(42u64),
..(req_attr.cryptographic_parameters).clone().unwrap()
}),
..attributes.clone()
};
updated_key_block.key_value = KeyValue::PlainText {
key_material: key_material.clone(),
attributes: Some(update_attr),
};
db.update_object(
&uid,
owner,
&Object::SymmetricKey {
key_block: updated_key_block,
},
)
.await?;
// check update
let sk2 = db
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.unwrap();
let req_key_block_2 = match &sk2.0 {
Object::SymmetricKey { key_block } => key_block,
_other => return Err(KmsError::ServerError("invalid object returned".to_owned())),
};
let req_attr_2 = req_key_block_2.key_value.attributes()?;
assert_eq!(
&42u64,
req_attr_2
.cryptographic_parameters
.as_ref()
.unwrap()
.invocation_field_length
.as_ref()
.unwrap()
);
// upsert
let uid_upsert = Uuid::new_v4().to_string();
db.upsert(&uid_upsert, owner, &sk, StateEnumeration::Active)
.await?;
db.upsert(&uid_upsert, owner, &sk2.0, StateEnumeration::Active)
.await?;
let sk2_ = db
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.unwrap();
assert_eq!(&sk2, &sk2_);
// delete and list
assert_eq!(2, db.list(owner).await?.len());
db.delete(&uid, owner).await?;
assert_eq!(1, db.list(owner).await?.len());
db.delete(&uid_upsert, owner).await?;
assert_eq!(0, db.list(owner).await?.len());
//cleanup
dir.close()?;
Ok(())
}
#[actix_rt::test]
async fn test_crud_2() -> KResult<()> {
log_init("debug");
let owner = "eyJhbGciOiJSUzI1Ni";
let dir = tempdir()?;
let file_path = dir.path().join("test_sqlite.db");
if file_path.exists() {
std::fs::remove_file(&file_path).unwrap();
}
let lwe_setup = lwe::Setup {
clients: 10,
message_length: 31,
message_bound: BigUint::from(std::u32::MAX),
vectors_bound: BigUint::from(std::u32::MAX),
n0: 1024,
};
let lwe_sk = lwe::SecretKey::try_from(&lwe_setup)?;
let sk = secret_key_from_lwe_secret_key(&lwe_setup, &lwe_sk)?;
//
let db = SqlitePool::instantiate(&file_path).await?;
//
let uid = db.create(None, owner, &sk).await?;
//
let list = db.list(owner).await?;
assert_eq!(1, list.len());
assert_eq!(uid, list[0].0);
//
let req_obj = db.retrieve(&uid, owner, ObjectOperationTypes::Get).await?;
assert!(req_obj.is_some());
let (req_sk, _state) =
req_obj.ok_or_else(|| KmsError::ServerError("invalid object returned".to_owned()))?;
assert_eq!(&sk, &req_sk);
// check attributes
let key_block = match &sk {
Object::SymmetricKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Symmetric Key".to_owned(),
))
}
};
let (key_material, attributes) = match &key_block.key_value {
KeyValue::PlainText {
key_material,
attributes,
..
} => (key_material, attributes),
_other => {
return Err(KmsError::ServerError(
"invalid Plain Text Key Value created".to_owned(),
))
}
};
let attributes = attributes
.as_ref()
.expect("attributes should have been created");
let req_key_block = match &req_sk {
Object::SymmetricKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Symmetric Key".to_owned(),
))
}
};
let req_attr = match &req_key_block.key_value {
KeyValue::PlainText { attributes, .. } => attributes
.as_ref()
.expect("attributes should have been recovered"),
_other => {
return Err(KmsError::ServerError(
"invalid Key Value returned".to_owned(),
))
}
};
assert_eq!(
&attributes.cryptographic_algorithm.unwrap(),
&req_attr.cryptographic_algorithm.unwrap()
);
assert_eq!(
&attributes.cryptographic_parameters,
&req_attr.cryptographic_parameters
);
// update
let mut updated_key_block = key_block.clone();
let update_attr = Attributes {
cryptographic_parameters: Some(CryptographicParameters {
invocation_field_length: Some(42u64),
..(req_attr.cryptographic_parameters).clone().unwrap()
}),
..attributes.clone()
};
updated_key_block.key_value = KeyValue::PlainText {
key_material: key_material.clone(),
attributes: Some(update_attr),
};
db.update_object(
&uid,
owner,
&Object::SymmetricKey {
key_block: updated_key_block,
},
)
.await?;
// check update
let (sk2, _state) = db
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.unwrap();
let req_key_block_2 = match &sk2 {
Object::SymmetricKey { key_block } => key_block,
_other => return Err(KmsError::ServerError("invalid object returned".to_owned())),
};
let req_attr_2 = match &req_key_block_2.key_value {
KeyValue::PlainText { attributes, .. } => attributes
.as_ref()
.expect("attributes should have been recovered"),
_other => {
return Err(KmsError::ServerError(
"invalid Key Value returned".to_owned(),
))
}
};
assert_eq!(
&42u64,
req_attr_2
.cryptographic_parameters
.as_ref()
.unwrap()
.invocation_field_length
.as_ref()
.unwrap()
);
// upsert
let uid_upsert = Uuid::new_v4().to_string();
db.upsert(&uid_upsert, owner, &sk, StateEnumeration::Active)
.await?;
db.upsert(&uid_upsert, owner, &sk2, StateEnumeration::Active)
.await?;
let (sk2_, _state) = db
.retrieve(&uid, owner, ObjectOperationTypes::Get)
.await?
.unwrap();
assert_eq!(&sk2, &sk2_);
// delete and list
assert_eq!(2, db.list(owner).await?.len());
db.delete(&uid, owner).await?;
assert_eq!(1, db.list(owner).await?.len());
db.delete(&uid_upsert, owner).await?;
assert_eq!(0, db.list(owner).await?.len());
//cleanup
dir.close()?;
Ok(())
}
#[actix_rt::test]
async fn test_curve_25519_key_pair() -> KResult<()> {
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
// request key pair creation
let request = curve_25519_tests::kmip_requests::create_key_pair_request();
let response = kms.create_key_pair(request, owner).await?;
// check that the private and public key exist
// check secret key
let sk_response = kms
.get(
curve_25519_tests::kmip_requests::get_private_key_request(
&response.private_key_unique_identifier,
),
owner,
)
.await?;
let sk = &sk_response.object;
let sk_key_block = match sk {
Object::PrivateKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Private Key".to_owned(),
))
}
};
assert_eq!(
sk_key_block.cryptographic_algorithm,
CryptographicAlgorithm::EC,
);
assert_eq!(
sk_key_block.cryptographic_length,
curve_25519::Q_LENGTH_BITS,
);
assert_eq!(
sk_key_block.key_format_type,
KeyFormatType::TransparentECPrivateKey
);
//check link to public key
let attr = sk_key_block.key_value.attributes()?;
assert_eq!(attr.link.len(), 1);
let link = &attr.link[0];
assert_eq!(link.link_type, LinkType::PublicKeyLink);
assert_eq!(
link.linked_object_identifier,
LinkedObjectIdentifier::TextString(response.public_key_unique_identifier.clone())
);
// check public key
let pk_response = kms
.get(
curve_25519_tests::kmip_requests::get_public_key_request(
&response.public_key_unique_identifier,
),
owner,
)
.await?;
let pk = &pk_response.object;
let pk_key_block = match &pk {
Object::PublicKey { key_block } => key_block.clone(),
_ => {
return Err(KmsError::ServerError(
"Expected a KMIP Public Key".to_owned(),
))
}
};
assert_eq!(
pk_key_block.cryptographic_algorithm,
CryptographicAlgorithm::EC,
);
assert_eq!(
pk_key_block.cryptographic_length,
curve_25519::Q_LENGTH_BITS,
);
assert_eq!(
pk_key_block.key_format_type,
KeyFormatType::TransparentECPublicKey
);
//check link to secret key
let attr = pk_key_block.key_value.attributes()?;
assert_eq!(attr.link.len(), 1);
let link = &attr.link[0];
assert_eq!(link.link_type, LinkType::PrivateKeyLink);
assert_eq!(
link.linked_object_identifier,
LinkedObjectIdentifier::TextString(response.private_key_unique_identifier)
);
// test import of public key
let pk_bytes = curve_25519_tests::kmip_requests::extract_key_bytes(pk)?;
let pk = curve_25519_tests::kmip_requests::parse_public_key(&pk_bytes)?;
let request = Import {
unique_identifier: "".to_string(),
object_type: ObjectType::PublicKey,
replace_existing: None,
key_wrap_type: None,
attributes: Attributes::new(ObjectType::PublicKey),
object: pk,
};
let new_uid = kms.import(request, owner).await?.unique_identifier;
// update
let pk = curve_25519_tests::kmip_requests::parse_public_key(&pk_bytes)?;
let request = Import {
unique_identifier: new_uid.clone(),
object_type: ObjectType::PublicKey,
replace_existing: Some(true),
key_wrap_type: None,
attributes: Attributes::new(ObjectType::PublicKey),
object: pk,
};
let update_response = kms.import(request, owner).await?;
assert_eq!(new_uid, update_response.unique_identifier);
Ok(())
}
#[actix_rt::test]
async fn test_import_wrapped_symmetric_key() -> KResult<()> {
log_init("info");
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
let wrapped_symmetric_key = [0_u8; 32];
let aesgcm_nonce = [0_u8; 12];
let symmetric_key = Object::SymmetricKey {
key_block: KeyBlock {
key_format_type: KeyFormatType::TransparentSymmetricKey,
key_compression_type: None,
key_value: KeyValue::Wrapped(wrapped_symmetric_key.to_vec()),
cryptographic_algorithm: CryptographicAlgorithm::AES,
cryptographic_length: wrapped_symmetric_key.len() as i32,
key_wrapping_data: Some(KeyWrappingData {
wrapping_method: WrappingMethod::Encrypt,
iv_counter_nonce: Some(aesgcm_nonce.to_vec()),
..KeyWrappingData::default()
}),
},
};
let uid = Uuid::new_v4().to_string();
let request = Import {
unique_identifier: uid,
object_type: ObjectType::SymmetricKey,
replace_existing: Some(false),
key_wrap_type: Some(KeyWrapType::AsRegistered),
attributes: Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(wrapped_symmetric_key.len() as i32),
key_format_type: Some(KeyFormatType::TransparentSymmetricKey),
object_type: ObjectType::SymmetricKey,
..Attributes::new(ObjectType::SymmetricKey)
},
object: symmetric_key,
};
trace!("request: {:?}", request);
let response = kms.import(request, owner).await?;
trace!("response: {:?}", response);
Ok(())
}
#[actix_rt::test]
async fn test_database_user_tenant() -> KResult<()> {
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
// request key pair creation
let request = curve_25519_tests::kmip_requests::create_key_pair_request();
let response = kms.create_key_pair(request, owner).await?;
// check that we can get the private and public key
// check secret key
kms.get(
curve_25519_tests::kmip_requests::get_private_key_request(
&response.private_key_unique_identifier,
),
owner,
)
.await?;
// check public key
kms.get(
curve_25519_tests::kmip_requests::get_public_key_request(
&response.public_key_unique_identifier,
),
owner,
)
.await?;
// request with an invalid `owner` but with the same `uid` and assert we don't get any key
let owner = "invalid_owner".to_string();
// check public key
let sk_response = kms
.get(
curve_25519_tests::kmip_requests::get_private_key_request(
&response.private_key_unique_identifier,
),
&owner,
)
.await;
assert!(sk_response.is_err());
let pk_response = kms
.get(
curve_25519_tests::kmip_requests::get_public_key_request(
&response.public_key_unique_identifier,
),
&owner,
)
.await;
assert!(pk_response.is_err());
Ok(())
}

View file

@ -0,0 +1,34 @@
use cosmian_kms_common::kmip::{
kmip_objects::ObjectType,
kmip_operations::Create,
kmip_types::{
AttributeReference, Attributes, CryptographicAlgorithm, KeyFormatType,
VendorAttributeReference,
},
};
use cosmian_mcfe::lwe;
use cosmian_rust_lib::crypto::mcfe::vendor_attributes_from_mcfe_setup;
use crate::result::KResult;
/// Build a `CreateRequest` for an LWE Secret Key
pub fn lwe_secret_key_create_request(setup: &lwe::Setup) -> KResult<Create> {
Ok(Create {
object_type: ObjectType::SymmetricKey,
attributes: Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::LWE),
key_format_type: Some(KeyFormatType::McfeSecretKey),
vendor_attributes: Some(vec![vendor_attributes_from_mcfe_setup(setup)?]),
..Attributes::new(ObjectType::SymmetricKey)
},
protection_storage_masks: None,
})
}
// The `AttributeReference` to recover the LWE `Setup` of a stored LWE Key
pub fn lwe_setup_attribute_reference() -> AttributeReference {
AttributeReference::Vendor(VendorAttributeReference {
vendor_identification: "cosmian".to_owned(),
attribute_name: "mcfe_setup".to_owned(),
})
}

View file

@ -0,0 +1,2 @@
mod kmip_requests;
mod unit_tests;

View file

@ -0,0 +1,117 @@
use std::{convert::TryFrom, sync::Arc};
use cosmian_kms_common::kmip::{
kmip_objects::{Object, ObjectType},
kmip_operations::{Get, GetAttributes},
};
use cosmian_mcfe::lwe;
use cosmian_rust_lib::crypto::mcfe::{
mcfe_secret_key_from_key_block, mcfe_setup_from_attributes, secret_key_from_lwe_secret_key,
};
use num_bigint::BigUint;
use uuid::Uuid;
use crate::{
config::init_config,
error::KmsError,
kmip::{
kmip_server::{server::kmip_server::KmipServer, KMSServer},
tests::mcfe_tests::kmip_requests::{
lwe_secret_key_create_request, lwe_setup_attribute_reference,
},
},
kms_bail,
result::KResult,
};
#[actix_rt::test]
async fn test_secret_key_crud() -> KResult<()> {
let config = crate::config::Config {
delegated_authority_domain: Some("dev-1mbsbmin.us.auth0.com".to_string()),
..Default::default()
};
init_config(&config).await?;
let kms = Arc::new(KMSServer::instantiate().await?);
let owner = "eyJhbGciOiJSUzI1Ni";
let lwe_setup = lwe::Setup {
clients: 10,
message_length: 31,
message_bound: BigUint::from(std::u32::MAX),
vectors_bound: BigUint::from(std::u32::MAX),
n0: 1024,
};
let lwe_sk = lwe::SecretKey::try_from(&lwe_setup)?;
let kms_sk = secret_key_from_lwe_secret_key(&lwe_setup, &lwe_sk)?;
let kms_sk_key_block = match kms_sk {
Object::SymmetricKey { key_block } => key_block,
_other => {
return Err(KmsError::ServerError(
"The object is not an MCFE LWE Secret Key".to_owned(),
))
}
};
let cr = kms
.create(lwe_secret_key_create_request(&lwe_setup)?, owner)
.await?;
assert_eq!(ObjectType::SymmetricKey, cr.object_type);
let uid = cr.unique_identifier;
// check the generated id is an UUID
let uid_ = Uuid::parse_str(&uid).map_err(|e| KmsError::UnexpectedError(e.to_string()))?;
assert_eq!(&uid, &uid_.to_string());
// get object
let gr = kms.get(Get::from(uid.as_str()), owner).await?;
assert_eq!(uid, gr.unique_identifier);
assert_eq!(ObjectType::SymmetricKey, gr.object_type);
// recover sk
let object = gr.object;
let recovered_kms_key_block = match object {
Object::SymmetricKey { key_block } => key_block,
_other => {
kms_bail!("The objet at uid: {uid} is not an MCFE LWE Secret Key")
}
};
assert_eq!(
kms_sk_key_block.cryptographic_algorithm,
recovered_kms_key_block.cryptographic_algorithm
);
assert_eq!(
kms_sk_key_block.cryptographic_length,
recovered_kms_key_block.cryptographic_length
);
assert_eq!(
kms_sk_key_block.key_format_type,
recovered_kms_key_block.key_format_type
);
let recovered_lwe_sk = mcfe_secret_key_from_key_block(&recovered_kms_key_block)?;
assert_eq!(lwe_sk.0.len(), recovered_lwe_sk.0.len());
// get all attributes
let gar = kms
.get_attributes(
GetAttributes {
unique_identifier: Some(uid.clone()),
attribute_references: None,
},
owner,
)
.await?;
let kms_sk_attr = kms_sk_key_block.key_value.attributes()?;
assert_eq!(&gar.attributes, kms_sk_attr);
// get LWE Setup from attributes
let gar = kms
.get_attributes(
GetAttributes {
unique_identifier: Some(uid),
attribute_references: Some(vec![lwe_setup_attribute_reference()]),
},
owner,
)
.await?;
let recovered_setup = mcfe_setup_from_attributes(&gar.attributes)?;
assert_eq!(lwe_setup, recovered_setup);
Ok(())
}

View file

@ -0,0 +1,6 @@
mod abe_tests;
mod curve_25519_tests;
mod fpe_tests;
mod kmip_server_tests;
mod mcfe_tests;
mod test_utils;

View file

@ -0,0 +1,57 @@
use std::sync::Arc;
use actix_http::Request;
use actix_web::{
body::MessageBody,
dev::{Service, ServiceResponse},
test,
web::Data,
App,
};
use cosmian_kms_common::kmip::ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLV};
use serde::{de::DeserializeOwned, Serialize};
use crate::{
kmip::kmip_server::KMSServer, kmip_endpoint, middlewares::auth::Auth, result::KResult,
};
/// Test auth0 token (expired) -
/// bnjjj: I know it's ugly but it's easy and sufficient for now
pub static AUTH0_TOKEN: &str = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRWdk5xTEtoUHhUSGdhYUNGRGRoSSJ9.eyJnaXZlbl9uYW1lIjoiTGFldGl0aWEiLCJmYW1pbHlfbmFtZSI6Ikxhbmdsb2lzIiwibmlja25hbWUiOiJsYWV0aXRpYS5sYW5nbG9pcyIsIm5hbWUiOiJMYWV0aXRpYSBMYW5nbG9pcyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp5UEJsSnpqRzNuMWZLLXNyS0ptdUVkYklUX29QRmhVbTd2T2dVWD1zOTYtYyIsImxvY2FsZSI6ImZyIiwidXBkYXRlZF9hdCI6IjIwMjEtMTItMjFUMDk6MjE6NDkuMDgxWiIsImVtYWlsIjoibGFldGl0aWEubGFuZ2xvaXNAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9kZXYtMW1ic2JtaW4udXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4NTgwMDU3NDAwMjkxNDc5ODQyIiwiYXVkIjoiYUZqSzJvTnkwR1RnNWphV3JNYkJBZzV0bjRIV3VJN1ciLCJpYXQiOjE2NDAwNzg1MTQsImV4cCI6MTY0MDExNDUxNCwibm9uY2UiOiJha0poV2xoMlRsTm1lRTVtVFc0NFJHSk5VVEl5WW14aVJUTnVRblV1VEVwa2RrTnFVa2R5WkdoWFdnPT0ifQ.Q4tCzvJTNxmDhIYOJbjsqupdQkWg29Ny0B8njEfSrLVXNaRMFE99eSXedCBaXSMBnZ9GuCV2Z1MAZL8ZjTxqPP_VYCnc2QufG1k1XZg--6Q48pPdpUBXu2Ny1eatwiDrRvgQfUHkiM8thUAOb4bXxGLrtQKlO_ePOehDbEOjfd11aVm3pwyVqj1v6Ki1D5QJsOHtkkpLMinmmyGDtmdHH2YXseZNHGUY7PWZ6DelpJaxI48W5FNDY4b0sJlzaJqdIcoOX7EeP1pfFoHVeZAo5mWyuDev2OaPYKeqpga4PjqHcFT0m1rQoWQHmfGr3EkA3w8NXmKnZmEbQcLLgcCATw";
pub async fn test_app()
-> impl Service<Request, Response = ServiceResponse<impl MessageBody>, Error = actix_web::Error> {
let kms_server = Arc::new(
KMSServer::instantiate()
.await
.expect("cannot instantiate KMS server"),
);
test::init_service(
App::new()
.wrap(Auth)
.app_data(Data::new(kms_server.clone()))
.service(kmip_endpoint::kmip)
.service(kmip_endpoint::access_insert)
.service(kmip_endpoint::access_delete),
)
.await
}
pub async fn post<B, O, R, S>(app: &S, operation: O) -> KResult<R>
where
O: Serialize,
R: DeserializeOwned,
S: Service<Request, Response = ServiceResponse<B>, Error = actix_web::Error>,
B: MessageBody,
{
let req = test::TestRequest::post()
.uri("/kmip/2_1")
.insert_header(("Authorization", format!("Bearer {}", AUTH0_TOKEN)))
.set_json(&to_ttlv(&operation)?)
.to_request();
let body = test::call_and_read_body(&app, req).await;
let json: TTLV = serde_json::from_slice(&body)?;
let result: R = from_ttlv(&json)?;
Ok(result)
}

View file

@ -0,0 +1,164 @@
use std::sync::Arc;
use actix_web::{
delete, post,
web::{Data, Json},
HttpMessage, HttpRequest, HttpResponse, HttpResponseBuilder,
};
use cosmian_kms_common::kmip::{
access::{Access, ResponseSuccess},
kmip_operations::{
Create, CreateKeyPair, Decrypt, Destroy, Encrypt, Get, GetAttributes, Import, Locate,
ReKeyKeyPair, Revoke,
},
ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLV},
};
use http::{header, StatusCode};
use tracing::{debug, error, warn};
use crate::{
error::KmsError,
kmip::kmip_server::{server::kmip_server::KmipServer, KMSServer},
middlewares::auth::AuthClaim,
result::KResult,
};
impl actix_web::error::ResponseError for KmsError {
fn error_response(&self) -> HttpResponse {
let status_code = self.status_code();
let message = self.to_string();
if status_code >= StatusCode::INTERNAL_SERVER_ERROR {
error!("{status_code} - {message}");
} else {
warn!("{status_code} - {message}");
}
HttpResponseBuilder::new(status_code)
.insert_header((header::CONTENT_TYPE, "text/html; charset=utf-8"))
.body(message)
}
fn status_code(&self) -> StatusCode {
match self {
&(Self::InvalidRequest(_) | Self::NotSupported(_)) => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
/// Generate KMIP generic key pair
#[post("/kmip/2_1")]
pub async fn kmip(
req_http: HttpRequest,
item: Json<TTLV>,
kms_client: Data<Arc<KMSServer>>,
) -> KResult<Json<TTLV>> {
let ttlv_req = item.into_inner();
let owner = get_owner(req_http)?;
debug!("POST /kmip. Request: {:?}", ttlv_req.tag.as_str());
let ttlv_resp = match ttlv_req.tag.as_str() {
"Create" => {
let req = from_ttlv::<Create>(&ttlv_req)?;
let resp = kms_client.create(req, &owner).await?;
to_ttlv(&resp)?
}
"CreateKeyPair" => {
let req = from_ttlv::<CreateKeyPair>(&ttlv_req)?;
let resp = kms_client.create_key_pair(req, &owner).await?;
to_ttlv(&resp)?
}
"Decrypt" => {
let req = from_ttlv::<Decrypt>(&ttlv_req)?;
let resp = kms_client.decrypt(req, &owner).await?;
to_ttlv(&resp)?
}
"Encrypt" => {
let req = from_ttlv::<Encrypt>(&ttlv_req)?;
let resp = kms_client.encrypt(req, &owner).await?;
to_ttlv(&resp)?
}
"Get" => {
let req = from_ttlv::<Get>(&ttlv_req)?;
let resp = kms_client.get(req, &owner).await?;
to_ttlv(&resp)?
}
"GetAttributes" => {
let req = from_ttlv::<GetAttributes>(&ttlv_req)?;
let resp = kms_client.get_attributes(req, &owner).await?;
to_ttlv(&resp)?
}
"Import" => {
let req = from_ttlv::<Import>(&ttlv_req)?;
let resp = kms_client.import(req, &owner).await?;
to_ttlv(&resp)?
}
"Revoke" => {
let req = from_ttlv::<Revoke>(&ttlv_req)?;
let resp = kms_client.revoke(req, &owner).await?;
to_ttlv(&resp)?
}
"Locate" => {
let req = from_ttlv::<Locate>(&ttlv_req)?;
let resp = kms_client.locate(req, &owner).await?;
to_ttlv(&resp)?
}
"ReKeyKeyPair" => {
let req = from_ttlv::<ReKeyKeyPair>(&ttlv_req)?;
let resp = kms_client.rekey_keypair(req, &owner).await?;
to_ttlv(&resp)?
}
"Destroy" => {
let req = from_ttlv::<Destroy>(&ttlv_req)?;
let resp = kms_client.destroy(req, &owner).await?;
to_ttlv(&resp)?
}
x => return Err(KmsError::NotSupported(format!("Operation: {}", x))),
};
Ok(Json(ttlv_resp))
}
/// Add an access authorization for an object, given a `userid`
#[post("/access")]
pub async fn access_insert(
req: HttpRequest,
access: Json<Access>,
kms_client: Data<Arc<KMSServer>>,
) -> KResult<Json<ResponseSuccess>> {
let access = access.into_inner();
let owner = get_owner(req)?;
kms_client.insert_access(&access, &owner).await?;
Ok(Json(ResponseSuccess {
success: format!("Access for {} successfully added", access.userid),
}))
}
/// Revoke an access authorization for an object, given a `userid`
#[delete("/access")]
pub async fn access_delete(
req: HttpRequest,
access: Json<Access>,
kms_client: Data<Arc<KMSServer>>,
) -> KResult<Json<ResponseSuccess>> {
let access = access.into_inner();
let owner = get_owner(req)?;
kms_client.delete_access(&access, &owner).await?;
Ok(Json(ResponseSuccess {
success: format!("Access for {} successfully deleted", access.userid),
}))
}
fn get_owner(req_http: HttpRequest) -> KResult<String> {
match req_http.extensions().get::<AuthClaim>() {
Some(claim) => Ok(claim.email.clone()),
None => Err(KmsError::Unauthorized(
"No valid auth claim owner (email) from JWT".to_owned(),
)),
}
}

32
kms_server/src/lib.rs Normal file
View file

@ -0,0 +1,32 @@
mod auth;
pub mod config;
pub mod error;
mod kmip;
mod kmip_endpoint;
mod middlewares;
pub mod result;
use std::sync::Arc;
use actix_web::{middleware::Condition, web::Data, App, HttpServer};
use config::{hostname, jwks, port};
use kmip::kmip_server::KMSServer;
use middlewares::auth::Auth;
use result::KResult;
pub async fn start_server() -> KResult<()> {
let kms_server = Arc::new(KMSServer::instantiate().await?);
HttpServer::new(move || {
App::new()
.wrap(Condition::new(jwks().is_some(), Auth))
.app_data(Data::new(kms_server.clone()))
.service(kmip_endpoint::kmip)
.service(kmip_endpoint::access_insert)
.service(kmip_endpoint::access_delete)
})
.bind(format!("{}:{}", hostname(), port()))?
.run()
.await
.map_err(Into::into)
}

33
kms_server/src/main.rs Normal file
View file

@ -0,0 +1,33 @@
use cosmian_kms_server::{
config::{init_config, Config},
error::KmsError,
result::KResult,
start_server,
};
use twelf::Layer;
#[actix_web::main]
async fn main() -> KResult<()> {
if option_env!("RUST_BACKTRACE").is_none() {
std::env::set_var("RUST_BACKTRACE", "1");
}
if option_env!("RUST_LOG").is_none() {
std::env::set_var(
"RUST_LOG",
"debug, actix_web=debug,hyper=info,reqwest=info,sqlx::query=error",
);
}
env_logger::init();
let matches = clap::Command::new("kms_server")
.args(&Config::clap_args())
.get_matches();
let conf = Config::with_layers(&[Layer::Env(Some("KMS_".to_string())), Layer::Clap(matches)])
.map_err(|e| KmsError::UnexpectedError(format!("config error: {}", e)))?;
init_config(&conf).await?;
start_server().await
}

View file

@ -0,0 +1,117 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use actix_identity::RequestIdentity;
use actix_service::{Service, Transform};
use actix_web::{
body::{BoxBody, EitherBody},
dev::{ServiceRequest, ServiceResponse},
Error, HttpMessage, HttpResponse,
};
use futures::{
future::{ok, Ready},
Future,
};
use tracing::{debug, error};
use crate::auth::decode_jwt_new;
pub struct Auth;
impl<S, B> Transform<S, ServiceRequest> for Auth
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Error = Error;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
type InitError = ();
type Response = ServiceResponse<EitherBody<B, BoxBody>>;
type Transform = AuthMiddleware<S>;
fn new_transform(&self, service: S) -> Self::Future {
ok(AuthMiddleware { service })
}
}
pub struct AuthMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Error = Error;
#[allow(clippy::type_complexity)]
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
type Response = ServiceResponse<EitherBody<B, BoxBody>>;
fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
debug!("Authentication...");
let identity = RequestIdentity::get_identity(&req)
.or_else(|| {
req.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok().map(|h| h.to_string()))
})
.unwrap_or_default();
debug!("Checking JWT");
let private_claim = decode_jwt_new(&identity).map(|claim| claim.email);
match private_claim {
Err(e) => Box::pin(async move {
error!(
"{:?} {} 401 unauthorized: bad JWT ({})",
req.method(),
req.path(),
e
);
Ok(req
.into_response(HttpResponse::Unauthorized().finish())
.map_into_right_body())
}),
Ok(None) => Box::pin(async move {
error!(
"{:?} {} 401 unauthorized, no email in JWT",
req.method(),
req.path()
);
Ok(req
.into_response(HttpResponse::Unauthorized().finish())
.map_into_right_body())
}),
Ok(Some(email)) => {
// forward to the endpoint the email got from this JWT
req.extensions_mut().insert(AuthClaim::new(email));
debug!("access granted !");
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
Ok(res.map_into_left_body())
})
}
}
}
}
#[derive(Debug)]
pub struct AuthClaim {
pub email: String,
}
impl AuthClaim {
fn new(email: String) -> Self {
AuthClaim { email }
}
}

View file

@ -0,0 +1 @@
pub mod auth;

Some files were not shown because too many files have changed in this diff Show more