🎉 import from cosmian_server
This commit is contained in:
commit
1df1f8fb7e
126 changed files with 36345 additions and 0 deletions
4
.dockerignore
Normal file
4
.dockerignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
**/target/
|
||||
**/.cargo_check/
|
||||
.git/
|
||||
.gitignore
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
target/
|
||||
.cargo_check/
|
||||
*nix
|
||||
*.swp
|
||||
TODO
|
4871
Cargo.lock
generated
Normal file
4871
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"kms_common",
|
||||
"kms_client",
|
||||
"kms_server",
|
||||
]
|
45
Dockerfile
Normal file
45
Dockerfile
Normal 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
66
Makefile.toml
Normal 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
4
kms_cli/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
target/
|
||||
TODO
|
||||
TODO.md
|
||||
*.swp
|
4240
kms_cli/Cargo.lock
generated
Normal file
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
30
kms_cli/Cargo.toml
Normal 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
63
kms_cli/README.md
Normal 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
4
kms_cli/kms.json
Normal 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
13
kms_cli/policy.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"policy": {
|
||||
"level": {
|
||||
"hierarchical": true,
|
||||
"attributes": ["confidential", "secret", "top-secret"]
|
||||
},
|
||||
"department": {
|
||||
"hierarchical": false,
|
||||
"attributes": ["finance", "marketing", "operations"]
|
||||
}
|
||||
},
|
||||
"max-rotations": 100
|
||||
}
|
78
kms_cli/src/actions/abe/decrypt.rs
Normal file
78
kms_cli/src/actions/abe/decrypt.rs
Normal 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(())
|
||||
}
|
||||
}
|
93
kms_cli/src/actions/abe/encrypt.rs
Normal file
93
kms_cli/src/actions/abe/encrypt.rs
Normal 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(())
|
||||
}
|
||||
}
|
43
kms_cli/src/actions/abe/entrypoint.rs
Normal file
43
kms_cli/src/actions/abe/entrypoint.rs
Normal 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(())
|
||||
}
|
||||
}
|
212
kms_cli/src/actions/abe/keys.rs
Normal file
212
kms_cli/src/actions/abe/keys.rs
Normal 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.")
|
||||
}
|
||||
}
|
||||
}
|
6
kms_cli/src/actions/abe/mod.rs
Normal file
6
kms_cli/src/actions/abe/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod decrypt;
|
||||
mod encrypt;
|
||||
mod keys;
|
||||
mod policy;
|
||||
|
||||
pub mod entrypoint;
|
84
kms_cli/src/actions/abe/policy.rs
Normal file
84
kms_cli/src/actions/abe/policy.rs
Normal 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"
|
||||
);
|
||||
}
|
||||
}
|
1
kms_cli/src/actions/mod.rs
Normal file
1
kms_cli/src/actions/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod abe;
|
75
kms_cli/src/config.rs
Normal file
75
kms_cli/src/config.rs
Normal 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
5
kms_cli/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod actions;
|
||||
pub mod config;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
27
kms_cli/src/main.rs
Normal file
27
kms_cli/src/main.rs
Normal 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(())
|
||||
}
|
641
kms_cli/src/tests/abe/integration_tests.rs
Normal file
641
kms_cli/src/tests/abe/integration_tests.rs
Normal 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(())
|
||||
}
|
1
kms_cli/src/tests/abe/mod.rs
Normal file
1
kms_cli/src/tests/abe/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod integration_tests;
|
5
kms_cli/src/tests/mod.rs
Normal file
5
kms_cli/src/tests/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod abe;
|
||||
|
||||
pub(crate) mod test_utils;
|
||||
|
||||
const PROG_NAME: &str = "kms-cli";
|
66
kms_cli/src/tests/test_utils.rs
Normal file
66
kms_cli/src/tests/test_utils.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
4
kms_cli/test_data/kms.bad
Normal file
4
kms_cli/test_data/kms.bad
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"kms_access_token":
|
||||
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRWdk5xTEtoUHhUSGdhYUNGRGRoSSJ9.eyJnaXZlbl9uYW1lIjoiTGFldGl0aWEiLCJmYW1pbHlfbmFtZSI6Ikxhbmdsb2lzIiwibmlja25hbWUiOiJsYWV0aXRpYS5sYW5nbG9pcyIsIm5hbWUiOiJMYWV0aXRpYSBMYW5nbG9pcyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp5UEJsSnpqRzNuMWZLLXNyS0ptdUVkYklUX29QRmhVbTd2T2dVWD1zOTYtYyIsImxvY2FsZSI6ImZyIiwidXBkYXRlZF9hdCI6IjIwMjEtMTItMjFUMDk6MjE6NDkuMDgxWiIsImVtYWlsIjoibGFldGl0aWEubGFuZ2xvaXNAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9kZXYtMW1ic2JtaW4udXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4NTgwMDU3NDAwMjkxNDc5ODQyIiwiYXVkIjoiYUZqSzJvTnkwR1RnNWphV3JNYkJBZzV0bjRIV3VJN1ciLCJpYXQiOjE2NDAwNzg1MTQsImV4cCI6MTY0MDExNDUxNCwibm9uY2UiOiJha0poV2xoMlRsTm1lRTVtVFc0NFJHSk5VVEl5WW14aVJUTnVRblV1VEVwa2RrTnFVa2R5WkdoWFdnPT0ifQ.Q4tCzvJTNxmDhIYOJbjsqupdQkWg29Ny0B8njEfSrLVXNaRMFE99eSXedCBaXSMBnZ9GuCV2Z1MAZL8ZjTxqPP_VYCnc2QufG1k1XZg--6Q48pPdpUBXu2Ny1eatwiDrRvgQfUHkiM8thUAOb4bXxGLrtQKlO_ePOehDbEOjfd11aVm3pwyVqj1v6Ki1D5QJsOHtkkpLMinmmyGDtmdHH2YXseZNHGUY7PWZ6DelpJaxI48W5FNDY4b0sJlzaJqdIcoOX7EeP1pfFoHVeZAo5mWyuDev2OaPYKeqpga4PjqHcFT0m1rQoWQHmfGr3EkA3w8NXmKnZmEbQcLLgcCATw"
|
||||
}
|
4
kms_cli/test_data/kms.json
Normal file
4
kms_cli/test_data/kms.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"kms_server_url": "http://127.0.0.1:9998",
|
||||
"kms_access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRWdk5xTEtoUHhUSGdhYUNGRGRoSSJ9.eyJnaXZlbl9uYW1lIjoiTGFldGl0aWEiLCJmYW1pbHlfbmFtZSI6Ikxhbmdsb2lzIiwibmlja25hbWUiOiJsYWV0aXRpYS5sYW5nbG9pcyIsIm5hbWUiOiJMYWV0aXRpYSBMYW5nbG9pcyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp5UEJsSnpqRzNuMWZLLXNyS0ptdUVkYklUX29QRmhVbTd2T2dVWD1zOTYtYyIsImxvY2FsZSI6ImZyIiwidXBkYXRlZF9hdCI6IjIwMjEtMTItMjFUMDk6MjE6NDkuMDgxWiIsImVtYWlsIjoibGFldGl0aWEubGFuZ2xvaXNAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6Ly9kZXYtMW1ic2JtaW4udXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4NTgwMDU3NDAwMjkxNDc5ODQyIiwiYXVkIjoiYUZqSzJvTnkwR1RnNWphV3JNYkJBZzV0bjRIV3VJN1ciLCJpYXQiOjE2NDAwNzg1MTQsImV4cCI6MTY0MDExNDUxNCwibm9uY2UiOiJha0poV2xoMlRsTm1lRTVtVFc0NFJHSk5VVEl5WW14aVJUTnVRblV1VEVwa2RrTnFVa2R5WkdoWFdnPT0ifQ.Q4tCzvJTNxmDhIYOJbjsqupdQkWg29Ny0B8njEfSrLVXNaRMFE99eSXedCBaXSMBnZ9GuCV2Z1MAZL8ZjTxqPP_VYCnc2QufG1k1XZg--6Q48pPdpUBXu2Ny1eatwiDrRvgQfUHkiM8thUAOb4bXxGLrtQKlO_ePOehDbEOjfd11aVm3pwyVqj1v6Ki1D5QJsOHtkkpLMinmmyGDtmdHH2YXseZNHGUY7PWZ6DelpJaxI48W5FNDY4b0sJlzaJqdIcoOX7EeP1pfFoHVeZAo5mWyuDev2OaPYKeqpga4PjqHcFT0m1rQoWQHmfGr3EkA3w8NXmKnZmEbQcLLgcCATw"
|
||||
}
|
1
kms_cli/test_data/plain.txt
Normal file
1
kms_cli/test_data/plain.txt
Normal file
|
@ -0,0 +1 @@
|
|||
I'm a plain text
|
12
kms_cli/test_data/policy.bad
Normal file
12
kms_cli/test_data/policy.bad
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"policy": {
|
||||
"level": {
|
||||
"attributes": ["confidential", "secret", "top-secret"]
|
||||
},
|
||||
"department": {
|
||||
"hierarchical": false,
|
||||
"attributes": ["finance", "marketing", "operations"]
|
||||
}
|
||||
},
|
||||
"max-rotations": 100
|
||||
}
|
13
kms_cli/test_data/policy.bad2
Normal file
13
kms_cli/test_data/policy.bad2
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"policy": {
|
||||
"level": {
|
||||
"hierarchical": true,
|
||||
"attributes": ["confidential", "secret", "secret"]
|
||||
},
|
||||
"department": {
|
||||
"hierarchical": false,
|
||||
"attributes": ["finance", "marketing", "operations"]
|
||||
}
|
||||
},
|
||||
"max-rotations": 100
|
||||
}
|
13
kms_cli/test_data/policy.json
Normal file
13
kms_cli/test_data/policy.json
Normal 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
2
kms_client/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target/
|
||||
.cargo_check/
|
12
kms_client/.vscode/settings.json
vendored
Normal file
12
kms_client/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"keypair",
|
||||
"kmip",
|
||||
"mcfe",
|
||||
"newtype",
|
||||
"PKCS",
|
||||
"serializers",
|
||||
"thiserror",
|
||||
"Ttlv"
|
||||
]
|
||||
}
|
16
kms_client/Cargo.toml
Normal file
16
kms_client/Cargo.toml
Normal 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
4
kms_client/README.md
Normal 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
53
kms_client/src/error.rs
Normal 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
392
kms_client/src/lib.rs
Normal 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 certificate’s PKCS#12 Password
|
||||
/// Link. The current certificate chain shall also be included
|
||||
/// as determined by using the private key’s Public Key link to get the
|
||||
/// corresponding public key (where relevant), and then using that
|
||||
/// public key’s PKCS#12 Certificate Link to get the base certificate, and
|
||||
/// then using each certificate’s 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
6
kms_common/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"Deserialization",
|
||||
"Kmip"
|
||||
]
|
||||
}
|
2137
kms_common/Cargo.lock
generated
Normal file
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
26
kms_common/Cargo.toml
Normal 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
8
kms_common/README.md
Normal 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
37
kms_common/src/error.rs
Normal 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())
|
||||
}
|
||||
}
|
36
kms_common/src/kmip/access.rs
Normal file
36
kms_common/src/kmip/access.rs
Normal 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,
|
||||
}
|
528
kms_common/src/kmip/kmip_data_structures.rs
Normal file
528
kms_common/src/kmip/kmip_data_structures.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
kms_common/src/kmip/kmip_key_utils.rs
Normal file
39
kms_common/src/kmip/kmip_key_utils.rs
Normal 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(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
282
kms_common/src/kmip/kmip_objects.rs
Normal file
282
kms_common/src/kmip/kmip_objects.rs
Normal 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,
|
||||
}
|
841
kms_common/src/kmip/kmip_operations.rs
Normal file
841
kms_common/src/kmip/kmip_operations.rs
Normal 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,
|
||||
}
|
1398
kms_common/src/kmip/kmip_types.rs
Normal file
1398
kms_common/src/kmip/kmip_types.rs
Normal file
File diff suppressed because it is too large
Load diff
7
kms_common/src/kmip/mod.rs
Normal file
7
kms_common/src/kmip/mod.rs
Normal 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;
|
868
kms_common/src/kmip/ttlv/deserializer.rs
Normal file
868
kms_common/src/kmip/ttlv/deserializer.rs
Normal 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)
|
||||
}
|
||||
}
|
47
kms_common/src/kmip/ttlv/error.rs
Normal file
47
kms_common/src/kmip/ttlv/error.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
461
kms_common/src/kmip/ttlv/mod.rs
Normal file
461
kms_common/src/kmip/ttlv/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
712
kms_common/src/kmip/ttlv/serializer.rs
Normal file
712
kms_common/src/kmip/ttlv/serializer.rs
Normal 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)
|
||||
}
|
||||
}
|
55
kms_common/src/kmip/ttlv/tests/attributes_with_links.json
Normal file
55
kms_common/src/kmip/ttlv/tests/attributes_with_links.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
139
kms_common/src/kmip/ttlv/tests/import.json
Normal file
139
kms_common/src/kmip/ttlv/tests/import.json
Normal file
File diff suppressed because one or more lines are too long
129
kms_common/src/kmip/ttlv/tests/import_abe_public_key_java.json
Normal file
129
kms_common/src/kmip/ttlv/tests/import_abe_public_key_java.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
661
kms_common/src/kmip/ttlv/tests/mod.rs
Normal file
661
kms_common/src/kmip/ttlv/tests/mod.rs
Normal 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
7
kms_common/src/lib.rs
Normal 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;
|
40
kms_common/src/log_utils.rs
Normal file
40
kms_common/src/log_utils.rs
Normal 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
2
kms_server/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target/
|
||||
.cargo_check/
|
33
kms_server/.vscode/settings.json
vendored
Normal file
33
kms_server/.vscode/settings.json
vendored
Normal 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
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
60
kms_server/Cargo.toml
Normal 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
43
kms_server/Postgresql.md
Normal 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
33
kms_server/README.md
Normal 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
64
kms_server/src/auth.rs
Normal 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
181
kms_server/src/config.rs
Normal 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
174
kms_server/src/error.rs
Normal 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)*)))
|
||||
};
|
||||
}
|
312
kms_server/src/kmip/kmip_server/abe.rs
Normal file
312
kms_server/src/kmip/kmip_server/abe.rs
Normal 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(¤t_key_attributes)?;
|
||||
// Generate a fresh User Decryption Key
|
||||
let new_user_decryption_key = create_user_decryption_key_object(
|
||||
master_private_key_bytes,
|
||||
policy,
|
||||
¤t_access_policy,
|
||||
Some(¤t_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)))
|
||||
}
|
108
kms_server/src/kmip/kmip_server/database.rs
Normal file
108
kms_server/src/kmip/kmip_server/database.rs
Normal 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),
|
||||
}
|
||||
}
|
7
kms_server/src/kmip/kmip_server/mod.rs
Normal file
7
kms_server/src/kmip/kmip_server/mod.rs
Normal 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;
|
965
kms_server/src/kmip/kmip_server/mysql.rs
Normal file
965
kms_server/src/kmip/kmip_server/mysql.rs
Normal 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(())
|
||||
}
|
||||
}
|
939
kms_server/src/kmip/kmip_server/pgsql.rs
Normal file
939
kms_server/src/kmip/kmip_server/pgsql.rs
Normal 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(())
|
||||
}
|
||||
}
|
533
kms_server/src/kmip/kmip_server/server/implementation.rs
Normal file
533
kms_server/src/kmip/kmip_server/server/implementation.rs
Normal 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),
|
||||
}
|
||||
}
|
836
kms_server/src/kmip/kmip_server/server/kmip_server.rs
Normal file
836
kms_server/src/kmip/kmip_server/server/kmip_server.rs
Normal 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 certificate’s PKCS#12 Password
|
||||
/// Link. The current certificate chain shall also be included
|
||||
/// as determined by using the private key’s Public Key link to get the
|
||||
/// corresponding public key (where relevant), and then using that
|
||||
/// public key’s PKCS#12 Certificate Link to get the base certificate, and
|
||||
/// then using each certificate’s 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(())
|
||||
}
|
||||
}
|
10
kms_server/src/kmip/kmip_server/server/mod.rs
Normal file
10
kms_server/src/kmip/kmip_server/server/mod.rs
Normal 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>,
|
||||
}
|
691
kms_server/src/kmip/kmip_server/sqlite.rs
Normal file
691
kms_server/src/kmip/kmip_server/sqlite.rs
Normal 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(())
|
||||
}
|
||||
}
|
64
kms_server/src/kmip/local_client.rs
Normal file
64
kms_server/src/kmip/local_client.rs
Normal 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)
|
||||
}
|
||||
}
|
6
kms_server/src/kmip/mod.rs
Normal file
6
kms_server/src/kmip/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
pub mod kmip_server;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
228
kms_server/src/kmip/tests/abe_tests/integration_tests.rs
Normal file
228
kms_server/src/kmip/tests/abe_tests/integration_tests.rs
Normal 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(())
|
||||
}
|
2
kms_server/src/kmip/tests/abe_tests/mod.rs
Normal file
2
kms_server/src/kmip/tests/abe_tests/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod integration_tests;
|
||||
mod unit_tests;
|
350
kms_server/src/kmip/tests/abe_tests/unit_tests.rs
Normal file
350
kms_server/src/kmip/tests/abe_tests/unit_tests.rs
Normal 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(())
|
||||
}
|
112
kms_server/src/kmip/tests/curve_25519_tests/kmip_requests.rs
Normal file
112
kms_server/src/kmip/tests/curve_25519_tests/kmip_requests.rs
Normal 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)),
|
||||
}
|
||||
}
|
2
kms_server/src/kmip/tests/curve_25519_tests/mod.rs
Normal file
2
kms_server/src/kmip/tests/curve_25519_tests/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub(crate) mod kmip_requests;
|
||||
mod unit_tests;
|
164
kms_server/src/kmip/tests/curve_25519_tests/unit_tests.rs
Normal file
164
kms_server/src/kmip/tests/curve_25519_tests/unit_tests.rs
Normal 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(())
|
||||
}
|
50
kms_server/src/kmip/tests/fpe_tests/kmip_requests.rs
Normal file
50
kms_server/src/kmip/tests/fpe_tests/kmip_requests.rs
Normal 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,
|
||||
}
|
||||
}
|
2
kms_server/src/kmip/tests/fpe_tests/mod.rs
Normal file
2
kms_server/src/kmip/tests/fpe_tests/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod kmip_requests;
|
||||
mod unit_tests;
|
97
kms_server/src/kmip/tests/fpe_tests/unit_tests.rs
Normal file
97
kms_server/src/kmip/tests/fpe_tests/unit_tests.rs
Normal 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(())
|
||||
}
|
556
kms_server/src/kmip/tests/kmip_server_tests.rs
Normal file
556
kms_server/src/kmip/tests/kmip_server_tests.rs
Normal 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(())
|
||||
}
|
34
kms_server/src/kmip/tests/mcfe_tests/kmip_requests.rs
Normal file
34
kms_server/src/kmip/tests/mcfe_tests/kmip_requests.rs
Normal 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(),
|
||||
})
|
||||
}
|
2
kms_server/src/kmip/tests/mcfe_tests/mod.rs
Normal file
2
kms_server/src/kmip/tests/mcfe_tests/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod kmip_requests;
|
||||
mod unit_tests;
|
117
kms_server/src/kmip/tests/mcfe_tests/unit_tests.rs
Normal file
117
kms_server/src/kmip/tests/mcfe_tests/unit_tests.rs
Normal 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(())
|
||||
}
|
6
kms_server/src/kmip/tests/mod.rs
Normal file
6
kms_server/src/kmip/tests/mod.rs
Normal 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;
|
57
kms_server/src/kmip/tests/test_utils.rs
Normal file
57
kms_server/src/kmip/tests/test_utils.rs
Normal 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)
|
||||
}
|
164
kms_server/src/kmip_endpoint.rs
Normal file
164
kms_server/src/kmip_endpoint.rs
Normal 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
32
kms_server/src/lib.rs
Normal 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
33
kms_server/src/main.rs
Normal 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
|
||||
}
|
117
kms_server/src/middlewares/auth.rs
Normal file
117
kms_server/src/middlewares/auth.rs
Normal 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 }
|
||||
}
|
||||
}
|
1
kms_server/src/middlewares/mod.rs
Normal file
1
kms_server/src/middlewares/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod auth;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue