From 63d531966afdec061beb70c7d5e2050e64785a82 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Mon, 9 Oct 2023 16:44:13 +0200 Subject: [PATCH 01/19] Update dead links in README --- crate/client/README.md | 2 +- crate/kmip/README.md | 2 +- crate/kmip/src/kmip/ttlv/tests/mod.rs | 7 +++---- crate/utils/README.md | 4 ++-- crate/utils/src/crypto/symmetric/aes_256_gcm.rs | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/crate/client/README.md b/crate/client/README.md index 69041727c..35544faa1 100644 --- a/crate/client/README.md +++ b/crate/client/README.md @@ -1,4 +1,4 @@ # Cosmian KMS Client The `cosmian_kms_client` forges and sends post query to the `cosmian_kms_server` given already prepared kmip data. -For preparing these kmip data, please refer to the [cosmian_kms_utils](http://gitlab.cosmian.com/core/kms/-/tree/main/utils). +For preparing these kmip data, please refer to the [cosmian_kms_utils](https://github.com/Cosmian/kms/tree/main/crate/utils). diff --git a/crate/kmip/README.md b/crate/kmip/README.md index a0302fc83..139d7a5e5 100644 --- a/crate/kmip/README.md +++ b/crate/kmip/README.md @@ -5,7 +5,7 @@ It also implements the TTLV serialization format. This KMIP data are then used by the `cosmian_kms_client` to query the `cosmian_kms_server` and then by the `cosmian_kms_server` to respond. -For specific Cosmian crypto-systems, you can use the [cosmian_kms_utils](http://gitlab.cosmian.com/core/kms/-/tree/main/utils) to generate KMIP data with an abstraction level. +For specific Cosmian crypto-systems, you can use the [cosmian_kms_utils](https://github.com/Cosmian/kms/tree/main/crate/utils) to generate KMIP data with an abstraction level. ## Supported KMIP Objects diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index 8cb90ec8a..33283323c 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -512,7 +512,6 @@ fn test_aes_key_material() { } #[test] -#[allow(clippy::large_enum_variant)] fn test_some_attributes() { log_init("info,hyper=info,reqwest=info"); #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -520,17 +519,17 @@ fn test_some_attributes() { enum Wrapper { Attr { #[serde(skip_serializing_if = "Option::is_none", rename = "Attributes")] - attributes: Option, + attributes: Option>, }, NoAttr { whatever: i32, }, } let value = Wrapper::Attr { - attributes: Some(Attributes { + attributes: Some(Box::new(Attributes { object_type: Some(ObjectType::SymmetricKey), ..Attributes::default() - }), + })), }; let ttlv = to_ttlv(&value).unwrap(); let json = serde_json::to_value(&ttlv).unwrap(); diff --git a/crate/utils/README.md b/crate/utils/README.md index e42d831d8..048bd78d9 100644 --- a/crate/utils/README.md +++ b/crate/utils/README.md @@ -2,7 +2,7 @@ To understand what the `cosmian_kms_utils` is, let's first remind the followings: -- [kms_server](http://gitlab.cosmian.com/core/kms/-/tree/main/server) exposes a KMS relying on the KMIP standard. The KMS server offers a REST API to run crypto operations like key generation, encryption or decryption relying on CoverCrypt. It stores the generated keys in a relational database. +- [kms_server](https://github.com/Cosmian/kms/tree/main/crate/server) exposes a KMS relying on the KMIP standard. The KMS server offers a REST API to run crypto operations like key generation, encryption or decryption relying on CoverCrypt. It stores the generated keys in a relational database. Therefore the `cosmian_kms_utils` offers upper functions to deal with the KMIP format for the crypto-systems designed by Cosmian. In deed, the KMS server waits for a query containing a Kmip-formatted data. This format is very exhautive and complexe but we just need a part of it to cover our needs. Then, in our library, we offer functions which specialized Kmip objects and operations for our crypto-systems, enabling the user to easily create queries to the KMS server without really be aware about the Kmip format. @@ -23,4 +23,4 @@ cargo build ## Testing -You can find a complete example of the library usage for ABE in the integration tests of the KMS server [here](http://gitlab.cosmian.com/core/cosmian_server/-/blob/develop/microservices/kms/kms_server/src/kmip/tests/abe_tests/integration_tests.rs). +You can find a complete example of the library usage in the integration tests of the KMS server [here](https://github.com/Cosmian/kms/tree/main/crate/server/src/tests). diff --git a/crate/utils/src/crypto/symmetric/aes_256_gcm.rs b/crate/utils/src/crypto/symmetric/aes_256_gcm.rs index 7601be526..fba4dcc94 100644 --- a/crate/utils/src/crypto/symmetric/aes_256_gcm.rs +++ b/crate/utils/src/crypto/symmetric/aes_256_gcm.rs @@ -110,7 +110,7 @@ impl EncryptionSystem for AesGcmSystem { Ok(EncryptResponse { unique_identifier: self.key_uid.clone(), - data: Some(data.clone()), + data: Some(data), iv_counter_nonce: Some(nonce.as_bytes().to_vec()), correlation_value, authenticated_encryption_tag: Some(tag), @@ -176,7 +176,7 @@ impl DecryptionSystem for AesGcmSystem { Ok(DecryptResponse { unique_identifier: self.key_uid.clone(), - data: Some(bytes.clone()), + data: Some(bytes), correlation_value, }) } From 947ce42152b0b0b91bfc680fc1d79604a87ef78a Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 13 Oct 2023 10:59:03 +0200 Subject: [PATCH 02/19] Define message request/response structs and enums --- crate/kmip/src/kmip/kmip_messages.rs | 185 ++++++++++++++++ crate/kmip/src/kmip/kmip_operations.rs | 5 +- crate/kmip/src/kmip/kmip_types.rs | 284 +++++++++++++++++++++++-- crate/kmip/src/kmip/mod.rs | 1 + 4 files changed, 452 insertions(+), 23 deletions(-) create mode 100644 crate/kmip/src/kmip/kmip_messages.rs diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs new file mode 100644 index 000000000..534ca966b --- /dev/null +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -0,0 +1,185 @@ +/// The messages in the protocol consist of a message header, +/// one or more batch items (which contain OPTIONAL message payloads), +/// and OPTIONAL message extensions. The message headers contain fields whose +/// presence is determined by the protocol features used (e.g., asynchronous responses). +/// The field contents are also determined by whether the message is a request or a response. +/// The message payload is determined by the specific operation being +/// requested or to which is being replied. +/// +/// The message headers are structures that contain some of the following objects. +/// +/// Messages contain the following objects and fields. +/// All fields SHALL appear in the order specified. +/// +/// If the client is capable of accepting asynchronous responses, +/// then it MAY set the Asynchronous +/// +/// Indicator in the header of a batched request. +/// The batched responses MAY contain a mixture of synchronous and +/// asynchronous responses only if the Asynchronous Indicator is present in the header. +use serde::Serialize; + +use super::{ + kmip_operations::ErrorReason, + kmip_types::{ + AsynchronousIndicator, AttestationType, BatchErrorContinuationOption, Credential, + MessageExtension, Nonce, OperationEnumeration, ProtocolVersion, ResultStatusEnumeration, + }, +}; + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct RequestMessage { + /// Header of the request + pub header: RequestHeader, + /// Batch items of the request + pub items: Vec, +} + +/// Header of the request +/// +/// Contains fields whose presence is determined by the protocol features used. +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct RequestHeader { + pub protocol_version: ProtocolVersion, + /// This is an OPTIONAL field contained in a request message, + /// and is used to indicate the maximum size of a response, in bytes, + /// that the requester SHALL be able to handle. + /// + /// It SHOULD only be sent in requests that possibly return large replies. + #[serde(skip_serializing_if = "Option::is_none")] + pub maximum_response_size: Option, + /// The Client Correlation Value is a string that MAY be added to messages by clients + /// to provide additional information to the server. It need not be unique. + /// The server SHOULD log this information. + /// + /// For client to server operations, the Client Correlation Value is provided in the request. + /// For server to client operations, the Client Correlation Value is provided in the response. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_correlation_value: Option, + /// The Server Correlation Value SHOULD be provided by the server and + /// SHOULD be globally unique, and SHOULD be logged by the server with each request. + /// + /// For client to server operations, the Server Correlation Value is provided in the response. + /// For server to client operations, the Server Correlation Value is provided in the request. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_correlation_value: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub asynchronous_indicator: Option, + /// Indicates whether the client is able to create + /// an Attestation Credential Object. + /// + /// If not present, the value `false` is assumed + #[serde(skip_serializing_if = "Option::is_none")] + pub attestation_capable_indicator: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub attestation_type: Option>, + /// Used to authenticate the requester + #[serde(skip_serializing_if = "Option::is_none")] + pub authentication: Option>, + /// If omitted, then `Stop` is assumed + #[serde(skip_serializing_if = "Option::is_none")] + pub batch_error_continuation_option: Option, + /// If omitted, then `true` is assumed + #[serde(skip_serializing_if = "Option::is_none")] + pub batch_order_option: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp: Option, // epoch millis + /// This field contains the number of Batch Items in a message and is REQUIRED. + /// + /// If only a single operation is being requested, then the batch count SHALL be set to 1. + /// The Message Payload, which follows the Message Header, contains one or more batch items. + pub batch_count: u32, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct RequestBatchItem { + operation: OperationEnumeration, + /// Indicates that the Data output of the operation should not + /// be returned to the client + ephemeral: Option, + /// Required if `batch_count` > 1 + unique_batch_item_id: Option, + /// Depends on the Operation + request_payload: Vec, + message_extension: Option>, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct ResponseMessage { + /// Header of the response + pub header: ResponseHeader, + /// Batch items of the response + pub items: Vec, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct ResponseHeader { + pub protocol_version: ProtocolVersion, + pub timestamp: u64, // epoch millis + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + /// Mandatory only if Hashed Password credential was used + /// + /// Hash(Timestamp || S1 || Hash(S2)), where S1, S2 and + /// the Hash algorithm are defined in the Hashed Password credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_hashed_password: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub attestation_type: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_correlation_value: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_correlation_value: Option, + /// This field contains the number of Batch Items in a message and is REQUIRED. + /// + /// If only a single operation is being requested, then the batch count SHALL be set to 1. + /// The Message Payload, which follows the Message Header, contains one or more batch items. + pub batch_count: u32, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct ResponseBatchItem { + /// Required if present in Request Batch Item + #[serde(skip_serializing_if = "Option::is_none")] + pub operation: Option, + /// Required if present in Request Batch Item + pub unique_batch_item_id: Option, + /// Indicates the success or failure of a request + pub result_status: ResultStatusEnumeration, + /// Indicates a reason for failure or a modifier for a + /// partially successful operation and SHALL be present in + /// responses that return a Result Status of Failure. + /// + /// Required if `result_status` is `Failure` + #[serde(skip_serializing_if = "Option::is_none")] + pub result_reason: Option, + /// Contains a more descriptive error message, + /// which MAY be provided to an end user or used for logging/auditing purposes. + /// + /// Required if `result_status` is NOT `Pending` or `Success` + #[serde(skip_serializing_if = "Option::is_none")] + pub result_message: Option, + /// Returned in the immediate response to an operation that is pending and + /// that requires asynchronous polling. Note: the server decides which + /// operations are performed synchronously or asynchronously. + /// + /// A server-generated correlation value SHALL be specified in any subsequent + /// Poll or Cancel operations that pertain to the original operation. + /// + /// Required if `result_status` is `Pending` + #[serde(skip_serializing_if = "Option::is_none")] + pub asynchronous_correlation_value: Option>, + /// Mandatory if a success, `None` in case of failure. + /// + /// Content depends on Operation. + #[serde(skip_serializing_if = "Option::is_none")] + pub response_payload: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub message_extension: Option, +} diff --git a/crate/kmip/src/kmip/kmip_operations.rs b/crate/kmip/src/kmip/kmip_operations.rs index b74ff7887..8abe76511 100644 --- a/crate/kmip/src/kmip/kmip_operations.rs +++ b/crate/kmip/src/kmip/kmip_operations.rs @@ -19,7 +19,7 @@ use super::{ use crate::error::KmipError; #[allow(non_camel_case_types)] -#[derive(Copy, Clone, Display, Debug, Eq, PartialEq)] +#[derive(Serialize, Copy, Clone, Display, Debug, Eq, PartialEq)] pub enum ErrorReason { Item_Not_Found = 0x0000_0001, Response_Too_Large = 0x0000_0002, @@ -88,6 +88,9 @@ pub enum ErrorReason { PKCS_11_Invalid_Interface = 0x0000_0047, Private_Protection_Storage_Unavailable = 0x0000_0048, Public_Protection_Storage_Unavailable = 0x0000_0049, + Unknown_Object_Group = 0x0000_004A, + Constraint_Violation = 0x0000_004B, + Duplicate_Process_Request = 0x0000_004C, General_Failure = 0x0000_0100, } diff --git a/crate/kmip/src/kmip/kmip_types.rs b/crate/kmip/src/kmip/kmip_types.rs index 0a5332e97..3962e4736 100644 --- a/crate/kmip/src/kmip/kmip_types.rs +++ b/crate/kmip/src/kmip/kmip_types.rs @@ -1012,6 +1012,7 @@ pub enum AttributeReference { Vendor(VendorAttributeReference), Standard(Tag), } + #[allow(non_camel_case_types)] #[allow(clippy::enum_variant_names)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, EnumString, Display)] @@ -1615,34 +1616,273 @@ pub enum KeyWrapType { #[allow(non_camel_case_types)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Display)] pub enum StateEnumeration { - // Pre-Active: The object exists and SHALL NOT be used for any cryptographic purpose. + /// Pre-Active: The object exists and SHALL NOT be used for any cryptographic purpose. PreActive = 0x0000_0001, - // Active: The object SHALL be transitioned to the Active state prior to being used for any - // cryptographic purpose. The object SHALL only be used for all cryptographic purposes that - // are allowed by its Cryptographic Usage Mask attribute. If a Process Start Date attribute is - // set, then the object SHALL NOT be used for cryptographic purposes prior to the Process - // Start Date. If a Protect Stop attribute is set, then the object SHALL NOT be used for - // cryptographic purposes after the Process Stop Date. + /// Active: The object SHALL be transitioned to the Active state prior to being used for any + /// cryptographic purpose. The object SHALL only be used for all cryptographic purposes that + /// are allowed by its Cryptographic Usage Mask attribute. If a Process Start Date attribute is + /// set, then the object SHALL NOT be used for cryptographic purposes prior to the Process + /// Start Date. If a Protect Stop attribute is set, then the object SHALL NOT be used for + /// cryptographic purposes after the Process Stop Date. Active = 0x0000_0002, - // Deactivated: The object SHALL NOT be used for applying cryptographic protection (e.g., - // encryption, signing, wrapping, MACing, deriving) . The object SHALL only be used for - // cryptographic purposes permitted by the Cryptographic Usage Mask attribute. The object - // SHOULD only be used to process cryptographically-protected information (e.g., decryption, - // signature verification, unwrapping, MAC verification under extraordinary circumstances and - // when special permission is granted. + /// Deactivated: The object SHALL NOT be used for applying cryptographic protection (e.g., + /// encryption, signing, wrapping, MACing, deriving) . The object SHALL only be used for + /// cryptographic purposes permitted by the Cryptographic Usage Mask attribute. The object + /// SHOULD only be used to process cryptographically-protected information (e.g., decryption, + /// signature verification, unwrapping, MAC verification under extraordinary circumstances and + /// when special permission is granted. Deactivated = 0x0000_0003, - // Compromised: The object SHALL NOT be used for applying cryptographic protection (e.g., - // encryption, signing, wrapping, MACing, deriving). The object SHOULD only be used to process - // cryptographically-protected information (e.g., decryption, signature verification, - // unwrapping, MAC verification in a client that is trusted to use managed objects that have - // been compromised. The object SHALL only be used for cryptographic purposes permitted by the - // Cryptographic Usage Mask attribute. + /// Compromised: The object SHALL NOT be used for applying cryptographic protection (e.g., + /// encryption, signing, wrapping, MACing, deriving). The object SHOULD only be used to process + /// cryptographically-protected information (e.g., decryption, signature verification, + /// unwrapping, MAC verification in a client that is trusted to use managed objects that have + /// been compromised. The object SHALL only be used for cryptographic purposes permitted by the + /// Cryptographic Usage Mask attribute. Compromised = 0x0000_0004, - // Destroyed: The object SHALL NOT be used for any cryptographic purpose. + /// Destroyed: The object SHALL NOT be used for any cryptographic purpose. Destroyed = 0x0000_0005, - // Destroyed Compromised: The object SHALL NOT be used for any cryptographic purpose; however - // its compromised status SHOULD be retained for audit or security purposes. + /// Destroyed Compromised: The object SHALL NOT be used for any cryptographic purpose; however + /// its compromised status SHOULD be retained for audit or security purposes. Destroyed_Compromised = 0x0000_0006, } pub type UniqueIdentifier = String; + +/// This field contains the version number of the protocol, ensuring that +/// the protocol is fully understood by both communicating parties. +/// +/// The version number SHALL be specified in two parts, major and minor. +/// +/// Servers and clients SHALL support backward compatibility with versions +/// of the protocol with the same major version. +/// +/// Support for backward compatibility with different major versions is OPTIONAL. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct ProtocolVersion { + pub protocol_version_major: u32, + pub protocol_version_minor: u32, +} + +/// This Enumeration indicates whether the client is able to accept +/// an asynchronous response. +/// +/// If not present in a request, then Prohibited is assumed. +/// +/// If the value is Prohibited, the server SHALL process the request synchronously. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum AsynchronousIndicator { + /// The server SHALL process all batch items in the request asynchronously + /// (returning an Asynchronous Correlation Value for each batch item). + Mandatory = 0x0000_0001, + /// The server MAY process each batch item in the request either asynchronously + /// (returning an Asynchronous Correlation Value for a batch item) or synchronously. + /// The method or policy by which the server determines whether or not to process + /// an individual batch item asynchronously is a decision of the server and + /// is outside of the scope of this protocol. + Optional = 0x0000_0002, + /// The server SHALL NOT process any batch item asynchronously. + /// All batch items SHALL be processed synchronously. + Prohibited = 0x0000_0003, +} + +/// Types of attestation supported by the server +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum AttestationType { + TPM_Quote = 0x0000_0001, + TCG_Integrity_Report = 0x0000_0002, + SAML_Assertion = 0x0000_0003, +} + +/// A Credential is a structure used for client identification purposes +/// and is not managed by the key management system +/// (e.g., user id/password pairs, Kerberos tokens, etc.). +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub enum Credential { + Username_and_Password { + username: String, + password: Option, + }, + Device { + device_serial_number: Option, + password: Option, + device_identifier: Option, + network_identifier: Option, + machine_identifier: Option, + media_identifier: Option, + }, + Attestation { + nonce: Nonce, + attestation_type: AttestationType, + attestation_measurement: Option>, + attestation_assertion: Option>, + }, + One_Time_Password { + username: String, + password: Option, + one_time_password: String, + }, + Hashed_Password { + username: String, + timestamp: u64, // epoch millis + hashing_algorithm: Option, + hashed_password: Vec, + }, + Ticket { + ticket_type: TicketType, + ticket_value: Vec, + }, +} + +impl Credential { + #[allow(dead_code)] + fn value(&self) -> u32 { + match *self { + Credential::Username_and_Password { .. } => 0x0000_0001, + Credential::Device { .. } => 0x0000_0002, + Credential::Attestation { .. } => 0x0000_0003, + Credential::One_Time_Password { .. } => 0x0000_0004, + Credential::Hashed_Password { .. } => 0x0000_0005, + Credential::Ticket { .. } => 0x0000_0006, + } + } +} + +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum TicketType { + Login = 0x0000_0001, +} + +/// A Nonce object is a structure used by the server to send a random value to the client. +/// +/// The Nonce Identifier is assigned by the server and used to identify the Nonce object. +/// The Nonce Value consists of the random data created by the server. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct Nonce { + nonce_id: Vec, + nonce_value: Vec, +} + +/// This option SHALL only be present if the Batch Count is greater than 1. +/// This option SHALL have one of three values (Undo, Stop or Continue). +/// If not specified, then Stop is assumed. +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum BatchErrorContinuationOption { + /// If any operation in the request fails, then the server SHALL undo all the previous operations. + /// + /// Batch item fails and Result Status is set to Operation Failed. + /// Responses to batch items that have already been processed are returned normally. + /// Responses to batch items that have not been processed are not returned. + Undo, + Stop, + Continue, +} + +/// The Message Extension is an OPTIONAL structure that MAY be appended to any Batch Item. +/// +/// It is used to extend protocol messages for the purpose of adding vendor-specified extensions. +/// The Message Extension is a structure that SHALL contain the Vendor Identification, +/// Criticality Indicator, and Vendor Extension fields. +/// +/// The Vendor Identification SHALL be a text string that uniquely identifies the vendor, +/// allowing a client to determine if it is able to parse and understand the extension. +/// +/// If a client or server receives a protocol message containing a message extension +/// that it does not understand, then its actions depend on the Criticality Indicator. +/// +/// If the indicator is True (i.e., Critical), and the receiver does not understand the extension, +/// then the receiver SHALL reject the entire message. +/// If the indicator is False (i.e., Non-Critical), and the receiver does not +/// understand the extension, then the receiver MAY process the rest of the message as +/// if the extension were not present. +/// +/// The Vendor Extension structure SHALL contain vendor-specific extensions. +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct MessageExtension { + /// Text String (with usage limited to alphanumeric, underscore and period – + /// i.e. [A-Za-z0-9_.]) + pub vendor_identification: String, + pub criticality_indicator: bool, + // Vendor extension structure is not precisely defined by KMIP reference + pub vendor_extension: Vec, +} + +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Display)] +pub enum OperationEnumeration { + Create = 0x0000_0001, + CreateKeyPair = 0x0000_0002, + Register = 0x0000_0003, + Rekey = 0x0000_0004, + DeriveKey = 0x0000_0005, + Certify = 0x0000_0006, + Recertify = 0x0000_0007, + Locate = 0x0000_0008, + Check = 0x0000_0009, + Get = 0x0000_000A, + GetAttributes = 0x0000_000B, + GetAttributeList = 0x0000_000C, + AddAttribute = 0x0000_000D, + ModifyAttribute = 0x0000_000E, + DeleteAttribute = 0x0000_000F, + ObtainLease = 0x0000_0010, + GetUsageAllocation = 0x0000_0011, + Activate = 0x0000_0012, + Revoke = 0x0000_0013, + Destroy = 0x0000_0014, + Archive = 0x0000_0015, + Recover = 0x0000_0016, + Validate = 0x0000_0017, + Query = 0x0000_0018, + Cancel = 0x0000_0019, + Poll = 0x0000_001A, + Notify = 0x0000_001B, + Put = 0x0000_001C, + RekeyKeyPair = 0x0000_001D, + DiscoverVersions = 0x0000_001E, + Encrypt = 0x0000_001F, + Decrypt = 0x0000_0020, + Sign = 0x0000_0021, + SignatureVerify = 0x0000_0022, + MAC = 0x0000_0023, + MACVerify = 0x0000_0024, + RNGRetrieve = 0x0000_0025, + RNGSeed = 0x0000_0026, + Hash = 0x0000_0027, + CreateSplitKey = 0x0000_0028, + JoinSplitKey = 0x0000_0029, + Import = 0x0000_002A, + Export = 0x0000_002B, + Log = 0x0000_002C, + Login = 0x0000_002D, + Logout = 0x0000_002E, + DelegatedLogin = 0x0000_002F, + AdjustAttribute = 0x0000_0030, + SetAttribute = 0x0000_0031, + SetEndpointRole = 0x0000_0032, + PKCS11 = 0x0000_0033, + Interop = 0x0000_0034, + ReProvision = 0x0000_0035, + SetDefaults = 0x0000_0036, + SetConstraints = 0x0000_0037, + GetConstraints = 0x0000_0038, + QueryAsynchronousRequests = 0x0000_0039, + Process = 0x0000_003A, + Ping = 0x0000_003B, +} + +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Display)] +pub enum ResultStatusEnumeration { + Success = 0x0000_0000, + OperationFailed = 0x0000_0001, + OperationPending = 0x0000_0002, + OperationUndone = 0x0000_0003, +} diff --git a/crate/kmip/src/kmip/mod.rs b/crate/kmip/src/kmip/mod.rs index e123e56e9..df6531d4e 100644 --- a/crate/kmip/src/kmip/mod.rs +++ b/crate/kmip/src/kmip/mod.rs @@ -1,4 +1,5 @@ pub mod kmip_data_structures; +pub mod kmip_messages; pub mod kmip_objects; pub mod kmip_operations; pub mod kmip_types; From 01b949592f4df78939e3bf44162f243bfc97bf48 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 19 Oct 2023 17:22:41 +0200 Subject: [PATCH 03/19] Implement custom deserializer to properly recover operation struct --- crate/client/src/kms_rest_client.rs | 2 - crate/kmip/src/kmip/kmip_data_structures.rs | 2 +- crate/kmip/src/kmip/kmip_messages.rs | 358 +++++++++++++++++++- crate/kmip/src/kmip/kmip_operations.rs | 34 +- crate/kmip/src/kmip/ttlv/deserializer.rs | 32 +- crate/kmip/src/kmip/ttlv/mod.rs | 2 + crate/kmip/src/kmip/ttlv/tests/mod.rs | 121 ++++++- 7 files changed, 514 insertions(+), 37 deletions(-) diff --git a/crate/client/src/kms_rest_client.rs b/crate/client/src/kms_rest_client.rs index fc243ae95..4121a724c 100644 --- a/crate/client/src/kms_rest_client.rs +++ b/crate/client/src/kms_rest_client.rs @@ -454,9 +454,7 @@ impl KmsRestClient { pub async fn version(&self) -> Result { self.get_no_ttlv("/version", None::<&()>).await } -} -impl KmsRestClient { /// Instantiate a new KMIP REST Client #[allow(clippy::too_many_arguments)] #[allow(dead_code)] diff --git a/crate/kmip/src/kmip/kmip_data_structures.rs b/crate/kmip/src/kmip/kmip_data_structures.rs index f92ca4e8e..b060156ee 100644 --- a/crate/kmip/src/kmip/kmip_data_structures.rs +++ b/crate/kmip/src/kmip/kmip_data_structures.rs @@ -408,7 +408,7 @@ pub enum KeyMaterial { } // Unfortunately, default serialization does not play well -// for ByteString, so we have to do it by had. Deserialization is OK though +// for ByteString, so we have to do it by hand. Deserialization is OK though impl Serialize for KeyMaterial { fn serialize(&self, serializer: S) -> Result where diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index 534ca966b..b82c925c4 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -17,17 +17,20 @@ /// Indicator in the header of a batched request. /// The batched responses MAY contain a mixture of synchronous and /// asynchronous responses only if the Asynchronous Indicator is present in the header. -use serde::Serialize; +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Serialize, +}; use super::{ - kmip_operations::ErrorReason, + kmip_operations::{ErrorReason, Operation}, kmip_types::{ AsynchronousIndicator, AttestationType, BatchErrorContinuationOption, Credential, MessageExtension, Nonce, OperationEnumeration, ProtocolVersion, ResultStatusEnumeration, }, }; -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct RequestMessage { /// Header of the request @@ -39,7 +42,7 @@ pub struct RequestMessage { /// Header of the request /// /// Contains fields whose presence is determined by the protocol features used. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct RequestHeader { pub protocol_version: ProtocolVersion, @@ -93,21 +96,169 @@ pub struct RequestHeader { pub batch_count: u32, } +/// Batch item for a message request +/// +/// `request_payload` depends on the request #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] pub struct RequestBatchItem { - operation: OperationEnumeration, + pub operation: OperationEnumeration, /// Indicates that the Data output of the operation should not /// be returned to the client - ephemeral: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ephemeral: Option, /// Required if `batch_count` > 1 - unique_batch_item_id: Option, - /// Depends on the Operation - request_payload: Vec, - message_extension: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub unique_batch_item_id: Option, + /// The KMIP request, which depends on the KMIP Operation + pub request_payload: Operation, + #[serde(skip_serializing_if = "Option::is_none")] + pub message_extension: Option>, } -#[derive(Debug, Serialize)] +impl<'de> Deserialize<'de> for RequestBatchItem { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(field_identifier)] + enum Field { + Operation, + Ephemeral, + UniqueBatchItemId, + RequestPayload, + MessageExtension, + } + + struct RequestBatchItemVisitor; + + impl<'de> Visitor<'de> for RequestBatchItemVisitor { + type Value = RequestBatchItem; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct RequestBatchItem") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut operation: Option = None; + let mut ephemeral: Option = None; + let mut unique_batch_item_id: Option = None; + let mut request_payload: Option = None; + let mut message_extension: Option> = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Operation => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")) + } + operation = Some(map.next_value()?); + } + Field::Ephemeral => { + if ephemeral.is_some() { + return Err(de::Error::duplicate_field("ephemeral")) + } + ephemeral = Some(map.next_value()?); + } + Field::UniqueBatchItemId => { + if unique_batch_item_id.is_some() { + return Err(de::Error::duplicate_field("unique_batch_item_id")) + } + unique_batch_item_id = Some(map.next_value()?); + } + Field::MessageExtension => { + if message_extension.is_some() { + return Err(de::Error::duplicate_field("message_extension")) + } + message_extension = Some(map.next_value()?); + } + Field::RequestPayload => { + if request_payload.is_some() { + return Err(de::Error::duplicate_field("request_payload")) + } + // we must have parsed the `operation` field before + // TODO: handle the case where the keys are not in right order + let Some(operation) = operation else { + return Err(de::Error::missing_field("operation")) + }; + // recover by hand the proper type of `request_payload` + // the default derived deserializer does not have enough + // information to properly recover which type has been + // serialized, we need to do the job by hand, + // using the `operation` enum. + request_payload = Some(match operation { + OperationEnumeration::Encrypt => { + Operation::Encrypt(map.next_value()?) + } + OperationEnumeration::Create => { + Operation::Create(map.next_value()?) + } + OperationEnumeration::CreateKeyPair => { + Operation::CreateKeyPair(map.next_value()?) + } + OperationEnumeration::Certify => { + Operation::Certify(map.next_value()?) + } + OperationEnumeration::Locate => { + Operation::Locate(map.next_value()?) + } + OperationEnumeration::Get => Operation::Get(map.next_value()?), + OperationEnumeration::GetAttributes => { + Operation::GetAttributes(map.next_value()?) + } + OperationEnumeration::Revoke => { + Operation::Revoke(map.next_value()?) + } + OperationEnumeration::Destroy => { + Operation::Destroy(map.next_value()?) + } + OperationEnumeration::Decrypt => { + Operation::Decrypt(map.next_value()?) + } + OperationEnumeration::Import => { + Operation::Import(map.next_value()?) + } + OperationEnumeration::Export => { + Operation::Export(map.next_value()?) + } + _ => return Err(de::Error::missing_field("valid enum operation")), + }); + } + } + } + let operation = operation.ok_or_else(|| de::Error::missing_field("operation"))?; + tracing::trace!("RequestBatchItem operation: {operation:?}"); + + let request_payload = + request_payload.ok_or_else(|| de::Error::missing_field("request_payload"))?; + tracing::trace!("RequestBatchItem request payload: {request_payload:?}"); + + Ok(RequestBatchItem { + operation, + ephemeral, + unique_batch_item_id, + request_payload, + message_extension, + }) + } + } + + const FIELDS: &[&str] = &[ + "operation", + "ephemeral", + "unique_batch_item_id", + "request_payload", + "message_extension", + ]; + deserializer.deserialize_struct("RequestBatchItem", FIELDS, RequestBatchItemVisitor) + } +} + +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ResponseMessage { /// Header of the response @@ -116,7 +267,7 @@ pub struct ResponseMessage { pub items: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ResponseHeader { pub protocol_version: ProtocolVersion, @@ -149,6 +300,7 @@ pub struct ResponseBatchItem { #[serde(skip_serializing_if = "Option::is_none")] pub operation: Option, /// Required if present in Request Batch Item + #[serde(skip_serializing_if = "Option::is_none")] pub unique_batch_item_id: Option, /// Indicates the success or failure of a request pub result_status: ResultStatusEnumeration, @@ -175,11 +327,191 @@ pub struct ResponseBatchItem { /// Required if `result_status` is `Pending` #[serde(skip_serializing_if = "Option::is_none")] pub asynchronous_correlation_value: Option>, + /// The KMIP response, which depends on the KMIP Operation + /// /// Mandatory if a success, `None` in case of failure. /// /// Content depends on Operation. #[serde(skip_serializing_if = "Option::is_none")] - pub response_payload: Option>, + pub response_payload: Option, #[serde(skip_serializing_if = "Option::is_none")] pub message_extension: Option, } + +impl<'de> Deserialize<'de> for ResponseBatchItem { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(field_identifier)] + enum Field { + Operation, + UniqueBatchItemId, + ResultStatus, + ResultReason, + ResultMessage, + AsynchronousCorrelationValue, + ResponsePayload, + MessageExtension, + } + + struct ResponseBatchItemVisitor; + + impl<'de> Visitor<'de> for ResponseBatchItemVisitor { + type Value = ResponseBatchItem; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct ResponseBatchItem") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut operation: Option = None; + let mut unique_batch_item_id: Option = None; + let mut result_status: Option = None; + let mut result_reason: Option = None; + let mut result_message: Option = None; + let mut asynchronous_correlation_value: Option> = None; + let mut response_payload: Option = None; + let mut message_extension: Option = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Operation => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")) + } + operation = Some(map.next_value()?); + } + Field::UniqueBatchItemId => { + if unique_batch_item_id.is_some() { + return Err(de::Error::duplicate_field("unique_batch_item_id")) + } + unique_batch_item_id = Some(map.next_value()?); + } + Field::MessageExtension => { + if message_extension.is_some() { + return Err(de::Error::duplicate_field("message_extension")) + } + message_extension = Some(map.next_value()?); + } + Field::ResultStatus => { + if result_status.is_some() { + return Err(de::Error::duplicate_field("result_status")) + } + result_status = Some(map.next_value()?); + } + Field::ResultReason => { + if result_reason.is_some() { + return Err(de::Error::duplicate_field("result_reason")) + } + result_reason = Some(map.next_value()?); + } + Field::ResultMessage => { + if result_message.is_some() { + return Err(de::Error::duplicate_field("result_message")) + } + result_message = Some(map.next_value()?); + } + Field::AsynchronousCorrelationValue => { + if asynchronous_correlation_value.is_some() { + return Err(de::Error::duplicate_field( + "asynchronous_correlation_value", + )) + } + asynchronous_correlation_value = Some(map.next_value()?); + } + Field::ResponsePayload => { + if response_payload.is_some() { + return Err(de::Error::duplicate_field("response_payload")) + } + // we must have parsed the `operation` field before + // TODO: handle the case where the keys are not in right order + let Some(operation) = operation else { + return Err(de::Error::missing_field("operation")) + }; + // recover by hand the proper type of `response_payload` + // the default derived deserializer does not have enough + // information to properly recover which type has been + // serialized, we need to do the job by hand, + // using the `operation` enum. + response_payload = Some(match operation { + OperationEnumeration::Encrypt => { + Operation::Encrypt(map.next_value()?) + } + OperationEnumeration::Create => { + Operation::Create(map.next_value()?) + } + OperationEnumeration::CreateKeyPair => { + Operation::CreateKeyPair(map.next_value()?) + } + OperationEnumeration::Certify => { + Operation::Certify(map.next_value()?) + } + OperationEnumeration::Locate => { + Operation::Locate(map.next_value()?) + } + OperationEnumeration::Get => Operation::Get(map.next_value()?), + OperationEnumeration::GetAttributes => { + Operation::GetAttributes(map.next_value()?) + } + OperationEnumeration::Revoke => { + Operation::Revoke(map.next_value()?) + } + OperationEnumeration::Destroy => { + Operation::Destroy(map.next_value()?) + } + OperationEnumeration::Decrypt => { + Operation::Decrypt(map.next_value()?) + } + OperationEnumeration::Import => { + Operation::Import(map.next_value()?) + } + OperationEnumeration::Export => { + Operation::Export(map.next_value()?) + } + _ => { + return Err(de::Error::missing_field( + "valid enum operation (unsupported operation ?)", + )) + } + }); + } + } + } + + let result_status = + result_status.ok_or_else(|| de::Error::missing_field("result_status"))?; + + tracing::trace!("ResponseBatchItem operation: {operation:?}"); + tracing::trace!("ResponseBatchItem response payload: {response_payload:?}"); + + Ok(ResponseBatchItem { + operation, + unique_batch_item_id, + result_status, + result_reason, + result_message, + asynchronous_correlation_value, + response_payload, + message_extension, + }) + } + } + + const FIELDS: &[&str] = &[ + "operation", + "unique_batch_item_id", + "result_status", + "result_reason", + "result_message", + "asynchronous_correlation_value", + "response_payload", + "message_extension", + ]; + deserializer.deserialize_struct("ResponseBatchItem", FIELDS, ResponseBatchItemVisitor) + } +} diff --git a/crate/kmip/src/kmip/kmip_operations.rs b/crate/kmip/src/kmip/kmip_operations.rs index 8abe76511..3a7b81bb8 100644 --- a/crate/kmip/src/kmip/kmip_operations.rs +++ b/crate/kmip/src/kmip/kmip_operations.rs @@ -19,7 +19,7 @@ use super::{ use crate::error::KmipError; #[allow(non_camel_case_types)] -#[derive(Serialize, Copy, Clone, Display, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Copy, Clone, Display, Debug, Eq, PartialEq)] pub enum ErrorReason { Item_Not_Found = 0x0000_0001, Response_Too_Large = 0x0000_0002, @@ -94,6 +94,38 @@ pub enum ErrorReason { General_Failure = 0x0000_0100, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +pub enum Operation { + Import(Import), + ImportResponse(ImportResponse), + Certify(Certify), + CertifyResponse(CertifyResponse), + Create(Create), + CreateResponse(CreateResponse), + CreateKeyPair(CreateKeyPair), + CreateKeyPairResponse(CreateKeyPairResponse), + Export(Export), + ExportResponse(ExportResponse), + Get(Get), + GetResponse(GetResponse), + GetAttributes(GetAttributes), + GetAttributesResponse(GetAttributesResponse), + Encrypt(Encrypt), + EncryptResponse(EncryptResponse), + Decrypt(Decrypt), + DecryptResponse(DecryptResponse), + Locate(Locate), + LocateResponse(LocateResponse), + Revoke(Revoke), + RevokeResponse(RevokeResponse), + ReKeyKeyPair(ReKeyKeyPair), + ReKeyKeyPairResponse(ReKeyKeyPairResponse), + Destroy(Destroy), + DestroyResponse(DestroyResponse), +} + /// 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 diff --git a/crate/kmip/src/kmip/ttlv/deserializer.rs b/crate/kmip/src/kmip/ttlv/deserializer.rs index 82c9aeecc..591841129 100644 --- a/crate/kmip/src/kmip/ttlv/deserializer.rs +++ b/crate/kmip/src/kmip/ttlv/deserializer.rs @@ -27,6 +27,7 @@ enum Inputs<'de> { BigInt(Vec), } +#[derive(Debug)] pub struct TtlvDeserializer<'de> { /// whether a tag or a value is being deserialized with this serializer deserializing: Deserializing, @@ -101,7 +102,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> { "deserialize_any {:?}: {:?} -> {:?}", self.deserializing, self.index, - &self.inputs + self.inputs ); if self.deserializing == Deserializing::ByteString { return visitor.visit_u8(self.get_bytes()?[self.index - 1]) @@ -116,25 +117,22 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> { 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); + trace!("deserialize_any child value {child_value:?}"); match child_value { TTLValue::Structure(elements) => { + trace!("deserialize_any self {self:?}"); + let ttlv_de = TtlvDeserializer { + deserializing: Deserializing::StructureValue, + inputs: Inputs::Structure(elements.iter().collect::>()), // can probably do better + // start at 0 because the Visit Map is going to increment first + index: 0, + }; 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::>()), // can probably do better - // start at 0 because the Visit Map is going to increment first - index: 0, - }) + visitor.visit_seq(ttlv_de) } else { - visitor.visit_map(TtlvDeserializer { - deserializing: Deserializing::StructureValue, - inputs: Inputs::Structure(elements.iter().collect::>()), // can probably do better - // start at 0 because the Visit Map is going to increment first - index: 0, - }) + visitor.visit_map(ttlv_de) } } TTLValue::Integer(i) => visitor.visit_i32(*i), @@ -214,7 +212,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> { 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:?}",))), + x => return Err(TtlvError::custom(format!("Invalid type for i32: {x:?}"))), }) } @@ -681,8 +679,8 @@ impl<'de> MapAccess<'de> for TtlvDeserializer<'de> { &self.get_structure()?[self.index - 1].tag ); - // tell the deserializer that it now going to deserialize the `tag` value - // of the next key + // tell the deserializer that it is now going + // to deserialize the `tag` value of the next key self.deserializing = Deserializing::StructureTag; // Deserialize a map key. diff --git a/crate/kmip/src/kmip/ttlv/mod.rs b/crate/kmip/src/kmip/ttlv/mod.rs index 4bb512296..94ef431c5 100644 --- a/crate/kmip/src/kmip/ttlv/mod.rs +++ b/crate/kmip/src/kmip/ttlv/mod.rs @@ -162,6 +162,7 @@ impl Serialize for TTLV { 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), @@ -303,6 +304,7 @@ impl<'de> Deserialize<'de> for TTLV { let mut tag: Option = None; let mut typ: Option = None; let mut value: Option = None; + while let Some(key) = map.next_key()? { match key { Field::Tag => { diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index 33283323c..bc5da9fdc 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -5,11 +5,15 @@ use time::OffsetDateTime; use crate::kmip::{ kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue}, + kmip_messages::{ + RequestBatchItem, RequestHeader, RequestMessage, ResponseBatchItem, ResponseHeader, + ResponseMessage, + }, kmip_objects::{Object, ObjectType}, - kmip_operations::{Create, Import, ImportResponse}, + kmip_operations::{Create, Decrypt, Encrypt, Import, ImportResponse, Locate, Operation}, kmip_types::{ Attributes, CryptographicAlgorithm, CryptographicUsageMask, KeyFormatType, Link, - LinkedObjectIdentifier, + LinkedObjectIdentifier, OperationEnumeration, ProtocolVersion, ResultStatusEnumeration, }, ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLVEnumeration, TTLValue, TTLV}, }; @@ -698,7 +702,118 @@ pub fn test_create() { create_.attributes.cryptographic_algorithm.unwrap() ); assert_eq!( - LinkedObjectIdentifier::TextString("SK".to_string(),), + LinkedObjectIdentifier::TextString("SK".to_string()), create_.attributes.link.as_ref().unwrap()[0].linked_object_identifier ); } + +#[test] +pub fn test_message_request() { + log_init("info,hyper=info,reqwest=info"); + + let req = RequestMessage { + header: RequestHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + maximum_response_size: Some(9999), + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![RequestBatchItem { + operation: OperationEnumeration::Encrypt, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::Encrypt(Encrypt { + data: Some(b"to be enc".to_vec()), + ..Default::default() + }), + message_extension: None, + }], + }; + let ttlv = to_ttlv(&req).unwrap(); + let req_: RequestMessage = from_ttlv(&ttlv).unwrap(); + assert_eq!(req_.items[0].operation, OperationEnumeration::Encrypt); + let Operation::Encrypt(encrypt) = &req_.items[0].request_payload else { + panic!( + "not an encrypt operation's request payload: {:?}", + req_.items[0] + ); + }; + assert_eq!(encrypt.data, Some(b"to be enc".to_vec())) +} + +#[test] +pub fn test_message_response() { + log_init("info,hyper=info,reqwest=info"); + + let res = ResponseMessage { + header: ResponseHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + attestation_type: None, + timestamp: 1697201574, + nonce: None, + server_hashed_password: None, + }, + items: vec![ + ResponseBatchItem { + operation: Some(OperationEnumeration::Locate), + unique_batch_item_id: None, + response_payload: Some(Operation::Locate(Locate::default())), + message_extension: None, + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + }, + ResponseBatchItem { + operation: Some(OperationEnumeration::Decrypt), + unique_batch_item_id: None, + response_payload: Some(Operation::Decrypt(Decrypt { + unique_identifier: Some("id_12345".to_string()), + data: Some(b"decrypted_data".to_vec()), + ..Default::default() + })), + message_extension: None, + result_status: ResultStatusEnumeration::Success, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + }, + ], + }; + let ttlv = to_ttlv(&res).unwrap(); + let res_: ResponseMessage = from_ttlv(&ttlv).unwrap(); + assert_eq!(res_.items.len(), 2); + assert_eq!(res_.items[0].operation, Some(OperationEnumeration::Locate)); + assert_eq!( + res_.items[0].result_status, + ResultStatusEnumeration::OperationPending + ); + assert_eq!(res_.items[1].operation, Some(OperationEnumeration::Decrypt)); + assert_eq!( + res_.items[1].result_status, + ResultStatusEnumeration::Success + ); + + let Some(Operation::Decrypt(decrypt)) = &res_.items[1].response_payload else { + panic!("not a decrypt operation's response payload"); + }; + assert_eq!(decrypt.data, Some(b"decrypted_data".to_vec())); + assert_eq!(decrypt.unique_identifier, Some("id_12345".to_string())); +} From 1c72f40a1764f735fa038d608b2bb3ebdc35dfcd Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 20 Oct 2023 12:11:23 +0200 Subject: [PATCH 04/19] Rework operation dispatcher + enable serial message processing --- crate/kmip/src/kmip/kmip_operations.rs | 30 ++++++- crate/kmip/src/kmip/kmip_types.rs | 2 +- crate/server/src/core/kms.rs | 10 +++ crate/server/src/core/operations/dispatch.rs | 87 +++++++++++++++++++ crate/server/src/core/operations/message.rs | 54 ++++++++++++ crate/server/src/core/operations/mod.rs | 4 + crate/server/src/routes/kmip.rs | 88 ++------------------ crate/server/src/tests/kmip_messages.rs | 83 ++++++++++++++++++ crate/server/src/tests/mod.rs | 1 + 9 files changed, 273 insertions(+), 86 deletions(-) create mode 100644 crate/server/src/core/operations/dispatch.rs create mode 100644 crate/server/src/core/operations/message.rs create mode 100644 crate/server/src/tests/kmip_messages.rs diff --git a/crate/kmip/src/kmip/kmip_operations.rs b/crate/kmip/src/kmip/kmip_operations.rs index 3a7b81bb8..c4442eec1 100644 --- a/crate/kmip/src/kmip/kmip_operations.rs +++ b/crate/kmip/src/kmip/kmip_operations.rs @@ -12,8 +12,8 @@ use super::{ kmip_objects::{Object, ObjectType}, kmip_types::{ AttributeReference, Attributes, CertificateRequestType, CryptographicParameters, - KeyCompressionType, KeyFormatType, KeyWrapType, ObjectGroupMember, ProtectionStorageMasks, - RevocationReason, StorageStatusMask, UniqueIdentifier, + KeyCompressionType, KeyFormatType, KeyWrapType, ObjectGroupMember, OperationEnumeration, + ProtectionStorageMasks, RevocationReason, StorageStatusMask, UniqueIdentifier, }, }; use crate::error::KmipError; @@ -126,6 +126,32 @@ pub enum Operation { DestroyResponse(DestroyResponse), } +impl Operation { + pub fn operation_enum(&self) -> OperationEnumeration { + match self { + Operation::Import(_) | Operation::ImportResponse(_) => OperationEnumeration::Import, + Operation::Certify(_) | Operation::CertifyResponse(_) => OperationEnumeration::Certify, + Operation::Create(_) | Operation::CreateResponse(_) => OperationEnumeration::Create, + Operation::CreateKeyPair(_) | Operation::CreateKeyPairResponse(_) => { + OperationEnumeration::CreateKeyPair + } + Operation::Export(_) | Operation::ExportResponse(_) => OperationEnumeration::Export, + Operation::Get(_) | Operation::GetResponse(_) => OperationEnumeration::Get, + Operation::GetAttributes(_) | Operation::GetAttributesResponse(_) => { + OperationEnumeration::GetAttributes + } + Operation::Encrypt(_) | Operation::EncryptResponse(_) => OperationEnumeration::Encrypt, + Operation::Decrypt(_) | Operation::DecryptResponse(_) => OperationEnumeration::Decrypt, + Operation::Locate(_) | Operation::LocateResponse(_) => OperationEnumeration::Locate, + Operation::Revoke(_) | Operation::RevokeResponse(_) => OperationEnumeration::Revoke, + Operation::ReKeyKeyPair(_) | Operation::ReKeyKeyPairResponse(_) => { + OperationEnumeration::RekeyKeyPair + } + Operation::Destroy(_) | Operation::DestroyResponse(_) => OperationEnumeration::Destroy, + } + } +} + /// 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 diff --git a/crate/kmip/src/kmip/kmip_types.rs b/crate/kmip/src/kmip/kmip_types.rs index 3962e4736..9f1f76cae 100644 --- a/crate/kmip/src/kmip/kmip_types.rs +++ b/crate/kmip/src/kmip/kmip_types.rs @@ -1657,7 +1657,7 @@ pub type UniqueIdentifier = String; /// of the protocol with the same major version. /// /// Support for backward compatibility with different major versions is OPTIONAL. -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "PascalCase")] pub struct ProtocolVersion { pub protocol_version_major: u32, diff --git a/crate/server/src/core/kms.rs b/crate/server/src/core/kms.rs index 9b483f1e2..72633f152 100644 --- a/crate/server/src/core/kms.rs +++ b/crate/server/src/core/kms.rs @@ -10,6 +10,7 @@ use base64::{ }; use cloudproof::reexport::crypto_core::{CsRng, RandomFixedSizeCBytes, SymmetricKey}; use cosmian_kmip::kmip::{ + kmip_messages::{RequestMessage, ResponseMessage}, kmip_operations::{ Certify, CertifyResponse, Create, CreateKeyPair, CreateKeyPairResponse, CreateResponse, Decrypt, DecryptResponse, Destroy, DestroyResponse, Encrypt, EncryptResponse, Export, @@ -756,4 +757,13 @@ impl KMS { }, ) } + + pub async fn message( + &self, + request: RequestMessage, + user: &str, + params: Option<&ExtraDatabaseParams>, + ) -> KResult { + operations::message(self, request, user, params).await + } } diff --git a/crate/server/src/core/operations/dispatch.rs b/crate/server/src/core/operations/dispatch.rs new file mode 100644 index 000000000..7de23896b --- /dev/null +++ b/crate/server/src/core/operations/dispatch.rs @@ -0,0 +1,87 @@ +use cosmian_kmip::kmip::{ + kmip_operations::{ + Certify, Create, CreateKeyPair, Decrypt, Destroy, Encrypt, Export, Get, GetAttributes, + Import, Locate, Operation, ReKeyKeyPair, Revoke, + }, + ttlv::{deserializer::from_ttlv, TTLV}, +}; +use cosmian_kms_utils::access::ExtraDatabaseParams; + +use crate::{core::KMS, error::KmsError, kms_bail, result::KResult}; + +/// Dispatch operation depending on the TTLV tag +pub async fn dispatch( + kms: &KMS, + ttlv: &TTLV, + user: &str, + database_params: Option<&ExtraDatabaseParams>, +) -> KResult { + Ok(match ttlv.tag.as_str() { + "Certify" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.certify(req, user, database_params).await?; + Operation::CertifyResponse(resp) + } + "Create" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.create(req, user, database_params).await?; + Operation::CreateResponse(resp) + } + "CreateKeyPair" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.create_key_pair(req, user, database_params).await?; + Operation::CreateKeyPairResponse(resp) + } + "Decrypt" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.decrypt(req, user, database_params).await?; + Operation::DecryptResponse(resp) + } + "Destroy" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.destroy(req, user, database_params).await?; + Operation::DestroyResponse(resp) + } + "Encrypt" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.encrypt(req, user, database_params).await?; + Operation::EncryptResponse(resp) + } + "Export" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.export(req, user, database_params).await?; + Operation::ExportResponse(resp) + } + "Get" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.get(req, user, database_params).await?; + Operation::GetResponse(resp) + } + "GetAttributes" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.get_attributes(req, user, database_params).await?; + Operation::GetAttributesResponse(resp) + } + "Import" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.import(req, user, database_params).await?; + Operation::ImportResponse(resp) + } + "Locate" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.locate(req, user, database_params).await?; + Operation::LocateResponse(resp) + } + "ReKeyKeyPair" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.rekey_keypair(req, user, database_params).await?; + Operation::ReKeyKeyPairResponse(resp) + } + "Revoke" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.revoke(req, user, database_params).await?; + Operation::RevokeResponse(resp) + } + x => kms_bail!(KmsError::RouteNotFound(format!("Operation: {x}"))), + }) +} diff --git a/crate/server/src/core/operations/message.rs b/crate/server/src/core/operations/message.rs new file mode 100644 index 000000000..0bfac78b7 --- /dev/null +++ b/crate/server/src/core/operations/message.rs @@ -0,0 +1,54 @@ +use cosmian_kmip::kmip::{ + kmip_messages::{RequestMessage, ResponseBatchItem, ResponseHeader, ResponseMessage}, + kmip_types::ResultStatusEnumeration, + ttlv::serializer::to_ttlv, +}; +use cosmian_kms_utils::access::ExtraDatabaseParams; +use tracing::trace; + +use crate::{ + core::{operations::dispatch, KMS}, + result::KResult, +}; + +pub async fn message( + kms: &KMS, + request: RequestMessage, + owner: &str, + params: Option<&ExtraDatabaseParams>, +) -> KResult { + trace!("Entering message KMIP operation: {request:#?}"); + + let mut response_items = Vec::new(); + for item_request in request.items { + let operation = item_request.request_payload; + let ttlv = to_ttlv(&operation)?; + let operation = dispatch(kms, &ttlv, owner, params).await?; + response_items.push(ResponseBatchItem { + operation: Some(operation.operation_enum()), + unique_batch_item_id: item_request.unique_batch_item_id, + result_status: ResultStatusEnumeration::Success, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + response_payload: Some(operation), + message_extension: None, + }); + } + + let response_message = ResponseMessage { + header: ResponseHeader { + protocol_version: request.header.protocol_version, + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + attestation_type: None, + timestamp: 1697201574, + nonce: None, + server_hashed_password: None, + }, + items: response_items, + }; + + Ok(response_message) +} diff --git a/crate/server/src/core/operations/mod.rs b/crate/server/src/core/operations/mod.rs index 8783372b8..dc96b97ca 100644 --- a/crate/server/src/core/operations/mod.rs +++ b/crate/server/src/core/operations/mod.rs @@ -3,12 +3,14 @@ mod create; mod create_key_pair; mod decrypt; mod destroy; +mod dispatch; mod encrypt; mod export; mod get; mod get_attributes; mod import; mod locate; +mod message; mod rekey_keypair; mod revoke; mod wrapping; @@ -18,12 +20,14 @@ pub(crate) use create::create; pub(crate) use create_key_pair::create_key_pair; pub(crate) use decrypt::decrypt; pub(crate) use destroy::{destroy_operation, recursively_destroy_key}; +pub(crate) use dispatch::dispatch; pub(crate) use encrypt::encrypt; pub(crate) use export::export; pub(crate) use get::get; pub(crate) use get_attributes::get_attributes; pub(crate) use import::import; pub(crate) use locate::locate; +pub(crate) use message::message; pub(crate) use rekey_keypair::rekey_keypair; pub(crate) use revoke::{recursively_revoke_key, revoke_operation}; pub(crate) use wrapping::unwrap_key; diff --git a/crate/server/src/routes/kmip.rs b/crate/server/src/routes/kmip.rs index 2154d39fb..b5bf32129 100644 --- a/crate/server/src/routes/kmip.rs +++ b/crate/server/src/routes/kmip.rs @@ -5,17 +5,11 @@ use actix_web::{ web::{Data, Json}, HttpRequest, }; -use cosmian_kmip::kmip::{ - kmip_operations::{ - Certify, Create, CreateKeyPair, Decrypt, Destroy, Encrypt, Export, Get, GetAttributes, - Import, Locate, ReKeyKeyPair, Revoke, - }, - ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLV}, -}; +use cosmian_kmip::kmip::ttlv::{serializer::to_ttlv, TTLV}; use josekit::jwe::{alg::ecdh_es::EcdhEsJweAlgorithm, deserialize_compact}; use tracing::info; -use crate::{database::KMSServer, error::KmsError, kms_bail, result::KResult}; +use crate::{core::operations::dispatch, database::KMSServer, error::KmsError, result::KResult}; /// Generate KMIP generic key pair #[post("/kmip/2_1")] @@ -55,79 +49,7 @@ pub async fn kmip( let user = kms.get_user(req_http)?; info!("POST /kmip. Request: {:?} {}", ttlv.tag.as_str(), user); - let ttlv_resp = match ttlv.tag.as_str() { - "Certify" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.certify(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "Create" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.create(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "CreateKeyPair" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms - .create_key_pair(req, &user, database_params.as_ref()) - .await?; - to_ttlv(&resp)? - } - "Decrypt" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.decrypt(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "Destroy" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.destroy(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "Encrypt" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.encrypt(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "Export" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.export(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "Get" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.get(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "GetAttributes" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms - .get_attributes(req, &user, database_params.as_ref()) - .await?; - to_ttlv(&resp)? - } - "Import" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.import(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "Locate" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.locate(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - "ReKeyKeyPair" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms - .rekey_keypair(req, &user, database_params.as_ref()) - .await?; - to_ttlv(&resp)? - } - "Revoke" => { - let req = from_ttlv::(&ttlv)?; - let resp = kms.revoke(req, &user, database_params.as_ref()).await?; - to_ttlv(&resp)? - } - x => kms_bail!(KmsError::RouteNotFound(format!("Operation: {x}"))), - }; - Ok(Json(ttlv_resp)) + let operation = dispatch(&kms, &ttlv, &user, database_params.as_ref()).await?; + let ttlv = to_ttlv(&operation)?; + Ok(Json(ttlv)) } diff --git a/crate/server/src/tests/kmip_messages.rs b/crate/server/src/tests/kmip_messages.rs new file mode 100644 index 000000000..53cf1d05d --- /dev/null +++ b/crate/server/src/tests/kmip_messages.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; + +use cloudproof::reexport::crypto_core::X25519_PUBLIC_KEY_LENGTH; +use cosmian_kmip::kmip::{ + kmip_messages::{RequestBatchItem, RequestHeader, RequestMessage}, + kmip_objects::{Object, ObjectType}, + kmip_operations::{Import, Operation}, + kmip_types::{ + Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier, + OperationEnumeration, ProtocolVersion, RecommendedCurve, + }, +}; +use cosmian_kms_utils::crypto::curve_25519::{ + kmip_requests::{ec_create_key_pair_request, get_private_key_request, get_public_key_request}, + operation::{self, to_curve_25519_256_public_key}, +}; +use cosmian_logger::log_utils::log_init; +use uuid::Uuid; + +use crate::{ + config::ServerParams, error::KmsError, result::KResult, tests::test_utils::https_clap_config, + KMSServer, +}; + +#[actix_rt::test] +async fn test_kmip_messages() -> KResult<()> { + log_init("trace,hyper=info,reqwest=info"); + + let clap_config = https_clap_config(); + + let kms = Arc::new(KMSServer::instantiate(ServerParams::try_from(&clap_config).await?).await?); + let owner = "eyJhbGciOiJSUzI1Ni"; + + // request key pair creation + let ec_create_request = + ec_create_key_pair_request(&[] as &[&str], RecommendedCurve::CURVE25519)?; + let message_request = RequestMessage { + header: RequestHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + maximum_response_size: Some(9999), + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![RequestBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(ec_create_request), + message_extension: None, + }], + }; + let response = kms.message(message_request, owner, None).await?; + // request import + // let import_pk = vec![1_u8, 2, 3]; + // let import_pk = to_curve_25519_256_public_key(&, sk_uid); + // let import_uid = Uuid::new_v4().to_string(); + // let import_request = Import { + // unique_identifier: import_uid.clone(), + // object_type: ObjectType::PublicKey, + // replace_existing: Some(true), + // key_wrap_type: None, + // attributes: Attributes { + // object_type: Some(ObjectType::PublicKey), + // ..Attributes::default() + // }, + // object: import_pk.clone(), + // }; + // let update_response = kms.import(request, owner, None).await?; + // assert_eq!(new_uid, update_response.unique_identifier); + + Ok(()) +} diff --git a/crate/server/src/tests/mod.rs b/crate/server/src/tests/mod.rs index a989518a5..d7033dbb8 100644 --- a/crate/server/src/tests/mod.rs +++ b/crate/server/src/tests/mod.rs @@ -1,5 +1,6 @@ mod certificate; mod cover_crypt_tests; mod curve_25519_tests; +mod kmip_messages; mod kmip_server_tests; pub mod test_utils; From 6a84162c6171662da434ee792fcae30f0515cdb2 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Mon, 23 Oct 2023 16:38:18 +0200 Subject: [PATCH 05/19] Handle messages that can fail + use KMIP ItemNotFound error where needed --- Cargo.lock | 1 + .../tests/cover_crypt/user_decryption_keys.rs | 2 +- crate/server/Cargo.toml | 1 + .../core/certificate/create_ca_certificate.rs | 5 +- crate/server/src/core/certificate/locate.rs | 30 ++-- .../cover_crypt/create_user_decryption_key.rs | 4 +- crate/server/src/core/operations/decrypt.rs | 4 +- crate/server/src/core/operations/destroy.rs | 7 +- crate/server/src/core/operations/encrypt.rs | 4 +- crate/server/src/core/operations/export.rs | 4 +- crate/server/src/core/operations/get.rs | 4 +- crate/server/src/core/operations/message.rs | 48 +++++-- .../src/core/operations/rekey_keypair.rs | 4 +- crate/server/src/core/operations/revoke.rs | 7 +- .../src/core/operations/wrapping/mod.rs | 11 +- crate/server/src/error.rs | 2 +- crate/server/src/tests/kmip_messages.rs | 128 +++++++++++++----- 17 files changed, 184 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a00da107..3176031b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1227,6 +1227,7 @@ dependencies = [ "async-recursion", "async-trait", "base64 0.21.4", + "chrono", "clap", "cloudproof", "cosmian_kmip", diff --git a/crate/cli/src/tests/cover_crypt/user_decryption_keys.rs b/crate/cli/src/tests/cover_crypt/user_decryption_keys.rs index 3b8a85de3..8cba642c4 100644 --- a/crate/cli/src/tests/cover_crypt/user_decryption_keys.rs +++ b/crate/cli/src/tests/cover_crypt/user_decryption_keys.rs @@ -109,6 +109,6 @@ pub async fn test_user_decryption_key_error() -> Result<(), CliError> { ) .err() .unwrap(); - assert!(err.to_string().contains("Item not found: BAD_KEY")); + assert!(err.to_string().contains("Item_Not_Found: BAD_KEY")); Ok(()) } diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index d77b5cabc..f5023ad7b 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -36,6 +36,7 @@ alcoholic_jwt = "4091" async-recursion = "1.0" async-trait = { workspace = true } base64 = { workspace = true } +chrono = "0.4" clap = { workspace = true } cloudproof = { workspace = true } cosmian_kmip = { path = "../kmip" } diff --git a/crate/server/src/core/certificate/create_ca_certificate.rs b/crate/server/src/core/certificate/create_ca_certificate.rs index a82097dcc..b0e1a8451 100644 --- a/crate/server/src/core/certificate/create_ca_certificate.rs +++ b/crate/server/src/core/certificate/create_ca_certificate.rs @@ -4,6 +4,7 @@ use cloudproof::reexport::crypto_core::{ reexport::x509_cert::{builder::Profile, name::Name}, Ed25519PublicKey, ED25519_PUBLIC_KEY_LENGTH, }; +use cosmian_kmip::kmip::kmip_operations::ErrorReason; use cosmian_kms_utils::access::ExtraDatabaseParams; use tracing::debug; @@ -103,7 +104,7 @@ pub(crate) async fn create_ca_chain( CASigningKey::from_private_key_uid(current_ca, &uid, kms, owner, params).await } Err(err) => match err { - KmsError::ItemNotFound(_) => { + KmsError::KmipError(ErrorReason::Item_Not_Found, _) => { debug!( "[0]: Creating the root CA certificate: CA: {current_ca}: Error: \ {err:?}" @@ -125,7 +126,7 @@ pub(crate) async fn create_ca_chain( CASigningKey::from_private_key_uid(current_ca, &uid, kms, owner, params).await } Err(err) => match err { - KmsError::ItemNotFound(_) => { + KmsError::KmipError(ErrorReason::Item_Not_Found, _) => { debug!("[{index}]: Creating the subCA certificate: {current_ca}"); create_sub_ca(last_ca_signing_key, current_ca, tags, kms, owner, params) .await diff --git a/crate/server/src/core/certificate/locate.rs b/crate/server/src/core/certificate/locate.rs index 629529ddd..545093d04 100644 --- a/crate/server/src/core/certificate/locate.rs +++ b/crate/server/src/core/certificate/locate.rs @@ -1,6 +1,6 @@ use cosmian_kmip::kmip::{ kmip_objects::{Object, ObjectType}, - kmip_operations::{Get, Locate}, + kmip_operations::{ErrorReason, Get, Locate}, kmip_types::Attributes, }; use cosmian_kms_utils::{access::ExtraDatabaseParams, tagging::set_tags}; @@ -31,9 +31,10 @@ async fn locate_by_tags( let locate_response = kms.locate(locate_request, owner, params).await?; match locate_response.unique_identifiers { Some(uids) => match uids.len() { - 0 => Err(KmsError::ItemNotFound(format!( - "locate_by_tags: {object_type:?} with tags '{tags:?}' not found" - ))), + 0 => Err(KmsError::KmipError( + ErrorReason::Item_Not_Found, + format!("locate_by_tags: {object_type:?} with tags '{tags:?}' not found"), + )), 1 => { let uid = uids[0].clone(); debug!( @@ -47,9 +48,10 @@ async fn locate_by_tags( ))), }, - None => Err(KmsError::ItemNotFound(format!( - "locate_by_tags: {object_type:?} with tags '{tags:?}' not found (None)" - ))), + None => Err(KmsError::KmipError( + ErrorReason::Item_Not_Found, + format!("locate_by_tags: {object_type:?} with tags '{tags:?}' not found (None)"), + )), } } @@ -92,9 +94,10 @@ pub(crate) async fn locate_by_spki( let locate_response = kms.locate(locate_request, owner, params).await?; match locate_response.unique_identifiers { Some(uids) => match uids.len() { - 0 => Err(KmsError::ItemNotFound(format!( - "locate_by_spki: {object_type:?} with tags '{tags:?}' not found" - ))), + 0 => Err(KmsError::KmipError( + ErrorReason::Item_Not_Found, + format!("locate_by_spki: {object_type:?} with tags '{tags:?}' not found"), + )), _ => { let uid = uids[0].clone(); debug!( @@ -105,9 +108,10 @@ pub(crate) async fn locate_by_spki( } }, - None => Err(KmsError::ItemNotFound(format!( - "locate_by_spki: {object_type:?} with tags '{tags:?}' not found (None)" - ))), + None => Err(KmsError::KmipError( + ErrorReason::Item_Not_Found, + format!("locate_by_spki: {object_type:?} with tags '{tags:?}' not found (None)"), + )), } } diff --git a/crate/server/src/core/cover_crypt/create_user_decryption_key.rs b/crate/server/src/core/cover_crypt/create_user_decryption_key.rs index 646f5fb6b..ffdac2cc2 100644 --- a/crate/server/src/core/cover_crypt/create_user_decryption_key.rs +++ b/crate/server/src/core/cover_crypt/create_user_decryption_key.rs @@ -1,7 +1,7 @@ use cloudproof::reexport::cover_crypt::Covercrypt; use cosmian_kmip::kmip::{ kmip_objects::{Object, ObjectType}, - kmip_operations::{Create, CreateKeyPair, Get}, + kmip_operations::{Create, CreateKeyPair, ErrorReason, Get}, kmip_types::{Attributes, KeyFormatType, StateEnumeration}, }; use cosmian_kms_utils::{ @@ -85,7 +85,7 @@ async fn create_user_decryption_key_( // there can only be one object let owm = owm_s .pop() - .ok_or_else(|| KmsError::ItemNotFound(msk_uid_or_tag.clone()))?; + .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, msk_uid_or_tag.clone()))?; if !owm_s.is_empty() { return Err(KmsError::InvalidRequest(format!( diff --git a/crate/server/src/core/operations/decrypt.rs b/crate/server/src/core/operations/decrypt.rs index df8ed9cbe..9dfbb2caf 100644 --- a/crate/server/src/core/operations/decrypt.rs +++ b/crate/server/src/core/operations/decrypt.rs @@ -1,7 +1,7 @@ use cloudproof::reexport::cover_crypt::Covercrypt; use cosmian_kmip::kmip::{ kmip_objects::ObjectType, - kmip_operations::{Decrypt, DecryptResponse}, + kmip_operations::{Decrypt, DecryptResponse, ErrorReason}, kmip_types::{KeyFormatType, StateEnumeration}, }; use cosmian_kms_utils::{ @@ -61,7 +61,7 @@ pub async fn decrypt( // there can only be one key let owm = owm_s .pop() - .ok_or_else(|| KmsError::ItemNotFound(uid_or_tags.to_string()))?; + .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.to_string()))?; if !owm_s.is_empty() { return Err(KmsError::InvalidRequest(format!( diff --git a/crate/server/src/core/operations/destroy.rs b/crate/server/src/core/operations/destroy.rs index 75ba24d5f..eea90cbcd 100644 --- a/crate/server/src/core/operations/destroy.rs +++ b/crate/server/src/core/operations/destroy.rs @@ -7,7 +7,7 @@ use cosmian_kmip::kmip::{ Object, ObjectType::{self, PrivateKey, PublicKey, SymmetricKey}, }, - kmip_operations::{Destroy, DestroyResponse}, + kmip_operations::{Destroy, DestroyResponse, ErrorReason}, kmip_types::{KeyFormatType, LinkType, StateEnumeration, UniqueIdentifier}, }; use cosmian_kms_utils::access::{ExtraDatabaseParams, ObjectOperationType}; @@ -67,7 +67,10 @@ pub(crate) async fn recursively_destroy_key<'a: 'async_recursion>( .collect::>(); if owm_s.is_empty() { - return Err(KmsError::ItemNotFound(uid_or_tags.to_owned())) + return Err(KmsError::KmipError( + ErrorReason::Item_Not_Found, + uid_or_tags.to_owned(), + )) } // destroy the keys found diff --git a/crate/server/src/core/operations/encrypt.rs b/crate/server/src/core/operations/encrypt.rs index 6469e9c15..23f231238 100644 --- a/crate/server/src/core/operations/encrypt.rs +++ b/crate/server/src/core/operations/encrypt.rs @@ -1,6 +1,6 @@ use cosmian_kmip::kmip::{ kmip_objects::ObjectType, - kmip_operations::{Encrypt, EncryptResponse}, + kmip_operations::{Encrypt, EncryptResponse, ErrorReason}, kmip_types::StateEnumeration, }; use cosmian_kms_utils::access::{ExtraDatabaseParams, ObjectOperationType}; @@ -44,7 +44,7 @@ pub async fn encrypt( // there can only be one key let owm = owm_s .pop() - .ok_or_else(|| KmsError::ItemNotFound(uid_or_tags.to_string()))?; + .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.to_string()))?; if !owm_s.is_empty() { return Err(KmsError::InvalidRequest(format!( diff --git a/crate/server/src/core/operations/export.rs b/crate/server/src/core/operations/export.rs index aaeec70e9..72ce4f031 100644 --- a/crate/server/src/core/operations/export.rs +++ b/crate/server/src/core/operations/export.rs @@ -1,6 +1,6 @@ use cosmian_kmip::kmip::{ kmip_data_structures::{KeyMaterial, KeyValue}, - kmip_operations::{Export, ExportResponse}, + kmip_operations::{ErrorReason, Export, ExportResponse}, kmip_types::{KeyWrapType, StateEnumeration}, }; use cosmian_kms_utils::access::{ExtraDatabaseParams, ObjectOperationType}; @@ -47,7 +47,7 @@ pub async fn export( // there can only be one object let mut owm = owm_s .pop() - .ok_or_else(|| KmsError::ItemNotFound(uid_or_tags.clone()))?; + .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.clone()))?; if !owm_s.is_empty() { return Err(KmsError::InvalidRequest(format!( diff --git a/crate/server/src/core/operations/get.rs b/crate/server/src/core/operations/get.rs index 99402fe55..c518921ba 100644 --- a/crate/server/src/core/operations/get.rs +++ b/crate/server/src/core/operations/get.rs @@ -1,5 +1,5 @@ use cosmian_kmip::kmip::{ - kmip_operations::{Get, GetResponse}, + kmip_operations::{ErrorReason, Get, GetResponse}, kmip_types::{KeyWrapType, StateEnumeration}, }; use cosmian_kms_utils::access::{ExtraDatabaseParams, ObjectOperationType}; @@ -94,7 +94,7 @@ pub(crate) async fn get_active_object( // there can only be one object let owm = owm_s .pop() - .ok_or_else(|| KmsError::ItemNotFound(uid_or_tags.to_owned()))?; + .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.to_owned()))?; if !owm_s.is_empty() { return Err(KmsError::InvalidRequest(format!( diff --git a/crate/server/src/core/operations/message.rs b/crate/server/src/core/operations/message.rs index 0bfac78b7..6ab4c1153 100644 --- a/crate/server/src/core/operations/message.rs +++ b/crate/server/src/core/operations/message.rs @@ -1,5 +1,6 @@ use cosmian_kmip::kmip::{ kmip_messages::{RequestMessage, ResponseBatchItem, ResponseHeader, ResponseMessage}, + kmip_operations::ErrorReason, kmip_types::ResultStatusEnumeration, ttlv::serializer::to_ttlv, }; @@ -8,9 +9,17 @@ use tracing::trace; use crate::{ core::{operations::dispatch, KMS}, + error::KmsError, result::KResult, }; +/// Processing of an input KMIP Message +/// +/// Process every item from the message request. +/// Each batch item contains an operation to process. +/// +/// The items are processed sequentially. +/// Each item may fail but a response is still sent back. pub async fn message( kms: &KMS, request: RequestMessage, @@ -22,16 +31,39 @@ pub async fn message( let mut response_items = Vec::new(); for item_request in request.items { let operation = item_request.request_payload; + // conversion for `dispatch` call convenience let ttlv = to_ttlv(&operation)?; - let operation = dispatch(kms, &ttlv, owner, params).await?; + + let (result_status, result_reason, result_message, response_payload) = + match dispatch(kms, &ttlv, owner, params).await { + Ok(operation) => ( + ResultStatusEnumeration::Success, + None, + None, + Some(operation), + ), + Err(KmsError::KmipError(reason, error_message)) => ( + ResultStatusEnumeration::OperationFailed, + Some(reason), + Some(error_message), + None, + ), + Err(err) => ( + ResultStatusEnumeration::OperationFailed, + Some(ErrorReason::Operation_Not_Supported), + Some(err.to_string()), + None, + ), + }; + response_items.push(ResponseBatchItem { - operation: Some(operation.operation_enum()), + operation: Some(item_request.operation), unique_batch_item_id: item_request.unique_batch_item_id, - result_status: ResultStatusEnumeration::Success, - result_reason: None, - result_message: None, + result_status, + result_reason, + result_message, asynchronous_correlation_value: None, - response_payload: Some(operation), + response_payload, message_extension: None, }); } @@ -39,11 +71,11 @@ pub async fn message( let response_message = ResponseMessage { header: ResponseHeader { protocol_version: request.header.protocol_version, - batch_count: 1, + batch_count: response_items.len() as u32, client_correlation_value: None, server_correlation_value: None, attestation_type: None, - timestamp: 1697201574, + timestamp: chrono::Utc::now().timestamp() as u64, nonce: None, server_hashed_password: None, }, diff --git a/crate/server/src/core/operations/rekey_keypair.rs b/crate/server/src/core/operations/rekey_keypair.rs index 55c8cbb6d..79bcd0f13 100644 --- a/crate/server/src/core/operations/rekey_keypair.rs +++ b/crate/server/src/core/operations/rekey_keypair.rs @@ -1,7 +1,7 @@ use cloudproof::reexport::cover_crypt::Covercrypt; use cosmian_kmip::kmip::{ kmip_objects::ObjectType, - kmip_operations::{ReKeyKeyPair, ReKeyKeyPairResponse}, + kmip_operations::{ErrorReason, ReKeyKeyPair, ReKeyKeyPairResponse}, kmip_types::{CryptographicAlgorithm, KeyFormatType, StateEnumeration}, }; use cosmian_kms_utils::{ @@ -67,7 +67,7 @@ pub async fn rekey_keypair( // there can only be one private key let owm = owm_s .pop() - .ok_or_else(|| KmsError::ItemNotFound(uid_or_tags.clone()))?; + .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.clone()))?; if !owm_s.is_empty() { return Err(KmsError::InvalidRequest(format!( diff --git a/crate/server/src/core/operations/revoke.rs b/crate/server/src/core/operations/revoke.rs index 113a6de78..c5ff163c0 100644 --- a/crate/server/src/core/operations/revoke.rs +++ b/crate/server/src/core/operations/revoke.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use async_recursion::async_recursion; use cosmian_kmip::kmip::{ kmip_objects::ObjectType::{self, PrivateKey, PublicKey, SymmetricKey}, - kmip_operations::{Revoke, RevokeResponse}, + kmip_operations::{ErrorReason, Revoke, RevokeResponse}, kmip_types::{ KeyFormatType, LinkType, RevocationReason, RevocationReasonEnumeration, StateEnumeration, UniqueIdentifier, @@ -82,7 +82,10 @@ pub(crate) async fn recursively_revoke_key<'a: 'async_recursion>( .collect::>(); if owm_s.is_empty() { - return Err(KmsError::ItemNotFound(uid_or_tags.to_owned())) + return Err(KmsError::KmipError( + ErrorReason::Item_Not_Found, + uid_or_tags.to_owned(), + )) } // revoke the keys found diff --git a/crate/server/src/core/operations/wrapping/mod.rs b/crate/server/src/core/operations/wrapping/mod.rs index d08c3a2ea..ddffcb8ca 100644 --- a/crate/server/src/core/operations/wrapping/mod.rs +++ b/crate/server/src/core/operations/wrapping/mod.rs @@ -1,4 +1,6 @@ -use cosmian_kmip::kmip::{kmip_objects::Object, kmip_types::StateEnumeration}; +use cosmian_kmip::kmip::{ + kmip_objects::Object, kmip_operations::ErrorReason, kmip_types::StateEnumeration, +}; use cosmian_kms_utils::access::{ExtraDatabaseParams, ObjectOperationType}; pub(crate) use unwrap::unwrap_key; pub(crate) use wrap::wrap_key; @@ -22,9 +24,10 @@ async fn get_key( .await? .remove(key_uid) .ok_or_else(|| { - KmsError::ItemNotFound(format!( - "unable to fetch the key with uid: {key_uid:} not found" - )) + KmsError::KmipError( + ErrorReason::Item_Not_Found, + format!("unable to fetch the key with uid: {key_uid:} not found"), + ) })?; // check if unwrapping key is active match owm.state { diff --git a/crate/server/src/error.rs b/crate/server/src/error.rs index 1e35a05e9..a918a74b8 100644 --- a/crate/server/src/error.rs +++ b/crate/server/src/error.rs @@ -40,7 +40,7 @@ pub enum KmsError { #[error("This KMIP server does not yet support protection masks")] UnsupportedProtectionMasks, - // When a user requests an id which does not exist + // // When a user requests an item which does not exist #[error("Item not found: {0}")] ItemNotFound(String), diff --git a/crate/server/src/tests/kmip_messages.rs b/crate/server/src/tests/kmip_messages.rs index 53cf1d05d..f6ce97551 100644 --- a/crate/server/src/tests/kmip_messages.rs +++ b/crate/server/src/tests/kmip_messages.rs @@ -1,30 +1,22 @@ use std::sync::Arc; -use cloudproof::reexport::crypto_core::X25519_PUBLIC_KEY_LENGTH; use cosmian_kmip::kmip::{ kmip_messages::{RequestBatchItem, RequestHeader, RequestMessage}, - kmip_objects::{Object, ObjectType}, - kmip_operations::{Import, Operation}, + kmip_operations::{Decrypt, ErrorReason, Locate, Operation}, kmip_types::{ - Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier, - OperationEnumeration, ProtocolVersion, RecommendedCurve, + OperationEnumeration, ProtocolVersion, RecommendedCurve, ResultStatusEnumeration, }, }; -use cosmian_kms_utils::crypto::curve_25519::{ - kmip_requests::{ec_create_key_pair_request, get_private_key_request, get_public_key_request}, - operation::{self, to_curve_25519_256_public_key}, -}; +use cosmian_kms_utils::crypto::curve_25519::kmip_requests::ec_create_key_pair_request; use cosmian_logger::log_utils::log_init; -use uuid::Uuid; use crate::{ - config::ServerParams, error::KmsError, result::KResult, tests::test_utils::https_clap_config, - KMSServer, + config::ServerParams, result::KResult, tests::test_utils::https_clap_config, KMSServer, }; #[actix_rt::test] async fn test_kmip_messages() -> KResult<()> { - log_init("trace,hyper=info,reqwest=info"); + log_init("info,hyper=info,reqwest=info"); let clap_config = https_clap_config(); @@ -34,6 +26,35 @@ async fn test_kmip_messages() -> KResult<()> { // request key pair creation let ec_create_request = ec_create_key_pair_request(&[] as &[&str], RecommendedCurve::CURVE25519)?; + + // prepare and send the single message + let items = vec![ + RequestBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(ec_create_request), + message_extension: None, + }, + RequestBatchItem { + operation: OperationEnumeration::Locate, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::Locate(Locate::default()), + message_extension: None, + }, + RequestBatchItem { + operation: OperationEnumeration::Decrypt, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::Decrypt(Decrypt { + unique_identifier: Some("id_12345".to_string()), + data: Some(b"decrypted_data".to_vec()), + ..Default::default() + }), + message_extension: None, + }, + ]; let message_request = RequestMessage { header: RequestHeader { protocol_version: ProtocolVersion { @@ -52,32 +73,65 @@ async fn test_kmip_messages() -> KResult<()> { batch_order_option: None, timestamp: None, }, - items: vec![RequestBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(ec_create_request), - message_extension: None, - }], + items, }; + let response = kms.message(message_request, owner, None).await?; - // request import - // let import_pk = vec![1_u8, 2, 3]; - // let import_pk = to_curve_25519_256_public_key(&, sk_uid); - // let import_uid = Uuid::new_v4().to_string(); - // let import_request = Import { - // unique_identifier: import_uid.clone(), - // object_type: ObjectType::PublicKey, - // replace_existing: Some(true), - // key_wrap_type: None, - // attributes: Attributes { - // object_type: Some(ObjectType::PublicKey), - // ..Attributes::default() - // }, - // object: import_pk.clone(), - // }; - // let update_response = kms.import(request, owner, None).await?; - // assert_eq!(new_uid, update_response.unique_identifier); + assert_eq!(response.items.len(), 3); + + // 1. Create keypair + assert_eq!( + response.items[0].operation, + Some(OperationEnumeration::CreateKeyPair) + ); + assert_eq!( + response.items[0].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::CreateKeyPairResponse(create_keypair_response)) = + &response.items[0].response_payload + else { + panic!("not a create key pair response payload"); + }; + + // 2. Locate + assert_eq!( + response.items[1].operation, + Some(OperationEnumeration::Locate) + ); + assert_eq!( + response.items[1].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::LocateResponse(locate_response)) = &response.items[1].response_payload + else { + panic!("not a locate response payload"); + }; + // locate response contains only 2 keys, the pair that was created + // by the first batch item, because processing is sequential and order is preserved + assert_eq!(locate_response.located_items, Some(2)); + let locate_uids = locate_response.unique_identifiers.clone().unwrap(); + assert_eq!(locate_uids.len(), 2); + assert!(locate_uids.contains(&create_keypair_response.private_key_unique_identifier)); + assert!(locate_uids.contains(&create_keypair_response.public_key_unique_identifier)); + // 3. Decrypt (that failed) + assert_eq!( + response.items[2].operation, + Some(OperationEnumeration::Decrypt) + ); + assert_eq!( + response.items[2].result_status, + ResultStatusEnumeration::OperationFailed + ); + assert_eq!( + response.items[2].result_reason, + Some(ErrorReason::Item_Not_Found) + ); + assert_eq!( + response.items[2].result_message, + Some("id_12345".to_string()) + ); + assert!(response.items[2].response_payload.is_none()); Ok(()) } From 1a408da4da2c6472b347232a10369f4baa194f3c Mon Sep 17 00:00:00 2001 From: ThibsG Date: Tue, 24 Oct 2023 17:10:28 +0200 Subject: [PATCH 06/19] Handle message request for REST API + rename msg struct + fix msg deser --- crate/kmip/src/kmip/kmip_messages.rs | 86 +++++++------- crate/kmip/src/kmip/ttlv/tests/mod.rs | 26 ++--- crate/server/src/core/kms.rs | 6 +- crate/server/src/core/operations/message.rs | 12 +- crate/server/src/routes/kmip.rs | 42 ++++++- .../integration_tests_bulk.rs | 110 ++++++++++++++++++ .../server/src/tests/cover_crypt_tests/mod.rs | 1 + crate/server/src/tests/curve_25519_tests.rs | 84 ++++++++++++- crate/server/src/tests/kmip_messages.rs | 12 +- 9 files changed, 305 insertions(+), 74 deletions(-) create mode 100644 crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index b82c925c4..1e8910dbf 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -32,11 +32,11 @@ use super::{ #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct RequestMessage { +pub struct Message { /// Header of the request - pub header: RequestHeader, + pub header: MessageHeader, /// Batch items of the request - pub items: Vec, + pub items: Vec, } /// Header of the request @@ -44,7 +44,7 @@ pub struct RequestMessage { /// Contains fields whose presence is determined by the protocol features used. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct RequestHeader { +pub struct MessageHeader { pub protocol_version: ProtocolVersion, /// This is an OPTIONAL field contained in a request message, /// and is used to indicate the maximum size of a response, in bytes, @@ -101,7 +101,7 @@ pub struct RequestHeader { /// `request_payload` depends on the request #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] -pub struct RequestBatchItem { +pub struct MessageBatchItem { pub operation: OperationEnumeration, /// Indicates that the Data output of the operation should not /// be returned to the client @@ -116,7 +116,7 @@ pub struct RequestBatchItem { pub message_extension: Option>, } -impl<'de> Deserialize<'de> for RequestBatchItem { +impl<'de> Deserialize<'de> for MessageBatchItem { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -131,13 +131,13 @@ impl<'de> Deserialize<'de> for RequestBatchItem { MessageExtension, } - struct RequestBatchItemVisitor; + struct MessageBatchItemVisitor; - impl<'de> Visitor<'de> for RequestBatchItemVisitor { - type Value = RequestBatchItem; + impl<'de> Visitor<'de> for MessageBatchItemVisitor { + type Value = MessageBatchItem; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct RequestBatchItem") + formatter.write_str("struct MessageBatchItem") } fn visit_map(self, mut map: V) -> Result @@ -231,13 +231,13 @@ impl<'de> Deserialize<'de> for RequestBatchItem { } } let operation = operation.ok_or_else(|| de::Error::missing_field("operation"))?; - tracing::trace!("RequestBatchItem operation: {operation:?}"); + tracing::trace!("MessageBatchItem operation: {operation:?}"); let request_payload = request_payload.ok_or_else(|| de::Error::missing_field("request_payload"))?; - tracing::trace!("RequestBatchItem request payload: {request_payload:?}"); + tracing::trace!("MessageBatchItem request payload: {request_payload:?}"); - Ok(RequestBatchItem { + Ok(MessageBatchItem { operation, ephemeral, unique_batch_item_id, @@ -254,22 +254,22 @@ impl<'de> Deserialize<'de> for RequestBatchItem { "request_payload", "message_extension", ]; - deserializer.deserialize_struct("RequestBatchItem", FIELDS, RequestBatchItemVisitor) + deserializer.deserialize_struct("MessageBatchItem", FIELDS, MessageBatchItemVisitor) } } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct ResponseMessage { +pub struct MessageResponse { /// Header of the response - pub header: ResponseHeader, + pub header: MessageResponseHeader, /// Batch items of the response - pub items: Vec, + pub items: Vec, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct ResponseHeader { +pub struct MessageResponseHeader { pub protocol_version: ProtocolVersion, pub timestamp: u64, // epoch millis #[serde(skip_serializing_if = "Option::is_none")] @@ -295,7 +295,7 @@ pub struct ResponseHeader { #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] -pub struct ResponseBatchItem { +pub struct MessageResponseBatchItem { /// Required if present in Request Batch Item #[serde(skip_serializing_if = "Option::is_none")] pub operation: Option, @@ -338,7 +338,7 @@ pub struct ResponseBatchItem { pub message_extension: Option, } -impl<'de> Deserialize<'de> for ResponseBatchItem { +impl<'de> Deserialize<'de> for MessageResponseBatchItem { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -356,13 +356,13 @@ impl<'de> Deserialize<'de> for ResponseBatchItem { MessageExtension, } - struct ResponseBatchItemVisitor; + struct MessageResponseBatchItemVisitor; - impl<'de> Visitor<'de> for ResponseBatchItemVisitor { - type Value = ResponseBatchItem; + impl<'de> Visitor<'de> for MessageResponseBatchItemVisitor { + type Value = MessageResponseBatchItem; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct ResponseBatchItem") + formatter.write_str("struct MessageResponseBatchItem") } fn visit_map(self, mut map: V) -> Result @@ -440,38 +440,40 @@ impl<'de> Deserialize<'de> for ResponseBatchItem { // using the `operation` enum. response_payload = Some(match operation { OperationEnumeration::Encrypt => { - Operation::Encrypt(map.next_value()?) + Operation::EncryptResponse(map.next_value()?) } OperationEnumeration::Create => { - Operation::Create(map.next_value()?) + Operation::CreateResponse(map.next_value()?) } OperationEnumeration::CreateKeyPair => { - Operation::CreateKeyPair(map.next_value()?) + Operation::CreateKeyPairResponse(map.next_value()?) } OperationEnumeration::Certify => { - Operation::Certify(map.next_value()?) + Operation::CertifyResponse(map.next_value()?) } OperationEnumeration::Locate => { - Operation::Locate(map.next_value()?) + Operation::LocateResponse(map.next_value()?) + } + OperationEnumeration::Get => { + Operation::GetResponse(map.next_value()?) } - OperationEnumeration::Get => Operation::Get(map.next_value()?), OperationEnumeration::GetAttributes => { - Operation::GetAttributes(map.next_value()?) + Operation::GetAttributesResponse(map.next_value()?) } OperationEnumeration::Revoke => { - Operation::Revoke(map.next_value()?) + Operation::RevokeResponse(map.next_value()?) } OperationEnumeration::Destroy => { - Operation::Destroy(map.next_value()?) + Operation::DestroyResponse(map.next_value()?) } OperationEnumeration::Decrypt => { - Operation::Decrypt(map.next_value()?) + Operation::DecryptResponse(map.next_value()?) } OperationEnumeration::Import => { - Operation::Import(map.next_value()?) + Operation::ImportResponse(map.next_value()?) } OperationEnumeration::Export => { - Operation::Export(map.next_value()?) + Operation::ExportResponse(map.next_value()?) } _ => { return Err(de::Error::missing_field( @@ -486,10 +488,10 @@ impl<'de> Deserialize<'de> for ResponseBatchItem { let result_status = result_status.ok_or_else(|| de::Error::missing_field("result_status"))?; - tracing::trace!("ResponseBatchItem operation: {operation:?}"); - tracing::trace!("ResponseBatchItem response payload: {response_payload:?}"); + tracing::trace!("MessageResponseBatchItem operation: {operation:?}"); + tracing::trace!("MessageResponseBatchItem response payload: {response_payload:?}"); - Ok(ResponseBatchItem { + Ok(MessageResponseBatchItem { operation, unique_batch_item_id, result_status, @@ -512,6 +514,10 @@ impl<'de> Deserialize<'de> for ResponseBatchItem { "response_payload", "message_extension", ]; - deserializer.deserialize_struct("ResponseBatchItem", FIELDS, ResponseBatchItemVisitor) + deserializer.deserialize_struct( + "MessageResponseBatchItem", + FIELDS, + MessageResponseBatchItemVisitor, + ) } } diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index bc5da9fdc..465f85fcf 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -6,8 +6,8 @@ use time::OffsetDateTime; use crate::kmip::{ kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue}, kmip_messages::{ - RequestBatchItem, RequestHeader, RequestMessage, ResponseBatchItem, ResponseHeader, - ResponseMessage, + Message, MessageBatchItem, MessageHeader, MessageResponse, MessageResponseBatchItem, + MessageResponseHeader, }, kmip_objects::{Object, ObjectType}, kmip_operations::{Create, Decrypt, Encrypt, Import, ImportResponse, Locate, Operation}, @@ -711,8 +711,8 @@ pub fn test_create() { pub fn test_message_request() { log_init("info,hyper=info,reqwest=info"); - let req = RequestMessage { - header: RequestHeader { + let req = Message { + header: MessageHeader { protocol_version: ProtocolVersion { protocol_version_major: 1, protocol_version_minor: 0, @@ -729,7 +729,7 @@ pub fn test_message_request() { batch_order_option: None, timestamp: None, }, - items: vec![RequestBatchItem { + items: vec![MessageBatchItem { operation: OperationEnumeration::Encrypt, ephemeral: None, unique_batch_item_id: None, @@ -741,7 +741,7 @@ pub fn test_message_request() { }], }; let ttlv = to_ttlv(&req).unwrap(); - let req_: RequestMessage = from_ttlv(&ttlv).unwrap(); + let req_: Message = from_ttlv(&ttlv).unwrap(); assert_eq!(req_.items[0].operation, OperationEnumeration::Encrypt); let Operation::Encrypt(encrypt) = &req_.items[0].request_payload else { panic!( @@ -756,8 +756,8 @@ pub fn test_message_request() { pub fn test_message_response() { log_init("info,hyper=info,reqwest=info"); - let res = ResponseMessage { - header: ResponseHeader { + let res = MessageResponse { + header: MessageResponseHeader { protocol_version: ProtocolVersion { protocol_version_major: 1, protocol_version_minor: 0, @@ -771,7 +771,7 @@ pub fn test_message_response() { server_hashed_password: None, }, items: vec![ - ResponseBatchItem { + MessageResponseBatchItem { operation: Some(OperationEnumeration::Locate), unique_batch_item_id: None, response_payload: Some(Operation::Locate(Locate::default())), @@ -781,7 +781,7 @@ pub fn test_message_response() { result_message: None, asynchronous_correlation_value: None, }, - ResponseBatchItem { + MessageResponseBatchItem { operation: Some(OperationEnumeration::Decrypt), unique_batch_item_id: None, response_payload: Some(Operation::Decrypt(Decrypt { @@ -798,7 +798,7 @@ pub fn test_message_response() { ], }; let ttlv = to_ttlv(&res).unwrap(); - let res_: ResponseMessage = from_ttlv(&ttlv).unwrap(); + let res_: MessageResponse = from_ttlv(&ttlv).unwrap(); assert_eq!(res_.items.len(), 2); assert_eq!(res_.items[0].operation, Some(OperationEnumeration::Locate)); assert_eq!( @@ -811,9 +811,9 @@ pub fn test_message_response() { ResultStatusEnumeration::Success ); - let Some(Operation::Decrypt(decrypt)) = &res_.items[1].response_payload else { + let Some(Operation::DecryptResponse(decrypt)) = &res_.items[1].response_payload else { panic!("not a decrypt operation's response payload"); }; assert_eq!(decrypt.data, Some(b"decrypted_data".to_vec())); - assert_eq!(decrypt.unique_identifier, Some("id_12345".to_string())); + assert_eq!(decrypt.unique_identifier, "id_12345".to_string()); } diff --git a/crate/server/src/core/kms.rs b/crate/server/src/core/kms.rs index 72633f152..c15124ca8 100644 --- a/crate/server/src/core/kms.rs +++ b/crate/server/src/core/kms.rs @@ -10,7 +10,7 @@ use base64::{ }; use cloudproof::reexport::crypto_core::{CsRng, RandomFixedSizeCBytes, SymmetricKey}; use cosmian_kmip::kmip::{ - kmip_messages::{RequestMessage, ResponseMessage}, + kmip_messages::{Message, MessageResponse}, kmip_operations::{ Certify, CertifyResponse, Create, CreateKeyPair, CreateKeyPairResponse, CreateResponse, Decrypt, DecryptResponse, Destroy, DestroyResponse, Encrypt, EncryptResponse, Export, @@ -760,10 +760,10 @@ impl KMS { pub async fn message( &self, - request: RequestMessage, + request: Message, user: &str, params: Option<&ExtraDatabaseParams>, - ) -> KResult { + ) -> KResult { operations::message(self, request, user, params).await } } diff --git a/crate/server/src/core/operations/message.rs b/crate/server/src/core/operations/message.rs index 6ab4c1153..66829abde 100644 --- a/crate/server/src/core/operations/message.rs +++ b/crate/server/src/core/operations/message.rs @@ -1,5 +1,5 @@ use cosmian_kmip::kmip::{ - kmip_messages::{RequestMessage, ResponseBatchItem, ResponseHeader, ResponseMessage}, + kmip_messages::{Message, MessageResponse, MessageResponseBatchItem, MessageResponseHeader}, kmip_operations::ErrorReason, kmip_types::ResultStatusEnumeration, ttlv::serializer::to_ttlv, @@ -22,10 +22,10 @@ use crate::{ /// Each item may fail but a response is still sent back. pub async fn message( kms: &KMS, - request: RequestMessage, + request: Message, owner: &str, params: Option<&ExtraDatabaseParams>, -) -> KResult { +) -> KResult { trace!("Entering message KMIP operation: {request:#?}"); let mut response_items = Vec::new(); @@ -56,7 +56,7 @@ pub async fn message( ), }; - response_items.push(ResponseBatchItem { + response_items.push(MessageResponseBatchItem { operation: Some(item_request.operation), unique_batch_item_id: item_request.unique_batch_item_id, result_status, @@ -68,8 +68,8 @@ pub async fn message( }); } - let response_message = ResponseMessage { - header: ResponseHeader { + let response_message = MessageResponse { + header: MessageResponseHeader { protocol_version: request.header.protocol_version, batch_count: response_items.len() as u32, client_correlation_value: None, diff --git a/crate/server/src/routes/kmip.rs b/crate/server/src/routes/kmip.rs index b5bf32129..7a5366f46 100644 --- a/crate/server/src/routes/kmip.rs +++ b/crate/server/src/routes/kmip.rs @@ -5,11 +5,20 @@ use actix_web::{ web::{Data, Json}, HttpRequest, }; -use cosmian_kmip::kmip::ttlv::{serializer::to_ttlv, TTLV}; +use cosmian_kmip::kmip::{ + kmip_messages::Message, + ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLV}, +}; +use cosmian_kms_utils::access::ExtraDatabaseParams; use josekit::jwe::{alg::ecdh_es::EcdhEsJweAlgorithm, deserialize_compact}; use tracing::info; -use crate::{core::operations::dispatch, database::KMSServer, error::KmsError, result::KResult}; +use crate::{ + core::{operations::dispatch, KMS}, + database::KMSServer, + error::KmsError, + result::KResult, +}; /// Generate KMIP generic key pair #[post("/kmip/2_1")] @@ -49,7 +58,32 @@ pub async fn kmip( let user = kms.get_user(req_http)?; info!("POST /kmip. Request: {:?} {}", ttlv.tag.as_str(), user); - let operation = dispatch(&kms, &ttlv, &user, database_params.as_ref()).await?; - let ttlv = to_ttlv(&operation)?; + let ttlv = handle_ttlv(&kms, &ttlv, &user, database_params.as_ref()).await?; Ok(Json(ttlv)) } + +/// Handle input TTLV requests +/// +/// Process the TTLV-serialized input request and returns +/// the TTLV-serialized response. +/// +/// The input request could be either a single KMIP `Operation` or +/// multiple KMIP `Operation`s serialized in a single KMIP `Message` +pub async fn handle_ttlv( + kms: &KMS, + ttlv: &TTLV, + user: &str, + database_params: Option<&ExtraDatabaseParams>, +) -> KResult { + match ttlv.tag.as_str() { + "Message" => { + let req = from_ttlv::(ttlv)?; + let resp = kms.message(req, user, database_params).await?; + Ok(to_ttlv(&resp)?) + } + _ => { + let operation = dispatch(kms, ttlv, user, database_params).await?; + Ok(to_ttlv(&operation)?) + } + } +} diff --git a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs new file mode 100644 index 000000000..1272bcf70 --- /dev/null +++ b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs @@ -0,0 +1,110 @@ +use cloudproof::reexport::cover_crypt::abe_policy::{EncryptionHint, Policy, PolicyAxis}; +use cosmian_kmip::kmip::{ + kmip_messages::{Message, MessageBatchItem, MessageHeader, MessageResponse}, + kmip_operations::Operation, + kmip_types::{OperationEnumeration, ProtocolVersion, ResultStatusEnumeration}, +}; +use cosmian_kms_utils::{ + crypto::cover_crypt::kmip_requests::build_create_master_keypair_request, tagging::EMPTY_TAGS, +}; + +use crate::{result::KResult, tests::test_utils}; + +#[actix_web::test] +async fn integration_tests_bulk() -> KResult<()> { + cosmian_logger::log_utils::log_init("trace,hyper=info,reqwest=info"); + + let app = test_utils::test_app().await; + + let mut policy = Policy::new(10); + policy.add_axis(PolicyAxis::new( + "Department", + vec![ + ("MKG", EncryptionHint::Classic), + ("FIN", EncryptionHint::Classic), + ("HR", EncryptionHint::Classic), + ], + false, + ))?; + policy.add_axis(PolicyAxis::new( + "Level", + vec![ + ("Confidential", EncryptionHint::Classic), + ("Top Secret", EncryptionHint::Hybridized), + ], + true, + ))?; + + let request_message = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 2, + maximum_response_size: None, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![ + MessageBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(build_create_master_keypair_request( + &policy, EMPTY_TAGS, + )?), + message_extension: None, + }, + MessageBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(build_create_master_keypair_request( + &policy, EMPTY_TAGS, + )?), + message_extension: None, + }, + ], + }; + + let response: MessageResponse = test_utils::post(&app, &request_message).await?; + + // tracing::trace!("{response:#?}"); + assert_eq!(response.items.len(), 2); + + // 1. Create keypair + assert_eq!( + response.items[0].operation, + Some(OperationEnumeration::CreateKeyPair) + ); + assert_eq!( + response.items[0].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::CreateKeyPairResponse(_)) = &response.items[0].response_payload else { + panic!("not a create key pair response payload"); + }; + + // 2. Create keypair + assert_eq!( + response.items[1].operation, + Some(OperationEnumeration::CreateKeyPair) + ); + assert_eq!( + response.items[1].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::CreateKeyPairResponse(_)) = &response.items[1].response_payload else { + panic!("not a create key pair response payload"); + }; + + Ok(()) +} diff --git a/crate/server/src/tests/cover_crypt_tests/mod.rs b/crate/server/src/tests/cover_crypt_tests/mod.rs index 3565e39af..df9d7379a 100644 --- a/crate/server/src/tests/cover_crypt_tests/mod.rs +++ b/crate/server/src/tests/cover_crypt_tests/mod.rs @@ -1,3 +1,4 @@ mod integration_tests; +mod integration_tests_bulk; mod integration_tests_tags; mod unit_tests; diff --git a/crate/server/src/tests/curve_25519_tests.rs b/crate/server/src/tests/curve_25519_tests.rs index 9c238a90b..6c8c2651f 100644 --- a/crate/server/src/tests/curve_25519_tests.rs +++ b/crate/server/src/tests/curve_25519_tests.rs @@ -2,17 +2,19 @@ use std::sync::Arc; use cloudproof::reexport::crypto_core::X25519_PUBLIC_KEY_LENGTH; use cosmian_kmip::kmip::{ + kmip_messages::{Message, MessageBatchItem, MessageHeader}, kmip_objects::{Object, ObjectType}, - kmip_operations::Import, + kmip_operations::{Import, Operation}, kmip_types::{ Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier, - RecommendedCurve, + OperationEnumeration, ProtocolVersion, RecommendedCurve, }, }; use cosmian_kms_utils::crypto::curve_25519::{ kmip_requests::{ec_create_key_pair_request, get_private_key_request, get_public_key_request}, operation::{self, to_curve_25519_256_public_key}, }; +use cosmian_logger::log_utils::log_init; use crate::{ config::ServerParams, error::KmsError, result::KResult, tests::test_utils::https_clap_config, @@ -155,3 +157,81 @@ async fn test_curve_25519_key_pair() -> KResult<()> { assert_eq!(new_uid, update_response.unique_identifier); Ok(()) } + +#[actix_rt::test] +async fn test_curve_25519_multiple() -> KResult<()> { + log_init("trace,hyper=info,reqwest=info"); + + let clap_config = https_clap_config(); + + let kms = Arc::new(KMSServer::instantiate(ServerParams::try_from(&clap_config).await?).await?); + let owner = "eyJhbGciOiJSUzI1Ni"; + + let request = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + maximum_response_size: Some(9999), + batch_count: 4, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![ + MessageBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVE25519, + )?), + message_extension: None, + }, + MessageBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVEED25519, + )?), + message_extension: None, + }, + MessageBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::SECP256K1, + )?), + message_extension: None, + }, + MessageBatchItem { + operation: OperationEnumeration::CreateKeyPair, + ephemeral: None, + unique_batch_item_id: None, + request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVEED25519, + )?), + message_extension: None, + }, + ], + }; + + let response = kms.message(request, owner, None).await?; + + tracing::trace!("response: {response:#?}"); + + Ok(()) +} diff --git a/crate/server/src/tests/kmip_messages.rs b/crate/server/src/tests/kmip_messages.rs index f6ce97551..09829dd40 100644 --- a/crate/server/src/tests/kmip_messages.rs +++ b/crate/server/src/tests/kmip_messages.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use cosmian_kmip::kmip::{ - kmip_messages::{RequestBatchItem, RequestHeader, RequestMessage}, + kmip_messages::{Message, MessageBatchItem, MessageHeader}, kmip_operations::{Decrypt, ErrorReason, Locate, Operation}, kmip_types::{ OperationEnumeration, ProtocolVersion, RecommendedCurve, ResultStatusEnumeration, @@ -29,21 +29,21 @@ async fn test_kmip_messages() -> KResult<()> { // prepare and send the single message let items = vec![ - RequestBatchItem { + MessageBatchItem { operation: OperationEnumeration::CreateKeyPair, ephemeral: None, unique_batch_item_id: None, request_payload: Operation::CreateKeyPair(ec_create_request), message_extension: None, }, - RequestBatchItem { + MessageBatchItem { operation: OperationEnumeration::Locate, ephemeral: None, unique_batch_item_id: None, request_payload: Operation::Locate(Locate::default()), message_extension: None, }, - RequestBatchItem { + MessageBatchItem { operation: OperationEnumeration::Decrypt, ephemeral: None, unique_batch_item_id: None, @@ -55,8 +55,8 @@ async fn test_kmip_messages() -> KResult<()> { message_extension: None, }, ]; - let message_request = RequestMessage { - header: RequestHeader { + let message_request = Message { + header: MessageHeader { protocol_version: ProtocolVersion { protocol_version_major: 1, protocol_version_minor: 0, From 38278c60810cfc84f5c1a2d948339dad61ef5221 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Wed, 25 Oct 2023 15:29:24 +0200 Subject: [PATCH 07/19] Manually impl Ser to enforce content constraints and checks --- crate/kmip/src/kmip/kmip_messages.rs | 224 +++++++++++++++-- crate/kmip/src/kmip/kmip_operations.rs | 14 +- crate/kmip/src/kmip/kmip_types.rs | 23 +- crate/kmip/src/kmip/ttlv/tests/mod.rs | 229 ++++++++++++++++++ .../integration_tests_bulk.rs | 2 +- crate/server/src/tests/kmip_messages.rs | 3 +- 6 files changed, 469 insertions(+), 26 deletions(-) diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index 1e8910dbf..7885246ad 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -19,6 +19,7 @@ /// asynchronous responses only if the Asynchronous Indicator is present in the header. use serde::{ de::{self, MapAccess, Visitor}, + ser::{self, SerializeStruct}, Deserialize, Serialize, }; @@ -30,7 +31,7 @@ use super::{ }, }; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Message { /// Header of the request @@ -39,6 +40,38 @@ pub struct Message { pub items: Vec, } +impl Serialize for Message { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // check batch item count + if self.header.batch_count as usize != self.items.len() { + return Err(ser::Error::custom(format!( + "mismatch number of batch items between header (`{}`) and items list (`{}`)", + self.header.batch_count, + self.items.len() + ))) + } + // check version of protocol version defined in the header is + // equal or greater than the protocol version of each item's payload. + for item in &self.items { + if self.header.protocol_version >= item.request_payload.protocol_version() { + return Err(ser::Error::custom(format!( + "item's protocol version is greater (`{}`) than header's protocol version \ + (`{}`)", + self.header.protocol_version, + item.request_payload.protocol_version() + ))) + } + } + let mut st = serializer.serialize_struct("Message", 2)?; + st.serialize_field("Header", &self.header)?; + st.serialize_field("Items", &self.items)?; + st.end() + } +} + /// Header of the request /// /// Contains fields whose presence is determined by the protocol features used. @@ -99,23 +132,52 @@ pub struct MessageHeader { /// Batch item for a message request /// /// `request_payload` depends on the request -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] +#[derive(Debug)] pub struct MessageBatchItem { + /// Type of the KMIP operation pub operation: OperationEnumeration, /// Indicates that the Data output of the operation should not /// be returned to the client - #[serde(skip_serializing_if = "Option::is_none")] pub ephemeral: Option, - /// Required if `batch_count` > 1 - #[serde(skip_serializing_if = "Option::is_none")] + /// This is an OPTIONAL field contained in a request, + /// and is used for correlation between requests and responses. + /// + /// If a request has a Unique Batch Item ID, then responses to + /// that request SHALL have the same Unique Batch Item ID. pub unique_batch_item_id: Option, /// The KMIP request, which depends on the KMIP Operation pub request_payload: Operation, - #[serde(skip_serializing_if = "Option::is_none")] pub message_extension: Option>, } +impl Serialize for MessageBatchItem { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.operation != self.request_payload.operation_enum() { + return Err(ser::Error::custom(format!( + "operation enum (`{}`) doesn't correspond to request payload (`{}`)", + self.operation, + self.request_payload.operation_enum() + ))) + } + let mut st = serializer.serialize_struct("MessageBatchItem", 5)?; + st.serialize_field("Operation", &self.operation)?; + if let Some(ephemeral) = &self.ephemeral { + st.serialize_field("Ephemeral", ephemeral)?; + } + if let Some(unique_batch_item_id) = &self.unique_batch_item_id { + st.serialize_field("UniqueBatchItemId", unique_batch_item_id)?; + } + st.serialize_field("RequestPayload", &self.request_payload)?; + if let Some(message_extension) = &self.message_extension { + st.serialize_field("MessageExtension", &message_extension)?; + } + st.end() + } +} + impl<'de> Deserialize<'de> for MessageBatchItem { fn deserialize(deserializer: D) -> Result where @@ -237,6 +299,14 @@ impl<'de> Deserialize<'de> for MessageBatchItem { request_payload.ok_or_else(|| de::Error::missing_field("request_payload"))?; tracing::trace!("MessageBatchItem request payload: {request_payload:?}"); + if operation != request_payload.operation_enum() { + return Err(de::Error::custom(format!( + "operation enum (`{}`) doesn't correspond to request payload (`{}`)", + operation, + request_payload.operation_enum() + ))) + } + Ok(MessageBatchItem { operation, ephemeral, @@ -258,7 +328,7 @@ impl<'de> Deserialize<'de> for MessageBatchItem { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct MessageResponse { /// Header of the response @@ -267,6 +337,40 @@ pub struct MessageResponse { pub items: Vec, } +impl Serialize for MessageResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // check batch item count + if self.header.batch_count as usize != self.items.len() { + return Err(ser::Error::custom(format!( + "mismatch number of batch items between header (`{}`) and items list (`{}`)", + self.header.batch_count, + self.items.len() + ))) + } + // check version of protocol version defined in the header is + // equal or greater than the protocol version of each item's payload. + for item in &self.items { + if let Some(response_payload) = &item.response_payload { + if self.header.protocol_version >= response_payload.protocol_version() { + return Err(ser::Error::custom(format!( + "item's protocol version is greater (`{}`) than header's protocol version \ + (`{}`)", + self.header.protocol_version, + response_payload.protocol_version() + ))) + } + } + } + let mut st = serializer.serialize_struct("Message", 2)?; + st.serialize_field("Header", &self.header)?; + st.serialize_field("Items", &self.items)?; + st.end() + } +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct MessageResponseHeader { @@ -293,14 +397,11 @@ pub struct MessageResponseHeader { pub batch_count: u32, } -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] +#[derive(Debug)] pub struct MessageResponseBatchItem { - /// Required if present in Request Batch Item - #[serde(skip_serializing_if = "Option::is_none")] + /// Required if present in request Batch Item pub operation: Option, - /// Required if present in Request Batch Item - #[serde(skip_serializing_if = "Option::is_none")] + /// Required if present in request Batch Item pub unique_batch_item_id: Option, /// Indicates the success or failure of a request pub result_status: ResultStatusEnumeration, @@ -309,13 +410,11 @@ pub struct MessageResponseBatchItem { /// responses that return a Result Status of Failure. /// /// Required if `result_status` is `Failure` - #[serde(skip_serializing_if = "Option::is_none")] pub result_reason: Option, /// Contains a more descriptive error message, /// which MAY be provided to an end user or used for logging/auditing purposes. /// /// Required if `result_status` is NOT `Pending` or `Success` - #[serde(skip_serializing_if = "Option::is_none")] pub result_message: Option, /// Returned in the immediate response to an operation that is pending and /// that requires asynchronous polling. Note: the server decides which @@ -325,19 +424,81 @@ pub struct MessageResponseBatchItem { /// Poll or Cancel operations that pertain to the original operation. /// /// Required if `result_status` is `Pending` - #[serde(skip_serializing_if = "Option::is_none")] pub asynchronous_correlation_value: Option>, /// The KMIP response, which depends on the KMIP Operation /// /// Mandatory if a success, `None` in case of failure. /// /// Content depends on Operation. - #[serde(skip_serializing_if = "Option::is_none")] pub response_payload: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub message_extension: Option, } +impl Serialize for MessageResponseBatchItem { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self.result_status { + ResultStatusEnumeration::OperationFailed if self.result_reason.is_none() => { + return Err(ser::Error::custom( + "missing `ResultReason` with failed status (`ResultStatus` is set to \ + `OperationFailed`)", + )) + } + ResultStatusEnumeration::OperationFailed | ResultStatusEnumeration::OperationUndone + if self.result_message.is_none() => + { + return Err(ser::Error::custom( + "missing `ResultMessage` with unsuccessful status (`ResultStatus` is set to \ + either `OperationFailed` or `OperationUndone`)", + )) + } + ResultStatusEnumeration::OperationPending + if self.asynchronous_correlation_value.is_none() => + { + return Err(ser::Error::custom( + "missing `AsynchronousCorrelationValue` with pending status (`ResultStatus` \ + is set to `OperationPending`)", + )) + } + _ => (), + } + + let mut st = serializer.serialize_struct("MessageResponseBatchItem", 5)?; + if let Some(operation) = self.operation { + if let Some(response_payload) = &self.response_payload { + if operation != response_payload.operation_enum() { + return Err(ser::Error::custom(format!( + "operation enum (`{}`) doesn't correspond to response payload (`{}`)", + operation, + response_payload.operation_enum() + ))) + } + } + + st.serialize_field("Operation", &self.operation)?; + } + st.serialize_field("ResultStatus", &self.result_status)?; + if let Some(result_reason) = &self.result_reason { + st.serialize_field("ResultReason", result_reason)?; + } + if let Some(result_message) = &self.result_message { + st.serialize_field("ResultMessage", result_message)?; + } + if let Some(acv) = &self.asynchronous_correlation_value { + st.serialize_field("AsynchronousCorrelationValue", &acv)?; + } + if let Some(response_payload) = &self.response_payload { + st.serialize_field("ResponsePayload", &response_payload)?; + } + if let Some(message_extension) = &self.message_extension { + st.serialize_field("MessageExtension", &message_extension)?; + } + st.end() + } +} + impl<'de> Deserialize<'de> for MessageResponseBatchItem { fn deserialize(deserializer: D) -> Result where @@ -485,11 +646,32 @@ impl<'de> Deserialize<'de> for MessageResponseBatchItem { } } + tracing::trace!("MessageResponseBatchItem operation: {operation:?}"); + tracing::trace!("MessageResponseBatchItem response payload: {response_payload:?}"); + let result_status = result_status.ok_or_else(|| de::Error::missing_field("result_status"))?; - tracing::trace!("MessageResponseBatchItem operation: {operation:?}"); - tracing::trace!("MessageResponseBatchItem response payload: {response_payload:?}"); + match result_status { + ResultStatusEnumeration::OperationFailed if result_reason.is_none() => { + // missing `ResultReason` with failed status + return Err(de::Error::missing_field("result_reason")) + } + ResultStatusEnumeration::OperationFailed + | ResultStatusEnumeration::OperationUndone + if result_message.is_none() => + { + // missing `ResultMessage` with unsuccessful status + return Err(de::Error::missing_field("result_message")) + } + ResultStatusEnumeration::OperationPending + if asynchronous_correlation_value.is_none() => + { + // missing `ResultMessage` with unsuccessful status + return Err(de::Error::missing_field("asynchronous_correlation_value")) + } + _ => (), + } Ok(MessageResponseBatchItem { operation, diff --git a/crate/kmip/src/kmip/kmip_operations.rs b/crate/kmip/src/kmip/kmip_operations.rs index c4442eec1..2f00af8a4 100644 --- a/crate/kmip/src/kmip/kmip_operations.rs +++ b/crate/kmip/src/kmip/kmip_operations.rs @@ -13,7 +13,8 @@ use super::{ kmip_types::{ AttributeReference, Attributes, CertificateRequestType, CryptographicParameters, KeyCompressionType, KeyFormatType, KeyWrapType, ObjectGroupMember, OperationEnumeration, - ProtectionStorageMasks, RevocationReason, StorageStatusMask, UniqueIdentifier, + ProtectionStorageMasks, ProtocolVersion, RevocationReason, StorageStatusMask, + UniqueIdentifier, }, }; use crate::error::KmipError; @@ -150,6 +151,17 @@ impl Operation { Operation::Destroy(_) | Operation::DestroyResponse(_) => OperationEnumeration::Destroy, } } + + /// Allow to ensure that the protocol version used by the operation + /// is compatible with this KMIP implementation. + /// + /// Backward compatibility within major version is mandatory. + /// + /// The check is enforced only if a upper version than the default one + /// is detected when receiving an operation. + pub fn protocol_version(&self) -> ProtocolVersion { + ProtocolVersion::default() + } } /// This operation requests the server to Import a Managed Object specified by diff --git a/crate/kmip/src/kmip/kmip_types.rs b/crate/kmip/src/kmip/kmip_types.rs index 9f1f76cae..7d3beccd2 100644 --- a/crate/kmip/src/kmip/kmip_types.rs +++ b/crate/kmip/src/kmip/kmip_types.rs @@ -1657,13 +1657,34 @@ pub type UniqueIdentifier = String; /// of the protocol with the same major version. /// /// Support for backward compatibility with different major versions is OPTIONAL. -#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] #[serde(rename_all = "PascalCase")] pub struct ProtocolVersion { pub protocol_version_major: u32, pub protocol_version_minor: u32, } +/// The KMIP version 2.1 is used as the reference +/// for the implementation here +impl Default for ProtocolVersion { + fn default() -> Self { + ProtocolVersion { + protocol_version_major: 2, + protocol_version_minor: 1, + } + } +} + +impl fmt::Display for ProtocolVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}.{}", + self.protocol_version_major, self.protocol_version_minor + ) + } +} + /// This Enumeration indicates whether the client is able to accept /// an asynchronous response. /// diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index 465f85fcf..2621a876b 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -752,6 +752,235 @@ pub fn test_message_request() { assert_eq!(encrypt.data, Some(b"to be enc".to_vec())) } +#[test] +pub fn test_message_request_enforce_enum() { + log_init("info,hyper=info,reqwest=info"); + + // check Message request serializer reinforcement + let req = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + maximum_response_size: Some(9999), + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![MessageBatchItem { + operation: OperationEnumeration::Create, + ephemeral: None, + unique_batch_item_id: None, + // mismatch operation regarding the enum + request_payload: Operation::Locate(Locate::default()), + message_extension: None, + }], + }; + assert_eq!( + to_ttlv(&req).unwrap_err().to_string(), + "operation enum (`Create`) doesn't correspond to request payload (`Locate`)".to_string() + ); + + let req = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + maximum_response_size: Some(9999), + batch_count: 15, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![MessageBatchItem { + operation: OperationEnumeration::Locate, + ephemeral: None, + unique_batch_item_id: None, + // mismatch operation regarding the enum + request_payload: Operation::Locate(Locate::default()), + message_extension: None, + }], + }; + assert_eq!( + to_ttlv(&req).unwrap_err().to_string(), + "mismatch number of batch items between header (`15`) and items list (`1`)".to_string() + ); + + let req = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 3, + protocol_version_minor: 0, + }, + batch_count: 1, + maximum_response_size: None, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![MessageBatchItem { + operation: OperationEnumeration::Locate, + ephemeral: None, + unique_batch_item_id: None, + // mismatch operation regarding the enum + request_payload: Operation::Locate(Locate::default()), + message_extension: None, + }], + }; + assert_eq!( + to_ttlv(&req).unwrap_err().to_string(), + "item's protocol version is greater (`3.0`) than header's protocol version (`2.1`)" + .to_string() + ); + + // check Message response serializer reinforcement + let res = MessageResponse { + header: MessageResponseHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + attestation_type: None, + timestamp: 1697201574, + nonce: None, + server_hashed_password: None, + }, + items: vec![MessageResponseBatchItem { + operation: Some(OperationEnumeration::Decrypt), + unique_batch_item_id: None, + // mismatch operation regarding the enum + response_payload: Some(Operation::Locate(Locate::default())), + message_extension: None, + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + }], + }; + assert_eq!( + to_ttlv(&res).unwrap_err().to_string(), + "missing `AsynchronousCorrelationValue` with pending status (`ResultStatus` is set to \ + `OperationPending`)" + .to_string() + ); + + let res = MessageResponse { + header: MessageResponseHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + attestation_type: None, + timestamp: 1697201574, + nonce: None, + server_hashed_password: None, + }, + items: vec![MessageResponseBatchItem { + operation: Some(OperationEnumeration::Decrypt), + unique_batch_item_id: None, + // mismatch operation regarding the enum + response_payload: Some(Operation::Locate(Locate::default())), + message_extension: None, + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: Some(vec![0, 0, 1]), + }], + }; + assert_eq!( + to_ttlv(&res).unwrap_err().to_string(), + "operation enum (`Decrypt`) doesn't correspond to response payload (`Locate`)".to_string() + ); + + let res = MessageResponse { + header: MessageResponseHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 22, + client_correlation_value: None, + server_correlation_value: None, + attestation_type: None, + timestamp: 1697201574, + nonce: None, + server_hashed_password: None, + }, + items: vec![MessageResponseBatchItem { + operation: Some(OperationEnumeration::Locate), + unique_batch_item_id: None, + response_payload: Some(Operation::Locate(Locate::default())), + message_extension: None, + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + }], + }; + assert_eq!( + to_ttlv(&res).unwrap_err().to_string(), + "mismatch number of batch items between header (`22`) and items list (`1`)".to_string() + ); + + let res = MessageResponse { + header: MessageResponseHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 2, + protocol_version_minor: 2, + }, + batch_count: 1, + client_correlation_value: None, + server_correlation_value: None, + attestation_type: None, + timestamp: 1697201574, + nonce: None, + server_hashed_password: None, + }, + items: vec![MessageResponseBatchItem { + operation: Some(OperationEnumeration::Locate), + unique_batch_item_id: None, + response_payload: Some(Operation::Locate(Locate::default())), + message_extension: None, + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + }], + }; + assert_eq!( + to_ttlv(&res).unwrap_err().to_string(), + "item's protocol version is greater (`2.2`) than header's protocol version (`2.1`)" + .to_string() + ); +} + #[test] pub fn test_message_response() { log_init("info,hyper=info,reqwest=info"); diff --git a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs index 1272bcf70..d3802e510 100644 --- a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs +++ b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs @@ -12,7 +12,7 @@ use crate::{result::KResult, tests::test_utils}; #[actix_web::test] async fn integration_tests_bulk() -> KResult<()> { - cosmian_logger::log_utils::log_init("trace,hyper=info,reqwest=info"); + // cosmian_logger::log_utils::log_init("trace,hyper=info,reqwest=info"); let app = test_utils::test_app().await; diff --git a/crate/server/src/tests/kmip_messages.rs b/crate/server/src/tests/kmip_messages.rs index 09829dd40..41ff2f56b 100644 --- a/crate/server/src/tests/kmip_messages.rs +++ b/crate/server/src/tests/kmip_messages.rs @@ -8,7 +8,6 @@ use cosmian_kmip::kmip::{ }, }; use cosmian_kms_utils::crypto::curve_25519::kmip_requests::ec_create_key_pair_request; -use cosmian_logger::log_utils::log_init; use crate::{ config::ServerParams, result::KResult, tests::test_utils::https_clap_config, KMSServer, @@ -16,7 +15,7 @@ use crate::{ #[actix_rt::test] async fn test_kmip_messages() -> KResult<()> { - log_init("info,hyper=info,reqwest=info"); + cosmian_logger::log_utils::log_init("info,hyper=info,reqwest=info"); let clap_config = https_clap_config(); From e9037c96ab743129c06ff9a3b2f61bc7c93dd759 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 27 Oct 2023 09:36:31 +0200 Subject: [PATCH 08/19] Fix deserialization for u8 --- crate/kmip/src/kmip/ttlv/deserializer.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crate/kmip/src/kmip/ttlv/deserializer.rs b/crate/kmip/src/kmip/ttlv/deserializer.rs index 591841129..72ac2107a 100644 --- a/crate/kmip/src/kmip/ttlv/deserializer.rs +++ b/crate/kmip/src/kmip/ttlv/deserializer.rs @@ -239,6 +239,16 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> { let u = &self.get_bytes()?[self.index - 1]; visitor.visit_u8(*u) } + Deserializing::StructureValue => { + let child = &self.get_structure()?[self.index - 1]; + trace!("deserialize_u8 child {child:?}"); + match &child.value { + TTLValue::Integer(i) => visitor.visit_i32(*i), + x => Err(TtlvError::custom(format!( + "deserialize_u8. Invalid type for value: {x:?}" + ))), + } + } x => Err(TtlvError::custom(format!( "deserialize_u8. Unexpected {x:?}" ))), From f5d6ff91f9934014ae5b55c66fd4c89450828255 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 27 Oct 2023 12:56:19 +0200 Subject: [PATCH 09/19] Enforce Response operation in a Response struct --- crate/kmip/src/kmip/kmip_messages.rs | 31 ++- crate/kmip/src/kmip/kmip_operations.rs | 90 ++++-- crate/kmip/src/kmip/kmip_types.rs | 361 ++++++++++++++++++++++++- crate/kmip/src/kmip/ttlv/tests/mod.rs | 246 ++++++++++++----- 4 files changed, 612 insertions(+), 116 deletions(-) diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index 7885246ad..8f8fbd8cf 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -24,14 +24,14 @@ use serde::{ }; use super::{ - kmip_operations::{ErrorReason, Operation}, + kmip_operations::{Direction, ErrorReason, Operation}, kmip_types::{ AsynchronousIndicator, AttestationType, BatchErrorContinuationOption, Credential, MessageExtension, Nonce, OperationEnumeration, ProtocolVersion, ResultStatusEnumeration, }, }; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Message { /// Header of the request @@ -75,7 +75,7 @@ impl Serialize for Message { /// Header of the request /// /// Contains fields whose presence is determined by the protocol features used. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct MessageHeader { pub protocol_version: ProtocolVersion, @@ -132,7 +132,7 @@ pub struct MessageHeader { /// Batch item for a message request /// /// `request_payload` depends on the request -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct MessageBatchItem { /// Type of the KMIP operation pub operation: OperationEnumeration, @@ -162,6 +162,13 @@ impl Serialize for MessageBatchItem { self.request_payload.operation_enum() ))) } + if self.request_payload.direction() != Direction::Request { + return Err(ser::Error::custom(format!( + "request payload operation is not a request type operation (`{:?}`)", + self.request_payload.direction() + ))) + } + let mut st = serializer.serialize_struct("MessageBatchItem", 5)?; st.serialize_field("Operation", &self.operation)?; if let Some(ephemeral) = &self.ephemeral { @@ -328,7 +335,7 @@ impl<'de> Deserialize<'de> for MessageBatchItem { } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct MessageResponse { /// Header of the response @@ -371,7 +378,7 @@ impl Serialize for MessageResponse { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct MessageResponseHeader { pub protocol_version: ProtocolVersion, @@ -397,7 +404,7 @@ pub struct MessageResponseHeader { pub batch_count: u32, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct MessageResponseBatchItem { /// Required if present in request Batch Item pub operation: Option, @@ -475,10 +482,20 @@ impl Serialize for MessageResponseBatchItem { response_payload.operation_enum() ))) } + + if response_payload.direction() != Direction::Response { + return Err(ser::Error::custom(format!( + "response payload operation is not a response type operation (`{:?}`)", + response_payload.direction() + ))) + } } st.serialize_field("Operation", &self.operation)?; } + if let Some(unique_batch_item_id) = &self.unique_batch_item_id { + st.serialize_field("UniqueBatchItemId", unique_batch_item_id)?; + } st.serialize_field("ResultStatus", &self.result_status)?; if let Some(result_reason) = &self.result_reason { st.serialize_field("ResultReason", result_reason)?; diff --git a/crate/kmip/src/kmip/kmip_operations.rs b/crate/kmip/src/kmip/kmip_operations.rs index 2f00af8a4..4953073f4 100644 --- a/crate/kmip/src/kmip/kmip_operations.rs +++ b/crate/kmip/src/kmip/kmip_operations.rs @@ -95,7 +95,13 @@ pub enum ErrorReason { General_Failure = 0x0000_0100, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Eq, PartialEq)] +pub enum Direction { + Request, + Response, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum Operation { @@ -128,6 +134,38 @@ pub enum Operation { } impl Operation { + pub fn direction(&self) -> Direction { + match self { + Operation::Import(_) + | Operation::Certify(_) + | Operation::Create(_) + | Operation::CreateKeyPair(_) + | Operation::Export(_) + | Operation::Get(_) + | Operation::GetAttributes(_) + | Operation::Encrypt(_) + | Operation::Decrypt(_) + | Operation::Locate(_) + | Operation::Revoke(_) + | Operation::ReKeyKeyPair(_) + | Operation::Destroy(_) => Direction::Request, + + Operation::ImportResponse(_) + | Operation::CertifyResponse(_) + | Operation::CreateResponse(_) + | Operation::CreateKeyPairResponse(_) + | Operation::ExportResponse(_) + | Operation::GetResponse(_) + | Operation::GetAttributesResponse(_) + | Operation::EncryptResponse(_) + | Operation::DecryptResponse(_) + | Operation::LocateResponse(_) + | Operation::RevokeResponse(_) + | Operation::ReKeyKeyPairResponse(_) + | Operation::DestroyResponse(_) => Direction::Response, + } + } + pub fn operation_enum(&self) -> OperationEnumeration { match self { Operation::Import(_) | Operation::ImportResponse(_) => OperationEnumeration::Import, @@ -175,7 +213,7 @@ impl Operation { /// 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)] +#[derive(Debug, Serialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Import { /// The Unique Identifier of the object to be imported @@ -346,7 +384,7 @@ pub struct ImportResponse { /// into the ID Placeholder variable. If the information in the Certificate /// Request conflicts with the attributes specified in the Attributes, then the /// information in the Certificate Request takes precedence. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Certify { // The Unique Identifier of the Public Key or the Certificate Request being certified. If @@ -370,7 +408,7 @@ pub struct Certify { pub protection_storage_masks: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct CertifyResponse { /// The Unique Identifier of the newly created object. @@ -384,7 +422,7 @@ pub struct CertifyResponse { /// 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)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Create { /// Determines the type of object to be created. @@ -397,7 +435,7 @@ pub struct Create { pub protection_storage_masks: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct CreateResponse { /// Type of object created. @@ -420,7 +458,7 @@ pub struct CreateResponse { /// 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)] +#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct CreateKeyPair { /// Specifies desired attributes to be associated with the new object that @@ -449,7 +487,7 @@ pub struct CreateKeyPair { pub public_protection_storage_masks: Option, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct CreateKeyPairResponse { /// The Unique Identifier of the newly created private key object. @@ -466,7 +504,7 @@ pub struct CreateKeyPairResponse { /// SHALL not be returned in the response. /// The server SHALL copy the Unique Identifier returned by this operations /// into the ID Placeholder variable. -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Export { /// Determines the object being requested. If omitted, then the ID @@ -545,7 +583,7 @@ impl From<&str> for Export { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct ExportResponse { pub object_type: ObjectType, @@ -584,7 +622,7 @@ pub struct ExportResponse { /// A Get operation may use both a Key Wrap Type and a Wrapping Key Specification, /// in which case the Key Wrap Type is processed as if there was no Wrapping Key Specification, /// and the result is then wrapped as specified. -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Get { /// Determines the object being requested. If omitted, then the ID @@ -663,7 +701,7 @@ impl From<&str> for Get { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct GetResponse { pub object_type: ObjectType, @@ -671,7 +709,7 @@ pub struct GetResponse { pub object: Object, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct GetAttributes { /// Determines the object whose attributes @@ -704,7 +742,7 @@ impl From<&str> for GetAttributes { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct GetAttributesResponse { /// The Unique Identifier of the object @@ -713,7 +751,7 @@ pub struct GetAttributesResponse { pub attributes: Attributes, } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Encrypt { /// The Unique Identifier of the Managed @@ -763,7 +801,7 @@ pub struct Encrypt { pub authenticated_encryption_additional_data: Option>, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct EncryptResponse { /// The Unique Identifier of the Managed @@ -797,7 +835,7 @@ pub struct EncryptResponse { pub authenticated_encryption_tag: Option>, } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Decrypt { /// The Unique Identifier of the Managed @@ -899,7 +937,7 @@ impl TryFrom<&[u8]> for DecryptedData { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct DecryptResponse { /// The Unique Identifier of the Managed @@ -1000,7 +1038,7 @@ pub struct DecryptResponse { /// 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. -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Locate { /// An Integer object that indicates the maximum number of object @@ -1025,7 +1063,7 @@ pub struct Locate { pub attributes: Attributes, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct LocateResponse { /// An Integer object that indicates the number of object identifiers that /// satisfy the identification criteria specified in the request. A server @@ -1055,7 +1093,7 @@ pub struct LocateResponse { /// 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)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Revoke { /// Determines the object being revoked. If omitted, then the ID Placeholder @@ -1071,7 +1109,7 @@ pub struct Revoke { pub compromise_occurrence_date: Option, // epoch millis } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct RevokeResponse { /// The Unique Identifier of the object. @@ -1103,7 +1141,7 @@ pub struct RevokeResponse { /// 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)] +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct ReKeyKeyPair { // Determines the existing Asymmetric key pair to be re-keyed. If omitted, then the ID @@ -1144,7 +1182,7 @@ pub struct ReKeyKeyPair { pub public_protection_storage_masks: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct ReKeyKeyPairResponse { // The Unique Identifier of the newly created replacement Private Key object. @@ -1153,7 +1191,7 @@ pub struct ReKeyKeyPairResponse { pub public_key_unique_identifier: UniqueIdentifier, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct Destroy { /// Determines the object being destroyed. If omitted, then the ID @@ -1162,7 +1200,7 @@ pub struct Destroy { pub unique_identifier: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct DestroyResponse { /// The Unique Identifier of the object. diff --git a/crate/kmip/src/kmip/kmip_types.rs b/crate/kmip/src/kmip/kmip_types.rs index 7d3beccd2..38e18b989 100644 --- a/crate/kmip/src/kmip/kmip_types.rs +++ b/crate/kmip/src/kmip/kmip_types.rs @@ -7,7 +7,8 @@ use std::{fmt, vec::Vec}; use serde::{ - de::{self, Visitor}, + de::{self, MapAccess, Visitor}, + ser::SerializeStruct, Deserialize, Serialize, }; use strum::{Display, EnumIter, EnumString}; @@ -388,7 +389,7 @@ impl<'de> Deserialize<'de> for CryptographicUsageMask { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct ProtectionStorageMasks(u32); @@ -476,7 +477,7 @@ pub enum ObjectGroupMember { // Extensions 8XXXXXXX } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct StorageStatusMask(u32); @@ -1719,11 +1720,9 @@ pub enum AttestationType { /// A Credential is a structure used for client identification purposes /// and is not managed by the key management system /// (e.g., user id/password pairs, Kerberos tokens, etc.). -#[allow(non_camel_case_types)] -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "PascalCase")] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum Credential { - Username_and_Password { + UsernameAndPassword { username: String, password: Option, }, @@ -1741,12 +1740,12 @@ pub enum Credential { attestation_measurement: Option>, attestation_assertion: Option>, }, - One_Time_Password { + OneTimePassword { username: String, password: Option, one_time_password: String, }, - Hashed_Password { + HashedPassword { username: String, timestamp: u64, // epoch millis hashing_algorithm: Option, @@ -1762,16 +1761,350 @@ impl Credential { #[allow(dead_code)] fn value(&self) -> u32 { match *self { - Credential::Username_and_Password { .. } => 0x0000_0001, + Credential::UsernameAndPassword { .. } => 0x0000_0001, Credential::Device { .. } => 0x0000_0002, Credential::Attestation { .. } => 0x0000_0003, - Credential::One_Time_Password { .. } => 0x0000_0004, - Credential::Hashed_Password { .. } => 0x0000_0005, + Credential::OneTimePassword { .. } => 0x0000_0004, + Credential::HashedPassword { .. } => 0x0000_0005, Credential::Ticket { .. } => 0x0000_0006, } } } +impl Serialize for Credential { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Credential::UsernameAndPassword { username, password } => { + let mut st = serializer.serialize_struct("UsernameAndPassword", 2)?; + st.serialize_field("Username", username)?; + if let Some(password) = password { + st.serialize_field("Password", password)?; + } + st.end() + } + Credential::Device { + device_serial_number, + password, + device_identifier, + network_identifier, + machine_identifier, + media_identifier, + } => { + let mut st = serializer.serialize_struct("Device", 6)?; + if let Some(device_serial_number) = device_serial_number { + st.serialize_field("DeviceSerialNumber", device_serial_number)?; + } + if let Some(password) = password { + st.serialize_field("Password", password)?; + } + if let Some(device_identifier) = device_identifier { + st.serialize_field("DeviceIdentifier", device_identifier)?; + } + if let Some(network_identifier) = network_identifier { + st.serialize_field("NetworkIdentifier", network_identifier)?; + } + if let Some(machine_identifier) = machine_identifier { + st.serialize_field("MachineIdentifier", machine_identifier)?; + } + if let Some(media_identifier) = media_identifier { + st.serialize_field("MediaIdentifier", media_identifier)?; + } + st.end() + } + Credential::Attestation { + nonce, + attestation_type, + attestation_measurement, + attestation_assertion, + } => { + let mut st = serializer.serialize_struct("Attestation", 4)?; + st.serialize_field("Nonce", nonce)?; + st.serialize_field("AttestationType", attestation_type)?; + if let Some(attestation_measurement) = attestation_measurement { + st.serialize_field("AttestationMeasurement", attestation_measurement)?; + } + if let Some(attestation_assertion) = attestation_assertion { + st.serialize_field("AttestationAssertion", attestation_assertion)?; + } + st.end() + } + Credential::OneTimePassword { + username, + password, + one_time_password, + } => { + let mut st = serializer.serialize_struct("OneTimePassword", 3)?; + st.serialize_field("Username", username)?; + if let Some(password) = password { + st.serialize_field("Password", password)?; + } + st.serialize_field("OneTimePassword", one_time_password)?; + st.end() + } + Credential::HashedPassword { + username, + timestamp, + hashing_algorithm, + hashed_password, + } => { + let mut st = serializer.serialize_struct("HashedPassword", 4)?; + st.serialize_field("Username", username)?; + st.serialize_field("Timestamp", timestamp)?; + if let Some(hashing_algorithm) = hashing_algorithm { + st.serialize_field("HashingAlgorithm", hashing_algorithm)?; + } + st.serialize_field("HashedPassword", hashed_password)?; + st.end() + } + Credential::Ticket { + ticket_type, + ticket_value, + } => { + let mut st = serializer.serialize_struct("Ticket", 2)?; + st.serialize_field("TicketType", ticket_type)?; + st.serialize_field("TicketValue", ticket_value)?; + st.end() + } + } + } +} + +impl<'de> Deserialize<'de> for Credential { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(field_identifier)] + enum Field { + Username, + Password, + DeviceSerialNumber, + DeviceIdentifier, + NetworkIdentifier, + MachineIdentifier, + MediaIdentifier, + Nonce, + AttestationType, + AttestationMeasurement, + AttestationAssertion, + OneTimePassword, + Timestamp, + HashingAlgorithm, + HashedPassword, + TicketType, + TicketValue, + } + + struct CredentialVisitor; + + impl<'de> Visitor<'de> for CredentialVisitor { + type Value = Credential; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct Credential") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut username: Option = None; + let mut password: Option = None; + let mut device_serial_number: Option = None; + let mut device_identifier: Option = None; + let mut network_identifier: Option = None; + let mut machine_identifier: Option = None; + let mut media_identifier: Option = None; + let mut nonce: Option = None; + let mut attestation_type: Option = None; + let mut attestation_measurement: Option> = None; + let mut attestation_assertion: Option> = None; + let mut one_time_password: Option = None; + let mut timestamp: Option = None; + let mut hashing_algorithm: Option = None; + let mut hashed_password: Option> = None; + let mut ticket_type: Option = None; + let mut ticket_value: Option> = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Username => { + if username.is_some() { + return Err(de::Error::duplicate_field("username")) + } + username = Some(map.next_value()?); + } + Field::Password => { + if password.is_some() { + return Err(de::Error::duplicate_field("password")) + } + password = Some(map.next_value()?); + } + Field::DeviceSerialNumber => { + if device_serial_number.is_some() { + return Err(de::Error::duplicate_field("device_serial_number")) + } + device_serial_number = Some(map.next_value()?); + } + Field::DeviceIdentifier => { + if device_identifier.is_some() { + return Err(de::Error::duplicate_field("device_identifier")) + } + device_identifier = Some(map.next_value()?); + } + Field::NetworkIdentifier => { + if network_identifier.is_some() { + return Err(de::Error::duplicate_field("network_identifier")) + } + network_identifier = Some(map.next_value()?); + } + Field::MachineIdentifier => { + if machine_identifier.is_some() { + return Err(de::Error::duplicate_field("machine_identifier")) + } + machine_identifier = Some(map.next_value()?); + } + Field::MediaIdentifier => { + if media_identifier.is_some() { + return Err(de::Error::duplicate_field("media_identifier")) + } + media_identifier = Some(map.next_value()?); + } + Field::Nonce => { + if nonce.is_some() { + return Err(de::Error::duplicate_field("nonce")) + } + nonce = Some(map.next_value()?); + } + Field::AttestationType => { + if attestation_type.is_some() { + return Err(de::Error::duplicate_field("attestation_type")) + } + attestation_type = Some(map.next_value()?); + } + Field::AttestationMeasurement => { + if attestation_measurement.is_some() { + return Err(de::Error::duplicate_field( + "attesattestation_measurementtation_type", + )) + } + attestation_measurement = Some(map.next_value()?); + } + Field::AttestationAssertion => { + if attestation_assertion.is_some() { + return Err(de::Error::duplicate_field("attestation_assertion")) + } + attestation_assertion = Some(map.next_value()?); + } + Field::OneTimePassword => { + if one_time_password.is_some() { + return Err(de::Error::duplicate_field("one_time_password")) + } + one_time_password = Some(map.next_value()?); + } + Field::Timestamp => { + if timestamp.is_some() { + return Err(de::Error::duplicate_field("timestamp")) + } + timestamp = Some(map.next_value()?); + } + Field::HashingAlgorithm => { + if hashing_algorithm.is_some() { + return Err(de::Error::duplicate_field("hashing_algorithm")) + } + hashing_algorithm = Some(map.next_value()?); + } + Field::HashedPassword => { + if hashed_password.is_some() { + return Err(de::Error::duplicate_field("hashed_password")) + } + hashed_password = Some(map.next_value()?); + } + Field::TicketType => { + if ticket_type.is_some() { + return Err(de::Error::duplicate_field("ticket_type")) + } + ticket_type = Some(map.next_value()?); + } + Field::TicketValue => { + if ticket_value.is_some() { + return Err(de::Error::duplicate_field("ticket_value")) + } + ticket_value = Some(map.next_value()?); + } + } + } + + if let (Some(nonce), Some(attestation_type)) = (nonce, attestation_type) { + return Ok(Credential::Attestation { + nonce, + attestation_type, + attestation_measurement, + attestation_assertion, + }) + } else if let (Some(ticket_type), Some(ticket_value)) = (ticket_type, ticket_value) + { + return Ok(Credential::Ticket { + ticket_type, + ticket_value, + }) + } else if let Some(username) = username { + if let (Some(timestamp), Some(hashed_password)) = (timestamp, hashed_password) { + return Ok(Credential::HashedPassword { + username, + timestamp, + hashing_algorithm, + hashed_password, + }) + } else if let Some(one_time_password) = one_time_password { + return Ok(Credential::OneTimePassword { + username, + password, + one_time_password, + }) + } + + return Ok(Credential::UsernameAndPassword { username, password }) + } + + Ok(Credential::Device { + device_serial_number, + password, + device_identifier, + network_identifier, + machine_identifier, + media_identifier, + }) + } + } + + const FIELDS: &[&str] = &[ + "username", + "password", + "device_serial_number", + "device_identifier", + "network_identifier", + "machine_identifier", + "media_identifier", + "nonce", + "attestation_type", + "attestation_measurement", + "attestation_assertion", + "one_time_password", + "timestamp", + "hashing_algorithm", + "hashed_password", + "ticket_type", + "ticket_value", + ]; + deserializer.deserialize_struct("Credential", FIELDS, CredentialVisitor) + } +} + #[allow(non_camel_case_types)] #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub enum TicketType { @@ -1785,8 +2118,8 @@ pub enum TicketType { #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "PascalCase")] pub struct Nonce { - nonce_id: Vec, - nonce_value: Vec, + pub nonce_id: Vec, + pub nonce_value: Vec, } /// This option SHALL only be present if the Batch Count is greater than 1. diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index 2621a876b..327244df3 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -10,10 +10,15 @@ use crate::kmip::{ MessageResponseHeader, }, kmip_objects::{Object, ObjectType}, - kmip_operations::{Create, Decrypt, Encrypt, Import, ImportResponse, Locate, Operation}, + kmip_operations::{ + Create, DecryptResponse, Encrypt, ErrorReason, Import, ImportResponse, Locate, + LocateResponse, Operation, + }, kmip_types::{ - Attributes, CryptographicAlgorithm, CryptographicUsageMask, KeyFormatType, Link, - LinkedObjectIdentifier, OperationEnumeration, ProtocolVersion, ResultStatusEnumeration, + AsynchronousIndicator, AttestationType, Attributes, BatchErrorContinuationOption, + Credential, CryptographicAlgorithm, CryptographicUsageMask, KeyFormatType, Link, + LinkedObjectIdentifier, MessageExtension, Nonce, OperationEnumeration, ProtocolVersion, + ResultStatusEnumeration, }, ttlv::{deserializer::from_ttlv, serializer::to_ttlv, TTLVEnumeration, TTLValue, TTLV}, }; @@ -715,19 +720,27 @@ pub fn test_message_request() { header: MessageHeader { protocol_version: ProtocolVersion { protocol_version_major: 1, - protocol_version_minor: 0, + protocol_version_minor: 2, }, maximum_response_size: Some(9999), batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + client_correlation_value: Some("client_123".to_string()), + server_correlation_value: Some("server_234".to_string()), + asynchronous_indicator: Some(AsynchronousIndicator::Optional), + attestation_capable_indicator: Some(true), + attestation_type: Some(vec![AttestationType::TPM_Quote]), + authentication: Some(vec![Credential::Attestation { + nonce: Nonce { + nonce_id: vec![9, 8, 7], + nonce_value: vec![10, 11, 12], + }, + attestation_type: AttestationType::TCG_Integrity_Report, + attestation_measurement: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + attestation_assertion: Some(vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20]), + }]), + batch_error_continuation_option: Some(BatchErrorContinuationOption::Undo), + batch_order_option: Some(true), + timestamp: Some(1950940403), }, items: vec![MessageBatchItem { operation: OperationEnumeration::Encrypt, @@ -737,7 +750,11 @@ pub fn test_message_request() { data: Some(b"to be enc".to_vec()), ..Default::default() }), - message_extension: None, + message_extension: Some(vec![MessageExtension { + vendor_identification: "CosmianVendor".to_string(), + criticality_indicator: false, + vendor_extension: vec![42_u8], + }]), }], }; let ttlv = to_ttlv(&req).unwrap(); @@ -749,11 +766,93 @@ pub fn test_message_request() { req_.items[0] ); }; - assert_eq!(encrypt.data, Some(b"to be enc".to_vec())) + assert_eq!(encrypt.data, Some(b"to be enc".to_vec())); + assert_eq!(req, req_); +} + +#[test] +pub fn test_message_response() { + log_init("info,hyper=info,reqwest=info"); + + let res = MessageResponse { + header: MessageResponseHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 2, + client_correlation_value: Some("client_123".to_string()), + server_correlation_value: Some("server_234".to_string()), + attestation_type: Some(vec![AttestationType::TPM_Quote]), + timestamp: 1697201574, + nonce: Some(Nonce { + nonce_id: vec![5, 6, 7], + nonce_value: vec![8, 9, 0], + }), + server_hashed_password: Some("5e8953ab".to_string()), + }, + items: vec![ + MessageResponseBatchItem { + operation: Some(OperationEnumeration::Locate), + unique_batch_item_id: Some(1234), + response_payload: Some(Operation::LocateResponse(LocateResponse { + located_items: Some(134), + unique_identifiers: Some(vec!["some_id".to_string()]), + })), + message_extension: Some(MessageExtension { + vendor_identification: "CosmianVendor".to_string(), + criticality_indicator: false, + vendor_extension: vec![42_u8], + }), + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: Some(vec![42_u8, 5]), + }, + MessageResponseBatchItem { + operation: Some(OperationEnumeration::Decrypt), + unique_batch_item_id: Some(1235), + response_payload: Some(Operation::DecryptResponse(DecryptResponse { + unique_identifier: "id_12345".to_string(), + data: Some(b"decrypted_data".to_vec()), + correlation_value: Some(vec![9_u8, 13]), + })), + message_extension: Some(MessageExtension { + vendor_identification: "CosmianVendor".to_string(), + criticality_indicator: true, + vendor_extension: vec![42_u8], + }), + result_status: ResultStatusEnumeration::OperationUndone, + result_reason: Some(ErrorReason::Response_Too_Large), + result_message: Some("oversized data".to_string()), + asynchronous_correlation_value: Some(vec![43_u8, 6]), + }, + ], + }; + let ttlv = to_ttlv(&res).unwrap(); + let res_: MessageResponse = from_ttlv(&ttlv).unwrap(); + assert_eq!(res_.items.len(), 2); + assert_eq!(res_.items[0].operation, Some(OperationEnumeration::Locate)); + assert_eq!( + res_.items[0].result_status, + ResultStatusEnumeration::OperationPending + ); + assert_eq!(res_.items[1].operation, Some(OperationEnumeration::Decrypt)); + assert_eq!( + res_.items[1].result_status, + ResultStatusEnumeration::OperationUndone + ); + + let Some(Operation::DecryptResponse(decrypt)) = &res_.items[1].response_payload else { + panic!("not a decrypt operation's response payload"); + }; + assert_eq!(decrypt.data, Some(b"decrypted_data".to_vec())); + assert_eq!(decrypt.unique_identifier, "id_12345".to_string()); + assert_eq!(res, res_); } #[test] -pub fn test_message_request_enforce_enum() { +pub fn test_message_enforce_enum() { log_init("info,hyper=info,reqwest=info"); // check Message request serializer reinforcement @@ -854,6 +953,42 @@ pub fn test_message_request_enforce_enum() { .to_string() ); + let req = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 1, + protocol_version_minor: 0, + }, + batch_count: 1, + maximum_response_size: None, + client_correlation_value: None, + server_correlation_value: None, + asynchronous_indicator: None, + attestation_capable_indicator: None, + attestation_type: None, + authentication: None, + batch_error_continuation_option: None, + batch_order_option: None, + timestamp: None, + }, + items: vec![MessageBatchItem { + operation: OperationEnumeration::Decrypt, + ephemeral: None, + unique_batch_item_id: None, + // mismatch operation regarding the enum + request_payload: Operation::DecryptResponse(DecryptResponse { + unique_identifier: "id_12345".to_string(), + data: Some(b"decrypted_data".to_vec()), + correlation_value: None, + }), + message_extension: None, + }], + }; + assert_eq!( + to_ttlv(&req).unwrap_err().to_string(), + "request payload operation is not a request type operation (`Response`)".to_string() + ); + // check Message response serializer reinforcement let res = MessageResponse { header: MessageResponseHeader { @@ -979,11 +1114,6 @@ pub fn test_message_request_enforce_enum() { "item's protocol version is greater (`2.2`) than header's protocol version (`2.1`)" .to_string() ); -} - -#[test] -pub fn test_message_response() { - log_init("info,hyper=info,reqwest=info"); let res = MessageResponse { header: MessageResponseHeader { @@ -992,57 +1122,35 @@ pub fn test_message_response() { protocol_version_minor: 0, }, batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - attestation_type: None, + client_correlation_value: Some("client_123".to_string()), + server_correlation_value: Some("server_234".to_string()), + attestation_type: Some(vec![AttestationType::TPM_Quote]), timestamp: 1697201574, - nonce: None, - server_hashed_password: None, + nonce: Some(Nonce { + nonce_id: vec![5, 6, 7], + nonce_value: vec![8, 9, 0], + }), + server_hashed_password: Some("5e8953ab".to_string()), }, - items: vec![ - MessageResponseBatchItem { - operation: Some(OperationEnumeration::Locate), - unique_batch_item_id: None, - response_payload: Some(Operation::Locate(Locate::default())), - message_extension: None, - result_status: ResultStatusEnumeration::OperationPending, - result_reason: None, - result_message: None, - asynchronous_correlation_value: None, - }, - MessageResponseBatchItem { - operation: Some(OperationEnumeration::Decrypt), - unique_batch_item_id: None, - response_payload: Some(Operation::Decrypt(Decrypt { - unique_identifier: Some("id_12345".to_string()), - data: Some(b"decrypted_data".to_vec()), - ..Default::default() - })), - message_extension: None, - result_status: ResultStatusEnumeration::Success, - result_reason: None, - result_message: None, - asynchronous_correlation_value: None, - }, - ], + items: vec![MessageResponseBatchItem { + operation: Some(OperationEnumeration::Locate), + unique_batch_item_id: Some(1234), + // in a message response, we can't have `Operation::Locate`, + // we could only have an `Operation::LocateResponse` + response_payload: Some(Operation::Locate(Locate::default())), + message_extension: Some(MessageExtension { + vendor_identification: "CosmianVendor".to_string(), + criticality_indicator: false, + vendor_extension: vec![42_u8], + }), + result_status: ResultStatusEnumeration::OperationPending, + result_reason: None, + result_message: None, + asynchronous_correlation_value: Some(vec![42_u8, 5]), + }], }; - let ttlv = to_ttlv(&res).unwrap(); - let res_: MessageResponse = from_ttlv(&ttlv).unwrap(); - assert_eq!(res_.items.len(), 2); - assert_eq!(res_.items[0].operation, Some(OperationEnumeration::Locate)); - assert_eq!( - res_.items[0].result_status, - ResultStatusEnumeration::OperationPending - ); - assert_eq!(res_.items[1].operation, Some(OperationEnumeration::Decrypt)); assert_eq!( - res_.items[1].result_status, - ResultStatusEnumeration::Success + to_ttlv(&res).unwrap_err().to_string(), + "response payload operation is not a response type operation (`Request`)".to_string() ); - - let Some(Operation::DecryptResponse(decrypt)) = &res_.items[1].response_payload else { - panic!("not a decrypt operation's response payload"); - }; - assert_eq!(decrypt.data, Some(b"decrypted_data".to_vec())); - assert_eq!(decrypt.unique_identifier, "id_12345".to_string()); } From 84b4632f54aa27f3913ef2ff30245769ff039dac Mon Sep 17 00:00:00 2001 From: ThibsG Date: Tue, 31 Oct 2023 10:54:14 +0100 Subject: [PATCH 10/19] Use default and new builders when possible --- Cargo.lock | 1 + Cargo.toml | 1 + crate/kmip/Cargo.toml | 1 + crate/kmip/src/kmip/kmip_messages.rs | 61 ++++++++- crate/kmip/src/kmip/ttlv/tests/mod.rs | 120 ++++-------------- crate/server/Cargo.toml | 2 +- .../integration_tests_bulk.rs | 37 +----- crate/server/src/tests/curve_25519_tests.rs | 115 +++++++++-------- crate/server/src/tests/kmip_messages.rs | 45 ++----- 9 files changed, 166 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3176031b1..6e2161432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1121,6 +1121,7 @@ name = "cosmian_kmip" version = "4.8.1" dependencies = [ "bitflags 2.3.2", + "chrono", "cloudproof", "cosmian_logger", "hex", diff --git a/Cargo.toml b/Cargo.toml index c6099b596..e07950db1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ opt-level = 0 async-trait = "0.1" base64 = "0.21" bitflags = "2.3" +chrono = "0.4" clap = { version = "4.4", default-features = false, features = [ "help", "env", diff --git a/crate/kmip/Cargo.toml b/crate/kmip/Cargo.toml index 22de33574..25e24e7d9 100644 --- a/crate/kmip/Cargo.toml +++ b/crate/kmip/Cargo.toml @@ -6,6 +6,7 @@ license-file = "../../LICENSE.md" [dependencies] bitflags = { workspace = true } +chrono = { workspace = true } cloudproof = { workspace = true } hex = { workspace = true } num-bigint = { workspace = true } diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index 8f8fbd8cf..7645ed674 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -17,6 +17,7 @@ /// Indicator in the header of a batched request. /// The batched responses MAY contain a mixture of synchronous and /// asynchronous responses only if the Asynchronous Indicator is present in the header. +use chrono::Utc; use serde::{ de::{self, MapAccess, Visitor}, ser::{self, SerializeStruct}, @@ -75,7 +76,7 @@ impl Serialize for Message { /// Header of the request /// /// Contains fields whose presence is determined by the protocol features used. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct MessageHeader { pub protocol_version: ProtocolVersion, @@ -150,6 +151,18 @@ pub struct MessageBatchItem { pub message_extension: Option>, } +impl MessageBatchItem { + pub fn new(request: Operation) -> Self { + Self { + operation: request.operation_enum(), + ephemeral: None, + unique_batch_item_id: None, + request_payload: request, + message_extension: None, + } + } +} + impl Serialize for MessageBatchItem { fn serialize(&self, serializer: S) -> Result where @@ -404,6 +417,24 @@ pub struct MessageResponseHeader { pub batch_count: u32, } +/// Default implementation for a message response header +/// +/// The timestamp is automatically set to now (UTC time) +impl Default for MessageResponseHeader { + fn default() -> Self { + Self { + timestamp: Utc::now().timestamp() as u64, + protocol_version: ProtocolVersion::default(), + nonce: None, + server_hashed_password: None, + attestation_type: None, + client_correlation_value: None, + server_correlation_value: None, + batch_count: 0, + } + } +} + #[derive(Debug, PartialEq, Eq)] pub struct MessageResponseBatchItem { /// Required if present in request Batch Item @@ -441,6 +472,34 @@ pub struct MessageResponseBatchItem { pub message_extension: Option, } +impl MessageResponseBatchItem { + pub fn new(result_status: ResultStatusEnumeration) -> Self { + Self { + result_status, + operation: None, + unique_batch_item_id: None, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + response_payload: None, + message_extension: None, + } + } + + pub fn new_with_response(result_status: ResultStatusEnumeration, response: Operation) -> Self { + Self { + result_status, + operation: Some(response.operation_enum()), + response_payload: Some(response), + unique_batch_item_id: None, + result_reason: None, + result_message: None, + asynchronous_correlation_value: None, + message_extension: None, + } + } +} + impl Serialize for MessageResponseBatchItem { fn serialize(&self, serializer: S) -> Result where diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index 327244df3..b622a0685 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -864,15 +864,7 @@ pub fn test_message_enforce_enum() { }, maximum_response_size: Some(9999), batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, items: vec![MessageBatchItem { operation: OperationEnumeration::Create, @@ -895,25 +887,11 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, maximum_response_size: Some(9999), + // mismatch number of batch items batch_count: 15, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, - items: vec![MessageBatchItem { - operation: OperationEnumeration::Locate, - ephemeral: None, - unique_batch_item_id: None, - // mismatch operation regarding the enum - request_payload: Operation::Locate(Locate::default()), - message_extension: None, - }], + items: vec![MessageBatchItem::new(Operation::Locate(Locate::default()))], }; assert_eq!( to_ttlv(&req).unwrap_err().to_string(), @@ -927,25 +905,9 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, batch_count: 1, - maximum_response_size: None, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, - items: vec![MessageBatchItem { - operation: OperationEnumeration::Locate, - ephemeral: None, - unique_batch_item_id: None, - // mismatch operation regarding the enum - request_payload: Operation::Locate(Locate::default()), - message_extension: None, - }], + items: vec![MessageBatchItem::new(Operation::Locate(Locate::default()))], }; assert_eq!( to_ttlv(&req).unwrap_err().to_string(), @@ -960,16 +922,7 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, batch_count: 1, - maximum_response_size: None, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, items: vec![MessageBatchItem { operation: OperationEnumeration::Decrypt, @@ -997,12 +950,8 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - attestation_type: None, timestamp: 1697201574, - nonce: None, - server_hashed_password: None, + ..Default::default() }, items: vec![MessageResponseBatchItem { operation: Some(OperationEnumeration::Decrypt), @@ -1030,12 +979,8 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - attestation_type: None, timestamp: 1697201574, - nonce: None, - server_hashed_password: None, + ..Default::default() }, items: vec![MessageResponseBatchItem { operation: Some(OperationEnumeration::Decrypt), @@ -1060,24 +1005,15 @@ pub fn test_message_enforce_enum() { protocol_version_major: 1, protocol_version_minor: 0, }, + // mismatch number of items batch_count: 22, - client_correlation_value: None, - server_correlation_value: None, - attestation_type: None, timestamp: 1697201574, - nonce: None, - server_hashed_password: None, + ..Default::default() }, - items: vec![MessageResponseBatchItem { - operation: Some(OperationEnumeration::Locate), - unique_batch_item_id: None, - response_payload: Some(Operation::Locate(Locate::default())), - message_extension: None, - result_status: ResultStatusEnumeration::OperationPending, - result_reason: None, - result_message: None, - asynchronous_correlation_value: None, - }], + items: vec![ + MessageResponseBatchItem::new(ResultStatusEnumeration::OperationPending), + MessageResponseBatchItem::new(ResultStatusEnumeration::OperationPending), + ], }; assert_eq!( to_ttlv(&res).unwrap_err().to_string(), @@ -1087,31 +1023,21 @@ pub fn test_message_enforce_enum() { let res = MessageResponse { header: MessageResponseHeader { protocol_version: ProtocolVersion { - protocol_version_major: 2, - protocol_version_minor: 2, + protocol_version_major: 128, + protocol_version_minor: 128, }, batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - attestation_type: None, timestamp: 1697201574, - nonce: None, - server_hashed_password: None, + ..Default::default() }, - items: vec![MessageResponseBatchItem { - operation: Some(OperationEnumeration::Locate), - unique_batch_item_id: None, - response_payload: Some(Operation::Locate(Locate::default())), - message_extension: None, - result_status: ResultStatusEnumeration::OperationPending, - result_reason: None, - result_message: None, - asynchronous_correlation_value: None, - }], + items: vec![MessageResponseBatchItem::new_with_response( + ResultStatusEnumeration::OperationPending, + Operation::Locate(Locate::default()), + )], }; assert_eq!( to_ttlv(&res).unwrap_err().to_string(), - "item's protocol version is greater (`2.2`) than header's protocol version (`2.1`)" + "item's protocol version is greater (`128.128`) than header's protocol version (`2.1`)" .to_string() ); diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index f5023ad7b..484f49bef 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -36,7 +36,7 @@ alcoholic_jwt = "4091" async-recursion = "1.0" async-trait = { workspace = true } base64 = { workspace = true } -chrono = "0.4" +chrono = { workspace = true } clap = { workspace = true } cloudproof = { workspace = true } cosmian_kmip = { path = "../kmip" } diff --git a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs index d3802e510..42726665e 100644 --- a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs +++ b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs @@ -42,42 +42,19 @@ async fn integration_tests_bulk() -> KResult<()> { protocol_version_minor: 0, }, batch_count: 2, - maximum_response_size: None, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, items: vec![ - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(build_create_master_keypair_request( - &policy, EMPTY_TAGS, - )?), - message_extension: None, - }, - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(build_create_master_keypair_request( - &policy, EMPTY_TAGS, - )?), - message_extension: None, - }, + MessageBatchItem::new(Operation::CreateKeyPair( + build_create_master_keypair_request(&policy, EMPTY_TAGS)?, + )), + MessageBatchItem::new(Operation::CreateKeyPair( + build_create_master_keypair_request(&policy, EMPTY_TAGS)?, + )), ], }; let response: MessageResponse = test_utils::post(&app, &request_message).await?; - - // tracing::trace!("{response:#?}"); assert_eq!(response.items.len(), 2); // 1. Create keypair diff --git a/crate/server/src/tests/curve_25519_tests.rs b/crate/server/src/tests/curve_25519_tests.rs index 6c8c2651f..da6cf70a6 100644 --- a/crate/server/src/tests/curve_25519_tests.rs +++ b/crate/server/src/tests/curve_25519_tests.rs @@ -4,10 +4,10 @@ use cloudproof::reexport::crypto_core::X25519_PUBLIC_KEY_LENGTH; use cosmian_kmip::kmip::{ kmip_messages::{Message, MessageBatchItem, MessageHeader}, kmip_objects::{Object, ObjectType}, - kmip_operations::{Import, Operation}, + kmip_operations::{ErrorReason, Import, Operation}, kmip_types::{ Attributes, CryptographicAlgorithm, KeyFormatType, LinkType, LinkedObjectIdentifier, - OperationEnumeration, ProtocolVersion, RecommendedCurve, + ProtocolVersion, RecommendedCurve, ResultStatusEnumeration, }, }; use cosmian_kms_utils::crypto::curve_25519::{ @@ -141,7 +141,6 @@ async fn test_curve_25519_key_pair() -> KResult<()> { }; let new_uid = kms.import(request, owner, None).await?.unique_identifier; // update - let request = Import { unique_identifier: new_uid.clone(), object_type: ObjectType::PublicKey, @@ -160,7 +159,7 @@ async fn test_curve_25519_key_pair() -> KResult<()> { #[actix_rt::test] async fn test_curve_25519_multiple() -> KResult<()> { - log_init("trace,hyper=info,reqwest=info"); + log_init("info,hyper=info,reqwest=info"); let clap_config = https_clap_config(); @@ -175,63 +174,71 @@ async fn test_curve_25519_multiple() -> KResult<()> { }, maximum_response_size: Some(9999), batch_count: 4, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, items: vec![ - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( - &[] as &[&str], - RecommendedCurve::CURVE25519, - )?), - message_extension: None, - }, - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( - &[] as &[&str], - RecommendedCurve::CURVEED25519, - )?), - message_extension: None, - }, - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( - &[] as &[&str], - RecommendedCurve::SECP256K1, - )?), - message_extension: None, - }, - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(ec_create_key_pair_request( - &[] as &[&str], - RecommendedCurve::CURVEED25519, - )?), - message_extension: None, - }, + MessageBatchItem::new(Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVE25519, + )?)), + MessageBatchItem::new(Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVEED25519, + )?)), + MessageBatchItem::new(Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::SECP256K1, + )?)), + MessageBatchItem::new(Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVEED25519, + )?)), ], }; let response = kms.message(request, owner, None).await?; + assert_eq!(response.header.batch_count, 4); + assert_eq!(response.items.len(), 4); + + assert_eq!( + response.items[0].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::CreateKeyPairResponse(_)) = &response.items[0].response_payload else { + panic!("not a create key pair response payload"); + }; + + assert_eq!( + response.items[1].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::CreateKeyPairResponse(_)) = &response.items[1].response_payload else { + panic!("not a create key pair response payload"); + }; + + assert_eq!( + response.items[2].result_status, + ResultStatusEnumeration::OperationFailed + ); + assert_eq!( + response.items[2].result_reason, + Some(ErrorReason::Operation_Not_Supported) + ); + assert_eq!( + response.items[2].result_message, + Some( + "Not Supported: Generation of Key Pair for curve: SECP256K1, is not supported" + .to_string() + ) + ); - tracing::trace!("response: {response:#?}"); + assert_eq!( + response.items[3].result_status, + ResultStatusEnumeration::Success + ); + let Some(Operation::CreateKeyPairResponse(_)) = &response.items[3].response_payload else { + panic!("not a create key pair response payload"); + }; Ok(()) } diff --git a/crate/server/src/tests/kmip_messages.rs b/crate/server/src/tests/kmip_messages.rs index 41ff2f56b..f96f50aa3 100644 --- a/crate/server/src/tests/kmip_messages.rs +++ b/crate/server/src/tests/kmip_messages.rs @@ -28,31 +28,13 @@ async fn test_kmip_messages() -> KResult<()> { // prepare and send the single message let items = vec![ - MessageBatchItem { - operation: OperationEnumeration::CreateKeyPair, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::CreateKeyPair(ec_create_request), - message_extension: None, - }, - MessageBatchItem { - operation: OperationEnumeration::Locate, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::Locate(Locate::default()), - message_extension: None, - }, - MessageBatchItem { - operation: OperationEnumeration::Decrypt, - ephemeral: None, - unique_batch_item_id: None, - request_payload: Operation::Decrypt(Decrypt { - unique_identifier: Some("id_12345".to_string()), - data: Some(b"decrypted_data".to_vec()), - ..Default::default() - }), - message_extension: None, - }, + MessageBatchItem::new(Operation::CreateKeyPair(ec_create_request)), + MessageBatchItem::new(Operation::Locate(Locate::default())), + MessageBatchItem::new(Operation::Decrypt(Decrypt { + unique_identifier: Some("id_12345".to_string()), + data: Some(b"decrypted_data".to_vec()), + ..Default::default() + })), ]; let message_request = Message { header: MessageHeader { @@ -61,21 +43,16 @@ async fn test_kmip_messages() -> KResult<()> { protocol_version_minor: 0, }, maximum_response_size: Some(9999), + // wrong number of items but it is only checked + // when TTLV-serialization is done batch_count: 1, - client_correlation_value: None, - server_correlation_value: None, - asynchronous_indicator: None, - attestation_capable_indicator: None, - attestation_type: None, - authentication: None, - batch_error_continuation_option: None, - batch_order_option: None, - timestamp: None, + ..Default::default() }, items, }; let response = kms.message(message_request, owner, None).await?; + assert_eq!(response.header.batch_count, 3); assert_eq!(response.items.len(), 3); // 1. Create keypair From 2144a6f96bf92de820dc294c93cb2faed1a0bc86 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Tue, 31 Oct 2023 11:18:50 +0100 Subject: [PATCH 11/19] fix test --- crate/kmip/src/kmip/ttlv/tests/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index b622a0685..84598aaf5 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -1010,10 +1010,9 @@ pub fn test_message_enforce_enum() { timestamp: 1697201574, ..Default::default() }, - items: vec![ - MessageResponseBatchItem::new(ResultStatusEnumeration::OperationPending), - MessageResponseBatchItem::new(ResultStatusEnumeration::OperationPending), - ], + items: vec![MessageResponseBatchItem::new( + ResultStatusEnumeration::OperationPending, + )], }; assert_eq!( to_ttlv(&res).unwrap_err().to_string(), From c013c1af911d2b103d75e4530ca514ff2375a4fa Mon Sep 17 00:00:00 2001 From: ThibsG Date: Tue, 31 Oct 2023 11:30:38 +0100 Subject: [PATCH 12/19] Fix protocol version check --- crate/kmip/src/kmip/kmip_messages.rs | 4 +-- crate/server/src/tests/curve_25519_tests.rs | 31 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index 7645ed674..b8e27192d 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -57,7 +57,7 @@ impl Serialize for Message { // check version of protocol version defined in the header is // equal or greater than the protocol version of each item's payload. for item in &self.items { - if self.header.protocol_version >= item.request_payload.protocol_version() { + if self.header.protocol_version > item.request_payload.protocol_version() { return Err(ser::Error::custom(format!( "item's protocol version is greater (`{}`) than header's protocol version \ (`{}`)", @@ -374,7 +374,7 @@ impl Serialize for MessageResponse { // equal or greater than the protocol version of each item's payload. for item in &self.items { if let Some(response_payload) = &item.response_payload { - if self.header.protocol_version >= response_payload.protocol_version() { + if self.header.protocol_version > response_payload.protocol_version() { return Err(ser::Error::custom(format!( "item's protocol version is greater (`{}`) than header's protocol version \ (`{}`)", diff --git a/crate/server/src/tests/curve_25519_tests.rs b/crate/server/src/tests/curve_25519_tests.rs index da6cf70a6..0b5f2fdd7 100644 --- a/crate/server/src/tests/curve_25519_tests.rs +++ b/crate/server/src/tests/curve_25519_tests.rs @@ -166,6 +166,36 @@ async fn test_curve_25519_multiple() -> KResult<()> { let kms = Arc::new(KMSServer::instantiate(ServerParams::try_from(&clap_config).await?).await?); let owner = "eyJhbGciOiJSUzI1Ni"; + let request = Message { + header: MessageHeader { + protocol_version: ProtocolVersion { + protocol_version_major: 2, + protocol_version_minor: 1, + }, + maximum_response_size: Some(9999), + batch_count: 2, + ..Default::default() + }, + items: vec![ + MessageBatchItem::new(Operation::CreateKeyPair(ec_create_key_pair_request( + &[] as &[&str], + RecommendedCurve::CURVE25519, + )?)), + MessageBatchItem::new(Operation::Locate( + cosmian_kmip::kmip::kmip_operations::Locate::default(), + )), + ], + }; + println!( + "{:#?}", + cosmian_kmip::kmip::ttlv::serializer::to_ttlv(&request) + ); + let response = kms.message(request, owner, None).await?; + println!( + "{:#?}", + cosmian_kmip::kmip::ttlv::serializer::to_ttlv(&response) + ); + let request = Message { header: MessageHeader { protocol_version: ProtocolVersion { @@ -216,6 +246,7 @@ async fn test_curve_25519_multiple() -> KResult<()> { panic!("not a create key pair response payload"); }; + assert!(response.items[2].response_payload.is_none()); assert_eq!( response.items[2].result_status, ResultStatusEnumeration::OperationFailed From 1cfbbfca7adca283733fd0dbe6c12085fecea7fd Mon Sep 17 00:00:00 2001 From: ThibsG Date: Tue, 31 Oct 2023 15:09:10 +0100 Subject: [PATCH 13/19] Add documentation --- documentation/docs/kmip_2_1/attributes.md | 10 +- documentation/docs/kmip_2_1/messages.md | 205 ++++++++++++++++++++++ 2 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 documentation/docs/kmip_2_1/messages.md diff --git a/documentation/docs/kmip_2_1/attributes.md b/documentation/docs/kmip_2_1/attributes.md index 30f75acf7..8917d3bd8 100644 --- a/documentation/docs/kmip_2_1/attributes.md +++ b/documentation/docs/kmip_2_1/attributes.md @@ -1,10 +1,10 @@ -In [chapter 4](https://docs.oasis-open.org/kmip/kmip-spec/v2.1/cs01/kmip-spec-v2.1-cs01.html#_Toc32239322), the KMIP 2.1 specifications specifies a list of 63 Attributes, mostly made of enumerations and data structures, often nested in each other. Despite this impressive list, and as expected in such a large specification, KMIP allows for extensions to support new cryptographic schemes such as the ones enabled by Cosmian. +In [chapter 4](https://docs.oasis-open.org/kmip/kmip-spec/v2.1/cs01/kmip-spec-v2.1-cs01.html#_Toc32239322), the KMIP 2.1 specification specifies a list of 63 Attributes, mostly made of enumerations and data structures, often nested in each other. Despite this impressive list, and as expected in such a large specification, KMIP allows for extensions to support new cryptographic schemes such as the ones enabled by Cosmian. Extensions in KMIP consist mostly in augmenting enumerations with new values and attributing a specific prefix values, usually `0x8880` to the new variants. The Cosmian extensions are listed below. They can also be viewed in the open sourced [Cloudproof Java library](https://github.com/Cosmian/cloudproof_java/) which implements the KMIP specifications required to interact with the Cosmian KMS server. For instance, the `CryptographicAlgorithm` enumeration extensions can be viewed in [this java source code](https://github.com/Cosmian/cloudproof_java/blob/main/src/main/java/com/cosmian/rest/kmip/types/CryptographicAlgorithm.java) -#### KeyFormatType +#### Key Format Type The Key Format Type attribute is a required attribute of a Cryptographic Object. @@ -17,7 +17,7 @@ CoverCryptSecretKey = 0x8880_000C, CoverCryptPublicKey = 0x8880_000D, ``` -#### CryptographicAlgorithm +#### Cryptographic Algorithm The Cryptographic Parameters attribute is a structure that contains a set of OPTIONAL fields that describe certain cryptographic parameters to be used when performing cryptographic operations using the object. Specific fields MAY pertain only to certain types of Managed Objects. The Cryptographic Parameters attribute of a Certificate object identifies the cryptographic parameters of the public key contained within the Certificate. @@ -43,7 +43,7 @@ CoverCrypt = 0x8880_0004, All keys managed by the Cosmian KMS server are primarily a `KeyMaterial` made of bytes. Some keys, typically those of ABE, also carry information regarding the underlying access policies. This information is carried together with the keys using [VendorAttributes](https://docs.oasis-open.org/kmip/kmip-spec/v2.1/cs01/kmip-spec-v2.1-cs01.html#_Toc32239382) -Typically a vendor attribute is made of 3 values: a `Vendor Identification` - always hardcoded to `cosmian` - and a tuple `Attribute Name`, `Attribute Value`. +Typically a vendor attribute is made of 3 values: a `Vendor Identification` - always hardcoded to `cosmian` - and a tuple `Attribute Name`, `Attribute Value`. The different attribute names can be seen in the [VendorAttributes.java](https://github.com/Cosmian/cloudproof_java/blob/main/src/main/java/com/cosmian/rest/kmip/types/VendorAttribute.java) file of the Cloudproof JavaLib. The attributes names and corresponding values used for a given `KeyFormatType` are as follows: @@ -52,6 +52,6 @@ The attributes names and corresponding values used for a given `KeyFormatType` a - `VENDOR_ATTR_COVER_CRYPT_POLICY = "cover_crypt_policy"` : the JSONified Policy - `AbeUserDecryptionKey`: - - `VENDOR_ATTR_COVER_CRYPT_ACCESS_POLICY = "cover_crypt_access_policy"`: The JSONified boolean Access Policy of the key + - `VENDOR_ATTR_COVER_CRYPT_ACCESS_POLICY = "cover_crypt_access_policy"`: the JSONified boolean Access Policy of the key In addition, the `VENDOR_ATTR_COVER_CRYPT_ATTR = "cover_crypt_attributes"` name is used in Locate requests to identify User Decryption Keys holding certain Policy Attributes. diff --git a/documentation/docs/kmip_2_1/messages.md b/documentation/docs/kmip_2_1/messages.md new file mode 100644 index 000000000..f9285b54e --- /dev/null +++ b/documentation/docs/kmip_2_1/messages.md @@ -0,0 +1,205 @@ +In [chapter 8](https://docs.oasis-open.org/kmip/kmip-spec/v2.1/os/kmip-spec-v2.1-os.html#_Toc57115738), the KMIP 2.1 specification defines Messages functionality, which is the proper way to send/receive multiple requests/responses at once in KMIP (also called bulk mode). + +One could insert multiple requests in a single Message query. +These requests are processed sequentially and simultaneously by the server. +The requests wrapped into the batch items are totally independant. + +For each message request sent, a message response is returned, yielding a result status of the requested operation, and potentially associated result data or error messages. + +### Request and response example + +Two operation requests and their responses are packed into a single Message, with one `CreateKeyPair` operation and one `Locate` operation. + +=== "Message Request" + + ```json + { + "tag": "Message", + "type": "Structure", + "value": [ { + "tag": "Header", + "type": "Structure", + "value": [ { + "tag": "ProtocolVersion", + "type": "Structure", + "value": [ { + "tag": "ProtocolVersionMajor", + "type": "Integer", + "value": 2, + }, { + "tag": "ProtocolVersionMinor", + "type": "Integer", + "value": 1, + }, + ] + }, { + "tag": "MaximumResponseSize", + "type": "Integer", + "value": 9999, + }, { + "tag": "BatchCount", + "type": "Integer", + "value": 2, + } ] + }, { + "tag": "Items", + "type": "Structure", + "value": [ { + "tag": "Items", + "type": "Structure", + "value": [ { + "tag": "Operation", + "type": "Enumeration", + "value": "CreateKeyPair", + }, { + "tag": "RequestPayload", + "type": "Structure", + "value": [ { + "tag": "CommonAttributes", + "type": "Structure", + "value": [ { + "tag": "CryptographicAlgorithm", + "type": "Enumeration", + "value": "ECDH", + }, { + "tag": "CryptographicLength", + "type": "Integer", + "value": 256, + }, { + "tag": "CryptographicDomainParameters", + "type": "Structure", + "value": [ { + "tag": "QLength", + "type": "Integer", + "value": 256, + }, { + "tag": "RecommendedCurve", + "type": "Enumeration", + "value": "CURVE25519", + }, + ], + }, { + "tag": "CryptographicUsageMask", + "type": "Integer", + "value": 2108, + }, { + "tag": "KeyFormatType", + "type": "Enumeration", + "value": "ECPrivateKey", + }, { + "tag": "ObjectType", + "type": "Enumeration", + "value": "PrivateKey", + } ], + } ], + } ], + }, { + "tag": "Items", + "type": "Structure", + "value": [ { + "tag": "Operation", + "type": "Enumeration", + "value": "Locate" + }, { + "tag": "RequestPayload", + "type": "Structure", + "value": [ { + "tag": "Attributes", + "type": "Structure", + "value": [], + } ], + }, + } ], + } ], + } + ``` + +=== "Message Response" + + ```json + { + "tag": "Message", + "type": "Structure", + "value": [ { + "tag": "Header", + "type": "Structure", + "value": [ { + "tag": "ProtocolVersion", + "type": "Structure", + "value": [ { + "tag": "ProtocolVersionMajor", + "type": "Integer", + "value": 2, + }, { + "tag": "ProtocolVersionMinor", + "type": "Integer", + "value": 1, + } ] + }, { + "tag": "Timestamp", + "type": "LongInteger", + "value": 1698748303, + }, { + "tag": "BatchCount", + "type": "Integer", + "value": 2, + } ] + }, { + "tag": "Items", + "type": "Structure", + "value": [ { + "tag": "Items", + "type": "Structure", + "value": [ { + "tag": "Operation", + "type": "Enumeration", + "value": "CreateKeyPair", + }, { + "tag": "ResultStatus", + "type": "Enumeration", + "value": "Success", + }, { + "tag": "ResponsePayload", + "type": "Structure", + "value": [ { + "tag": "PrivateKeyUniqueIdentifier", + "type": "TextString", + "value": "7c293777-794f-41fa-95f2-4f0a3bc730b8", + }, { + "tag": "PublicKeyUniqueIdentifier", + "type": "TextString", + "value": "042c8439-16f8-406f-b425-c18a69fb56a7", + } ], + } ], + }, { + "tag": "Items", + "type": "Structure", + "value": [ { + "tag": "Operation", + "type": "Enumeration", + "value": "Locate" + }, { + "tag": "ResponsePayload", + "type": "Structure", + "value": [ { + "tag": "LocatedItems", + "type": "Integer", + "value": 2, + }, { + "tag": "UniqueIdentifier", + "type": "Structure", + "value": [ { + "tag": "PrivateKeyUniqueIdentifier", + "type": "TextString", + "value": "7c293777-794f-41fa-95f2-4f0a3bc730b8", + }, { + "tag": "PublicKeyUniqueIdentifier", + "type": "TextString", + "value": "042c8439-16f8-406f-b425-c18a69fb56a7", + } ], + } ], + } ], + } ], + } ], + } + ``` From 895a6cb89b7883ea5584bd45cecef26c96ab58bd Mon Sep 17 00:00:00 2001 From: Pauline <59414053+phochard@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:25:28 +0100 Subject: [PATCH 14/19] Docker sgx kms (#79) * add ci job --- .github/workflows/ci.yml | 3 + .github/workflows/docker_kms_sgx.yml | 67 +++++++++++ .github/workflows/docker_kms_sgx_tests.yml | 63 +++++++++++ ci/sgx/Dockerfile.sgx | 125 +++++++++++++++++++++ ci/sgx/Makefile | 47 ++++++++ ci/sgx/build_and_run.sh | 27 +++++ ci/sgx/kms-test-ci.json | 6 + ci/sgx/kms.manifest.template | 76 +++++++++++++ 8 files changed, 414 insertions(+) create mode 100644 .github/workflows/docker_kms_sgx.yml create mode 100644 .github/workflows/docker_kms_sgx_tests.yml create mode 100644 ci/sgx/Dockerfile.sgx create mode 100644 ci/sgx/Makefile create mode 100755 ci/sgx/build_and_run.sh create mode 100644 ci/sgx/kms-test-ci.json create mode 100644 ci/sgx/kms.manifest.template diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99a8d49be..d301d6678 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,9 @@ jobs: python_and_docker: uses: ./.github/workflows/python_and_docker.yml + sgx_docker: + uses: ./.github/workflows/docker_kms_sgx.yml + ############################################################################## ### Releases ############################################################################## diff --git a/.github/workflows/docker_kms_sgx.yml b/.github/workflows/docker_kms_sgx.yml new file mode 100644 index 000000000..2c57fb1f7 --- /dev/null +++ b/.github/workflows/docker_kms_sgx.yml @@ -0,0 +1,67 @@ +--- +name: Docker KMS SGX + +on: + workflow_call: + +env: + REGISTRY: ghcr.io + REGISTRY_IMAGE: ghcr.io/cosmian/enclave-kms-insecure + +jobs: + build-and-push-image: + runs-on: [self-hosted, sgx] + defaults: + run: + working-directory: ci/sgx/ + container: docker:19.03.15 + + steps: + + - uses: actions/checkout@v1 + + - name: Login to GitHub Packages + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY_IMAGE }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + # Specify staging features to use Let's Encrypt staging environment in order to be able to request more than 5 certificates by week + # Documentation : https://letsencrypt.org/docs/duplicate-certificate-limit/ + - name: Build and tag docker container + uses: docker/build-push-action@v3 + with: + file: ./ci/sgx/Dockerfile.sgx + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + network: host + build-args: | + FEATURES=--features staging + + outputs: + image-tag: ${{ steps.meta.outputs.version }} + + tests: + needs: + - build-and-push-image + uses: ./.github/workflows/docker_kms_sgx_tests.yml + secrets: inherit + with: + kms-version: ${{ needs.build-and-push-image.outputs.image-tag }} \ No newline at end of file diff --git a/.github/workflows/docker_kms_sgx_tests.yml b/.github/workflows/docker_kms_sgx_tests.yml new file mode 100644 index 000000000..852ccf6e4 --- /dev/null +++ b/.github/workflows/docker_kms_sgx_tests.yml @@ -0,0 +1,63 @@ +--- +name: KMS SGX tests + +env: + KMS_USE_BOOTSTRAP_SERVER: true + KMS_USE_CERTBOT: true + KMS_CERTBOT_HOSTNAME: kms.sgx.ci.cosmian.dev + KMS_CERTBOT_EMAIL: tech@cosmian.com + KMS_CERTBOT_USE_TEE_KEY: abcdef0123456789 + +on: + workflow_call: + inputs: + kms-version: + required: true + type: string + +jobs: + + run_and_test: + + services: + kms: + image: ghcr.io/cosmian/enclave-kms-insecure:${{ inputs.kms-version }} + + runs-on: [self-hosted, sgx] + steps: + - name: Docker start container + run: | + docker run \ + --device /dev/sgx_enclave \ + --device /dev/sgx_provision \ + -e KMS_USE_BOOTSTRAP_SERVER="${{ env.KMS_USE_BOOTSTRAP_SERVER }}" \ + -e KMS_USE_CERTBOT="${{ env.KMS_USE_CERTBOT }}" \ + -e KMS_CERTBOT_HOSTNAME="${{ env.KMS_CERTBOT_HOSTNAME }}" \ + -e KMS_CERTBOT_EMAIL="${{ env.KMS_CERTBOT_EMAIL }}" \ + -e KMS_CERTBOT_USE_TEE_KEY="${{ env.KMS_CERTBOT_USE_TEE_KEY }}" \ + -v /var/run/aesmd:/var/run/aesmd/ \ + -v /opt/cosmian-internal:/opt/cosmian-internal \ + -p 80:80 \ + -p 9998:9998 \ + -d --rm --name enclave-kms-insecure ghcr.io/cosmian/enclave-kms-insecure:${{ inputs.kms-version }} + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + + - name: Install ckms + run: cargo install --locked --path crate/cli + + - name: Bootstrap + run: | + KMS_CLI_CONF=ci/sgx/kms-test-ci.json ckms bootstrap-start --sqlite-path private_data/ --database-type sqlite + sleep 20 + + - name: Verify + run: KMS_CLI_CONF=ci/sgx/kms-test-ci.json ckms verify + + - name: Symmetric key create + run: KMS_CLI_CONF=ci/sgx/kms-test-ci.json ckms sym keys create + + - name: Docker stop container + if: success() || failure() + run: docker stop enclave-kms-insecure diff --git a/ci/sgx/Dockerfile.sgx b/ci/sgx/Dockerfile.sgx new file mode 100644 index 000000000..43ef7661a --- /dev/null +++ b/ci/sgx/Dockerfile.sgx @@ -0,0 +1,125 @@ +FROM ubuntu:22.04 as minimal-sgx + +USER root +ENV DEBIAN_FRONTEND=noninteractive +ENV TS=Etc/UTC +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 + +WORKDIR /root + +RUN echo 'APT::Install-Suggests "0";' >> /etc/apt/apt.conf.d/00-docker +RUN echo 'APT::Install-Recommends "0";' >> /etc/apt/apt.conf.d/00-docker +RUN apt-get update && apt-get install --no-install-recommends -qq -y \ + build-essential \ + protobuf-compiler \ + libprotobuf-dev \ + libprotobuf-c-dev \ + python3 \ + gnupg \ + ca-certificates \ + curl \ + libsodium-dev \ + tzdata \ + && apt-get -y -q upgrade \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Gramine APT repository +RUN curl -fsSLo /usr/share/keyrings/gramine-keyring.gpg https://packages.gramineproject.io/gramine-keyring.gpg && \ + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/gramine-keyring.gpg] https://packages.gramineproject.io/ jammy main" \ + | tee /etc/apt/sources.list.d/gramine.list + +# Intel SGX APT repository +RUN curl -fsSLo /usr/share/keyrings/intel-sgx-deb.asc https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key && \ + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-sgx-deb.asc] https://download.01.org/intel-sgx/sgx_repo/ubuntu jammy main" \ + | tee /etc/apt/sources.list.d/intel-sgx.list + +# Install Intel SGX dependencies and Gramine +RUN apt-get update && apt-get install -y \ + libsgx-launch \ + libsgx-urts \ + libsgx-quote-ex \ + libsgx-epid \ + libsgx-dcap-ql \ + libsgx-dcap-quote-verify \ + linux-base-sgx \ + libsgx-dcap-default-qpl \ + sgx-aesm-service \ + libsgx-aesm-quote-ex-plugin \ + gramine && \ + rm -rf /var/lib/apt/lists/* + +# SGX SDK is installed in /opt/intel directory. +WORKDIR /opt/intel + +ARG SGX_SDK_VERSION=2.21 +ARG SGX_SDK_INSTALLER=sgx_linux_x64_sdk_2.21.100.1.bin + +# Install SGX SDK +RUN curl -fsSLo $SGX_SDK_INSTALLER https://download.01.org/intel-sgx/sgx-linux/$SGX_SDK_VERSION/distro/ubuntu22.04-server/$SGX_SDK_INSTALLER \ + && chmod +x $SGX_SDK_INSTALLER \ + && echo "yes" | ./$SGX_SDK_INSTALLER \ + && rm $SGX_SDK_INSTALLER + +# +# Minimal Rust image +# +FROM ubuntu:22.04 as minimal-rust + +USER root +ENV DEBIAN_FRONTEND=noninteractive +ENV TS=Etc/UTC +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 + +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 + +COPY . /root + +ARG FEATURES + +RUN /root/.cargo/bin/cargo build --release --no-default-features ${FEATURES} + +# +# Minimal kms +# +FROM minimal-sgx as kms + +USER root + +ENV DEBIAN_FRONTEND=noninteractive +ENV TS=Etc/UTC +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 +ENV KMS_USE_BOOTSTRAP_SERVER =$KMS_USE_BOOTSTRAP_SERVER +ENV KMS_USE_CERTBOT=$KMS_USE_CERTBOT +ENV KMS_CERTBOT_HOSTNAME=$KMS_CERTBOT_HOSTNAME +ENV KMS_CERTBOT_EMAIL=$KMS_CERTBOT_EMAIL +ENV KMS_CERTBOT_USE_TEE_KEY=$KMS_CERTBOT_USE_TEE_KEY + +WORKDIR /root + +RUN mkdir -p scripts +# private_data, public_data and shared_data are supposed to be given as parameters of the docker run + +COPY --from=minimal-rust /root/target/release/cosmian_kms_server scripts/server + +COPY ci/sgx/build_and_run.sh ci/sgx/Makefile ci/sgx/kms.manifest.template /root/ + +ENTRYPOINT ["./build_and_run.sh"] diff --git a/ci/sgx/Makefile b/ci/sgx/Makefile new file mode 100644 index 000000000..72f3528c7 --- /dev/null +++ b/ci/sgx/Makefile @@ -0,0 +1,47 @@ +ARCH_LIBDIR ?= /lib/$(shell $(CC) -dumpmachine) +SGX_SIGNER_KEY ?= /opt/cosmian-internal/cosmian-signer-key.pem +SGX_SIGNER_PUBLIC_KEY = cosmian-signer-key.pub + +ifeq ($(DEBUG),1) +GRAMINE_LOG_LEVEL = debug +# can also use "all" for having something like `strace` +else +GRAMINE_LOG_LEVEL = error +endif + +.PHONY: all +all: kms.manifest +ifeq ($(SGX),1) +all: kms.manifest.sgx kms.sig +endif + +kms.manifest: kms.manifest.template + gramine-manifest \ + -Dlog_level=$(GRAMINE_LOG_LEVEL) \ + -Darch_libdir=$(ARCH_LIBDIR) \ + -Dentrypoint_abs="$(PWD)/scripts" \ + -Dentrypoint="/scripts" \ + -Dsgx_signer_public_key=$(SGX_SIGNER_PUBLIC_KEY) \ + -Dkms_use_bootstrap_server=$(KMS_USE_BOOTSTRAP_SERVER) \ + -Dkms_use_cerbot=$(KMS_USE_CERTBOT) \ + -Dkms_cerbot_hostname=$(KMS_CERTBOT_HOSTNAME) \ + -Dkms_cerbot_email=$(KMS_CERTBOT_EMAIL) \ + -Dkms_cerbot_use_tee_key=$(KMS_CERTBOT_USE_TEE_KEY) \ + -Dkms_domain="$(KMS_DOMAIN)" \ + $< >$@ + +kms.manifest.sgx: kms.manifest + gramine-sgx-sign \ + --key $(SGX_SIGNER_KEY) \ + --manifest kms.manifest \ + --output $@ + openssl rsa -in $(SGX_SIGNER_KEY) -pubout -out public_data/$(SGX_SIGNER_PUBLIC_KEY) + +kms.sig: kms.manifest.sgx + +.PHONY: clean +clean: + $(RM) *.manifest *.manifest.sgx *.sig OUTPUT scripts/testdir/* + +.PHONY: distclean +distclean: clean diff --git a/ci/sgx/build_and_run.sh b/ci/sgx/build_and_run.sh new file mode 100755 index 000000000..59eed7be1 --- /dev/null +++ b/ci/sgx/build_and_run.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SGX_SIGNER_KEY="/opt/cosmian-internal/cosmian-signer-key.pem" + +mkdir -p /root/public_data/ +mkdir -p /root/private_data/ + +if [ $# -eq 0 ]; then + if ! [ -e "/dev/sgx_enclave" ]; then + echo "You are not running on an sgx machine" + echo "If you want to compute the MR_ENCLAVE, re-run with --emulation parameter" + exit 1 + fi + make SGX=1 SGX_SIGNER_KEY=$SGX_SIGNER_KEY KMS_USE_BOOTSTRAP_SERVER="${KMS_USE_BOOTSTRAP_SERVER}" KMS_USE_CERTBOT="${KMS_USE_CERTBOT}" KMS_CERTBOT_HOSTNAME="${KMS_CERTBOT_HOSTNAME}" KMS_CERTBOT_EMAIL="${KMS_CERTBOT_EMAIL}" KMS_CERTBOT_USE_TEE_KEY="${KMS_CERTBOT_USE_TEE_KEY}" && gramine-sgx ./kms +elif [ $# -eq 1 ] && [ "$1" = "--emulation" ]; then + mkdir /opt/cosmian-internal + # Generate a dummy key. `MR_ENCLAVE` does not depend on it. + openssl genrsa -3 -out $SGX_SIGNER_KEY 3072 + # Compile but don't start + make SGX=1 SGX_SIGNER_KEY=$SGX_SIGNER_KEY KMS_USE_BOOTSTRAP_SERVER="${KMS_USE_BOOTSTRAP_SERVER}" KMS_USE_CERTBOT="${KMS_USE_CERTBOT}" KMS_CERTBOT_HOSTNAME="${KMS_CERTBOT_HOSTNAME}" KMS_CERTBOT_EMAIL="${KMS_CERTBOT_EMAIL}" KMS_CERTBOT_USE_TEE_KEY="${KMS_CERTBOT_USE_TEE_KEY}" + # Note: if `public_data` is mounted inside the docker, the user can read `kms.manifest.kms` from outside the docker +else + echo "Usage: $0 [--emulation]" + echo "" + echo "Using --emulation enables you to get the MR_ENCLAVE of the enclave server" + echo "You don't need to use an SGX machine to use --emulation param" +fi diff --git a/ci/sgx/kms-test-ci.json b/ci/sgx/kms-test-ci.json new file mode 100644 index 000000000..af9889b32 --- /dev/null +++ b/ci/sgx/kms-test-ci.json @@ -0,0 +1,6 @@ +{ + "kms_server_url": "https://kms.sgx.ci.cosmian.dev:9998", + "kms_access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjVVU1FrSVlULW9QMWZrcjQtNnRrciJ9.eyJuaWNrbmFtZSI6InRlY2giLCJuYW1lIjoidGVjaEBjb3NtaWFuLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci81MmZiMzFjOGNjYWQzNDU4MTIzZDRmYWQxNDA4NTRjZj9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRnRlLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDIzLTA1LTMwVDA5OjMxOjExLjM4NloiLCJlbWFpbCI6InRlY2hAY29zbWlhbi5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOi8va21zLWNvc21pYW4uZXUuYXV0aDAuY29tLyIsImF1ZCI6IkszaXhldXhuVDVrM0Roa0tocWhiMXpYbjlFNjJGRXdJIiwiaWF0IjoxNjg1NDM5MDc0LCJleHAiOjE2ODU0NzUwNzQsInN1YiI6ImF1dGgwfDYzZDNkM2VhOTNmZjE2ANDJjNzdkZjkyOCIsInNpZCI6ImJnVUNuTTNBRjVxMlpaVHFxMTZwclBCMi11Z0NNaUNPIiwibm9uY2UiOiJVRUZWTlZWeVluWTVUbHBwWjJScGNqSmtVMEZ4TmxkUFEwc3dTVGMwWHpaV2RVVmtkVnBEVGxSMldnPT0ifQ.HmU9fFwZ-JjJVlSy_PTei3ys0upeWQbWWiESmKBtRSClGnAXJNCpwuP4Jw7fgKn-8IBf-PYmP1_54u2Rw3RcJFVl7EblVoGMghYxVq5hViGpd00st3VwZmyCwOUz2CE5RBnBAoES4C8xA3zWg6oau0xjFQbC3jNU20eyFYMDewXA8UXCHQrEiQ56ylqSbyqlBbQIWbmOO4m5w2WDkx0bVyyJ893JfIJr_NANEQMJITYo8Mp_iHCyKp7llsfgCt07xN8ZqnsrMsJ15zC1n50bHGrTQisxURS1dpuFXF1hfrxhzogxYMX8CEISjsFgROjPY84GRMmvpYZfyaJbDDql3A", + "kms_database_secret": "eyJncm91cF9pZCI6MTY5NjE5MjAzMzQ1MDY0MDQxNjY1ODIyNzgwNjczNDY1ODkyNjcyLCJrZXkiOiJhN2EyNWY2YWUxMzExODMyYTBiYmRkZDNjMjk3ZjhjYTAxZTg4OWEzNzFlNjNhZmMyNjU4MDc2NzE1MmQ4YTA2In0=", + "accept_invalid_certs": true +} diff --git a/ci/sgx/kms.manifest.template b/ci/sgx/kms.manifest.template new file mode 100644 index 000000000..8bb93f139 --- /dev/null +++ b/ci/sgx/kms.manifest.template @@ -0,0 +1,76 @@ +loader.entrypoint = "file:{{ gramine.libos }}" +libos.entrypoint = "{{ entrypoint }}/server" + +# We don't use argv, therefore we have to set argv[0] +loader.argv0_override = "kms_server" +# loader.argv_src_file = "file:scripts/args" + +loader.log_level = "{{ log_level }}" + +# Currently required for Tokio (eventfd is done by the host not the enclave, so less secure) +sys.insecure__allow_eventfd = true +# This specifies the stack size of each thread in each Gramine process +# Note: if you remove that, the KMS won't work. Errors you can get: +# - "thread panicked while processing panic. aborting." +# - "The futex facility returned an unexpected error code." +sys.stack.size = "1G" + +sys.enable_extra_runtime_domain_names_conf = true + +loader.env.LD_LIBRARY_PATH = "/lib:/lib64:{{ arch_libdir }}:/usr/{{ arch_libdir }}" +loader.env.KMS_SGX_PUBLIC_SIGNER_KEY_FILENAME = "{{ sgx_signer_public_key }}" +loader.env.KMS_TEE_DIR_PATH = "/public_data" +loader.env.KMS_ROOT_DATA_PATH = "/private_data" +loader.env.RUST_BACKTRACE="0" +loader.env.KMS_USE_BOOTSTRAP_SERVER = "{{ kms_use_bootstrap_server }}" +loader.env.KMS_USE_CERTBOT = "{{ kms_use_cerbot }}" +loader.env.KMS_CERTBOT_HOSTNAME = "{{ kms_cerbot_hostname }}" +loader.env.KMS_CERTBOT_EMAIL = "{{ kms_cerbot_email }}" +loader.env.KMS_CERTBOT_USE_TEE_KEY = "{{ kms_cerbot_use_tee_key }}" + + + +sgx.nonpie_binary = true +sgx.remote_attestation = 'dcap' +sgx.enclave_size = "16G" +sgx.max_threads = 256 +sgx.debug = false + +sgx.isvprodid = 1 +sgx.isvsvn = 10 + +# File to mount into the enclave +fs.mounts = [ + { type = "chroot", uri = "file:{{ gramine.runtimedir() }}", path = "/lib" }, + { type = "chroot", uri = "file:{{ arch_libdir }}", path = "{{ arch_libdir }}" }, + { type = "chroot", uri = "file:/usr/{{ arch_libdir }}", path = "/usr/{{ arch_libdir }}" }, + { type = "chroot", uri = "file:/etc", path = "/etc" }, + { type = "chroot", uri = "file:{{ entrypoint_abs }}", path = "{{ entrypoint }}" }, + { type = "tmpfs", path = "/tmp" }, + { type = "encrypted", uri = "file:private_data", path = "/private_data", key_name = "_sgx_mrenclave" }, +] + +# Public path +sgx.allowed_files = [ + "file:public_data" +] + + +# Files to hash at build time and allowed to be accessed in runtime if hashes match +sgx.trusted_files = [ + "file:{{ entrypoint_abs }}/server", + "file:{{ gramine.libos }}", + "file:{{ gramine.runtimedir() }}/", + "file:{{ arch_libdir }}/", + "file:/etc/nsswitch.conf", + "file:/etc/group", + "file:/etc/passwd", + "file:/etc/host.conf", + "file:/etc/gai.conf", + "file:/etc/resolv.conf", + "file:/etc/localtime", + "file:/etc/ld.so.cache", + "file:{{ arch_libdir }}/libsodium.so.23", + "file:/usr/lib/ssl/certs/", + "file:/etc/ssl/certs/ca-certificates.crt", +] From 05c16b4cc0af395a2f3cbc3086a9a224e1c6fc30 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 2 Nov 2023 14:20:24 +0100 Subject: [PATCH 15/19] Enable doc for KMIP Messages --- documentation/mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 90d73d363..8695bed1a 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -77,4 +77,5 @@ nav: - Objects: kmip_2_1/objects.md - Attributes: kmip_2_1/attributes.md - Operations: kmip_2_1/operations.md + - Messages: kmip_2_1/messages.md # - SaaS: saas.md From d8e1dcd4d95230d6496919340fab05044952918f Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 2 Nov 2023 14:32:11 +0100 Subject: [PATCH 16/19] Fix links for SGX doc --- crate/cli/README.md | 4 ++-- crate/server/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crate/cli/README.md b/crate/cli/README.md index 4bd7522b7..c24b775a5 100644 --- a/crate/cli/README.md +++ b/crate/cli/README.md @@ -154,9 +154,9 @@ cargo build --bin cosmian_kms_cli cargo test -p cosmian_kms_cli ``` -A kms server is started by the test. Make sure, you don't start another one by yourself. +A KMS server is started by the test. Make sure, you don't start another one by yourself. -You can also test using a remote kms running inside an enclave. First, generate and start a docker as described in [README.md](../../enclave/README.md). +You can also test using a remote KMS running inside an enclave. First, generate and start a docker as described in [README.md](../../sgx/README.md). Then: diff --git a/crate/server/README.md b/crate/server/README.md index 9f85f6045..afabfec56 100644 --- a/crate/server/README.md +++ b/crate/server/README.md @@ -139,7 +139,7 @@ At _Cosmian_, for production, the architecture and the security rely on secure e ![Production](./resources/production.drawio.svg) -To set up the enclave to run the kms server, please refer to the dedicated [Readme](../../enclave/server/README.md) +To set up the SGX enclave to run the KMS server, please refer to the dedicated [Readme](../../sgx/README.md) **Mandatory**: all KMS source codes are fully open source. From b30222a63d54613a3265eb372a4a831cbd7633c5 Mon Sep 17 00:00:00 2001 From: Thibs Date: Fri, 10 Nov 2023 06:23:44 +0100 Subject: [PATCH 17/19] fix: migrate to num-bigint-dig for bigint (#85) --- Cargo.lock | 8 +++--- Cargo.toml | 6 ++++- crate/kmip/Cargo.toml | 2 +- crate/kmip/src/kmip/kmip_data_structures.rs | 2 +- crate/kmip/src/kmip/kmip_objects.rs | 2 +- crate/kmip/src/kmip/ttlv/deserializer.rs | 6 ++--- crate/kmip/src/kmip/ttlv/mod.rs | 27 +++++++++++++++++-- crate/kmip/src/kmip/ttlv/serializer.rs | 2 +- crate/kmip/src/kmip/ttlv/tests/mod.rs | 8 ++++-- crate/server/Cargo.toml | 1 + crate/server/src/core/operations/import.rs | 3 ++- crate/utils/Cargo.toml | 4 +-- .../utils/src/crypto/curve_25519/operation.rs | 2 +- 13 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bab25da42..c6051f817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,7 +1125,7 @@ dependencies = [ "cloudproof", "cosmian_logger", "hex", - "num-bigint", + "num-bigint-dig", "serde", "serde_json", "strum", @@ -1243,6 +1243,7 @@ dependencies = [ "josekit", "lazy_static", "mime", + "num-bigint-dig", "openssl", "ratls", "rawsql", @@ -1270,7 +1271,7 @@ dependencies = [ "cloudproof", "cosmian_crypto_core", "cosmian_kmip", - "num-bigint", + "num-bigint-dig", "openssl", "serde", "serde_json", @@ -2596,8 +2597,6 @@ dependencies = [ "autocfg", "num-integer", "num-traits", - "rand", - "serde", ] [[package]] @@ -2613,6 +2612,7 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index e07950db1..f5f1c913a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,11 @@ hex = "0.4" http = "0.2" josekit = "0.8.3" native-tls = "0.2" -num-bigint = { version = "0.4", features = ["rand", "serde"] } +num-bigint-dig = { version = "0.8", default-features = false, features = [ + "std", + "rand", + "serde", +] } openssl = { version = "0.10", features = ["vendored"] } ratls = { git = "https://github.com/Cosmian/tee-tools", branch = "get_quote" } redis = { version = "0.23", features = [ diff --git a/crate/kmip/Cargo.toml b/crate/kmip/Cargo.toml index 898f4cf67..3da07de4d 100644 --- a/crate/kmip/Cargo.toml +++ b/crate/kmip/Cargo.toml @@ -9,7 +9,7 @@ bitflags = { workspace = true } chrono = { workspace = true } cloudproof = { workspace = true } hex = { workspace = true } -num-bigint = { workspace = true } +num-bigint-dig = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } strum = { workspace = true } diff --git a/crate/kmip/src/kmip/kmip_data_structures.rs b/crate/kmip/src/kmip/kmip_data_structures.rs index b060156ee..9335a58a2 100644 --- a/crate/kmip/src/kmip/kmip_data_structures.rs +++ b/crate/kmip/src/kmip/kmip_data_structures.rs @@ -1,6 +1,6 @@ use std::clone::Clone; -use num_bigint::BigUint; +use num_bigint_dig::BigUint; use serde::{ser::SerializeStruct, Deserialize, Serialize}; use super::kmip_types::{LinkType, LinkedObjectIdentifier}; diff --git a/crate/kmip/src/kmip/kmip_objects.rs b/crate/kmip/src/kmip/kmip_objects.rs index fbb5e38e8..a3bb54ca4 100644 --- a/crate/kmip/src/kmip/kmip_objects.rs +++ b/crate/kmip/src/kmip/kmip_objects.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use num_bigint::BigUint; +use num_bigint_dig::BigUint; use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; diff --git a/crate/kmip/src/kmip/ttlv/deserializer.rs b/crate/kmip/src/kmip/ttlv/deserializer.rs index 72ac2107a..fd6541579 100644 --- a/crate/kmip/src/kmip/ttlv/deserializer.rs +++ b/crate/kmip/src/kmip/ttlv/deserializer.rs @@ -7,7 +7,7 @@ use serde::{ use time::format_description::well_known::Rfc3339; use tracing::trace; -use crate::kmip::ttlv::{error::TtlvError, TTLVEnumeration, TTLValue, TTLV}; +use crate::kmip::ttlv::{error::TtlvError, to_u32_digits, TTLVEnumeration, TTLValue, TTLV}; type Result = std::result::Result; @@ -150,7 +150,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> { }), TTLValue::BigInteger(e) => visitor.visit_seq(TtlvDeserializer { deserializing: Deserializing::BigInt, - inputs: Inputs::BigInt(e.to_u32_digits()), + inputs: Inputs::BigInt(to_u32_digits(e)), // start at 0 because the Visit Map is going to increment first index: 0, }), @@ -475,7 +475,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut TtlvDeserializer<'de> { { visitor.visit_seq(TtlvDeserializer { deserializing: Deserializing::BigInt, - inputs: Inputs::BigInt(big_int.to_u32_digits()), + inputs: Inputs::BigInt(to_u32_digits(big_int)), index: 0, }) } diff --git a/crate/kmip/src/kmip/ttlv/mod.rs b/crate/kmip/src/kmip/ttlv/mod.rs index 94ef431c5..b929101c5 100644 --- a/crate/kmip/src/kmip/ttlv/mod.rs +++ b/crate/kmip/src/kmip/ttlv/mod.rs @@ -8,7 +8,7 @@ mod tests; use core::fmt; use std::convert::TryInto; -use num_bigint::BigUint; +use num_bigint_dig::BigUint; use serde::{ de::{self, MapAccess, Visitor}, ser::{self, SerializeStruct, Serializer}, @@ -366,7 +366,10 @@ impl<'de> Deserialize<'de> for TTLV { 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()); + // build the `BigUint` using the `Vec` representation. + let v = BigUint::from_slice(&to_u32_digits( + &BigUint::from_bytes_be(bytes.as_slice()), + )); TTLValue::BigInteger(v) } "Enumeration" => { @@ -445,3 +448,23 @@ impl<'de> Deserialize<'de> for TTLV { deserializer.deserialize_struct("TTLV", FIELDS, TTLVVisitor) } } + +/// Convert a `BigUint` into a `Vec`. +/// +/// Get the `Vec` representation from the `BigUint`, +/// and chunk it 4-by-4 bytes to create the multiple +/// `u32` bytes needed for `Vec` representation. +/// +/// This conversion is done manually, as `num-bigint-dig` +/// doesn't provide such conversion. +pub fn to_u32_digits(big_int: &BigUint) -> Vec { + big_int + .to_bytes_be() + .chunks(4) + .map(|group_of_4_bytes| { + group_of_4_bytes + .iter() + .fold(0, |acc, byte| (acc << 8) + (*byte as u32)) + }) + .collect::>() +} diff --git a/crate/kmip/src/kmip/ttlv/serializer.rs b/crate/kmip/src/kmip/ttlv/serializer.rs index 103f08790..add7d5741 100644 --- a/crate/kmip/src/kmip/ttlv/serializer.rs +++ b/crate/kmip/src/kmip/ttlv/serializer.rs @@ -1,6 +1,6 @@ use std::convert::TryInto; -use num_bigint::BigUint; +use num_bigint_dig::BigUint; use serde::{ ser::{self, Error, SerializeSeq}, Serialize, diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index 84598aaf5..d201a94ca 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -1,5 +1,5 @@ use cosmian_logger::log_utils::log_init; -use num_bigint::BigUint; +use num_bigint_dig::BigUint; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -278,7 +278,11 @@ fn test_ser_big_int() { TTLV { tag: "BigInt", value: BigInteger( - 1229782938265199138, + BigUint { + data: [ + 1229782938265199138, + ], + }, ), }, ], diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index b960f406b..caa58457d 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -50,6 +50,7 @@ http = { workspace = true } josekit = { workspace = true } lazy_static = "1.4" mime = "0.3" +num-bigint-dig = { workspace = true } openssl = { workspace = true } ratls = { workspace = true } tee_attestation = { workspace = true } diff --git a/crate/server/src/core/operations/import.rs b/crate/server/src/core/operations/import.rs index 5bf5df831..0b0e802fd 100644 --- a/crate/server/src/core/operations/import.rs +++ b/crate/server/src/core/operations/import.rs @@ -15,6 +15,7 @@ use cosmian_kms_utils::{ crypto::curve_25519::operation::Q_LENGTH_BITS, tagging::{check_user_tags, get_tags}, }; +use num_bigint_dig::BigUint; use openssl::{ ec::{EcKey, PointConversionForm}, nid::Nid, @@ -22,7 +23,7 @@ use openssl::{ sha::Sha1, }; use tracing::{debug, trace, warn}; -use x509_parser::{num_bigint::BigUint, parse_x509_certificate, prelude::parse_x509_pem}; +use x509_parser::{parse_x509_certificate, prelude::parse_x509_pem}; use super::wrapping::unwrap_key; use crate::{ diff --git a/crate/utils/Cargo.toml b/crate/utils/Cargo.toml index b2051491e..2bc1d59b1 100644 --- a/crate/utils/Cargo.toml +++ b/crate/utils/Cargo.toml @@ -11,9 +11,9 @@ curve25519 = [] [dependencies] argon2 = "0.5.0" cloudproof = { workspace = true } -cosmian_crypto_core = "9.3.0" # Must use `crypto_core` from `cloudproof_rust` reexport +cosmian_crypto_core = "9.3.0" # Must use `crypto_core` from `cloudproof_rust` reexport cosmian_kmip = { path = "../kmip" } -num-bigint = { workspace = true } +num-bigint-dig = { workspace = true } openssl = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crate/utils/src/crypto/curve_25519/operation.rs b/crate/utils/src/crypto/curve_25519/operation.rs index 3fd60a942..71674377f 100644 --- a/crate/utils/src/crypto/curve_25519/operation.rs +++ b/crate/utils/src/crypto/curve_25519/operation.rs @@ -14,7 +14,7 @@ use cosmian_kmip::{ }, }, }; -use num_bigint::BigUint; +use num_bigint_dig::BigUint; use crate::KeyPair; From cc369c9906deaf46c5a597f72a32425508bf119f Mon Sep 17 00:00:00 2001 From: Hugo Rosenkranz-Costa Date: Fri, 10 Nov 2023 10:25:13 +0100 Subject: [PATCH 18/19] feat: update Covercrypt version to support Policy V2 (#63) * feat: update Covercrypt version to support Policy V2 * fix: integration bulk tests * ci: bump js and java branches --------- Co-authored-by: Manuthor --- .github/workflows/python_and_docker.yml | 5 +- .github/workflows/python_tests.yml | 25 +- Cargo.lock | 17 +- Cargo.toml | 2 +- crate/cli/src/actions/cover_crypt/policy.rs | 58 ++-- .../actions/cover_crypt/rotate_attributes.rs | 7 +- crate/kmip/src/kmip/kmip_messages.rs | 3 + crate/kmip/src/kmip/kmip_operations.rs | 3 + crate/kmip/src/kmip/ttlv/mod.rs | 3 +- crate/kmip/src/kmip/ttlv/tests/mod.rs | 14 +- crate/pyo3/python/cosmian_kms/__init__.pyi | 113 +++++++- crate/pyo3/python/requirements.txt | 6 +- crate/pyo3/python/scripts/test_kms.py | 107 +++++++- crate/pyo3/src/py_kms_client.rs | 249 +++++++++++++++--- crate/server/src/core/cover_crypt/mod.rs | 4 +- .../{rotate.rs => update_policy.rs} | 169 +++++++----- .../src/core/operations/rekey_keypair.rs | 27 +- .../cover_crypt_tests/integration_tests.rs | 170 +++++++++++- .../integration_tests_bulk.rs | 8 +- .../integration_tests_tags.rs | 22 +- .../src/tests/cover_crypt_tests/unit_tests.rs | 26 +- crate/utils/Cargo.toml | 1 - .../src/crypto/cover_crypt/attributes.rs | 55 +++- .../src/crypto/cover_crypt/kmip_requests.rs | 10 +- crate/utils/src/crypto/cover_crypt/locate.rs | 4 +- .../src/crypto/hybrid_encryption_system.rs | 2 +- crate/utils/src/error/mod.rs | 4 +- 27 files changed, 885 insertions(+), 229 deletions(-) rename crate/server/src/core/cover_crypt/{rotate.rs => update_policy.rs} (63%) diff --git a/.github/workflows/python_and_docker.yml b/.github/workflows/python_and_docker.yml index 6bfefca61..f232e7c47 100644 --- a/.github/workflows/python_and_docker.yml +++ b/.github/workflows/python_and_docker.yml @@ -67,6 +67,7 @@ jobs: secrets: inherit with: kms-version: ${{ needs.build-and-push-image.outputs.image-tag }} + branch: develop cloudproof_js: needs: @@ -86,7 +87,7 @@ jobs: - pyo3 uses: Cosmian/reusable_workflows/.github/workflows/cloudproof_java_in_docker.yml@develop with: - branch: v6.0.0 + branch: develop target: x86_64-unknown-linux-gnu extension: so destination: linux-x86-64 @@ -101,7 +102,7 @@ jobs: - pyo3 uses: Cosmian/reusable_workflows/.github/workflows/cloudproof_python.yml@develop with: - branch: v4.1.0 + branch: develop target: x86_64-unknown-linux-gnu kms-version: ${{ needs.build-and-push-image.outputs.image-tag }} copy_fresh_build: true diff --git a/.github/workflows/python_tests.yml b/.github/workflows/python_tests.yml index e74f4ad60..2e74e88b2 100644 --- a/.github/workflows/python_tests.yml +++ b/.github/workflows/python_tests.yml @@ -7,6 +7,9 @@ on: kms-version: required: true type: string + branch: + required: true + type: string jobs: pyo3-test-linux: @@ -24,19 +27,37 @@ jobs: run: | docker run --rm ghcr.io/cosmian/kms:${{ inputs.kms-version }} --help + - uses: actions/checkout@v3 + with: + repository: Cosmian/cloudproof_python + ref: ${{ inputs.branch }} + + - name: Install cloudproof python deps + env: + COVER_CRYPT_TAG: last_build + FINDEX_TAG: last_build + run: | + scripts/ci_install_pyo3_builds.sh + - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 - run: find . - - name: Test KMS python client on KMS server + - name: Install KMS python run: | # Check python code pip install kms_python_linux/*manylinux*.whl pip install -r crate/pyo3/python/requirements.txt + + - name: Test KMS python client on KMS server + run: | + # Check python code mypy crate/pyo3/python/scripts/test_kms.py python3 crate/pyo3/python/scripts/test_kms.py - # Check that the lib version is the same as the server + + - name: Check that the lib version is the same as the server + run: | cargo install cargo-get diff <(cargo get --entry crate/pyo3 package.version) <(cargo get --entry crate/server package.version) || (echo "Update the version in crate/pyo3/Cargo.toml"; exit 1) diff --git a/Cargo.lock b/Cargo.lock index c6051f817..4a8658554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -877,9 +877,9 @@ checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "cloudproof" -version = "2.2.5" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8c63d1d8c59273a84ebdb9957b74f47a86a25a8ec721aba1cae906e7d014236" +checksum = "6fdb38d2205ae5e365ab0e062d93f5f9d007cb2f3592167da5a347ca528284ca" dependencies = [ "cloudproof_aesgcm", "cloudproof_anonymization", @@ -918,9 +918,9 @@ dependencies = [ [[package]] name = "cloudproof_cover_crypt" -version = "12.0.3" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656f2f93f4a6e2d421bff10fbad745f5329c63cec24b3937e0d7c7bf9a87302d" +checksum = "04394b48452b351566bc9412215e24361a1425766b5fa06db5e980acd029ed42" dependencies = [ "cosmian_cover_crypt", "cosmian_crypto_core", @@ -1038,9 +1038,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cosmian_cover_crypt" -version = "12.0.3" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f85070e5197dc924174ab174eaa1ce8058cd153dda296c83d6dc54fae19b02a" +checksum = "10df4c34e0d5da7060a5ed1eb9ec9364b2c366d84903ed28ab017aa50aae78f6" dependencies = [ "cosmian_crypto_core", "pqc_kyber", @@ -1269,7 +1269,6 @@ version = "4.8.2" dependencies = [ "argon2", "cloudproof", - "cosmian_crypto_core", "cosmian_kmip", "num-bigint-dig", "openssl", @@ -3632,9 +3631,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "indexmap 2.0.0", "itoa", diff --git a/Cargo.toml b/Cargo.toml index f5f1c913a..ee899ae31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ clap = { version = "4.4", default-features = false, features = [ "derive", "cargo", ] } -cloudproof = { version = "2.2.5", features = ["findex-redis"] } +cloudproof = { version = "2.3.0", features = ["findex-redis"] } env_logger = "0.10" hex = "0.4" http = "0.2" diff --git a/crate/cli/src/actions/cover_crypt/policy.rs b/crate/cli/src/actions/cover_crypt/policy.rs index e05c3af85..d04c3eeca 100644 --- a/crate/cli/src/actions/cover_crypt/policy.rs +++ b/crate/cli/src/actions/cover_crypt/policy.rs @@ -4,9 +4,7 @@ use std::{ }; use clap::{Parser, Subcommand}; -use cloudproof::reexport::cover_crypt::abe_policy::{ - Attribute, EncryptionHint, Policy, PolicyAxis, -}; +use cloudproof::reexport::cover_crypt::abe_policy::{DimensionBuilder, EncryptionHint, Policy}; use cosmian_kmip::kmip::{ kmip_objects::Object, ttlv::{deserializer::from_ttlv, TTLV}, @@ -29,7 +27,7 @@ pub struct PolicySpecifications(HashMap>); impl PolicySpecifications { /// Create a `Policy` from `PolicySpecifications` pub fn to_policy(&self) -> Result { - let mut policy = Policy::new(u32::MAX); + let mut policy = Policy::new(); for (axis, attributes) in &self.0 { // Split the axis into axis name and hierarchy flag let (axis_name, hierarchical) = match axis.split_once("::") { @@ -65,7 +63,7 @@ impl PolicySpecifications { } // Add the axis to the policy - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( axis_name, attributes_properties, hierarchical, @@ -88,24 +86,25 @@ impl TryInto for PolicySpecifications { } } -impl TryFrom<&Policy> for PolicySpecifications { +impl TryFrom for PolicySpecifications { type Error = CliError; - fn try_from(policy: &Policy) -> Result { - let mut result: HashMap> = HashMap::new(); - for (axis_name, params) in &policy.axes { - let axis_full_name = - axis_name.clone() + if params.is_hierarchical { "::+" } else { "" }; - let mut attributes = Vec::with_capacity(params.attribute_names.len()); - for att in ¶ms.attribute_names { - let name = att.clone() - + match policy.attribute_hybridization_hint(&Attribute::new(axis_name, att))? { + fn try_from(policy: Policy) -> Result { + let mut result: HashMap> = + HashMap::with_capacity(policy.dimensions.len()); + for (dim_name, dimension) in policy.dimensions { + let dim_full_name = dim_name + if dimension.order.is_some() { "::+" } else { "" }; + let attributes = dimension + .attributes_properties() + .into_iter() + .map(|(name, enc_hint)| { + name + match enc_hint { EncryptionHint::Hybridized => "::+", EncryptionHint::Classic => "", - }; - attributes.push(name); - } - result.insert(axis_full_name, attributes); + } + }) + .collect(); + result.insert(dim_full_name, attributes); } Ok(Self(result)) } @@ -275,7 +274,7 @@ impl SpecsAction { kms_rest_client, ) .await?; - let specs = PolicySpecifications::try_from(&policy)?; + let specs = PolicySpecifications::try_from(policy)?; // save the policy to the specifications file write_json_object_to_file(&specs, &self.policy_specs_file) } @@ -358,7 +357,7 @@ impl ViewAction { let json = if self.detailed { serde_json::to_string_pretty(&policy)? } else { - let specs = PolicySpecifications::try_from(&policy)?; + let specs = PolicySpecifications::try_from(policy)?; serde_json::to_string_pretty(&specs)? }; println!("{json}"); @@ -436,15 +435,22 @@ mod tests { let policy_json: PolicySpecifications = serde_json::from_str(json).unwrap(); let policy = policy_json.to_policy()?; - assert_eq!(policy.axes.len(), 2); - assert!(policy.axes.get("Security Level").unwrap().is_hierarchical); - assert!(!policy.axes.get("Department").unwrap().is_hierarchical); + assert_eq!(policy.dimensions.len(), 2); + assert!( + policy + .dimensions + .get("Security Level") + .unwrap() + .order + .is_some() + ); + assert!(policy.dimensions.get("Department").unwrap().order.is_none()); assert_eq!( policy - .axes + .dimensions .get("Security Level") .unwrap() - .attribute_names + .attributes .len(), 3 ); diff --git a/crate/cli/src/actions/cover_crypt/rotate_attributes.rs b/crate/cli/src/actions/cover_crypt/rotate_attributes.rs index efcc006cf..9b4cc5e69 100644 --- a/crate/cli/src/actions/cover_crypt/rotate_attributes.rs +++ b/crate/cli/src/actions/cover_crypt/rotate_attributes.rs @@ -1,7 +1,9 @@ use clap::Parser; use cloudproof::reexport::cover_crypt::abe_policy::Attribute; use cosmian_kms_client::KmsRestClient; -use cosmian_kms_utils::crypto::cover_crypt::kmip_requests::build_rekey_keypair_request; +use cosmian_kms_utils::crypto::cover_crypt::{ + attributes::EditPolicyAction, kmip_requests::build_rekey_keypair_request, +}; use crate::{ cli_bail, @@ -55,7 +57,8 @@ impl RotateAttributesAction { }; // Create the kmip query - let rotate_query = build_rekey_keypair_request(&id, ats)?; + let rotate_query = + build_rekey_keypair_request(&id, EditPolicyAction::RotateAttributes(ats))?; // Query the KMS with your kmip data let rotate_response = kms_rest_client diff --git a/crate/kmip/src/kmip/kmip_messages.rs b/crate/kmip/src/kmip/kmip_messages.rs index b8e27192d..44d5fd043 100644 --- a/crate/kmip/src/kmip/kmip_messages.rs +++ b/crate/kmip/src/kmip/kmip_messages.rs @@ -152,6 +152,7 @@ pub struct MessageBatchItem { } impl MessageBatchItem { + #[must_use] pub fn new(request: Operation) -> Self { Self { operation: request.operation_enum(), @@ -473,6 +474,7 @@ pub struct MessageResponseBatchItem { } impl MessageResponseBatchItem { + #[must_use] pub fn new(result_status: ResultStatusEnumeration) -> Self { Self { result_status, @@ -486,6 +488,7 @@ impl MessageResponseBatchItem { } } + #[must_use] pub fn new_with_response(result_status: ResultStatusEnumeration, response: Operation) -> Self { Self { result_status, diff --git a/crate/kmip/src/kmip/kmip_operations.rs b/crate/kmip/src/kmip/kmip_operations.rs index 4953073f4..d6710fb1b 100644 --- a/crate/kmip/src/kmip/kmip_operations.rs +++ b/crate/kmip/src/kmip/kmip_operations.rs @@ -134,6 +134,7 @@ pub enum Operation { } impl Operation { + #[must_use] pub fn direction(&self) -> Direction { match self { Operation::Import(_) @@ -166,6 +167,7 @@ impl Operation { } } + #[must_use] pub fn operation_enum(&self) -> OperationEnumeration { match self { Operation::Import(_) | Operation::ImportResponse(_) => OperationEnumeration::Import, @@ -197,6 +199,7 @@ impl Operation { /// /// The check is enforced only if a upper version than the default one /// is detected when receiving an operation. + #[must_use] pub fn protocol_version(&self) -> ProtocolVersion { ProtocolVersion::default() } diff --git a/crate/kmip/src/kmip/ttlv/mod.rs b/crate/kmip/src/kmip/ttlv/mod.rs index b929101c5..d8121be50 100644 --- a/crate/kmip/src/kmip/ttlv/mod.rs +++ b/crate/kmip/src/kmip/ttlv/mod.rs @@ -457,6 +457,7 @@ impl<'de> Deserialize<'de> for TTLV { /// /// This conversion is done manually, as `num-bigint-dig` /// doesn't provide such conversion. +#[must_use] pub fn to_u32_digits(big_int: &BigUint) -> Vec { big_int .to_bytes_be() @@ -464,7 +465,7 @@ pub fn to_u32_digits(big_int: &BigUint) -> Vec { .map(|group_of_4_bytes| { group_of_4_bytes .iter() - .fold(0, |acc, byte| (acc << 8) + (*byte as u32)) + .fold(0, |acc, byte| (acc << 8) + u32::from(*byte)) }) .collect::>() } diff --git a/crate/kmip/src/kmip/ttlv/tests/mod.rs b/crate/kmip/src/kmip/ttlv/tests/mod.rs index d201a94ca..6f41069f0 100644 --- a/crate/kmip/src/kmip/ttlv/tests/mod.rs +++ b/crate/kmip/src/kmip/ttlv/tests/mod.rs @@ -744,7 +744,7 @@ pub fn test_message_request() { }]), batch_error_continuation_option: Some(BatchErrorContinuationOption::Undo), batch_order_option: Some(true), - timestamp: Some(1950940403), + timestamp: Some(1_950_940_403), }, items: vec![MessageBatchItem { operation: OperationEnumeration::Encrypt, @@ -788,7 +788,7 @@ pub fn test_message_response() { client_correlation_value: Some("client_123".to_string()), server_correlation_value: Some("server_234".to_string()), attestation_type: Some(vec![AttestationType::TPM_Quote]), - timestamp: 1697201574, + timestamp: 1_697_201_574, nonce: Some(Nonce { nonce_id: vec![5, 6, 7], nonce_value: vec![8, 9, 0], @@ -954,7 +954,7 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, batch_count: 1, - timestamp: 1697201574, + timestamp: 1_697_201_574, ..Default::default() }, items: vec![MessageResponseBatchItem { @@ -983,7 +983,7 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 0, }, batch_count: 1, - timestamp: 1697201574, + timestamp: 1_697_201_574, ..Default::default() }, items: vec![MessageResponseBatchItem { @@ -1011,7 +1011,7 @@ pub fn test_message_enforce_enum() { }, // mismatch number of items batch_count: 22, - timestamp: 1697201574, + timestamp: 1_697_201_574, ..Default::default() }, items: vec![MessageResponseBatchItem::new( @@ -1030,7 +1030,7 @@ pub fn test_message_enforce_enum() { protocol_version_minor: 128, }, batch_count: 1, - timestamp: 1697201574, + timestamp: 1_697_201_574, ..Default::default() }, items: vec![MessageResponseBatchItem::new_with_response( @@ -1054,7 +1054,7 @@ pub fn test_message_enforce_enum() { client_correlation_value: Some("client_123".to_string()), server_correlation_value: Some("server_234".to_string()), attestation_type: Some(vec![AttestationType::TPM_Quote]), - timestamp: 1697201574, + timestamp: 1_697_201_574, nonce: Some(Nonce { nonce_id: vec![5, 6, 7], nonce_value: vec![8, 9, 0], diff --git a/crate/pyo3/python/cosmian_kms/__init__.pyi b/crate/pyo3/python/cosmian_kms/__init__.pyi index c23342e1a..63dd3df3d 100644 --- a/crate/pyo3/python/cosmian_kms/__init__.pyi +++ b/crate/pyo3/python/cosmian_kms/__init__.pyi @@ -105,7 +105,7 @@ class KmsClient: self, attributes: List[Union[Attribute, str]], master_secret_key_identifier: Optional[str], - tags: Optional[List[str]] = None, + tags: Optional[List[str]] = None ) -> Future[Tuple[str, str]]: """Rotate the given policy attributes. This will rekey in the KMS: - the Master Keys @@ -114,11 +114,120 @@ class KmsClient: Args: attributes (List[Union[Attribute, str]]): attributes to rotate e.g. ["Department::HR"] master_secret_key_identifier (Optional[str]): master secret key UID. Tags should be supplied if the ID is not given. - tags: (Optional[List[str][]) tags to retrieve the master secret key if it the id is not satisfied + tags (Optional[List[str]]): tags to retrieve the master secret key if it the id is not satisfied Returns: Future[Tuple[str, str]]: (Public key UID, Master secret key UID) """ + async def clear_cover_crypt_attributes_rotations( + self, + attributes: List[Union[Attribute, str]], + master_secret_key_identifier: str, + tags: Optional[List[str]] = None + ) -> Tuple[str, str]: + """ + Remove old rotations from the specified policy attributes. + + This will rekey in the KMS: + - the Master Keys + - all User Decryption Keys that contain one of these attributes in their policy. + + Args: + - attributes (List[Union[Attribute, str]): Attributes to rotate e.g. ["Department::HR"] + - master_secret_key_identifier (str): Master secret key UID + - tags (List[str]): Tags to use when the master_secret_key_identifier is not provided (default: None) + + Returns: + Tuple[str, str]: (Public key UID, Master secret key UID) + """ + async def remove_cover_crypt_attribute( + self, + attribute: Union[Attribute, str], + master_secret_key_identifier: str, + tags: Optional[List[str]] = None + ) -> Tuple[str, str]: + """ + Remove a specific attribute from a keypair's policy. + + This will rekey in the KMS: + - the Master Keys + - all User Decryption Keys that contain one of these attributes in their policy. + + Args: + - attributes (List[Union[Attribute, str]): Attributes to remove e.g. "Department::HR" + - master_secret_key_identifier (str): Master secret key UID + - tags (List[str]): Tags to use when the master_secret_key_identifier is not provided (default: None) + + Returns: + Tuple[str, str]: (Public key UID, Master secret key UID) + """ + async def disable_cover_crypt_attribute( + self, + attribute: Union[Attribute, str], + master_secret_key_identifier: str, + tags: Optional[List[str]] = None + ) -> Tuple[str, str]: + """ + Disable a specific attribute from a keypair's policy. + + This will rekey in the KMS: + - the Master Keys + - all User Decryption Keys that contain one of these attributes in their policy. + + Args: + - attributes (List[Union[Attribute, str]): Attributes to disable e.g. "Department::HR" + - master_secret_key_identifier (str): Master secret key UID + - tags (List[str]): Tags to use when the master_secret_key_identifier is not provided (default: None) + + Returns: + Tuple[str, str]: (Public key UID, Master secret key UID) + """ + async def add_cover_crypt_attribute( + self, + attribute: Union[Attribute, str], + is_hybridized: bool, + master_secret_key_identifier: str, + tags: Optional[List[str]] = None + ) -> Tuple[str, str]: + """ + Add a specific attribute to a keypair's policy. + + This will rekey in the KMS: + - the Master Keys + - all User Decryption Keys that contain one of these attributes in their policy. + + Args: + - attributes (List[Union[Attribute, str]): Attributes to disable e.g. "Department::HR" + - is_hybridized (bool): hint for encryption + - master_secret_key_identifier (str): Master secret key UID + - tags (List[str]): Tags to use when the master_secret_key_identifier is not provided (default: None) + + Returns: + Tuple[str, str]: (Public key UID, Master secret key UID) + """ + async def rename_cover_crypt_attribute( + self, + attribute: Union[Attribute, str], + new_name: str, + master_secret_key_identifier: str, + tags: Optional[List[str]] = None + ) -> Tuple[str, str]: + """ + Add a specific attribute to a keypair's policy. + + This will rekey in the KMS: + - the Master Keys + - all User Decryption Keys that contain one of these attributes in their policy. + + Args: + - attributes (List[Union[Attribute, str]): Attributes to disable e.g. "Department::HR" + - new_name (str): the new name for the attribute + - master_secret_key_identifier (str): Master secret key UID + - tags (List[str]): Tags to use when the master_secret_key_identifier is not provided (default: None) + + Returns: + Tuple[str, str]: (Public key UID, Master secret key UID) + """ def create_cover_crypt_user_decryption_key( self, access_policy_str: str, master_secret_key_identifier: str ) -> Future[str]: diff --git a/crate/pyo3/python/requirements.txt b/crate/pyo3/python/requirements.txt index 440fdc48b..0f3289fd1 100644 --- a/crate/pyo3/python/requirements.txt +++ b/crate/pyo3/python/requirements.txt @@ -1,3 +1,3 @@ -cover-crypt==12.0.1 -maturin==1.2.3 -mypy>=0.99 +cover-crypt>=12.0.3 +maturin==1.3 +mypy>=1.6 diff --git a/crate/pyo3/python/scripts/test_kms.py b/crate/pyo3/python/scripts/test_kms.py index d61946c60..55c5b0306 100644 --- a/crate/pyo3/python/scripts/test_kms.py +++ b/crate/pyo3/python/scripts/test_kms.py @@ -2,10 +2,10 @@ import unittest from cloudproof_cover_crypt import ( + MasterPublicKey, MasterSecretKey, Policy, PolicyAxis, - MasterPublicKey, UserSecretKey, ) from cosmian_kms import KmsClient @@ -22,8 +22,8 @@ async def asyncSetUp(self) -> None: 'Security Level', [ ('Protected', False), - ('Confidential', False), - ('Top Secret', False), + ('Confidential', True), + ('Top Secret', True), ], hierarchical=True, ) @@ -234,6 +234,107 @@ async def test_policy_rotation_encryption_decryption(self) -> None: ) self.assertEqual(bytes(plaintext), new_message) + # Clear old rotations + ( + new_pub_key_uid, + new_priv_key_uid, + ) = await self.client.clear_cover_crypt_attributes_rotations( + ['Department::HR'], + self.priv_key_uid, + ) + + # Old message decryption should fail + with self.assertRaises(Exception): + plaintext, _ = await self.client.cover_crypt_decryption( + old_ciphertext, + user_key_uid, + ) + + # New message can still be decrypted + plaintext, _ = await self.client.cover_crypt_decryption( + new_ciphertext, + user_key_uid, + ) + self.assertEqual(bytes(plaintext), new_message) + + async def test_policy_edit_encryption_decryption(self) -> None: + # Encryption + message = b'My secret data part 1' + ciphertext = await self.client.cover_crypt_encryption( + 'Department::HR && Security Level::Confidential', + message, + self.pub_key_uid, + ) + + # Generate user key + user_key_uid = await self.client.create_cover_crypt_user_decryption_key( + 'Department::HR && Security Level::Top Secret', + self.priv_key_uid, + ) + + # Disable attribute "Confidential" + ( + new_pub_key_uid, + new_priv_key_uid, + ) = await self.client.disable_cover_crypt_attribute( + 'Security Level::Confidential', + self.priv_key_uid, + ) + + # Confidential message can still be decrypted + plaintext, _ = await self.client.cover_crypt_decryption( + ciphertext, + user_key_uid, + ) + self.assertEqual(bytes(plaintext), message) + + # New encryption with disabled attribute will fail + with self.assertRaises(Exception): + await self.client.cover_crypt_encryption( + 'Department::MKG && Security Level::Confidential', + b'will fail', + self.pub_key_uid, + ) + + + # Rename attribute "FIN" + # await self.client.rename_cover_crypt_attribute( + # 'Department::FIN', + # 'Finance', + # self.priv_key_uid, + # ) + + # Add attribute "R&D" + ( + new_pub_key_uid, + new_priv_key_uid, + ) = await self.client.add_cover_crypt_attribute( + 'Department::R&D', + False, + self.priv_key_uid, + ) + + # Encrypt for new and renamed attribute + message = b'My secret data part 2' + ciphertext = await self.client.cover_crypt_encryption( + '(Department::FIN || Department::R&D) && Security Level::Protected', + message, + self.pub_key_uid, + ) + + # Generate user key + user_key_uid = await self.client.create_cover_crypt_user_decryption_key( + 'Department::R&D && Security Level::Protected', + self.priv_key_uid, + ) + + # Decryption as usual + plaintext, _ = await self.client.cover_crypt_decryption( + ciphertext, + user_key_uid, + ) + self.assertEqual(bytes(plaintext), message) + if __name__ == '__main__': unittest.main() diff --git a/crate/pyo3/src/py_kms_client.rs b/crate/pyo3/src/py_kms_client.rs index 67a2a6baa..25646eec7 100644 --- a/crate/pyo3/src/py_kms_client.rs +++ b/crate/pyo3/src/py_kms_client.rs @@ -1,15 +1,18 @@ use cloudproof::reexport::{ - cover_crypt::abe_policy::{AccessPolicy, Attribute, Policy}, + cover_crypt::abe_policy::{AccessPolicy, Attribute, EncryptionHint, Policy}, crypto_core::bytes_ser_de::Deserializer, }; use cosmian_kmip::kmip::{kmip_operations::Get, kmip_types::RevocationReason}; use cosmian_kms_client::KmsRestClient; use cosmian_kms_utils::crypto::{ - cover_crypt::kmip_requests::{ - build_create_master_keypair_request, build_create_user_decryption_private_key_request, - build_destroy_key_request, build_import_decryption_private_key_request, - build_import_private_key_request, build_import_public_key_request, - build_rekey_keypair_request, + cover_crypt::{ + attributes::EditPolicyAction, + kmip_requests::{ + build_create_master_keypair_request, build_create_user_decryption_private_key_request, + build_destroy_key_request, build_import_decryption_private_key_request, + build_import_private_key_request, build_import_public_key_request, + build_rekey_keypair_request, + }, }, generic::kmip_requests::{ build_decryption_request, build_encryption_request, build_revoke_key_request, @@ -24,6 +27,54 @@ use rustls::Certificate; use crate::py_kms_object::KmsObject; +/// Create a Rekey Keypair request from `PyO3` arguments +/// Returns a `PyO3` Future +macro_rules! rekey_keypair { + ( + $self:ident, + $attributes:expr, + $master_secret_key_identifier:expr, + $tags:expr, + $policy_attributes:ident, + $action:expr, + $py:ident + ) => {{ + // TODO: use key_id over tags if both specified? + let id = match ($master_secret_key_identifier, $tags) { + (Some(key_id), None) => key_id, + (None, Some(tags)) => serde_json::to_string(&tags) + .map_err(|_e| PyException::new_err("invalid tag(s) specified"))?, + (Some(_), Some(_)) => { + return Err(PyException::new_err( + "both key id and tags were specified, please use only one", + )) + } + _ => return Err(PyException::new_err("please specify a key id or tags")), + }; + + let $policy_attributes = $attributes + .into_iter() + .map(Attribute::try_from) + .collect::, _>>() + .map_err(|e| PyTypeError::new_err(e.to_string()))?; + + let request = build_rekey_keypair_request(&id, $action) + .map_err(|e| PyException::new_err(e.to_string()))?; + + let client = $self.0.clone(); + pyo3_asyncio::tokio::future_into_py($py, async move { + let response = client + .rekey_keypair(request) + .await + .map_err(|e| PyException::new_err(e.to_string()))?; + Ok(( + response.public_key_unique_identifier, + response.private_key_unique_identifier, + )) + }) + }}; +} + #[pyclass(subclass)] pub struct KmsClient(KmsRestClient); @@ -264,35 +315,169 @@ impl KmsClient { tags: Option>, py: Python<'p>, ) -> PyResult<&PyAny> { - let policy_attributes = attributes - .into_iter() - .map(Attribute::try_from) - .collect::, _>>() - .map_err(|e| PyTypeError::new_err(e.to_string()))?; + rekey_keypair!( + self, + attributes, + master_secret_key_identifier, + tags, + policy_attributes, + EditPolicyAction::RotateAttributes(policy_attributes), + py + ) + } - let id = if let Some(key_id) = master_secret_key_identifier { - key_id - } else if let Some(tags) = tags { - serde_json::to_string(&tags) - .map_err(|_e| PyException::new_err("invalid tag(s) specified"))? - } else { - return Err(PyException::new_err("please specify a key id or tags")) - }; + /// Remove old rotations from the specified policy attributes. + /// This will rekey in the KMS: + /// - the Master Keys + /// - any user key which associated access policy covers one of these attributes + /// + /// Args: + /// - `attributes` (List[Union[Attribute, str]]): attributes to rotate e.g. ["Department::HR"] + /// - `master_secret_key_identifier` (str): master secret key UID + /// - `tags` to use when the `master_secret_key_identifier` is not provided + /// + /// Returns: + /// Future[Tuple[str, str]]: (Public key UID, Master secret key UID) + pub fn clear_cover_crypt_attributes_rotations<'p>( + &'p self, + attributes: Vec<&str>, + master_secret_key_identifier: Option, + tags: Option>, + py: Python<'p>, + ) -> PyResult<&PyAny> { + rekey_keypair!( + self, + attributes, + master_secret_key_identifier, + tags, + policy_attributes, + EditPolicyAction::ClearOldAttributeValues(policy_attributes), + py + ) + } - let request = build_rekey_keypair_request(&id, policy_attributes) - .map_err(|e| PyException::new_err(e.to_string()))?; + /// Remove a specific attribute from a keypair's policy. + /// + /// Args: + /// - `attribute` (Union[Attribute, str]): attribute to remove e.g. "Department::HR" + /// - `master_secret_key_identifier` (str): master secret key UID + /// - `tags` to use when the `master_secret_key_identifier` is not provided + /// + /// Returns: + /// Future[Tuple[str, str]]: (Public key UID, Master secret key UID) + pub fn remove_cover_crypt_attribute<'p>( + &'p self, + _attribute: &str, + _master_secret_key_identifier: Option, + _tags: Option>, + _py: Python<'p>, + ) -> PyResult<&PyAny> { + /*rekey_keypair!( + self, + vec![attribute], + master_secret_key_identifier, + tags, + policy_attributes, + EditPolicyAction::RemoveAttribute(policy_attributes), + py + )*/ + Err(PyException::new_err("Not supported yet")) + } - let client = self.0.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - let response = client - .rekey_keypair(request) - .await - .map_err(|e| PyException::new_err(e.to_string()))?; - Ok(( - response.public_key_unique_identifier, - response.private_key_unique_identifier, - )) - }) + /// Disable a specific attribute for a keypair. + /// + /// Args: + /// - `attribute` (Union[Attribute, str]): attribute to remove e.g. "Department::HR" + /// - `master_secret_key_identifier` (str): master secret key UID + /// - `tags` to use when the `master_secret_key_identifier` is not provided + /// + /// Returns: + /// Future[Tuple[str, str]]: (Public key UID, Master secret key UID) + pub fn disable_cover_crypt_attribute<'p>( + &'p self, + attribute: &str, + master_secret_key_identifier: Option, + tags: Option>, + py: Python<'p>, + ) -> PyResult<&PyAny> { + rekey_keypair!( + self, + vec![attribute], + master_secret_key_identifier, + tags, + policy_attributes, + EditPolicyAction::DisableAttribute(policy_attributes), + py + ) + } + + /// Add a specific attribute to a keypair's policy. + /// + /// Args: + /// - `attribute` (Union[Attribute, str]): attribute to remove e.g. "Department::HR" + /// - `is_hybridized` (bool): hint for encryption + /// - `master_secret_key_identifier` (str): master secret key UID + /// - `tags` to use when the `master_secret_key_identifier` is not provided + /// + /// Returns: + /// Future[Tuple[str, str]]: (Public key UID, Master secret key UID) + pub fn add_cover_crypt_attribute<'p>( + &'p self, + attribute: &str, + is_hybridized: bool, + master_secret_key_identifier: Option, + tags: Option>, + py: Python<'p>, + ) -> PyResult<&PyAny> { + rekey_keypair!( + self, + vec![attribute], + master_secret_key_identifier, + tags, + policy_attributes, + EditPolicyAction::AddAttribute( + policy_attributes + .into_iter() + .map(|attr| (attr, EncryptionHint::new(is_hybridized))) + .collect() + ), + py + ) + } + + /// Rename a specific attribute in a keypair's policy. + /// + /// Args: + /// - `attribute` (Union[Attribute, str]): attribute to remove e.g. "Department::HR" + /// - `new_name` (str): the new name for the attribute + /// - `master_secret_key_identifier` (str): master secret key UID + /// - `tags` to use when the `master_secret_key_identifier` is not provided + /// + /// Returns: + /// Future[Tuple[str, str]]: (Public key UID, Master secret key UID) + pub fn rename_cover_crypt_attribute<'p>( + &'p self, + _attribute: &str, + _new_name: &str, + _master_secret_key_identifier: Option, + _tags: Option>, + _py: Python<'p>, + ) -> PyResult<&PyAny> { + /*rekey_keypair!( + self, + vec![attribute], + master_secret_key_identifier, + tags, + policy_attributes, + EditPolicyAction::RenameAttribute( + policy_attributes + .into_iter() + .map(|attr| (attr, new_name.to_string())) + .collect() + ), + py + )*/ + Err(PyException::new_err("Not supported yet")) } /// Generate a user secret key. diff --git a/crate/server/src/core/cover_crypt/mod.rs b/crate/server/src/core/cover_crypt/mod.rs index 48b570649..e4af82330 100644 --- a/crate/server/src/core/cover_crypt/mod.rs +++ b/crate/server/src/core/cover_crypt/mod.rs @@ -4,10 +4,10 @@ mod create_user_decryption_key; mod destroy_user_decryption_keys; mod locate_user_decryption_keys; mod revoke_user_decryption_keys; -mod rotate; +mod update_policy; pub(crate) use create_user_decryption_key::create_user_decryption_key; pub(crate) use destroy_user_decryption_keys::destroy_user_decryption_keys; pub(crate) use locate_user_decryption_keys::locate_user_decryption_keys; pub(crate) use revoke_user_decryption_keys::revoke_user_decryption_keys; -pub(crate) use rotate::rekey_keypair_cover_crypt; +pub(crate) use update_policy::rekey_keypair_cover_crypt; diff --git a/crate/server/src/core/cover_crypt/rotate.rs b/crate/server/src/core/cover_crypt/update_policy.rs similarity index 63% rename from crate/server/src/core/cover_crypt/rotate.rs rename to crate/server/src/core/cover_crypt/update_policy.rs index b7349cbac..acef735f7 100644 --- a/crate/server/src/core/cover_crypt/rotate.rs +++ b/crate/server/src/core/cover_crypt/update_policy.rs @@ -1,16 +1,13 @@ -use cloudproof::reexport::cover_crypt::{ - abe_policy::{Attribute, Policy}, - Covercrypt, -}; +use cloudproof::reexport::cover_crypt::{abe_policy::Policy, Covercrypt}; use cosmian_kmip::kmip::{ kmip_objects::{Object, ObjectType}, kmip_operations::{ErrorReason, Get, Import, ReKeyKeyPairResponse}, - kmip_types::{Attributes, KeyFormatType, LinkType, StateEnumeration}, + kmip_types::{LinkType, StateEnumeration}, }; use cosmian_kms_utils::{ access::ExtraDatabaseParams, crypto::cover_crypt::{ - attributes::{attributes_from_attributes, policy_from_attributes}, + attributes::{policy_from_attributes, EditPolicyAction}, master_keys::update_master_keys, user_key::UserDecryptionKeysHandler, }, @@ -28,31 +25,12 @@ pub async fn rekey_keypair_cover_crypt( kmip_server: &KMS, cover_crypt: Covercrypt, master_private_key_uid: &str, - attributes: &Attributes, owner: &str, + action: EditPolicyAction, params: Option<&ExtraDatabaseParams>, ) -> KResult { trace!("Internal rekey key pair CoverCrypt"); - // Verify the operation is performed for a CoverCrypt Master Key - let key_format_type = attributes.key_format_type.as_ref().ok_or_else(|| { - KmsError::InvalidRequest( - "Unable to rekey a CoverCrypt key, the format type is not specified".to_owned(), - ) - })?; - if key_format_type != &KeyFormatType::CoverCryptSecretKey { - kms_bail!(KmsError::NotSupported( - "ReKey: the format of the key must be a CoverCrypt master key".to_string() - )) - } - - // Determine the list of policy attributes which will be revoked (i.e. their value increased) - let cover_crypt_policy_attributes_to_revoke = attributes_from_attributes(attributes)?; - trace!( - "Revoking attributes: {:?}", - &cover_crypt_policy_attributes_to_revoke - ); - // Recover the master private key let master_private_key = kmip_server .get(Get::from(master_private_key_uid), owner, params) @@ -65,11 +43,10 @@ pub async fn rekey_keypair_cover_crypt( )); } - // Rotate the policy - let policy = rotate_policy( - &master_private_key, - &cover_crypt_policy_attributes_to_revoke, - )?; + // Edit the policy according to the requested action + let private_key_attributes = master_private_key.attributes()?; + let mut policy = policy_from_attributes(private_key_attributes)?; + update_policy(&action, &mut policy)?; // Rekey the master keys let master_public_key_uid = rekey_master_keys( @@ -83,59 +60,64 @@ pub async fn rekey_keypair_cover_crypt( ) .await?; - // Search the user decryption keys that need to be refreshed - let locate_response = locate_user_decryption_keys( + // Rekey the user secret keys if needed + locate_update_user_secret_keys( + action, kmip_server, + cover_crypt, master_private_key_uid, - Some(cover_crypt_policy_attributes_to_revoke), - Some(StateEnumeration::Active), owner, params, ) .await?; - // Refresh the User Decryption Key that were found - if let Some(unique_identifiers) = &locate_response { - // refresh the user keys - refresh_all_user_decryption_keys( - kmip_server, - cover_crypt, - master_private_key_uid, - unique_identifiers, - true, //TODO: do we want to conserve this or make it a parameter ? - owner, - params, - ) - .await?; - } - Ok(ReKeyKeyPairResponse { private_key_unique_identifier: master_private_key_uid.to_string(), public_key_unique_identifier: master_public_key_uid, }) } -/// Rotate the policy of the given Master Private Key -/// and return it -fn rotate_policy( - private_key: &Object, - cover_crypt_policy_attributes_to_revoke: &[Attribute], -) -> KResult { - // Recover the current policy - let private_key_attributes = private_key.attributes()?; - let mut policy = policy_from_attributes(private_key_attributes)?; - - // Rotate the Attributes values in the Policy - for attr in cover_crypt_policy_attributes_to_revoke { - policy.rotate(attr).map_err(|e| { - KmsError::KmipError( - ErrorReason::Unsupported_Cryptographic_Parameters, - e.to_string(), - ) - })?; +/// Update a Covercrypt policy based on the specified action. +/// +/// # Parameters +/// +/// - `action`: An `EditPolicyAction` enum. +/// - `policy`: the master private key Policy. +fn update_policy(action: &EditPolicyAction, policy: &mut Policy) -> KResult<()> { + match action { + EditPolicyAction::RotateAttributes(attrs) => { + attrs.iter().try_for_each(|attr| policy.rotate(attr)) + } + EditPolicyAction::ClearOldAttributeValues(attrs) => attrs + .iter() + .try_for_each(|attr| policy.clear_old_attribute_values(attr)), + EditPolicyAction::RemoveAttribute(attrs) => attrs + .iter() + .try_for_each(|attr| policy.remove_attribute(attr)), // TODO: revoke existing keys with deleted attribute?) + EditPolicyAction::DisableAttribute(attrs) => attrs + .iter() + .try_for_each(|attr| policy.disable_attribute(attr)), + EditPolicyAction::RenameAttribute(pairs_attr_name) => pairs_attr_name + .iter() + .try_for_each(|(attr, new_name)| policy.rename_attribute(attr, new_name)), // TODO: rename attributes in existing AccessPolicy + EditPolicyAction::AddAttribute(attrs_properties) => { + attrs_properties + .iter() + .try_for_each(|(attr, encryption_hint)| { + policy.add_attribute(attr.clone(), *encryption_hint) + }) + } } + .map_err(|e| { + KmsError::KmipError( + ErrorReason::Unsupported_Cryptographic_Parameters, + e.to_string(), + ) + })?; + trace!("The new policy is : {policy:#?}"); - Ok(policy) + //Ok(attributes_to_update) + Ok(()) } /// Rekey the Master keys given the provided Private Master Key and Policy @@ -210,7 +192,54 @@ async fn rekey_master_keys( Ok(master_public_key_uid) } -async fn refresh_all_user_decryption_keys( +/// Updates user secret keys if the action requires it. For actions like attribute rotation or +/// clearing old attribute values, it identifies which attributes need updates and locates +/// the corresponding user decryption keys to refresh them accordingly. +async fn locate_update_user_secret_keys( + action: EditPolicyAction, + kmip_server: &KMS, + cover_crypt: Covercrypt, + master_private_key_uid: &str, + owner: &str, + params: Option<&ExtraDatabaseParams>, +) -> KResult<()> { + // Get attributes to update in existing usk + let attributes_usk_to_update = match action { + EditPolicyAction::RotateAttributes(attrs) => Some(attrs), + EditPolicyAction::ClearOldAttributeValues(attrs) => Some(attrs), + _ => None, // no need to update existing usk + }; + if let Some(attrs) = attributes_usk_to_update { + // Search the user decryption keys that need to be refreshed + let locate_response = locate_user_decryption_keys( + kmip_server, + master_private_key_uid, + Some(attrs), + Some(StateEnumeration::Active), + owner, + params, + ) + .await?; + + // Refresh the User Decryption Key that were found + if let Some(unique_identifiers) = &locate_response { + // refresh the user keys + refresh_user_decryption_keys( + kmip_server, + cover_crypt, + master_private_key_uid, + unique_identifiers, + true, // new keys will get access to previous rotations, TODO: do we want make this a parameter ? + owner, + params, + ) + .await?; + } + } + Ok(()) +} + +async fn refresh_user_decryption_keys( kmip_server: &KMS, cover_crypt: Covercrypt, master_private_key_uid: &str, diff --git a/crate/server/src/core/operations/rekey_keypair.rs b/crate/server/src/core/operations/rekey_keypair.rs index 79bcd0f13..4eb0939d3 100644 --- a/crate/server/src/core/operations/rekey_keypair.rs +++ b/crate/server/src/core/operations/rekey_keypair.rs @@ -6,7 +6,7 @@ use cosmian_kmip::kmip::{ }; use cosmian_kms_utils::{ access::{ExtraDatabaseParams, ObjectOperationType}, - crypto::cover_crypt::attributes::policy_from_attributes, + crypto::cover_crypt::attributes::{edit_policy_action_from_attributes, policy_from_attributes}, }; use tracing::trace; @@ -75,25 +75,18 @@ pub async fn rekey_keypair( ))) } - match &attributes.cryptographic_algorithm { - Some(CryptographicAlgorithm::CoverCrypt) => { - rekey_keypair_cover_crypt( - kms, - Covercrypt::default(), - &owm.id, - attributes, - user, - params, - ) - .await - } - Some(other) => kms_bail!(KmsError::NotSupported(format!( + if Some(CryptographicAlgorithm::CoverCrypt) == attributes.cryptographic_algorithm { + let action = edit_policy_action_from_attributes(attributes)?; + rekey_keypair_cover_crypt(kms, Covercrypt::default(), &owm.id, user, action, params).await + } else if let Some(other) = attributes.cryptographic_algorithm { + kms_bail!(KmsError::NotSupported(format!( "The rekey of a key pair for algorithm: {other:?} is not yet supported" - ))), - None => kms_bail!(KmsError::InvalidRequest( + ))) + } else { + kms_bail!(KmsError::InvalidRequest( "The cryptographic algorithm must be specified in the private key attributes for key \ pair creation" .to_string() - )), + )) } } diff --git a/crate/server/src/tests/cover_crypt_tests/integration_tests.rs b/crate/server/src/tests/cover_crypt_tests/integration_tests.rs index 6fa6044e3..1abe2c895 100644 --- a/crate/server/src/tests/cover_crypt_tests/integration_tests.rs +++ b/crate/server/src/tests/cover_crypt_tests/integration_tests.rs @@ -1,5 +1,5 @@ use cloudproof::reexport::cover_crypt::abe_policy::{ - Attribute, EncryptionHint, Policy, PolicyAxis, + Attribute, DimensionBuilder, EncryptionHint, Policy, }; use cosmian_kmip::kmip::{ kmip_operations::{ @@ -10,9 +10,13 @@ use cosmian_kmip::kmip::{ }; use cosmian_kms_utils::{ crypto::{ - cover_crypt::kmip_requests::{ - build_create_master_keypair_request, build_create_user_decryption_private_key_request, - build_destroy_key_request, build_rekey_keypair_request, + cover_crypt::{ + attributes::EditPolicyAction, + kmip_requests::{ + build_create_master_keypair_request, + build_create_user_decryption_private_key_request, build_destroy_key_request, + build_rekey_keypair_request, + }, }, generic::kmip_requests::{build_decryption_request, build_encryption_request}, }, @@ -30,8 +34,8 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { let app = test_utils::test_app().await; - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -40,7 +44,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("Confidential", EncryptionHint::Classic), @@ -178,7 +182,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { let request = build_decryption_request( user_decryption_key_identifier_2, None, - encrypted_data, + encrypted_data.clone(), None, Some(authentication_data.clone()), None, @@ -211,8 +215,10 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { // 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 request = build_rekey_keypair_request( + private_key_unique_identifier, + EditPolicyAction::RotateAttributes(abe_policy_attributes.clone()), + )?; let rekey_keypair_response: ReKeyKeyPairResponse = test_utils::post(&app, &request).await?; assert_eq!( &rekey_keypair_response.private_key_unique_identifier, @@ -237,7 +243,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { Some(CryptographicAlgorithm::CoverCrypt), )?; let encrypt_response: EncryptResponse = test_utils::post(&app, &request).await?; - let encrypted_data = encrypt_response + let new_encrypted_data = encrypt_response .data .expect("There should be encrypted data"); @@ -245,7 +251,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { let request = build_decryption_request( user_decryption_key_identifier_1, None, - encrypted_data.clone(), + new_encrypted_data.clone(), None, Some(authentication_data.clone()), None, @@ -257,7 +263,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { let request = build_decryption_request( user_decryption_key_identifier_2, None, - encrypted_data, + new_encrypted_data, None, Some(authentication_data.clone()), None, @@ -273,6 +279,144 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> { assert_eq!(&data, &decrypted_data.plaintext); assert!(decrypted_data.metadata.is_empty()); + // + // Clear old rotations for ABE Attribute + let request = build_rekey_keypair_request( + private_key_unique_identifier, + EditPolicyAction::ClearOldAttributeValues(abe_policy_attributes.clone()), + )?; + let rekey_keypair_response: KResult = + test_utils::post(&app, &request).await; + assert!(rekey_keypair_response.is_ok()); + + // test user2 can no longer decrypt old message + let request = build_decryption_request( + user_decryption_key_identifier_2, + None, + encrypted_data, + None, + Some(authentication_data.clone()), + None, + ); + let post_ttlv_decrypt: KResult = test_utils::post(&app, &request).await; + assert!(post_ttlv_decrypt.is_err()); + + // + // Add new Attributes + let new_policy_attributes = vec![ + ( + Attribute::from(("Department", "IT")), + EncryptionHint::Classic, + ), + ( + Attribute::from(("Department", "R&D")), + EncryptionHint::Hybridized, + ), + ]; + let request = build_rekey_keypair_request( + private_key_unique_identifier, + EditPolicyAction::AddAttribute(new_policy_attributes), + )?; + let rekey_keypair_response: KResult = + test_utils::post(&app, &request).await; + assert!(rekey_keypair_response.is_ok()); + + // Encrypt for new attribute + let data = "New tech research data".as_bytes(); + let encryption_policy = "Level::Confidential && (Department::IT || Department::R&D)"; + + let request = build_encryption_request( + public_key_unique_identifier, + Some(encryption_policy.to_string()), + data.to_vec(), + None, + Some(authentication_data.clone()), + Some(CryptographicAlgorithm::CoverCrypt), + )?; + let encrypt_response: KResult = test_utils::post(&app, &request).await; + assert!(encrypt_response.is_ok()); + + // + // Rename Attributes + let rename_policy_attributes_pair = vec![( + Attribute::from(("Department", "HR")), + "HumanResources".to_string(), + )]; + let request = build_rekey_keypair_request( + private_key_unique_identifier, + EditPolicyAction::RenameAttribute(rename_policy_attributes_pair), + )?; + let rekey_keypair_response: KResult = + test_utils::post(&app, &request).await; + assert!(rekey_keypair_response.is_ok()); + + // Encrypt for renamed attribute + let data = "hr data".as_bytes(); + let encryption_policy = "Level::Confidential && Department::HumanResources"; + + let request = build_encryption_request( + public_key_unique_identifier, + Some(encryption_policy.to_string()), + data.to_vec(), + None, + Some(authentication_data.clone()), + Some(CryptographicAlgorithm::CoverCrypt), + )?; + let encrypt_response: KResult = test_utils::post(&app, &request).await; + assert!(encrypt_response.is_ok()); + + // + // Disable ABE Attribute + let request = build_rekey_keypair_request( + private_key_unique_identifier, + EditPolicyAction::DisableAttribute(abe_policy_attributes.clone()), + )?; + let rekey_keypair_response: KResult = + test_utils::post(&app, &request).await; + assert!(rekey_keypair_response.is_ok()); + + // Encrypt with disabled ABE attribute will fail + let authentication_data = b"cc the uid".to_vec(); + let data = "Will fail".as_bytes(); + let encryption_policy = "Level::Confidential && Department::MKG"; + + let request = build_encryption_request( + public_key_unique_identifier, + Some(encryption_policy.to_string()), + data.to_vec(), + None, + Some(authentication_data.clone()), + Some(CryptographicAlgorithm::CoverCrypt), + )?; + let encrypt_response: KResult = test_utils::post(&app, &request).await; + assert!(encrypt_response.is_err()); + + // + // Delete attribute + let remove_policy_attributes_pair = vec![Attribute::from(("Department", "HumanResources"))]; + let request = build_rekey_keypair_request( + private_key_unique_identifier, + EditPolicyAction::RemoveAttribute(remove_policy_attributes_pair), + )?; + let rekey_keypair_response: KResult = + test_utils::post(&app, &request).await; + assert!(rekey_keypair_response.is_ok()); + + // Encrypt for removed attribute will fail + let data = "New hr data".as_bytes(); + let encryption_policy = "Level::Confidential && Department::HumanResources"; + + let request = build_encryption_request( + public_key_unique_identifier, + Some(encryption_policy.to_string()), + data.to_vec(), + None, + Some(authentication_data.clone()), + Some(CryptographicAlgorithm::CoverCrypt), + )?; + let encrypt_response: KResult = test_utils::post(&app, &request).await; + assert!(encrypt_response.is_err()); + // // Destroy user decryption key let request = build_destroy_key_request(user_decryption_key_identifier_1)?; diff --git a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs index 42726665e..e41e97da3 100644 --- a/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs +++ b/crate/server/src/tests/cover_crypt_tests/integration_tests_bulk.rs @@ -1,4 +1,4 @@ -use cloudproof::reexport::cover_crypt::abe_policy::{EncryptionHint, Policy, PolicyAxis}; +use cloudproof::reexport::cover_crypt::abe_policy::{DimensionBuilder, EncryptionHint, Policy}; use cosmian_kmip::kmip::{ kmip_messages::{Message, MessageBatchItem, MessageHeader, MessageResponse}, kmip_operations::Operation, @@ -16,8 +16,8 @@ async fn integration_tests_bulk() -> KResult<()> { let app = test_utils::test_app().await; - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -26,7 +26,7 @@ async fn integration_tests_bulk() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("Confidential", EncryptionHint::Classic), diff --git a/crate/server/src/tests/cover_crypt_tests/integration_tests_tags.rs b/crate/server/src/tests/cover_crypt_tests/integration_tests_tags.rs index 643795159..4d93f5dc4 100644 --- a/crate/server/src/tests/cover_crypt_tests/integration_tests_tags.rs +++ b/crate/server/src/tests/cover_crypt_tests/integration_tests_tags.rs @@ -1,5 +1,5 @@ use cloudproof::reexport::cover_crypt::abe_policy::{ - Attribute, EncryptionHint, Policy, PolicyAxis, + Attribute, DimensionBuilder, EncryptionHint, Policy, }; use cosmian_kmip::kmip::{ kmip_operations::{ @@ -9,9 +9,12 @@ use cosmian_kmip::kmip::{ kmip_types::RevocationReason, }; use cosmian_kms_utils::crypto::{ - cover_crypt::kmip_requests::{ - build_create_master_keypair_request, build_create_user_decryption_private_key_request, - build_destroy_key_request, build_rekey_keypair_request, + cover_crypt::{ + attributes::EditPolicyAction, + kmip_requests::{ + build_create_master_keypair_request, build_create_user_decryption_private_key_request, + build_destroy_key_request, build_rekey_keypair_request, + }, }, generic::kmip_requests::{build_decryption_request, build_encryption_request}, }; @@ -27,8 +30,8 @@ async fn integration_tests_with_tags() -> KResult<()> { let app = test_utils::test_app().await; - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -37,7 +40,7 @@ async fn integration_tests_with_tags() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("Confidential", EncryptionHint::Classic), @@ -201,7 +204,10 @@ async fn integration_tests_with_tags() -> KResult<()> { // Rekey all key pairs with matching ABE attributes let abe_policy_attributes = vec![Attribute::from(("Department", "MKG"))]; - let request = build_rekey_keypair_request(&mkp_json_tag, abe_policy_attributes)?; + let request = build_rekey_keypair_request( + &mkp_json_tag, + EditPolicyAction::RotateAttributes(abe_policy_attributes), + )?; let rekey_keypair_response: ReKeyKeyPairResponse = test_utils::post(&app, &request).await?; assert_eq!( &rekey_keypair_response.private_key_unique_identifier, diff --git a/crate/server/src/tests/cover_crypt_tests/unit_tests.rs b/crate/server/src/tests/cover_crypt_tests/unit_tests.rs index e359e2cd6..ea72a4ba6 100644 --- a/crate/server/src/tests/cover_crypt_tests/unit_tests.rs +++ b/crate/server/src/tests/cover_crypt_tests/unit_tests.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use cloudproof::reexport::cover_crypt::abe_policy::{EncryptionHint, Policy, PolicyAxis}; +use cloudproof::reexport::cover_crypt::abe_policy::{DimensionBuilder, EncryptionHint, Policy}; use cosmian_kmip::kmip::{ kmip_objects::{Object, ObjectType}, kmip_operations::{DecryptedData, Get, Import, Locate}, @@ -41,8 +41,8 @@ async fn test_cover_crypt_keys() -> KResult<()> { let owner = "cceyJhbGciOiJSUzI1Ni"; // - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -51,7 +51,7 @@ async fn test_cover_crypt_keys() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("confidential", EncryptionHint::Classic), @@ -222,8 +222,8 @@ async fn test_abe_encrypt_decrypt() -> KResult<()> { let owner = "cceyJhbGciOiJSUzI1Ni"; let nonexistent_owner = "invalid_owner"; // - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -232,7 +232,7 @@ async fn test_abe_encrypt_decrypt() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("confidential", EncryptionHint::Classic), @@ -439,8 +439,8 @@ async fn test_abe_json_access() -> KResult<()> { let kms = Arc::new(KMSServer::instantiate(ServerParams::try_from(&clap_config).await?).await?); let owner = "cceyJhbGciOiJSUzI1Ni"; // - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -449,7 +449,7 @@ async fn test_abe_json_access() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("confidential", EncryptionHint::Classic), @@ -536,8 +536,8 @@ async fn test_import_decrypt() -> KResult<()> { let kms = Arc::new(KMSServer::instantiate(ServerParams::try_from(&clap_config).await?).await?); let owner = "cceyJhbGciOiJSUzI1Ni"; - let mut policy = Policy::new(10); - policy.add_axis(PolicyAxis::new( + let mut policy = Policy::new(); + policy.add_dimension(DimensionBuilder::new( "Department", vec![ ("MKG", EncryptionHint::Classic), @@ -546,7 +546,7 @@ async fn test_import_decrypt() -> KResult<()> { ], false, ))?; - policy.add_axis(PolicyAxis::new( + policy.add_dimension(DimensionBuilder::new( "Level", vec![ ("confidential", EncryptionHint::Classic), diff --git a/crate/utils/Cargo.toml b/crate/utils/Cargo.toml index 2bc1d59b1..aef3f3e79 100644 --- a/crate/utils/Cargo.toml +++ b/crate/utils/Cargo.toml @@ -11,7 +11,6 @@ curve25519 = [] [dependencies] argon2 = "0.5.0" cloudproof = { workspace = true } -cosmian_crypto_core = "9.3.0" # Must use `crypto_core` from `cloudproof_rust` reexport cosmian_kmip = { path = "../kmip" } num-bigint-dig = { workspace = true } openssl = { workspace = true } diff --git a/crate/utils/src/crypto/cover_crypt/attributes.rs b/crate/utils/src/crypto/cover_crypt/attributes.rs index 139da1ece..2baa1a6dc 100644 --- a/crate/utils/src/crypto/cover_crypt/attributes.rs +++ b/crate/utils/src/crypto/cover_crypt/attributes.rs @@ -1,4 +1,4 @@ -use cloudproof::reexport::cover_crypt::abe_policy::{self, Policy}; +use cloudproof::reexport::cover_crypt::abe_policy::{self, EncryptionHint, Policy}; use cosmian_kmip::{ error::KmipError, kmip::{ @@ -6,6 +6,7 @@ use cosmian_kmip::{ kmip_types::{Attributes, VendorAttribute}, }, }; +use serde::{Deserialize, Serialize}; use crate::kmip_utils::VENDOR_ID_COSMIAN; @@ -15,6 +16,7 @@ pub const VENDOR_ATTR_COVER_CRYPT_ACCESS_POLICY: &str = "cover_crypt_access_poli pub const VENDOR_ATTR_COVER_CRYPT_HEADER_UID: &str = "cover_crypt_header_uid"; pub const VENDOR_ATTR_COVER_CRYPT_MASTER_PRIV_KEY_ID: &str = "cover_crypt_master_private_key_id"; pub const VENDOR_ATTR_COVER_CRYPT_MASTER_PUB_KEY_ID: &str = "cover_crypt_master_public_key_id"; +pub const VENDOR_ATTR_COVER_CRYPT_POLICY_EDIT_ACTION: &str = "cover_crypt_policy_edit_action"; /// Convert an policy to a vendor attribute pub fn policy_as_vendor_attribute(policy: &Policy) -> Result { @@ -239,3 +241,54 @@ pub fn header_uid_from_attributes(attributes: &Attributes) -> Result<&[u8], Kmip )) } } + +#[derive(Debug, Serialize, Deserialize)] +pub enum EditPolicyAction { + RotateAttributes(Vec), + ClearOldAttributeValues(Vec), + RemoveAttribute(Vec), + DisableAttribute(Vec), + AddAttribute(Vec<(abe_policy::Attribute, EncryptionHint)>), + RenameAttribute(Vec<(abe_policy::Attribute, String)>), +} + +/// Convert an edit policy action to a vendor attribute +pub fn edit_policy_action_as_vendor_attribute( + action: EditPolicyAction, +) -> Result { + Ok(VendorAttribute { + vendor_identification: VENDOR_ID_COSMIAN.to_owned(), + attribute_name: VENDOR_ATTR_COVER_CRYPT_POLICY_EDIT_ACTION.to_owned(), + attribute_value: serde_json::to_vec(&action).map_err(|e| { + KmipError::InvalidKmipValue( + ErrorReason::Invalid_Attribute_Value, + format!("failed serializing the CoverCrypt action: {e}"), + ) + })?, + }) +} + +/// Extract an edit `CoverCrypt` policy action from attributes. +/// +/// If Covercrypt attributes are specified without an `EditPolicyAction`, +/// a `RotateAttributes` action is returned by default to keep backward compatibility. +pub fn edit_policy_action_from_attributes( + attributes: &Attributes, +) -> Result { + if let Some(bytes) = attributes.get_vendor_attribute_value( + VENDOR_ID_COSMIAN, + VENDOR_ATTR_COVER_CRYPT_POLICY_EDIT_ACTION, + ) { + serde_json::from_slice::(bytes).map_err(|e| { + KmipError::InvalidKmipValue( + ErrorReason::Invalid_Attribute_Value, + format!("failed reading the CoverCrypt action from the attribute bytes: {e}"), + ) + }) + } else { + // Backward compatibility + Ok(EditPolicyAction::RotateAttributes( + attributes_from_attributes(attributes)?, + )) + } +} diff --git a/crate/utils/src/crypto/cover_crypt/kmip_requests.rs b/crate/utils/src/crypto/cover_crypt/kmip_requests.rs index b71a36cf2..fd34eec13 100644 --- a/crate/utils/src/crypto/cover_crypt/kmip_requests.rs +++ b/crate/utils/src/crypto/cover_crypt/kmip_requests.rs @@ -10,7 +10,8 @@ use cosmian_kmip::kmip::{ }; use super::attributes::{ - access_policy_as_vendor_attribute, attributes_as_vendor_attribute, policy_as_vendor_attribute, + access_policy_as_vendor_attribute, edit_policy_action_as_vendor_attribute, + policy_as_vendor_attribute, EditPolicyAction, }; use crate::{error::KmipUtilsError, kmip_utils::wrap_key_bytes, tagging::set_tags}; @@ -280,10 +281,11 @@ pub fn build_destroy_key_request(unique_identifier: &str) -> Result, + action: EditPolicyAction, ) -> Result { Ok(ReKeyKeyPair { private_key_unique_identifier: Some(master_private_key_unique_identifier.to_string()), @@ -291,9 +293,7 @@ pub fn build_rekey_keypair_request( object_type: Some(ObjectType::PrivateKey), cryptographic_algorithm: Some(CryptographicAlgorithm::CoverCrypt), key_format_type: Some(KeyFormatType::CoverCryptSecretKey), - vendor_attributes: Some(vec![attributes_as_vendor_attribute( - cover_crypt_policy_attributes, - )?]), + vendor_attributes: Some(vec![edit_policy_action_as_vendor_attribute(action)?]), ..Attributes::default() }), ..ReKeyKeyPair::default() diff --git a/crate/utils/src/crypto/cover_crypt/locate.rs b/crate/utils/src/crypto/cover_crypt/locate.rs index 7dec9dfb3..3d8c91395 100644 --- a/crate/utils/src/crypto/cover_crypt/locate.rs +++ b/crate/utils/src/crypto/cover_crypt/locate.rs @@ -54,14 +54,14 @@ pub fn compare_cover_crypt_attributes( let access_policy_attributes = access_policy.attributes(); let access_policy_axes: HashSet = access_policy_attributes .iter() - .map(|att| att.axis.clone()) + .map(|att| att.dimension.clone()) .collect(); // if a research attribute axis is not present in the access policy, // it means that the access policy accepts all values for that axis, // so there is an intersection if cover_crypt_attributes .iter() - .any(|attr| !access_policy_axes.contains(&attr.axis)) + .any(|attr| !access_policy_axes.contains(&attr.dimension)) { return Ok(true) } diff --git a/crate/utils/src/crypto/hybrid_encryption_system.rs b/crate/utils/src/crypto/hybrid_encryption_system.rs index e14649d7b..77390aff1 100644 --- a/crate/utils/src/crypto/hybrid_encryption_system.rs +++ b/crate/utils/src/crypto/hybrid_encryption_system.rs @@ -7,7 +7,7 @@ //! use std::sync::{Arc, Mutex}; -use cosmian_crypto_core::{ +use cloudproof::reexport::crypto_core::{ reexport::{ pkcs8::{DecodePrivateKey, DecodePublicKey}, rand_core::SeedableRng, diff --git a/crate/utils/src/error/mod.rs b/crate/utils/src/error/mod.rs index d20439950..822cfd25a 100644 --- a/crate/utils/src/error/mod.rs +++ b/crate/utils/src/error/mod.rs @@ -75,8 +75,8 @@ impl From for K } } -impl From for KmipUtilsError { - fn from(e: cosmian_crypto_core::reexport::pkcs8::Error) -> Self { +impl From for KmipUtilsError { + fn from(e: cloudproof::reexport::crypto_core::reexport::pkcs8::Error) -> Self { Self::ConversionError(e.to_string()) } } From d02975fabbd06dcbc4207c3e1e87010332724d24 Mon Sep 17 00:00:00 2001 From: Manuthor Date: Fri, 10 Nov 2023 10:56:05 +0100 Subject: [PATCH 19/19] chore: release 4.9.0 --- .pre-commit-config.yaml | 3 ++- CHANGELOG.md | 17 ++++++++++++++++- Cargo.lock | 14 +++++++------- crate/cli/Cargo.toml | 2 +- crate/client/Cargo.toml | 2 +- crate/kmip/Cargo.toml | 2 +- crate/logger/Cargo.toml | 2 +- crate/pyo3/Cargo.toml | 2 +- crate/server/Cargo.toml | 2 +- crate/utils/Cargo.toml | 2 +- delivery/Dockerfile.standalone | 2 +- documentation/docs/authentication.md | 4 ++-- documentation/docs/bootstrap.md | 2 +- documentation/docs/cli/cli.md | 2 +- documentation/docs/high_availability_mode.md | 6 +++--- documentation/docs/index.md | 6 +++--- documentation/docs/single_server_mode.md | 6 +++--- documentation/docs/tls.md | 4 ++-- documentation/docs/zero_trust.md | 2 +- 19 files changed, 49 insertions(+), 33 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41402a97d..3caac20b7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -132,7 +132,7 @@ repos: entry: bash -c 'cd documentation && mkdocs build --strict' - repo: https://github.com/Cosmian/git-hooks.git - rev: v1.0.16 + rev: v1.0.20 hooks: - id: cargo-format # - id: cargo-upgrade @@ -148,3 +148,4 @@ repos: - id: clippy-autofix-others - id: clippy-all-targets-all-features - id: cargo-format # in last du to clippy fixes + - id: docker-compose-down diff --git a/CHANGELOG.md b/CHANGELOG.md index db545304d..60371ca00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## [4.9.0] - 2023-11-10 + +### Bug Fixes + +- fix: migrate to num-bigint-dig for bigint (#85) + +### Ci + +- Test KMS inside a SGX machine + +### Features + +- Update Covercrypt version to support Policy V2 ([#63]) +- Generalize bulk operations using KMIP `Messages` structure + ## [4.8.2] - 2023-10-31 ### Bug Fixes @@ -12,7 +27,7 @@ All notable changes to this project will be documented in this file. ### Bug Fixes -Fix for [#64](https://github.com/Cosmian/kms/issues/64) +- Fix for [#64](https://github.com/Cosmian/kms/issues/64) ## [4.8.0] - 2023-10-07 diff --git a/Cargo.lock b/Cargo.lock index 4a8658554..f99941f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,7 +1118,7 @@ dependencies = [ [[package]] name = "cosmian_kmip" -version = "4.8.2" +version = "4.9.0" dependencies = [ "bitflags 2.3.2", "chrono", @@ -1136,7 +1136,7 @@ dependencies = [ [[package]] name = "cosmian_kms_cli" -version = "4.8.2" +version = "4.9.0" dependencies = [ "actix-rt", "actix-server", @@ -1173,7 +1173,7 @@ dependencies = [ [[package]] name = "cosmian_kms_client" -version = "4.8.2" +version = "4.9.0" dependencies = [ "base64 0.21.4", "cosmian_kmip", @@ -1196,7 +1196,7 @@ dependencies = [ [[package]] name = "cosmian_kms_python" -version = "4.8.2" +version = "4.9.0" dependencies = [ "cloudproof", "cosmian_kmip", @@ -1212,7 +1212,7 @@ dependencies = [ [[package]] name = "cosmian_kms_server" -version = "4.8.2" +version = "4.9.0" dependencies = [ "acme-lib", "actix-cors", @@ -1265,7 +1265,7 @@ dependencies = [ [[package]] name = "cosmian_kms_utils" -version = "4.8.2" +version = "4.9.0" dependencies = [ "argon2", "cloudproof", @@ -1280,7 +1280,7 @@ dependencies = [ [[package]] name = "cosmian_logger" -version = "4.8.2" +version = "4.9.0" dependencies = [ "tracing", "tracing-subscriber", diff --git a/crate/cli/Cargo.toml b/crate/cli/Cargo.toml index 06e0eac94..49b84145c 100644 --- a/crate/cli/Cargo.toml +++ b/crate/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_kms_cli" -version = "4.8.2" +version = "4.9.0" edition = "2021" license-file = "../../LICENSE.md" description = "CLI used to manage the Cosmian KMS." diff --git a/crate/client/Cargo.toml b/crate/client/Cargo.toml index 1546d990a..94c1d0218 100644 --- a/crate/client/Cargo.toml +++ b/crate/client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_kms_client" -version = "4.8.2" +version = "4.9.0" authors = ["Bruno Grieder "] edition = "2021" license-file = "../../LICENSE.md" diff --git a/crate/kmip/Cargo.toml b/crate/kmip/Cargo.toml index 3da07de4d..fb229af01 100644 --- a/crate/kmip/Cargo.toml +++ b/crate/kmip/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_kmip" -version = "4.8.2" +version = "4.9.0" edition = "2021" license-file = "../../LICENSE.md" diff --git a/crate/logger/Cargo.toml b/crate/logger/Cargo.toml index f656d2f07..e8a8f3566 100644 --- a/crate/logger/Cargo.toml +++ b/crate/logger/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_logger" -version = "4.8.2" +version = "4.9.0" authors = ["Emmanuel Coste "] edition = "2021" license-file = "../../LICENSE.md" diff --git a/crate/pyo3/Cargo.toml b/crate/pyo3/Cargo.toml index e8a5b1449..0469d5c79 100644 --- a/crate/pyo3/Cargo.toml +++ b/crate/pyo3/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_kms_python" -version = "4.8.2" +version = "4.9.0" authors = ["Hugo Rosenkranz-Costa "] edition = "2021" license-file = "../../LICENSE.md" diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml index caa58457d..99b203487 100644 --- a/crate/server/Cargo.toml +++ b/crate/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_kms_server" -version = "4.8.2" +version = "4.9.0" authors = ["Bruno Grieder "] edition = "2021" license-file = "../../LICENSE.md" diff --git a/crate/utils/Cargo.toml b/crate/utils/Cargo.toml index aef3f3e79..c75a33974 100644 --- a/crate/utils/Cargo.toml +++ b/crate/utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cosmian_kms_utils" -version = "4.8.2" +version = "4.9.0" authors = ["Bruno Grieder "] edition = "2021" license-file = "../../LICENSE.md" diff --git a/delivery/Dockerfile.standalone b/delivery/Dockerfile.standalone index e99481f2c..df259cb0e 100644 --- a/delivery/Dockerfile.standalone +++ b/delivery/Dockerfile.standalone @@ -1,6 +1,6 @@ FROM ubuntu:22.04 as builder -LABEL version="4.8.2" +LABEL version="4.9.0" LABEL name="Cosmian KMS docker container" ENV DEBIAN_FRONTEND=noninteractive diff --git a/documentation/docs/authentication.md b/documentation/docs/authentication.md index 5be973608..8db23c6a0 100644 --- a/documentation/docs/authentication.md +++ b/documentation/docs/authentication.md @@ -26,7 +26,7 @@ The server must be started using TLS, and the certificate used to verify the cli !!! info "Example client TLS authentication." ```sh - docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.8.2 \ + docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.9.0 \ --https-p12-file kms.server.p12 --https-p12-password password \ --authority-cert-file verifier.cert.pem ``` @@ -65,7 +65,7 @@ The KMS server JWT authentication is configured using three command line options Below is an example of a JWT configuration for the KMS server using Google as the authorization server. ```sh -docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.8.2 \ +docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.9.0 \ --jwt-issuer-uri=https://accounts.google.com \ --jwks-uri=https://www.googleapis.com/oauth2/v3/certs \ --jwt-audience=cosmian_kms diff --git a/documentation/docs/bootstrap.md b/documentation/docs/bootstrap.md index 832bc91ea..5a3a66033 100644 --- a/documentation/docs/bootstrap.md +++ b/documentation/docs/bootstrap.md @@ -11,7 +11,7 @@ When [running in a zero-trust environment](./zero_trust.md) inside a confidentia To start the KMS server in bootstrap mode, use the `--use-bootstrap-server` option: ```sh -docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.8.2 \ +docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.9.0 \ --use-bootstrap-server ``` diff --git a/documentation/docs/cli/cli.md b/documentation/docs/cli/cli.md index e158faa00..a337edecd 100644 --- a/documentation/docs/cli/cli.md +++ b/documentation/docs/cli/cli.md @@ -1,7 +1,7 @@ The `ckms` binary is a command line interface (CLI) used to manage cryptographic objects inside the KMS. !!! info "Download ckms" - Please download the latest version of the CLI for your Operating System from the [Cosmian public packages repository](https://package.cosmian.com/kms/4.8.2/) + Please download the latest version of the CLI for your Operating System from the [Cosmian public packages repository](https://package.cosmian.com/kms/4.9.0/) The CLI expects a configuration file to be located at `~/.cosmian/kms.json` where `~` is your home folder. diff --git a/documentation/docs/high_availability_mode.md b/documentation/docs/high_availability_mode.md index f1299fa52..cb3da06a6 100644 --- a/documentation/docs/high_availability_mode.md +++ b/documentation/docs/high_availability_mode.md @@ -53,7 +53,7 @@ e.g. ```sh docker run --rm -p 9998:9998 \ - --name kms ghcr.io/cosmian/kms:4.8.2 \ + --name kms ghcr.io/cosmian/kms:4.9.0 \ --database-type=postgresql \ --database-url=postgres://kms_user:kms_password@pgsql-server:5432/kms @@ -68,7 +68,7 @@ Example: ```sh docker run --rm -p 9998:9998 \ - --name kms ghcr.io/cosmian/kms:4.8.2 \ + --name kms ghcr.io/cosmian/kms:4.9.0 \ --database-type=redis-findex \ --database-url=redis://localhost:6379 \ --redis-master-password password \ @@ -105,7 +105,7 @@ Say the certificate is called `cert.p12` and is in a directory called `/certific ```sh docker run --rm -p 9998:9998 \ - --name kms ghcr.io/cosmian/kms:4.8.2 \ + --name kms ghcr.io/cosmian/kms:4.9.0 \ -v /certificate/cert.p12:/root/cosmian-kms/cert.p12 \ --database-type=mysql \ --database-url=mysql://mysql_server:3306/kms \ diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 3fe0f1778..25e211157 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -6,7 +6,7 @@ The Cosmian KMS is designed to [operate in **zero-trust** environments](./zero_t To quick-start a Cosmian KMS server on `http://localhost:9998` that stores its data inside the container, simply run ```sh - docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.8.2 + docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.9.0 ``` Check the Cosmian KMS server version @@ -78,7 +78,7 @@ The KMS has an easy-to-use command line interface client built for many operatin The KMS server is available as a Docker image on the [Cosmian public Docker repository](https://github.com/Cosmian/kms/pkgs/container/kms). -Raw binaries for multiple operating systems are also available on the [Cosmian public packages repository](https://package.cosmian.com/kms/4.8.2/) +Raw binaries for multiple operating systems are also available on the [Cosmian public packages repository](https://package.cosmian.com/kms/4.9.0/) #### Integrated with Cloudproof libraries @@ -91,7 +91,7 @@ The libraries are available in many languages, including Javascript, Java, Dart, Just like the [`ckms` Command Line Interface](./cli/cli.md), the KMS server has a built-in help system that can be accessed using the `--help` command line option. ```sh -docker run --rm ghcr.io/cosmian/kms:4.8.2 --help +docker run --rm ghcr.io/cosmian/kms:4.9.0 --help ``` The options are enabled on the docker command line or using the environment variables listed in the options help. diff --git a/documentation/docs/single_server_mode.md b/documentation/docs/single_server_mode.md index 90f775944..66d10cc6b 100644 --- a/documentation/docs/single_server_mode.md +++ b/documentation/docs/single_server_mode.md @@ -9,7 +9,7 @@ This configuration also supports user encrypted databases, a secure way to store To run in single server mode, using the defaults, run the container as follows: ```sh -docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.8.2 +docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.9.0 ``` The KMS will be available on `http://localhost:9998`, and the server will store its data inside the container in the `/root/cosmian-kms/sqlite-data` directory. @@ -21,7 +21,7 @@ To persist data between restarts, map the `/root/cosmian-kms/sqlite-data` path t ```sh docker run --rm -p 9998:9998 \ -v cosmian-kms:/root/cosmian-kms/sqlite-data \ - --name kms ghcr.io/cosmian/kms:4.8.2 + --name kms ghcr.io/cosmian/kms:4.9.0 ``` ### Using user encrypted databases @@ -31,7 +31,7 @@ To start the KMS server with user encrypted SQLite databases, pass the `--databa ```sh docker run --rm -p 9998:9998 \ -v cosmian-kms:/root/cosmian-kms/sqlite-data \ - --name kms ghcr.io/cosmian/kms:4.8.2 \ + --name kms ghcr.io/cosmian/kms:4.9.0 \ --database-type=sqlite-enc ``` diff --git a/documentation/docs/tls.md b/documentation/docs/tls.md index 7414965f4..d3080672c 100644 --- a/documentation/docs/tls.md +++ b/documentation/docs/tls.md @@ -29,7 +29,7 @@ Say the certificate is called `server.mydomain.com.p12`, is protected by the pas ```sh docker run --rm -p 443:9998 \ -v /certificate/server.mydomain.com.p12:/root/cosmian-kms/server.mydomain.com.p12 \ - --name kms ghcr.io/cosmian/kms:4.8.2 \ + --name kms ghcr.io/cosmian/kms:4.9.0 \ --database-type=mysql \ --database-url=mysql://mysql_server:3306/kms \ --https-p12-file=server.mydomain.com.p12 \ @@ -67,7 +67,7 @@ Example: docker run --rm -p 443:9998 \ -v cosmian-kms:/root/cosmian-kms/sqlite-data \ -v cosmian-kms-certs:/root/cosmian-kms/certbot-ssl \ - --name kms ghcr.io/cosmian/kms:4.8.2 \ + --name kms ghcr.io/cosmian/kms:4.9.0 \ --database-type=sqlite-enc \ --use-certbot \ --certbot-server-name server.mydomain.com \ diff --git a/documentation/docs/zero_trust.md b/documentation/docs/zero_trust.md index dd2181884..d20d98ae7 100644 --- a/documentation/docs/zero_trust.md +++ b/documentation/docs/zero_trust.md @@ -37,7 +37,7 @@ The KMS servers must be installed in confidential VMs and started in bootstrap m - To start the database server in bootstrap mode, use the `-use-bootstrap-server` option (see [bootstrap](./bootstrap.md) from more details) on the docker started in the confidential VM : ```bash -docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.8.2 --use-bootstrap-server +docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.9.0 --use-bootstrap-server ``` - To use the TLS generation using LetsEncrypt inside the confidential VM add the arguments described in [tls](./tls.md#using-the-certificates-bot)