From ea9b3a2e7dc7f51f5722ab2d7d60376fbe5fe3ac Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 13 May 2024 13:27:52 +0100 Subject: [PATCH 1/2] 93: added support for reading asymmetric keys from the /transit/keys route Previously the route assumed a symmetric key's creation unix timestamp would be returned. For asymmetric keys the response differs; it returns the creation RFC3339 timestamp, public key, and key type. Mentions #93. Signed-off-by: Matt Davis --- Cargo.toml | 1 + src/api/transit.rs | 4 ++++ src/api/transit/responses.rs | 20 +++++++++++++++++- tests/transit.rs | 41 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5432832..ec99a50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ tracing = { version = "0.1.37", features = ["log"] } [dev-dependencies] base64 = "0.21" +chrono = "0.4.38" data-encoding = "2.3.3" tokio-test = "0.4.2" tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } diff --git a/src/api/transit.rs b/src/api/transit.rs index c17b11b..58568e2 100644 --- a/src/api/transit.rs +++ b/src/api/transit.rs @@ -26,10 +26,14 @@ pub enum KeyType { /// ECDSA using the P-521 elliptic curve (asymmetric) EcdsaP521, /// RSA with bit size of 2048 (asymmetric) + // kebab-case conversion doesn't work for words starting with a digit. + #[serde(rename = "rsa-2048")] Rsa2048, /// RSA with bit size of 3072 (asymmetric) + #[serde(rename = "rsa-3072")] Rsa3072, /// RSA with bit size of 4096 (asymmetric) + #[serde(rename = "rsa-4096")] Rsa4096, } diff --git a/src/api/transit/responses.rs b/src/api/transit/responses.rs index 446cb58..a39e205 100644 --- a/src/api/transit/responses.rs +++ b/src/api/transit/responses.rs @@ -12,7 +12,8 @@ pub struct ReadKeyResponse { pub derived: bool, pub exportable: bool, pub allow_plaintext_backup: bool, - pub keys: HashMap, + /// If the key is asymmetric, the API returns the public keys + pub keys: ReadKeyData, pub min_decryption_version: u64, pub min_encryption_version: u64, pub name: String, @@ -23,6 +24,23 @@ pub struct ReadKeyResponse { pub imported: Option, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ReadKeyData { + /// A key ID integer (string) to unix timestamp. + Symmetric(HashMap), + /// A key ID integer (string) to public key mapping. + Asymmetric(HashMap), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ReadPublicKeyEntry { + /// An ISO8601 timestamp + pub creation_time: String, + pub name: String, + pub public_key: String, +} + /// Response from executing /// [ListKeysRequest][crate::api::transit::requests::ListKeysRequest] #[derive(Deserialize, Debug, Serialize)] diff --git a/tests/transit.rs b/tests/transit.rs index 1de1d6d..e2e0870 100644 --- a/tests/transit.rs +++ b/tests/transit.rs @@ -89,6 +89,20 @@ mod key { ) .await; assert!(resp.is_ok()); + + let resp = key::create( + &endpoint.client, + &endpoint.path, + &endpoint.keys.asymmetric, + Some( + CreateKeyRequest::builder() + .exportable(false) + .derived(false) + .key_type(KeyType::Rsa2048), + ), + ) + .await; + assert!(resp.is_ok()); } pub async fn test_read(endpoint: &TransitEndpoint) { @@ -107,6 +121,31 @@ mod key { .unwrap(); // requires config update first assert!(!&resp.deletion_allowed); + + let resp = key::read(&endpoint.client, &endpoint.path, &endpoint.keys.asymmetric) + .await + .unwrap(); + assert_eq!(&resp.name, &endpoint.keys.asymmetric); + assert!(matches!(&resp.key_type, KeyType::Rsa2048)); + match &resp.keys { + vaultrs::api::transit::responses::ReadKeyData::Symmetric(_) => { + panic!("Key must be asymmetric") + } + vaultrs::api::transit::responses::ReadKeyData::Asymmetric(keys) => { + for (_key_id, key_metadata) in keys { + let _datetime: chrono::DateTime = key_metadata + .creation_time + .parse() + .expect("Parse ISO8601 timestamp correctly"); + assert!(key_metadata + .public_key + .starts_with("-----BEGIN PUBLIC KEY-----\n")); + assert!(key_metadata + .public_key + .ends_with("\n-----END PUBLIC KEY-----\n")); + } + } + } } pub async fn test_list(endpoint: &TransitEndpoint) { @@ -418,6 +457,7 @@ pub struct TestKeys { pub export: String, pub delete: String, pub signing: String, + pub asymmetric: String, } pub struct TestData { @@ -458,6 +498,7 @@ impl TransitEndpoint { export: "export-key".into(), delete: "delete-key".into(), signing: "signing-key".into(), + asymmetric: "asymmetric-key".into(), }, data: TestData::new("test-context", "super secret data"), }; From d81e9add320aeec44d799a0df57fbf4edb4d019f Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 29 May 2024 19:04:25 +0100 Subject: [PATCH 2/2] chore: made ReadPublicKeyEntry impl PartialEq & Clone Signed-off-by: Matt Davis --- src/api/transit/responses.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/transit/responses.rs b/src/api/transit/responses.rs index a39e205..73b51be 100644 --- a/src/api/transit/responses.rs +++ b/src/api/transit/responses.rs @@ -33,7 +33,7 @@ pub enum ReadKeyData { Asymmetric(HashMap), } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct ReadPublicKeyEntry { /// An ISO8601 timestamp pub creation_time: String,