Repository: Cosmian/kms (language: python)

Issue:
Build a generic database upgrade mechanism
A generic database upgrade mechanism on startup should be built.

This involves:

- Creating a `context` table with a single record holding the last version of the software run (when the table is not present, assume the version run is the one before the version implementing the change)
- On server startup:
  - the server checks if the software version is greater than the last version run
  - if no,  it simply starts
  - if yes,
    -  it looks for all upgrades to apply in order from the last version run to this version (this is done using a static function hardcoding the upgrade functions to call for each version) 
    - if there is any to run, it sets an `upgrading` flag on the `db state` field in the `context` table
    - it runs all the upgrades in order
    - it sets the flag from `upgrading` to `ready`
 - On every call to the DB, a check must be performed on the `db state` field to check if the DB is upgrading. If yes, calls should fail. Performance can be maintained using a secondary write-through cache of the `context` table

Upgrades should be programmed to resist being interrupted in the middle and resumed from start.
 



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build_rhel9.yml b/.github/workflows/build_rhel9.yml
index 7d5c41cc5..4dcd208e5 100644
--- a/.github/workflows/build_rhel9.yml
+++ b/.github/workflows/build_rhel9.yml
@@ -108,9 +108,7 @@ jobs:
           MYSQL_ROOT_PASSWORD: kms
           KMS_MYSQL_URL: mysql://root:kms@mariadb/kms
 
-          KMS_ENCLAVE_DIR_PATH: data/public
           KMS_SQLITE_PATH: data/shared
-          KMS_CERTBOT_SSL_PATH: data/private
 
           REDIS_HOST: redis
 
diff --git a/.gitignore b/.gitignore
index 232de6e0b..38e909ce4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 target/
 .cargo_check/
+.history/
 *nix
 *.swp
 TODO.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba9fd4c09..fff771c3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,40 @@
 
 All notable changes to this project will be documented in this file.
 
+## [4.18.0] - 2024-09-XX
+
+### 🚀 Features
+
+- Add ReKey KMIP operation ([#294](https://github.com/Cosmian/kms/pull/294))
+- Add API token authentication between server and clients ([#290](https://github.com/Cosmian/kms/pull/290))
+- Build a generic database upgrade mechanism ([#299](https://github.com/Cosmian/kms/pull/299))
+
+### 🐛 Bug Fixes
+
+- KMIP Attributes:
+  * In get_attributes, use attributes from ObjectWithMetadata instead of Object.Attributes ([#278](https://github.com/Cosmian/kms/pull/278))
+  * When inserting in db, force Object::Attributes to be synced with Attributes ([#279](https://github.com/Cosmian/kms/pull/279))
+- Certificates handling/tasks:
+  * **Validate** KMIP operation:
+    - Simplify getting CRLs and get returned errors ([#268](https://github.com/Cosmian/kms/pull/268))
+    - Validate certificate generation ([#283](https://github.com/Cosmian/kms/pull/283))
+    - Use certificate file path in ckms arguments ([#292](https://github.com/Cosmian/kms/pull/292))
+  * **Certify** KMIP operation: Server must sign x509 after adding X509 extensions ([#282](https://github.com/Cosmian/kms/pull/282))
+- Merge decrypt match in same function ([#295](https://github.com/Cosmian/kms/pull/295))
+- Fix Public RSA Key size in get attributes ([#275](https://github.com/Cosmian/kms/pull/275))
+- RUSTSEC:
+  * **RUSTSEC-2024-0357**: MemBio::get_buf has undefined behavior with empty buffers: upgrade crate `openssl` from 1.0.64 to 1.0.66 ([#280](https://github.com/Cosmian/kms/pull/280))
+  * **RUSTSEC-2024-0363**: Binary Protocol Misinterpretation caused by Truncating or Overflowing Casts: bump sqlx to 0.8.1 ([#291](https://github.com/Cosmian/kms/pull/291) and [#297](https://github.com/Cosmian/kms/pull/297))
+
+### ⚙️ Miscellaneous Tasks
+
+- **clippy** tasks:
+  * Only expose pub functions that need to be public ([#277](https://github.com/Cosmian/kms/pull/277))
+  * Hardcode clippy lints ([#293](https://github.com/Cosmian/kms/pull/293))
+- Rename MacOS artifacts giving CPU architecture
+- Configure `ckms` to build reqwest with minimal idle connections reuse ([#272](https://github.com/Cosmian/kms/pull/272))
+- Do not delete tags if none are provided ([#276](https://github.com/Cosmian/kms/pull/276))
+
 ## [4.17.0] - 2024-07-05
 
 ### 🚀 Features
diff --git a/Cargo.lock b/Cargo.lock
index 564c9863d..40fa107de 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1404,6 +1404,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sqlx",
+ "tempfile",
  "thiserror",
  "time",
  "tokio",
@@ -1413,6 +1414,7 @@ dependencies = [
  "tracing-subscriber",
  "url",
  "uuid",
+ "version-compare",
  "x509-parser",
  "zeroize",
 ]
@@ -4992,6 +4994,12 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
+[[package]]
+name = "version-compare"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
+
 [[package]]
 name = "version_check"
 version = "0.9.5"
diff --git a/crate/cli/src/actions/access.rs b/crate/cli/src/actions/access.rs
index b073e51ba..6d924cf76 100644
--- a/crate/cli/src/actions/access.rs
+++ b/crate/cli/src/actions/access.rs
@@ -21,6 +21,15 @@ pub enum AccessAction {
 }
 
 impl AccessAction {
+    /// Processes the access action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - The KMS client used for the action.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if there was a problem running the action.
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         match self {
             Self::Grant(action) => action.run(kms_rest_client).await?,
@@ -58,6 +67,16 @@ pub struct GrantAccess {
 }
 
 impl GrantAccess {
+    /// Runs the `GrantAccess` action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let access = Access {
             unique_identifier: Some(UniqueIdentifier::TextString(self.object_uid.clone())),
@@ -104,6 +123,16 @@ pub struct RevokeAccess {
 }
 
 impl RevokeAccess {
+    /// Runs the `RevokeAccess` action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let access = Access {
             unique_identifier: Some(UniqueIdentifier::TextString(self.object_uid.clone())),
@@ -138,6 +167,16 @@ pub struct ListAccessesGranted {
 }
 
 impl ListAccessesGranted {
+    /// Runs the `ListAccessesGranted` action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let accesses = kms_rest_client
             .list_access(&self.object_uid)
@@ -164,6 +203,16 @@ impl ListAccessesGranted {
 pub struct ListOwnedObjects;
 
 impl ListOwnedObjects {
+    /// Runs the `ListOwnedObjects` action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let objects = kms_rest_client
             .list_owned_objects()
@@ -190,6 +239,16 @@ impl ListOwnedObjects {
 pub struct ListAccessRightsObtained;
 
 impl ListAccessRightsObtained {
+    /// Runs the `ListAccessRightsObtained` action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let objects = kms_rest_client
             .list_access_rights_obtained()
diff --git a/crate/cli/src/actions/certificates/mod.rs b/crate/cli/src/actions/certificates/mod.rs
index 1557454d9..08931ee15 100644
--- a/crate/cli/src/actions/certificates/mod.rs
+++ b/crate/cli/src/actions/certificates/mod.rs
@@ -36,6 +36,16 @@ pub enum CertificatesCommands {
 }
 
 impl CertificatesCommands {
+    /// Process the `Certificates` main commands.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn process(&self, client_connector: &KmsClient) -> CliResult<()> {
         match self {
             Self::Certify(action) => action.run(client_connector).await,
diff --git a/crate/cli/src/actions/console.rs b/crate/cli/src/actions/console.rs
index f2d1a94ca..885d1cee0 100644
--- a/crate/cli/src/actions/console.rs
+++ b/crate/cli/src/actions/console.rs
@@ -90,53 +90,78 @@ impl Stdout {
         self.object_owned = Some(object_owned);
     }
 
+    /// Writes the output to the console.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if there is an issue with writing to the console.
     pub fn write(&self) -> CliResult<()> {
+        // Check if the output format should be JSON
         let json_format_from_env = std::env::var(KMS_CLI_FORMAT)
             .unwrap_or_else(|_| CLI_DEFAULT_FORMAT.to_string())
             .to_lowercase()
             == CLI_JSON_FORMAT;
 
         if json_format_from_env {
+            // Serialize the output as JSON and print it
             let console_stdout = serde_json::to_string_pretty(&self)?;
             println!("{console_stdout}");
         } else {
+            // Print the output in text format
             if !self.stdout.is_empty() {
                 println!("{}", self.stdout);
             }
 
+            // Print the unique identifier if present
             if let Some(id) = &self.unique_identifier {
                 println!("\t  Unique identifier: {id}");
             }
+
+            // Print the list of unique identifiers if present
             if let Some(ids) = &self.unique_identifiers {
                 for id in ids {
                     println!("{id}");
                 }
             }
+
+            // Print the public key unique identifier if present
             if let Some(id) = &self.public_key_unique_identifier {
                 println!("\t  Public key unique identifier: {id}");
             }
+
+            // Print the private key unique identifier if present
             if let Some(id) = &self.private_key_unique_identifier {
                 println!("\t  Private key unique identifier: {id}");
             }
+
+            // Print the attributes if present
             if let Some(attributes) = &self.attributes {
                 let json = serde_json::to_string_pretty(attributes)?;
                 println!("{json}");
             }
+
+            // Print the list of accesses if present
             if let Some(accesses) = &self.accesses {
                 for access in accesses {
                     println!(" - {}: {:?}", access.user_id, access.operations);
                 }
             }
+
+            // Print the list of access rights obtained if present
             if let Some(access_rights_obtained) = &self.access_rights_obtained {
                 for access in access_rights_obtained {
                     println!("{access}");
                 }
             }
+
+            // Print the list of objects owned if present
             if let Some(object_owned) = &self.object_owned {
                 for obj in object_owned {
                     println!("{obj}");
                 }
             }
+
+            // Print the list of tags if present
             if let Some(t) = &self.tags {
                 if !t.is_empty() {
                     println!("\n  Tags:");
diff --git a/crate/cli/src/actions/cover_crypt/decrypt.rs b/crate/cli/src/actions/cover_crypt/decrypt.rs
index 8a37ff9df..7e7696563 100644
--- a/crate/cli/src/actions/cover_crypt/decrypt.rs
+++ b/crate/cli/src/actions/cover_crypt/decrypt.rs
@@ -107,13 +107,13 @@ impl DecryptAction {
                 &metadata_and_cleartext.plaintext,
                 &self.input_files,
                 self.output_file.as_ref(),
-            )?
+            )?;
         } else {
             write_single_decrypted_data(
                 &metadata_and_cleartext.plaintext,
                 &self.input_files[0],
                 self.output_file.as_ref(),
-            )?
+            )?;
         }
         Ok(())
     }
diff --git a/crate/cli/src/actions/cover_crypt/encrypt.rs b/crate/cli/src/actions/cover_crypt/encrypt.rs
index 55bc46bad..46fd804c2 100644
--- a/crate/cli/src/actions/cover_crypt/encrypt.rs
+++ b/crate/cli/src/actions/cover_crypt/encrypt.rs
@@ -26,7 +26,7 @@ pub struct EncryptAction {
     input_files: Vec<PathBuf>,
 
     /// The encryption policy to encrypt the file with
-    /// Example: "department::marketing && level::confidential"`
+    /// Example: "`department::marketing` && `level::confidential`"
     #[clap(required = true)]
     encryption_policy: String,
 
@@ -105,9 +105,9 @@ impl EncryptAction {
 
         // Write the encrypted data
         if cryptographic_algorithm == CryptographicAlgorithm::CoverCryptBulk {
-            write_bulk_encrypted_data(&data, &self.input_files, self.output_file.as_ref())?
+            write_bulk_encrypted_data(&data, &self.input_files, self.output_file.as_ref())?;
         } else {
-            write_single_encrypted_data(&data, &self.input_files[0], self.output_file.as_ref())?
+            write_single_encrypted_data(&data, &self.input_files[0], self.output_file.as_ref())?;
         }
         Ok(())
     }
diff --git a/crate/cli/src/actions/cover_crypt/keys/create_user_key.rs b/crate/cli/src/actions/cover_crypt/keys/create_user_key.rs
index e746301f2..274e3ed3a 100644
--- a/crate/cli/src/actions/cover_crypt/keys/create_user_key.rs
+++ b/crate/cli/src/actions/cover_crypt/keys/create_user_key.rs
@@ -54,7 +54,7 @@ pub struct CreateUserKeyAction {
 
     /// The access policy as a boolean expression combining policy attributes.
     ///
-    /// Example: "(Department::HR || Department::MKG) && Security Level::Confidential"
+    /// Example: "(`Department::HR` || `Department::MKG`) && Security `Level::Confidential`"
     #[clap(required = true)]
     access_policy: String,
 
diff --git a/crate/cli/src/actions/cover_crypt/mod.rs b/crate/cli/src/actions/cover_crypt/mod.rs
index a16d2101b..6b05eb65f 100644
--- a/crate/cli/src/actions/cover_crypt/mod.rs
+++ b/crate/cli/src/actions/cover_crypt/mod.rs
@@ -25,6 +25,16 @@ pub enum CovercryptCommands {
 }
 
 impl CovercryptCommands {
+    /// Process the Covercrypt command and execute the corresponding action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - The KMS client used for communication with the KMS service.
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if any of the underlying actions encounter an error.
+    ///
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         match self {
             Self::Policy(command) => command.process(kms_rest_client).await?,
diff --git a/crate/cli/src/actions/cover_crypt/policy.rs b/crate/cli/src/actions/cover_crypt/policy.rs
index b49dfa8bf..bae05025e 100644
--- a/crate/cli/src/actions/cover_crypt/policy.rs
+++ b/crate/cli/src/actions/cover_crypt/policy.rs
@@ -64,7 +64,7 @@ impl PolicyCommands {
             Self::View(action) => action.run(kms_rest_client).await?,
             Self::Specs(action) => action.run(kms_rest_client).await?,
             Self::Binary(action) => action.run(kms_rest_client).await?,
-            Self::Create(action) => action.run().await?,
+            Self::Create(action) => action.run()?,
             Self::AddAttribute(action) => action.run(kms_rest_client).await?,
             Self::RemoveAttribute(action) => action.run(kms_rest_client).await?,
             Self::DisableAttribute(action) => action.run(kms_rest_client).await?,
@@ -125,7 +125,7 @@ pub struct CreateAction {
 }
 
 impl CreateAction {
-    pub async fn run(&self) -> CliResult<()> {
+    pub fn run(&self) -> CliResult<()> {
         // Parse the json policy file
         let policy = policy_from_json_file(&self.policy_specifications_file)?;
 
@@ -524,6 +524,7 @@ impl RemoveAttributeAction {
 }
 
 #[cfg(test)]
+#[allow(clippy::items_after_statements)]
 mod tests {
     use std::path::PathBuf;
 
diff --git a/crate/cli/src/actions/elliptic_curves/mod.rs b/crate/cli/src/actions/elliptic_curves/mod.rs
index b81650dfe..b52229430 100644
--- a/crate/cli/src/actions/elliptic_curves/mod.rs
+++ b/crate/cli/src/actions/elliptic_curves/mod.rs
@@ -24,6 +24,16 @@ pub enum EllipticCurveCommands {
 }
 
 impl EllipticCurveCommands {
+    /// Runs the `EllipticCurveCommands` main commands.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         match self {
             Self::Keys(command) => command.process(kms_rest_client).await?,
diff --git a/crate/cli/src/actions/google/mod.rs b/crate/cli/src/actions/google/mod.rs
index 9ddc147a5..1520e6a72 100644
--- a/crate/cli/src/actions/google/mod.rs
+++ b/crate/cli/src/actions/google/mod.rs
@@ -20,6 +20,16 @@ pub enum GoogleCommands {
 }
 
 impl GoogleCommands {
+    /// Process the Google command by delegating the execution to the appropriate subcommand.
+    ///
+    /// # Arguments
+    ///
+    /// * `conf_path` - The path to the configuration file.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `CliResult` indicating the success or failure of the command.
+    ///
     pub async fn process(&self, conf_path: &PathBuf) -> CliResult<()> {
         match self {
             Self::Keypairs(command) => command.process(conf_path).await?,
diff --git a/crate/cli/src/actions/login.rs b/crate/cli/src/actions/login.rs
index fbcbd171c..48c4f27e6 100644
--- a/crate/cli/src/actions/login.rs
+++ b/crate/cli/src/actions/login.rs
@@ -48,6 +48,29 @@ use crate::{
 pub struct LoginAction;
 
 impl LoginAction {
+    /// This function processes the login action.
+    /// It loads the client configuration from the specified path, retrieves the `OAuth2` configuration,
+    /// initializes the login state, prompts the user to browse to the authorization URL,
+    /// finalizes the login process by receiving the authorization code and exchanging it for an access token,
+    /// updates the configuration with the access token, and saves the configuration to the specified path.
+    ///
+    /// # Arguments
+    ///
+    /// * `conf_path` - The path to the client configuration file.
+    ///
+    /// # Errors
+    ///
+    /// This function can return a `CliError` in the following cases:
+    ///
+    /// * The `login` command requires an Identity Provider (`IdP`) that must be configured in the `oauth2_conf` object in the client configuration file.
+    /// * The client configuration file cannot be loaded.
+    /// * The `OAuth2` configuration is missing or invalid in the client configuration file.
+    /// * The authorization URL cannot be parsed.
+    /// * The authorization code is not received or does not match the CSRF token.
+    /// * The access token cannot be requested from the Identity Provider.
+    /// * The token exchange request fails.
+    /// * The token exchange response cannot be parsed.
+    /// * The client configuration cannot be updated or saved.
     pub async fn process(&self, conf_path: &PathBuf) -> CliResult<()> {
         let mut conf = ClientConf::load(conf_path)?;
         let oauth2_conf = conf.oauth2_conf.as_ref().ok_or_else(|| {
@@ -189,6 +212,18 @@ impl LoginState {
     /// This function should be called immediately after the user has been instructed to browse to the authorization URL.
     /// It starts a server on localhost:17899 and waits for the authorization code to be received
     /// from the browser window. Once the code is received, the server is closed and the code is returned.
+    ///
+    /// # Errors
+    ///
+    /// This function can return a `CliError` in the following cases:
+    ///
+    /// * The authorization code, state, or other parameters are not received from the redirect URL.
+    /// * The received state does not match the CSRF token.
+    /// * The authorization code is not received on authentication.
+    /// * The code received on authentication does not match the CSRF token.
+    /// * The access token cannot be requested from the Identity Provider.
+    /// * The token exchange request fails.
+    /// * The token exchange response cannot be parsed.
     pub async fn finalize(&self) -> CliResult<String> {
         // recover the authorization code, state and other parameters from the redirect URL
         let auth_parameters = Self::receive_authorization_parameters()?;
@@ -294,6 +329,20 @@ pub struct OAuthResponse {
 /// not in the `access_token` field.
 ///
 /// For Google see: <https://developers.google.com/identity/openid-connect/openid-connect#obtainuserinfo>
+///
+/// # Arguments
+///
+/// * `login_config` - The `Oauth2LoginConfig` containing the client configuration.
+/// * `redirect_url` - The redirect URL used in the `OAuth2` flow.
+/// * `pkce_verifier` - The PKCE code verifier used in the `OAuth2` flow.
+/// * `authorization_code` - The authorization code received from the Identity Provider.
+///
+/// # Errors
+///
+/// This function can return a `CliError` in the following cases:
+///
+/// * The token exchange request fails.
+/// * The token exchange response cannot be parsed.
 pub async fn request_token(
     login_config: &Oauth2LoginConfig,
     redirect_url: &Url,
diff --git a/crate/cli/src/actions/logout.rs b/crate/cli/src/actions/logout.rs
index 0cc5c0d4f..3e7b8368f 100644
--- a/crate/cli/src/actions/logout.rs
+++ b/crate/cli/src/actions/logout.rs
@@ -13,6 +13,16 @@ use crate::error::result::CliResult;
 pub struct LogoutAction;
 
 impl LogoutAction {
+    /// Process the logout action.
+    ///
+    /// # Arguments
+    ///
+    /// * `conf_path` - The path to the ckms configuration file.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if there is an issue loading or saving the configuration file.
+    ///
     pub fn process(&self, conf_path: &PathBuf) -> CliResult<()> {
         let mut conf = ClientConf::load(conf_path)?;
         conf.kms_access_token = None;
diff --git a/crate/cli/src/actions/markdown.rs b/crate/cli/src/actions/markdown.rs
index 896e01d55..52ca3252a 100644
--- a/crate/cli/src/actions/markdown.rs
+++ b/crate/cli/src/actions/markdown.rs
@@ -13,6 +13,11 @@ pub struct MarkdownAction {
 }
 
 impl MarkdownAction {
+    /// Process the given command and generate the markdown documentation.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if there is an issue creating or writing to the markdown file.
     pub fn process(&self, cmd: &Command) -> CliResult<()> {
         let mut output = String::new();
         writeln!(
diff --git a/crate/cli/src/actions/new_database.rs b/crate/cli/src/actions/new_database.rs
index d925b8e1f..af47033fa 100644
--- a/crate/cli/src/actions/new_database.rs
+++ b/crate/cli/src/actions/new_database.rs
@@ -19,6 +19,16 @@ use crate::error::result::{CliResult, CliResultHelper};
 pub struct NewDatabaseAction;
 
 impl NewDatabaseAction {
+    /// Process the `NewDatabaseAction` by querying the KMS to get a new database.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - The KMS client used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the query execution on the KMS server fails.
+    ///
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         // Query the KMS to get a new database
         let token = kms_rest_client
diff --git a/crate/cli/src/actions/rsa/mod.rs b/crate/cli/src/actions/rsa/mod.rs
index e4608c37b..943926838 100644
--- a/crate/cli/src/actions/rsa/mod.rs
+++ b/crate/cli/src/actions/rsa/mod.rs
@@ -24,6 +24,15 @@ pub enum RsaCommands {
 }
 
 impl RsaCommands {
+    /// Process the RSA command by executing the corresponding action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - A reference to the KMS client used for communication with the KMS service.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if there is an issue executing the command.
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         match self {
             Self::Keys(command) => command.process(kms_rest_client).await?,
diff --git a/crate/cli/src/actions/shared/export_key.rs b/crate/cli/src/actions/shared/export_key.rs
index bb41001de..a2b942785 100644
--- a/crate/cli/src/actions/shared/export_key.rs
+++ b/crate/cli/src/actions/shared/export_key.rs
@@ -108,6 +108,14 @@ pub struct ExportKeyAction {
 
 impl ExportKeyAction {
     /// Export a key from the KMS
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if:
+    ///
+    /// - Either `--key-id` or one or more `--tag` is not specified.
+    /// - There is a server error while exporting the object.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let id = if let Some(key_id) = &self.key_id {
             key_id.clone()
diff --git a/crate/cli/src/actions/shared/get_attributes.rs b/crate/cli/src/actions/shared/get_attributes.rs
index fa1c277b0..3401687fd 100644
--- a/crate/cli/src/actions/shared/get_attributes.rs
+++ b/crate/cli/src/actions/shared/get_attributes.rs
@@ -78,6 +78,21 @@ pub struct GetAttributesAction {
 }
 
 impl GetAttributesAction {
+    /// Get the KMIP object attributes and tags.
+    ///
+    /// When using tags to retrieve the object, rather than the object id,
+    /// an error is returned if multiple objects matching the tags are found.
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if:
+    ///
+    /// - The `--id` or one or more `--tag` options is not specified.
+    /// - There is an error serializing the tags to a string.
+    /// - There is an error performing the Get Attributes request.
+    /// - There is an error serializing the attributes to JSON.
+    /// - There is an error writing the attributes to the output file.
+    /// - There is an error writing to the console.
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         trace!("GetAttributesAction: {:?}", self);
         let id = if let Some(key_id) = &self.id {
diff --git a/crate/cli/src/actions/shared/import_key.rs b/crate/cli/src/actions/shared/import_key.rs
index 864010d3f..7eead374d 100644
--- a/crate/cli/src/actions/shared/import_key.rs
+++ b/crate/cli/src/actions/shared/import_key.rs
@@ -111,6 +111,23 @@ pub struct ImportKeyAction {
 }
 
 impl ImportKeyAction {
+    /// Run the import key action.
+    ///
+    /// # Errors
+    ///
+    /// This function can return a [`CliError`] if an error occurs during the import process.
+    ///
+    /// Possible error cases include:
+    ///
+    /// - Failed to read the key file.
+    /// - Failed to parse the key file in the specified format.
+    /// - Invalid key format specified.
+    /// - Failed to assign cryptographic usage mask.
+    /// - Failed to generate import attributes.
+    /// - Failed to import the key.
+    /// - Failed to write the response to stdout.
+    ///
+    /// [`CliError`]: ../error/result/enum.CliError.html
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let cryptographic_usage_mask = self
             .key_usage
diff --git a/crate/cli/src/actions/shared/locate.rs b/crate/cli/src/actions/shared/locate.rs
index 992a7f05e..1cc10c664 100644
--- a/crate/cli/src/actions/shared/locate.rs
+++ b/crate/cli/src/actions/shared/locate.rs
@@ -94,6 +94,10 @@ pub struct LocateObjectsAction {
 
 impl LocateObjectsAction {
     /// Export a key from the KMS
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if there is a problem communicating with the KMS or if the requested key cannot be located.
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let mut attributes = Attributes::default();
 
diff --git a/crate/cli/src/actions/shared/unwrap_key.rs b/crate/cli/src/actions/shared/unwrap_key.rs
index 069ac1892..b61088dc7 100644
--- a/crate/cli/src/actions/shared/unwrap_key.rs
+++ b/crate/cli/src/actions/shared/unwrap_key.rs
@@ -68,6 +68,18 @@ pub struct UnwrapKeyAction {
 
 impl UnwrapKeyAction {
     /// Export a key from the KMS
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if:
+    ///
+    /// - The key file cannot be read.
+    /// - The unwrap key fails to decode from base64.
+    /// - The unwrapping key fails to be created.
+    /// - The unwrapping key fails to unwrap the key.
+    /// - The output file fails to be written.
+    /// - The console output fails to be written.
+    ///
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         // read the key file
         let mut object = read_object_from_json_ttlv_file(&self.key_file_in)?;
diff --git a/crate/cli/src/actions/shared/wrap_key.rs b/crate/cli/src/actions/shared/wrap_key.rs
index 5333eb4e8..6acc2dbbd 100644
--- a/crate/cli/src/actions/shared/wrap_key.rs
+++ b/crate/cli/src/actions/shared/wrap_key.rs
@@ -62,6 +62,21 @@ pub struct WrapKeyAction {
 }
 
 impl WrapKeyAction {
+    /// Run the wrap key action.
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if:
+    ///
+    /// - The key file cannot be read.
+    /// - The key is already wrapped and cannot be wrapped again.
+    /// - The wrap key cannot be decoded from base64.
+    /// - The wrap password cannot be derived into a symmetric key.
+    /// - The wrap key cannot be exported from the KMS.
+    /// - The wrap key file cannot be read.
+    /// - The key block cannot be wrapped with the wrapping key.
+    /// - The wrapped key object cannot be written to the output file.
+    /// - The console output cannot be written.
     pub async fn run(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         // read the key file
         let mut object = read_object_from_json_ttlv_file(&self.key_file_in)?;
diff --git a/crate/cli/src/actions/symmetric/mod.rs b/crate/cli/src/actions/symmetric/mod.rs
index 5e8d7c850..8655fad5a 100644
--- a/crate/cli/src/actions/symmetric/mod.rs
+++ b/crate/cli/src/actions/symmetric/mod.rs
@@ -18,6 +18,16 @@ pub enum SymmetricCommands {
 }
 
 impl SymmetricCommands {
+    /// Process the symmetric command and execute the corresponding action.
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if any of the underlying actions encounter an error.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - The KMS client used for communication with the KMS service.
+    ///
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         match self {
             Self::Keys(command) => command.process(kms_rest_client).await?,
diff --git a/crate/cli/src/actions/version.rs b/crate/cli/src/actions/version.rs
index 217f2e14a..0f25e8529 100644
--- a/crate/cli/src/actions/version.rs
+++ b/crate/cli/src/actions/version.rs
@@ -10,6 +10,15 @@ use crate::error::result::{CliResult, CliResultHelper};
 pub struct ServerVersionAction;
 
 impl ServerVersionAction {
+    /// Process the server version action.
+    ///
+    /// # Arguments
+    ///
+    /// * `kms_rest_client` - The KMS client instance used to communicate with the KMS server.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the version query fails or if there is an issue writing to the console.
     pub async fn process(&self, kms_rest_client: &KmsClient) -> CliResult<()> {
         let version = kms_rest_client
             .version()
diff --git a/crate/cli/src/error/result.rs b/crate/cli/src/error/result.rs
index 233aeb04d..1ea6d751e 100644
--- a/crate/cli/src/error/result.rs
+++ b/crate/cli/src/error/result.rs
@@ -6,9 +6,27 @@ use super::CliError;
 
 pub type CliResult<R> = Result<R, CliError>;
 
+/// Trait for providing helper methods for `CliResult`.
 pub trait CliResultHelper<T> {
+    /// Sets the reason for the error.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `CliResult` with the specified `ErrorReason`.
     fn reason(self, reason: ErrorReason) -> CliResult<T>;
+
+    /// Sets the context for the error.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `CliResult` with the specified context.
     fn context(self, context: &str) -> CliResult<T>;
+
+    /// Sets the context for the error using a closure.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `CliResult` with the context returned by the closure.
     fn with_context<D, O>(self, op: O) -> CliResult<T>
     where
         D: Display + Send + Sync + 'static,
diff --git a/crate/cli/src/lib.rs b/crate/cli/src/lib.rs
index 649d3f3d1..026dedc58 100644
--- a/crate/cli/src/lib.rs
+++ b/crate/cli/src/lib.rs
@@ -6,6 +6,7 @@
     let_underscore,
     rust_2024_compatibility,
     unreachable_pub,
+    unused,
     clippy::all,
     clippy::suspicious,
     clippy::complexity,
@@ -16,7 +17,6 @@
 )]
 #![allow(
     clippy::module_name_repetitions,
-    clippy::missing_errors_doc,
     clippy::too_many_lines,
     clippy::cargo_common_metadata,
     clippy::multiple_crate_versions
diff --git a/crate/kmip/src/crypto/symmetric/aead.rs b/crate/kmip/src/crypto/symmetric/aead.rs
index b118bc0d5..cd6089c8b 100644
--- a/crate/kmip/src/crypto/symmetric/aead.rs
+++ b/crate/kmip/src/crypto/symmetric/aead.rs
@@ -104,7 +104,7 @@ impl AeadCipher {
             CryptographicAlgorithm::ChaCha20 => {
                 if block_cipher_mode.is_some() {
                     kmip_bail!(KmipError::NotSupported(
-                        "ChaCha20 is only supported with Pooly1305. Do not specify the Block \
+                        "ChaCha20 is only supported with Poly1305. Do not specify the Block \
                          Cipher Mode"
                             .to_owned()
                     ));
diff --git a/crate/kmip/src/crypto/wrap/unwrap_key.rs b/crate/kmip/src/crypto/wrap/unwrap_key.rs
index 9b6b2089e..07799eb6e 100644
--- a/crate/kmip/src/crypto/wrap/unwrap_key.rs
+++ b/crate/kmip/src/crypto/wrap/unwrap_key.rs
@@ -157,7 +157,7 @@ fn unwrap_with_private_key(
     match private_key.id() {
         Id::RSA => unwrap_with_rsa(private_key, key_wrapping_data, ciphertext),
         #[cfg(not(feature = "fips"))]
-        Id::EC | Id::X25519 | Id::ED25519 => ecies_decrypt(&private_key, ciphertext),
+        Id::EC | Id::X25519 | Id::ED25519 => ecies_decrypt(private_key, ciphertext),
         other => {
             kmip_bail!(
                 "Unable to wrap key: wrapping public key type not supported: {:?}",
diff --git a/crate/kmip/src/crypto/wrap/wrap_key.rs b/crate/kmip/src/crypto/wrap/wrap_key.rs
index 52a1caf2e..00f1912a7 100644
--- a/crate/kmip/src/crypto/wrap/wrap_key.rs
+++ b/crate/kmip/src/crypto/wrap/wrap_key.rs
@@ -197,7 +197,7 @@ fn wrap_with_public_key(
     match public_key.id() {
         Id::RSA => wrap_with_rsa(public_key, key_wrapping_data, key_to_wrap),
         #[cfg(not(feature = "fips"))]
-        Id::EC | Id::X25519 | Id::ED25519 => ecies_encrypt(&public_key, key_to_wrap),
+        Id::EC | Id::X25519 | Id::ED25519 => ecies_encrypt(public_key, key_to_wrap),
         other => Err(kmip_error!(
             "Unable to wrap key: wrapping public key type not supported: {other:?}"
         )),
diff --git a/crate/kmip/src/openssl/private_key.rs b/crate/kmip/src/openssl/private_key.rs
index 7b8ddf55e..ab30964d5 100644
--- a/crate/kmip/src/openssl/private_key.rs
+++ b/crate/kmip/src/openssl/private_key.rs
@@ -482,7 +482,7 @@ mod tests {
     fn test_private_key_conversion_pkcs(
         private_key: &PKey<Private>,
         id: Id,
-        keysize: u32,
+        key_size: u32,
         kft: KeyFormatType,
     ) {
         #[cfg(feature = "fips")]
@@ -511,14 +511,14 @@ mod tests {
         if kft == KeyFormatType::PKCS8 {
             let private_key_ = PKey::private_key_from_pkcs8(&key_value).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key_.private_key_to_pkcs8().unwrap(),
                 key_value.to_vec()
             );
             let private_key_ = kmip_private_key_to_openssl(&object_).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key_.private_key_to_pkcs8().unwrap(),
                 key_value.to_vec()
@@ -526,14 +526,14 @@ mod tests {
         } else {
             let private_key_ = PKey::private_key_from_der(&key_value).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key_.private_key_to_der().unwrap(),
                 key_value.to_vec()
             );
             let private_key_ = kmip_private_key_to_openssl(&object_).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key_.private_key_to_der().unwrap(),
                 key_value.to_vec()
@@ -541,7 +541,7 @@ mod tests {
         }
     }
 
-    fn test_private_key_conversion_sec1(private_key: &PKey<Private>, id: Id, keysize: u32) {
+    fn test_private_key_conversion_sec1(private_key: &PKey<Private>, id: Id, key_size: u32) {
         #[cfg(feature = "fips")]
         let mask = Some(FIPS_PRIVATE_ECC_MASK_SIGN_ECDH);
         #[cfg(not(feature = "fips"))]
@@ -568,14 +568,14 @@ mod tests {
         let private_key_ =
             PKey::from_ec_key(EcKey::private_key_from_der(&key_value).unwrap()).unwrap();
         assert_eq!(private_key_.id(), id);
-        assert_eq!(private_key_.bits(), keysize);
+        assert_eq!(private_key_.bits(), key_size);
         assert_eq!(
             private_key_.private_key_to_der().unwrap(),
             key_value.to_vec()
         );
         let private_key_ = kmip_private_key_to_openssl(&object_).unwrap();
         assert_eq!(private_key_.id(), id);
-        assert_eq!(private_key_.bits(), keysize);
+        assert_eq!(private_key_.bits(), key_size);
         assert_eq!(
             private_key_.private_key_to_der().unwrap(),
             key_value.to_vec()
@@ -585,7 +585,7 @@ mod tests {
     fn test_private_key_conversion_transparent_rsa(
         private_key: &PKey<Private>,
         id: Id,
-        keysize: u32,
+        key_size: u32,
     ) {
         #[cfg(feature = "fips")]
         let mask = Some(FIPS_PRIVATE_RSA_MASK);
@@ -636,7 +636,7 @@ mod tests {
         .unwrap();
 
         assert_eq!(private_key_.id(), id);
-        assert_eq!(private_key_.bits(), keysize);
+        assert_eq!(private_key_.bits(), key_size);
         assert_eq!(
             private_key.private_key_to_der().unwrap(),
             private_key_.private_key_to_der().unwrap()
@@ -644,7 +644,7 @@ mod tests {
 
         let private_key_ = kmip_private_key_to_openssl(&object_).unwrap();
         assert_eq!(private_key_.id(), id);
-        assert_eq!(private_key_.bits(), keysize);
+        assert_eq!(private_key_.bits(), key_size);
         assert_eq!(
             private_key.private_key_to_der().unwrap(),
             private_key_.private_key_to_der().unwrap()
@@ -657,7 +657,7 @@ mod tests {
         ec_group: Option<&EcGroup>,
         curve: RecommendedCurve,
         id: Id,
-        keysize: u32,
+        key_size: u32,
     ) {
         #[cfg(feature = "fips")]
         let mask = Some(FIPS_PRIVATE_ECC_MASK_SIGN_ECDH);
@@ -692,9 +692,9 @@ mod tests {
         let mut privkey_vec = d.to_bytes_be();
 
         // privkey size on curve.
-        let bytes_keysize = 1 + ((keysize as usize - 1) / 8);
+        let bytes_key_size = 1 + ((key_size as usize - 1) / 8);
 
-        pad_be_bytes(&mut privkey_vec, bytes_keysize);
+        pad_be_bytes(&mut privkey_vec, bytes_key_size);
         if id == Id::EC {
             let private_key_ = PKey::from_ec_key(
                 EcKey::from_private_components(
@@ -706,14 +706,14 @@ mod tests {
             )
             .unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key.private_key_to_der().unwrap(),
                 private_key_.private_key_to_der().unwrap()
             );
             let private_key_ = kmip_private_key_to_openssl(&object_).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key.private_key_to_der().unwrap(),
                 private_key_.private_key_to_der().unwrap()
@@ -721,14 +721,14 @@ mod tests {
         } else {
             let private_key_ = PKey::private_key_from_raw_bytes(&privkey_vec, id).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key.raw_private_key().unwrap(),
                 private_key_.raw_private_key().unwrap()
             );
             let private_key_ = kmip_private_key_to_openssl(&object_).unwrap();
             assert_eq!(private_key_.id(), id);
-            assert_eq!(private_key_.bits(), keysize);
+            assert_eq!(private_key_.bits(), key_size);
             assert_eq!(
                 private_key.raw_private_key().unwrap(),
                 private_key_.raw_private_key().unwrap()
@@ -742,26 +742,26 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 2048;
-        let rsa_private_key = Rsa::generate(keysize).unwrap();
+        let key_size = 2048;
+        let rsa_private_key = Rsa::generate(key_size).unwrap();
         let private_key = PKey::from_rsa(rsa_private_key).unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::RSA, keysize, KeyFormatType::PKCS8);
-        test_private_key_conversion_pkcs(&private_key, Id::RSA, keysize, KeyFormatType::PKCS1);
-        test_private_key_conversion_transparent_rsa(&private_key, Id::RSA, keysize);
+        test_private_key_conversion_pkcs(&private_key, Id::RSA, key_size, KeyFormatType::PKCS8);
+        test_private_key_conversion_pkcs(&private_key, Id::RSA, key_size, KeyFormatType::PKCS1);
+        test_private_key_conversion_transparent_rsa(&private_key, Id::RSA, key_size);
     }
 
     #[test]
     #[cfg(not(feature = "fips"))]
     fn test_conversion_ec_p_192_private_key() {
-        let keysize = 192;
+        let key_size = 192;
         let ec_group = EcGroup::from_curve_name(Nid::X9_62_PRIME192V1).unwrap();
         let ec_key = EcKey::generate(&ec_group).unwrap();
         let ec_public_key = ec_key.public_key().to_owned(&ec_group).unwrap();
         let private_key = PKey::from_ec_key(ec_key).unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::EC, keysize, KeyFormatType::PKCS8);
-        test_private_key_conversion_sec1(&private_key, Id::EC, keysize);
+        test_private_key_conversion_pkcs(&private_key, Id::EC, key_size, KeyFormatType::PKCS8);
+        test_private_key_conversion_sec1(&private_key, Id::EC, key_size);
 
         test_private_key_conversion_transparent_ec(
             &private_key,
@@ -769,7 +769,7 @@ mod tests {
             Some(&ec_group),
             RecommendedCurve::P192,
             Id::EC,
-            keysize,
+            key_size,
         );
     }
 
@@ -779,14 +779,14 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 224;
+        let key_size = 224;
         let ec_group = EcGroup::from_curve_name(Nid::SECP224R1).unwrap();
         let ec_key = EcKey::generate(&ec_group).unwrap();
         let ec_public_key = ec_key.public_key().to_owned(&ec_group).unwrap();
         let private_key = PKey::from_ec_key(ec_key).unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::EC, keysize, KeyFormatType::PKCS8);
-        test_private_key_conversion_sec1(&private_key, Id::EC, keysize);
+        test_private_key_conversion_pkcs(&private_key, Id::EC, key_size, KeyFormatType::PKCS8);
+        test_private_key_conversion_sec1(&private_key, Id::EC, key_size);
 
         test_private_key_conversion_transparent_ec(
             &private_key,
@@ -794,7 +794,7 @@ mod tests {
             Some(&ec_group),
             RecommendedCurve::P224,
             Id::EC,
-            keysize,
+            key_size,
         );
     }
 
@@ -804,14 +804,14 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 256;
+        let key_size = 256;
         let ec_group = EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1).unwrap();
         let ec_key = EcKey::generate(&ec_group).unwrap();
         let ec_public_key = ec_key.public_key().to_owned(&ec_group).unwrap();
         let private_key = PKey::from_ec_key(ec_key).unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::EC, keysize, KeyFormatType::PKCS8);
-        test_private_key_conversion_sec1(&private_key, Id::EC, keysize);
+        test_private_key_conversion_pkcs(&private_key, Id::EC, key_size, KeyFormatType::PKCS8);
+        test_private_key_conversion_sec1(&private_key, Id::EC, key_size);
 
         test_private_key_conversion_transparent_ec(
             &private_key,
@@ -819,7 +819,7 @@ mod tests {
             Some(&ec_group),
             RecommendedCurve::P256,
             Id::EC,
-            keysize,
+            key_size,
         );
     }
 
@@ -829,14 +829,14 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 384;
+        let key_size = 384;
         let ec_group = EcGroup::from_curve_name(openssl::nid::Nid::SECP384R1).unwrap();
         let ec_key = EcKey::generate(&ec_group).unwrap();
         let ec_public_key = ec_key.public_key().to_owned(&ec_group).unwrap();
         let private_key = PKey::from_ec_key(ec_key).unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::EC, keysize, KeyFormatType::PKCS8);
-        test_private_key_conversion_sec1(&private_key, Id::EC, keysize);
+        test_private_key_conversion_pkcs(&private_key, Id::EC, key_size, KeyFormatType::PKCS8);
+        test_private_key_conversion_sec1(&private_key, Id::EC, key_size);
 
         test_private_key_conversion_transparent_ec(
             &private_key,
@@ -844,7 +844,7 @@ mod tests {
             Some(&ec_group),
             RecommendedCurve::P384,
             Id::EC,
-            keysize,
+            key_size,
         );
     }
 
@@ -854,14 +854,14 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 521;
+        let key_size = 521;
         let ec_group = EcGroup::from_curve_name(openssl::nid::Nid::SECP521R1).unwrap();
         let ec_key = EcKey::generate(&ec_group).unwrap();
         let ec_public_key = ec_key.public_key().to_owned(&ec_group).unwrap();
         let private_key = PKey::from_ec_key(ec_key).unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::EC, keysize, KeyFormatType::PKCS8);
-        test_private_key_conversion_sec1(&private_key, Id::EC, keysize);
+        test_private_key_conversion_pkcs(&private_key, Id::EC, key_size, KeyFormatType::PKCS8);
+        test_private_key_conversion_sec1(&private_key, Id::EC, key_size);
 
         test_private_key_conversion_transparent_ec(
             &private_key,
@@ -869,24 +869,24 @@ mod tests {
             Some(&ec_group),
             RecommendedCurve::P521,
             Id::EC,
-            keysize,
+            key_size,
         );
     }
 
     #[test]
     #[cfg(not(feature = "fips"))]
     fn test_conversion_ec_x25519_private_key() {
-        let keysize = 253;
+        let key_size = 253;
         let private_key = PKey::generate_x25519().unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::X25519, keysize, KeyFormatType::PKCS8);
+        test_private_key_conversion_pkcs(&private_key, Id::X25519, key_size, KeyFormatType::PKCS8);
         test_private_key_conversion_transparent_ec(
             &private_key,
             None,
             None,
             RecommendedCurve::CURVE25519,
             Id::X25519,
-            keysize,
+            key_size,
         );
     }
 
@@ -896,34 +896,34 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 256;
+        let key_size = 256;
         let private_key = PKey::generate_ed25519().unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::ED25519, keysize, KeyFormatType::PKCS8);
+        test_private_key_conversion_pkcs(&private_key, Id::ED25519, key_size, KeyFormatType::PKCS8);
         test_private_key_conversion_transparent_ec(
             &private_key,
             None,
             None,
             RecommendedCurve::CURVEED25519,
             Id::ED25519,
-            keysize,
+            key_size,
         );
     }
 
     #[test]
     #[cfg(not(feature = "fips"))]
     fn test_conversion_ec_x448_private_key() {
-        let keysize = 448;
+        let key_size = 448;
         let private_key = PKey::generate_x448().unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::X448, keysize, KeyFormatType::PKCS8);
+        test_private_key_conversion_pkcs(&private_key, Id::X448, key_size, KeyFormatType::PKCS8);
         test_private_key_conversion_transparent_ec(
             &private_key,
             None,
             None,
             RecommendedCurve::CURVE448,
             Id::X448,
-            keysize,
+            key_size,
         );
     }
 
@@ -933,17 +933,17 @@ mod tests {
         // Load FIPS provider module from OpenSSL.
         openssl::provider::Provider::load(None, "fips").unwrap();
 
-        let keysize = 456;
+        let key_size = 456;
         let private_key = PKey::generate_ed448().unwrap();
 
-        test_private_key_conversion_pkcs(&private_key, Id::ED448, keysize, KeyFormatType::PKCS8);
+        test_private_key_conversion_pkcs(&private_key, Id::ED448, key_size, KeyFormatType::PKCS8);
         test_private_key_conversion_transparent_ec(
             &private_key,
             None,
             None,
             RecommendedCurve::CURVEED448,
             Id::ED448,
-            keysize,
+            key_size,
         );
     }
 }
diff --git a/crate/server/Cargo.toml b/crate/server/Cargo.toml
index c4e028857..1befab236 100644
--- a/crate/server/Cargo.toml
+++ b/crate/server/Cargo.toml
@@ -107,6 +107,7 @@ tracing-opentelemetry = "0.24.0"
 tracing-subscriber = { version = "0.3", features = ["env-filter"] }
 url = { workspace = true }
 uuid = { workspace = true, features = ["v4"] }
+version-compare = "0.2.0"
 x509-parser = { workspace = true }
 zeroize = { workspace = true }
 
@@ -114,6 +115,7 @@ zeroize = { workspace = true }
 actix-http = "3.6"
 cosmian_logger = { path = "../logger" }
 pem = "3.0.4"
+tempfile = "3.11"
 
 [build-dependencies]
 actix-http = "3.6"
diff --git a/crate/server/src/config/command_line/db.rs b/crate/server/src/config/command_line/db.rs
index ec0a15142..d8386ac40 100644
--- a/crate/server/src/config/command_line/db.rs
+++ b/crate/server/src/config/command_line/db.rs
@@ -201,7 +201,7 @@ impl DBConfig {
 
 fn ensure_url(database_url: Option<&str>, alternate_env_variable: &str) -> KResult<Url> {
     let url = if let Some(url) = database_url {
-        Ok(url.to_string())
+        Ok(url.to_owned())
     } else {
         std::env::var(alternate_env_variable).map_err(|_e| {
             kms_error!(
@@ -220,7 +220,7 @@ fn ensure_value(
     env_variable_name: &str,
 ) -> KResult<String> {
     if let Some(value) = value {
-        Ok(value.to_string())
+        Ok(value.to_owned())
     } else {
         std::env::var(env_variable_name).map_err(|_e| {
             kms_error!(
diff --git a/crate/server/src/config/command_line/http_config.rs b/crate/server/src/config/command_line/http_config.rs
index 74af7f0fa..2a9957ebd 100644
--- a/crate/server/src/config/command_line/http_config.rs
+++ b/crate/server/src/config/command_line/http_config.rs
@@ -65,7 +65,7 @@ impl Default for HttpConfig {
     fn default() -> Self {
         Self {
             port: DEFAULT_PORT,
-            hostname: DEFAULT_HOSTNAME.to_string(),
+            hostname: DEFAULT_HOSTNAME.to_owned(),
             https_p12_file: None,
             https_p12_password: None,
             authority_cert_file: None,
diff --git a/crate/server/src/config/params/db_params.rs b/crate/server/src/config/params/db_params.rs
index 731e8593b..6cdd11aaa 100644
--- a/crate/server/src/config/params/db_params.rs
+++ b/crate/server/src/config/params/db_params.rs
@@ -63,14 +63,16 @@ impl Display for DbParams {
 }
 
 /// Redact the username and password from the URL for logging purposes
+#[allow(clippy::expect_used)]
 fn redact_url(original: &Url) -> Url {
     let mut url = original.clone();
 
     if url.username() != "" {
-        url.set_username("****").unwrap();
+        url.set_username("****").expect("masking username failed");
     }
     if url.password().is_some() {
-        url.set_password(Some("****")).unwrap();
+        url.set_password(Some("****"))
+            .expect("masking password failed");
     }
 
     url
diff --git a/crate/server/src/config/params/http_params.rs b/crate/server/src/config/params/http_params.rs
index ce0accde3..9facbc0ce 100644
--- a/crate/server/src/config/params/http_params.rs
+++ b/crate/server/src/config/params/http_params.rs
@@ -1,4 +1,4 @@
-use std::{fmt, fs::File, io::Read};
+use std::fmt;
 
 use openssl::pkcs12::{ParsedPkcs12_2, Pkcs12};
 
@@ -13,16 +13,28 @@ pub enum HttpParams {
     Http,
 }
 
+/// Represents the HTTP parameters for the server configuration.
 impl HttpParams {
+    /// Tries to create an instance of `HttpParams` from the given `HttpConfig`.
+    ///
+    /// # Arguments
+    ///
+    /// * `config` - The `HttpConfig` object containing the configuration parameters.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `KResult` containing the created `HttpParams` instance on success.
+    ///
+    /// # Errors
+    ///
+    /// This function can return an error if there is an issue reading the PKCS#12 file or parsing it.
     pub fn try_from(config: &HttpConfig) -> KResult<Self> {
         // start in HTTPS mode if a PKCS#12 file is provided
         if let (Some(p12_file), Some(p12_password)) =
             (&config.https_p12_file, &config.https_p12_password)
         {
             // Open and read the file into a byte vector
-            let mut file = File::open(p12_file)?;
-            let mut der_bytes = Vec::new();
-            file.read_to_end(&mut der_bytes)?;
+            let der_bytes = std::fs::read(p12_file)?;
             // Parse the byte vector as a PKCS#12 object
             let sealed_p12 = Pkcs12::from_der(der_bytes.as_slice())?;
             let p12 = sealed_p12
@@ -35,6 +47,11 @@ impl HttpParams {
         }
     }
 
+    /// Checks if the server is running in HTTPS mode.
+    ///
+    /// # Returns
+    ///
+    /// Returns `true` if the server is running in HTTPS mode, `false` otherwise.
     #[must_use]
     pub const fn is_running_https(&self) -> bool {
         matches!(self, Self::Https(_))
diff --git a/crate/server/src/config/params/server_params.rs b/crate/server/src/config/params/server_params.rs
index a70ce246d..52ed2f50c 100644
--- a/crate/server/src/config/params/server_params.rs
+++ b/crate/server/src/config/params/server_params.rs
@@ -1,4 +1,4 @@
-use std::{fmt, fs::File, io::Read, path::PathBuf};
+use std::{fmt, path::PathBuf};
 
 use openssl::x509::X509;
 
@@ -59,7 +59,21 @@ pub struct ServerParams {
     pub ms_dke_service_url: Option<String>,
 }
 
+/// Represents the server parameters.
 impl ServerParams {
+    /// Tries to create a `ServerParams` instance from the given `ClapConfig`.
+    ///
+    /// # Arguments
+    ///
+    /// * `conf` - The `ClapConfig` object containing the configuration parameters.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `KResult` containing the `ServerParams` instance if successful, or an error if the conversion fails.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the conversion from `ClapConfig` to `ServerParams` fails.
     pub fn try_from(conf: ClapConfig) -> KResult<Self> {
         let http_params = HttpParams::try_from(&conf.http)?;
 
@@ -95,11 +109,22 @@ impl ServerParams {
         })
     }
 
+    /// Loads the certificate from the given file path.
+    ///
+    /// # Arguments
+    ///
+    /// * `authority_cert_file` - The path to the authority certificate file.
+    ///
+    /// # Returns
+    ///
+    /// Returns a `KResult` containing the loaded `X509` certificate if successful, or an error if the loading fails.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the certificate file cannot be read or if the parsing of the certificate fails.
     fn load_cert(authority_cert_file: &PathBuf) -> KResult<X509> {
         // Open and read the file into a byte vector
-        let mut file = File::open(authority_cert_file)?;
-        let mut pem_bytes = Vec::new();
-        file.read_to_end(&mut pem_bytes)?;
+        let pem_bytes = std::fs::read(authority_cert_file)?;
 
         // Parse the byte vector as a X509 object
         let x509 = X509::from_pem(pem_bytes.as_slice())?;
diff --git a/crate/server/src/core/certificate/find.rs b/crate/server/src/core/certificate/find.rs
index 7fa16dc86..937d17340 100644
--- a/crate/server/src/core/certificate/find.rs
+++ b/crate/server/src/core/certificate/find.rs
@@ -93,7 +93,7 @@ pub(crate) async fn retrieve_issuer_private_key_and_certificate(
 
     kms_bail!(KmsError::InvalidRequest(
         "Either an issuer certificate id or an issuer private key id or both must be provided"
-            .to_string(),
+            .to_owned(),
     ))
 }
 
@@ -120,7 +120,7 @@ pub(crate) async fn retrieve_certificate_for_private_key(
             .attributes
             .get_link(LinkType::PublicKeyLink)
             .ok_or_else(|| {
-                KmsError::InvalidRequest("No public key link found for the private key".to_string())
+                KmsError::InvalidRequest("No public key link found for the private key".to_owned())
             })?;
         find_link_in_public_key(
             LinkType::CertificateLink,
@@ -179,7 +179,7 @@ pub(crate) async fn retrieve_private_key_for_certificate(
             .get_link(LinkType::PublicKeyLink)
             .ok_or_else(|| {
                 KmsError::InvalidRequest(
-                    "No private or public key link found for the certificate".to_string(),
+                    "No private or public key link found for the certificate".to_owned(),
                 )
             })?;
         find_link_in_public_key(
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 fd16ecd9e..073773829 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
@@ -58,7 +58,7 @@ async fn create_user_decryption_key_(
         .ok_or_else(|| {
             KmsError::InvalidRequest(
                 "there should be a reference to the master private key in the creation attributes"
-                    .to_string(),
+                    .to_owned(),
             )
         })?
         .to_string();
@@ -130,7 +130,7 @@ pub(crate) async fn create_user_decryption_key_pair(
         .or(create_key_pair_request.common_attributes.as_ref())
         .ok_or_else(|| {
             KmsError::InvalidRequest(
-                "Missing private attributes in CoverCrypt Create Keypair request".to_string(),
+                "Missing private attributes in CoverCrypt Create Keypair request".to_owned(),
             )
         })?;
     let private_key = create_user_decryption_key_(
@@ -149,13 +149,13 @@ pub(crate) async fn create_user_decryption_key_pair(
         .or(create_key_pair_request.common_attributes.as_ref())
         .ok_or_else(|| {
             KmsError::InvalidRequest(
-                "Missing public attributes in CoverCrypt Create Keypair request".to_string(),
+                "Missing public attributes in CoverCrypt Create Keypair request".to_owned(),
             )
         })?;
     let master_public_key_uid = public_key_attributes.get_parent_id().ok_or_else(|| {
         KmsError::InvalidRequest(
             "the master public key id should be available in the public creation attributes"
-                .to_string(),
+                .to_owned(),
         )
     })?;
     let gr_public_key = kmip_server
diff --git a/crate/server/src/core/cover_crypt/rekey_keys.rs b/crate/server/src/core/cover_crypt/rekey_keys.rs
index 36c1d3d03..7827dd66c 100644
--- a/crate/server/src/core/cover_crypt/rekey_keys.rs
+++ b/crate/server/src/core/cover_crypt/rekey_keys.rs
@@ -190,7 +190,7 @@ async fn get_master_keys_and_policy(
         .ok_or_else(|| {
             KmsError::KmipError(
                 ErrorReason::Invalid_Object_Type,
-                "Private key MUST contain a public key link".to_string(),
+                "Private key MUST contain a public key link".to_owned(),
             )
         })?;
 
@@ -212,7 +212,7 @@ async fn import_rekeyed_master_keys(
 ) -> KResult<()> {
     // re-import master secret key
     let import_request = Import {
-        unique_identifier: UniqueIdentifier::TextString(msk.0.to_string()),
+        unique_identifier: UniqueIdentifier::TextString(msk.0),
         object_type: ObjectType::PrivateKey,
         replace_existing: Some(true),
         key_wrap_type: None,
@@ -223,7 +223,7 @@ async fn import_rekeyed_master_keys(
 
     // re-import master public key
     let import_request = Import {
-        unique_identifier: UniqueIdentifier::TextString(mpk.0.to_string()),
+        unique_identifier: UniqueIdentifier::TextString(mpk.0),
         object_type: ObjectType::PublicKey,
         replace_existing: Some(true),
         key_wrap_type: None,
diff --git a/crate/server/src/core/extra_database_params.rs b/crate/server/src/core/extra_database_params.rs
index b51757477..d60112860 100644
--- a/crate/server/src/core/extra_database_params.rs
+++ b/crate/server/src/core/extra_database_params.rs
@@ -26,14 +26,19 @@ impl<'de> Deserialize<'de> for ExtraDatabaseParams {
         D: serde::Deserializer<'de>,
     {
         let bytes = Zeroizing::from(<Vec<u8>>::deserialize(deserializer)?);
-        let group_id_bytes: [u8; 16] = bytes[0..16]
-            .try_into()
-            .map_err(|_| serde::de::Error::custom("Could not deserialize ExtraDatabaseParams"))?;
+        let group_id_bytes: [u8; 16] = bytes[0..16].try_into().map_err(|e| {
+            serde::de::Error::custom(format!(
+                "Could not deserialize ExtraDatabaseParams. Error: {e:?}"
+            ))
+        })?;
         let group_id = u128::from_be_bytes(group_id_bytes);
 
-        let mut key_bytes: [u8; AES_256_GCM_KEY_LENGTH] = bytes[16..48]
-            .try_into()
-            .map_err(|_| serde::de::Error::custom("Could not deserialize ExtraDatabaseParams"))?;
+        let mut key_bytes: [u8; AES_256_GCM_KEY_LENGTH] =
+            bytes[16..48].try_into().map_err(|e| {
+                serde::de::Error::custom(format!(
+                    "Could not deserialize ExtraDatabaseParams. Error: {e:?}"
+                ))
+            })?;
         let key = Secret::<AES_256_GCM_KEY_LENGTH>::from_unprotected_bytes(&mut key_bytes);
         Ok(Self { group_id, key })
     }
diff --git a/crate/server/src/core/implementation.rs b/crate/server/src/core/implementation.rs
index 39075852f..dc19e31bd 100644
--- a/crate/server/src/core/implementation.rs
+++ b/crate/server/src/core/implementation.rs
@@ -60,7 +60,7 @@ impl KMS {
                 DbParams::RedisFindex(url, master_key, label) => {
                     // There is no reason to keep a copy of the key in the shared config
                     // So we are going to create a "zeroizable" copy which will be passed to Redis with Findex
-                    // and zerorize the one in the shared config
+                    // and zeroize the one in the shared config
                     let new_master_key =
                         Secret::<REDIS_WITH_FINDEX_MASTER_KEY_LENGTH>::from_unprotected_bytes(
                             &mut master_key.to_bytes(),
@@ -94,7 +94,7 @@ impl KMS {
         // check that the cryptographic algorithm is specified
         let cryptographic_algorithm = &attributes.cryptographic_algorithm.ok_or_else(|| {
             KmsError::InvalidRequest(
-                "the cryptographic algorithm must be specified for secret key creation".to_string(),
+                "the cryptographic algorithm must be specified for secret key creation".to_owned(),
             )
         })?;
 
@@ -102,7 +102,7 @@ impl KMS {
         let mut tags = attributes.get_tags();
         Attributes::check_user_tags(&tags)?;
         //update the tags
-        tags.insert("_kk".to_string());
+        tags.insert("_kk".to_owned());
 
         match cryptographic_algorithm {
             CryptographicAlgorithm::AES
@@ -115,14 +115,15 @@ impl KMS {
             | CryptographicAlgorithm::SHAKE128
             | CryptographicAlgorithm::SHAKE256 => match attributes.key_format_type {
                 None => Err(KmsError::InvalidRequest(
-                    "Unable to create a symmetric key, the format type is not specified"
-                        .to_string(),
+                    "Unable to create a symmetric key, the format type is not specified".to_owned(),
                 )),
                 Some(KeyFormatType::TransparentSymmetricKey) => {
                     // create the key
-                    let key_len: usize = attributes
+                    let key_len = attributes
                         .cryptographic_length
-                        .map_or(AES_256_GCM_KEY_LENGTH, |v| v as usize / 8);
+                        .map(|len| usize::try_from(len / 8))
+                        .transpose()?
+                        .map_or(AES_256_GCM_KEY_LENGTH, |v| v);
                     let mut symmetric_key = Zeroizing::from(vec![0; key_len]);
                     rand_bytes(&mut symmetric_key)?;
                     let object =
@@ -159,8 +160,7 @@ impl KMS {
         // check that the cryptographic algorithm is specified
         let cryptographic_algorithm = &attributes.cryptographic_algorithm.ok_or_else(|| {
             KmsError::InvalidRequest(
-                "the cryptographic algorithm must be specified for private key creation"
-                    .to_string(),
+                "the cryptographic algorithm must be specified for private key creation".to_owned(),
             )
         })?;
 
@@ -168,7 +168,7 @@ impl KMS {
         let mut tags = attributes.get_tags();
         Attributes::check_user_tags(&tags)?;
         //update the tags
-        tags.insert("_uk".to_string());
+        tags.insert("_uk".to_owned());
 
         match &cryptographic_algorithm {
             CryptographicAlgorithm::CoverCrypt => {
diff --git a/crate/server/src/core/kms.rs b/crate/server/src/core/kms.rs
index a8bf02b32..e5e40d00d 100644
--- a/crate/server/src/core/kms.rs
+++ b/crate/server/src/core/kms.rs
@@ -553,7 +553,7 @@ impl KMS {
         if owner == access.user_id {
             kms_bail!(KmsError::Unauthorized(
                 "You can't grant yourself, you have already all rights on your own objects"
-                    .to_string()
+                    .to_owned()
             ))
         }
 
@@ -595,7 +595,7 @@ impl KMS {
         if owner == access.user_id {
             kms_bail!(KmsError::Unauthorized(
                 "You can't revoke yourself, you should keep all rights on your own objects"
-                    .to_string()
+                    .to_owned()
             ))
         }
 
diff --git a/crate/server/src/core/operations/certify/mod.rs b/crate/server/src/core/operations/certify/mod.rs
index f86ae2596..50d5c397c 100644
--- a/crate/server/src/core/operations/certify/mod.rs
+++ b/crate/server/src/core/operations/certify/mod.rs
@@ -258,7 +258,7 @@ async fn get_subject(
             CertificateRequestType::PEM => X509Req::from_pem(pkcs10_bytes),
             CertificateRequestType::PKCS10 => X509Req::from_der(pkcs10_bytes),
             CertificateRequestType::CRMF => kms_bail!(KmsError::InvalidRequest(
-                "Certificate Request Type CRMF not supported".to_string()
+                "Certificate Request Type CRMF not supported".to_owned()
             )),
         }?;
         let certificate_id = request
@@ -312,7 +312,7 @@ async fn get_subject(
     let attributes = request.attributes.as_ref().ok_or_else(|| {
         KmsError::InvalidRequest(
             "Certify from Subject: the attributes specifying the the subject name are missing"
-                .to_string(),
+                .to_owned(),
         )
     })?;
     let subject_name = attributes
@@ -320,7 +320,7 @@ async fn get_subject(
         .as_ref()
         .ok_or_else(|| {
             KmsError::InvalidRequest(
-                "Certify from Subject: the subject name is not found in the attributes".to_string(),
+                "Certify from Subject: the subject name is not found in the attributes".to_owned(),
             )
         })?
         .subject_name()?;
@@ -342,7 +342,7 @@ async fn get_subject(
     let (private_attributes, public_attributes) = {
         let cryptographic_algorithm = attributes.cryptographic_algorithm.ok_or_else(|| {
             KmsError::InvalidRequest(
-                "Keypair creation: the cryptographic algorithm is missing".to_string(),
+                "Keypair creation: the cryptographic algorithm is missing".to_owned(),
             )
         })?;
         let private_attributes = Attributes {
@@ -410,7 +410,10 @@ async fn get_issuer<'a>(
         }
         None => (None, None),
     };
-
+    trace!(
+        "Issuer certificate id: {issuer_certificate_id:?}, issuer private key id: \
+         {issuer_private_key_id:?}"
+    );
     if issuer_certificate_id.is_none() && issuer_private_key_id.is_none() {
         // If no issuer is provided, the subject is self-signed
         return issuer_for_self_signed_certificate(subject, kms, user, params).await;
@@ -479,7 +482,7 @@ async fn issuer_for_self_signed_certificate<'a>(
             .ok_or_else(|| {
                 KmsError::InvalidRequest(
                     "No private key linked to the certificate found to renew it as self-signed"
-                        .to_string(),
+                        .to_owned(),
                 )
             })?;
             Ok(Issuer::PrivateKeyAndCertificate(
@@ -503,7 +506,7 @@ async fn issuer_for_self_signed_certificate<'a>(
                 KmsError::InvalidRequest(
                     "No private key link found to create a self-signed certificate from a public \
                      key"
-                    .to_string(),
+                    .to_owned(),
                 )
             })?;
             // see if we can find an existing certificate to link to the public key
@@ -543,7 +546,7 @@ fn build_and_sign_certificate(
     issuer: &Issuer,
     subject: &Subject,
     request: Certify,
-) -> Result<(Object, HashSet<String>, Attributes), KmsError> {
+) -> KResult<(Object, HashSet<String>, Attributes)> {
     debug!("Building and signing certificate");
     // recover the attributes
     let mut attributes = request.attributes.unwrap_or_default();
@@ -566,10 +569,15 @@ fn build_and_sign_certificate(
     // Create a new Asn1Time object for the current time
     let now = Asn1Time::days_from_now(0).context("could not get a date in ASN.1")?;
     // retrieve the number of days for the validity of the certificate
-    let mut number_of_days = attributes.extract_requested_validity_days()?.unwrap_or(365) as u32;
+    let mut number_of_days =
+        u32::try_from(attributes.extract_requested_validity_days()?.unwrap_or(365))?;
+    trace!("Number of days: {}", number_of_days);
+
     // the number of days cannot exceed that of the issuer certificate
     if let Some(issuer_not_after) = issuer.not_after() {
-        number_of_days = min(issuer_not_after.diff(&now)?.days as u32, number_of_days);
+        trace!("Issuer certificate not after: {issuer_not_after}");
+        let days = u32::try_from(now.diff(issuer_not_after)?.days)?;
+        number_of_days = min(days, number_of_days);
     }
     x509_builder.set_not_before(now.as_ref())?;
     x509_builder.set_not_after(
@@ -611,7 +619,7 @@ fn build_and_sign_certificate(
     // add subject tags if any
     tags.extend(subject.tags().iter().cloned());
     // add the certificate "system" tag
-    tags.insert("_cert".to_string());
+    tags.insert("_cert".to_owned());
 
     // link the certificate to the issuer certificate
     attributes.add_link(
diff --git a/crate/server/src/core/operations/create_key_pair.rs b/crate/server/src/core/operations/create_key_pair.rs
index 2c2eb4505..feb53a318 100644
--- a/crate/server/src/core/operations/create_key_pair.rs
+++ b/crate/server/src/core/operations/create_key_pair.rs
@@ -106,9 +106,9 @@ pub(crate) fn generate_key_pair_and_tags(
     Attributes::check_user_tags(&tags)?;
     // Update the tags for the private key and the public key.
     let mut sk_tags = tags.clone();
-    sk_tags.insert("_sk".to_string());
+    sk_tags.insert("_sk".to_owned());
     let mut pk_tags = tags;
-    pk_tags.insert("_pk".to_string());
+    pk_tags.insert("_pk".to_owned());
 
     // Grab whatever attributes were supplied on the  create request.
     let any_attributes = Some(&common_attributes)
@@ -133,7 +133,7 @@ pub(crate) fn generate_key_pair_and_tags(
     // Check that the cryptographic algorithm is specified.
     let cryptographic_algorithm = any_attributes.cryptographic_algorithm.ok_or_else(|| {
         KmsError::InvalidRequest(
-            "the cryptographic algorithm must be specified for key pair creation".to_string(),
+            "the cryptographic algorithm must be specified for key pair creation".to_owned(),
         )
     })?;
 
@@ -195,7 +195,7 @@ pub(crate) fn generate_key_pair_and_tags(
                         || cryptographic_algorithm == CryptographicAlgorithm::EC
                     {
                         kms_bail!(KmsError::NotSupported(
-                            "Edwards curve can't be created for EC or ECDSA".to_string()
+                            "Edwards curve can't be created for EC or ECDSA".to_owned()
                         ))
                     }
                     warn!(
@@ -217,7 +217,7 @@ pub(crate) fn generate_key_pair_and_tags(
                     kms_bail!(KmsError::NotSupported(
                         "An Edwards Keypair on curve 25519 should not be requested to perform \
                          Elliptic Curves operations in FIPS mode"
-                            .to_string()
+                            .to_owned()
                     ))
                 }
                 #[cfg(not(feature = "fips"))]
@@ -226,7 +226,7 @@ pub(crate) fn generate_key_pair_and_tags(
                         || cryptographic_algorithm == CryptographicAlgorithm::EC
                     {
                         kms_bail!(KmsError::NotSupported(
-                            "Edwards curve can't be created for EC or ECDSA".to_string()
+                            "Edwards curve can't be created for EC or ECDSA".to_owned()
                         ))
                     }
                     warn!(
@@ -248,7 +248,7 @@ pub(crate) fn generate_key_pair_and_tags(
                     kms_bail!(KmsError::NotSupported(
                         "An Edwards Keypair on curve 448 should not be requested to perform ECDH \
                          in FIPS mode."
-                            .to_string()
+                            .to_owned()
                     ))
                 }
                 other => kms_bail!(KmsError::NotSupported(format!(
@@ -260,7 +260,7 @@ pub(crate) fn generate_key_pair_and_tags(
             let key_size_in_bits = u32::try_from(
                 any_attributes
                     .cryptographic_length
-                    .ok_or_else(|| KmsError::InvalidRequest("RSA key size: error".to_string()))?,
+                    .ok_or_else(|| KmsError::InvalidRequest("RSA key size: error".to_owned()))?,
             )?;
             trace!("RSA key pair generation: size in bits: {key_size_in_bits}");
 
diff --git a/crate/server/src/core/operations/decrypt.rs b/crate/server/src/core/operations/decrypt.rs
index 066bdb55d..25259b15f 100644
--- a/crate/server/src/core/operations/decrypt.rs
+++ b/crate/server/src/core/operations/decrypt.rs
@@ -203,7 +203,7 @@ fn dispatch_decrypt(request: &Decrypt, owm: &ObjectWithMetadata) -> KResult<Decr
                 .unwrap_or(EMPTY_SLICE);
             let plaintext = aead_decrypt(aead, &key_bytes, nonce, aad, ciphertext, tag)?;
             Ok(DecryptResponse {
-                unique_identifier: UniqueIdentifier::TextString(owm.id.to_string()),
+                unique_identifier: UniqueIdentifier::TextString(owm.id.clone()),
                 data: Some(plaintext),
                 correlation_value: request.correlation_value.clone(),
             })
@@ -234,7 +234,7 @@ fn decrypt_with_pkey(
         }
     };
     Ok(DecryptResponse {
-        unique_identifier: UniqueIdentifier::TextString(key_id.to_string()),
+        unique_identifier: UniqueIdentifier::TextString(key_id.to_owned()),
         data: Some(plaintext),
         correlation_value: request.correlation_value.clone(),
     })
diff --git a/crate/server/src/core/operations/encrypt.rs b/crate/server/src/core/operations/encrypt.rs
index 6c41aac6d..4e58b4596 100644
--- a/crate/server/src/core/operations/encrypt.rs
+++ b/crate/server/src/core/operations/encrypt.rs
@@ -78,7 +78,7 @@ async fn get_key(
         .ok_or(KmsError::UnsupportedPlaceholder)?
         .as_str()
         .context("Encrypt: the unique identifier or tags must be a string")?
-        .to_string();
+        .to_owned();
     trace!("operations::encrypt: uid_or_tags: {uid_or_tags}");
 
     // retrieve from tags or use passed identifier
@@ -178,7 +178,7 @@ fn encrypt_with_aead(request: &Encrypt, owm: &ObjectWithMetadata) -> KResult<Enc
                 .unwrap_or(EMPTY_SLICE);
             let (ciphertext, tag) = aead_encrypt(aead, &key_bytes, &nonce, aad, plaintext)?;
             Ok(EncryptResponse {
-                unique_identifier: UniqueIdentifier::TextString(owm.id.to_string()),
+                unique_identifier: UniqueIdentifier::TextString(owm.id.clone()),
                 data: Some(ciphertext),
                 iv_counter_nonce: Some(nonce),
                 correlation_value: request.correlation_value.clone(),
@@ -254,7 +254,7 @@ fn encrypt_with_pkey(
         }
     };
     Ok(EncryptResponse {
-        unique_identifier: UniqueIdentifier::TextString(key_id.to_string()),
+        unique_identifier: UniqueIdentifier::TextString(key_id.to_owned()),
         data: Some(ciphertext),
         iv_counter_nonce: None,
         correlation_value: request.correlation_value.clone(),
diff --git a/crate/server/src/core/operations/import.rs b/crate/server/src/core/operations/import.rs
index 8f92f6cf4..42620eeaf 100644
--- a/crate/server/src/core/operations/import.rs
+++ b/crate/server/src/core/operations/import.rs
@@ -98,7 +98,7 @@ pub(crate) async fn process_symmetric_key(
         Attributes::check_user_tags(tags)?;
         // Insert the tag corresponding to the object type if tags should be
         // updated.
-        tags.insert("_kk".to_string());
+        tags.insert("_kk".to_owned());
     }
 
     // check if the object will be replaced if it already exists
@@ -139,7 +139,7 @@ fn process_certificate(request: Import) -> Result<(String, Vec<AtomicOperation>)
         Attributes::check_user_tags(tags)?;
         // Insert the tag corresponding to the object type if tags should be
         // updated.
-        tags.insert("_cert".to_string());
+        tags.insert("_cert".to_owned());
     }
 
     // check if the object will be replaced if it already exists
@@ -213,7 +213,7 @@ async fn process_public_key(
     let mut tags = attributes.remove_tags();
     if let Some(tags) = tags.as_mut() {
         Attributes::check_user_tags(tags)?;
-        tags.insert("_pk".to_string());
+        tags.insert("_pk".to_owned());
     }
 
     // check if the object will be replaced if it already exists
@@ -384,7 +384,7 @@ fn private_key_from_openssl(
     let sk_uid = if request_uid.is_empty() {
         Uuid::new_v4().to_string()
     } else {
-        request_uid.to_string()
+        request_uid.to_owned()
     };
 
     let sk_key_block = sk.key_block_mut()?;
@@ -399,7 +399,7 @@ fn private_key_from_openssl(
     );
 
     let sk_tags = user_tags.map(|mut tags| {
-        tags.insert("_sk".to_string());
+        tags.insert("_sk".to_owned());
         tags
     });
     Ok((sk_uid, sk, sk_tags))
@@ -460,7 +460,7 @@ fn process_pkcs12(
     // build the private key
     let (private_key_id, mut private_key, private_key_tags) = {
         let openssl_sk = pkcs12.pkey.ok_or_else(|| {
-            KmsError::InvalidRequest("Private key not found in PKCS12".to_string())
+            KmsError::InvalidRequest("Private key not found in PKCS12".to_owned())
         })?;
         private_key_from_openssl(
             &openssl_sk,
@@ -479,12 +479,12 @@ fn process_pkcs12(
     ) = {
         // Recover the PKCS12 X509 certificate
         let openssl_cert = pkcs12.cert.ok_or_else(|| {
-            KmsError::InvalidRequest("X509 certificate not found in PKCS12".to_string())
+            KmsError::InvalidRequest("X509 certificate not found in PKCS12".to_owned())
         })?;
 
         // insert the tag corresponding to the object type if tags should be updated
         let mut leaf_certificate_tags = user_tags.clone().unwrap_or_default();
-        leaf_certificate_tags.insert("_cert".to_string());
+        leaf_certificate_tags.insert("_cert".to_owned());
 
         // convert to KMIP
         let leaf_certificate = openssl_certificate_to_kmip(&openssl_cert)?;
@@ -504,7 +504,7 @@ fn process_pkcs12(
         for openssl_cert in cas {
             // insert the tag corresponding to the object type if tags should be updated
             let mut chain_certificate_tags = user_tags.clone().unwrap_or_default();
-            chain_certificate_tags.insert("_cert".to_string());
+            chain_certificate_tags.insert("_cert".to_owned());
 
             // convert to KMIP
             let chain_certificate = openssl_certificate_to_kmip(&openssl_cert)?;
diff --git a/crate/server/src/core/operations/locate.rs b/crate/server/src/core/operations/locate.rs
index 2f7a2d3bf..2cc2880d7 100644
--- a/crate/server/src/core/operations/locate.rs
+++ b/crate/server/src/core/operations/locate.rs
@@ -40,7 +40,7 @@ pub(crate) async fn locate(
 
     trace!("UIDs: {:?}", uids);
     let response = LocateResponse {
-        located_items: Some(uids.len() as i32),
+        located_items: Some(i32::try_from(uids.len())?),
         unique_identifiers: if uids.is_empty() { None } else { Some(uids) },
     };
 
diff --git a/crate/server/src/core/operations/message.rs b/crate/server/src/core/operations/message.rs
index dbd75dce8..aa12fb5f9 100644
--- a/crate/server/src/core/operations/message.rs
+++ b/crate/server/src/core/operations/message.rs
@@ -70,11 +70,11 @@ pub(crate) async fn message(
     let response_message = MessageResponse {
         header: MessageResponseHeader {
             protocol_version: request.header.protocol_version,
-            batch_count: response_items.len() as u32,
+            batch_count: u32::try_from(response_items.len())?,
             client_correlation_value: None,
             server_correlation_value: None,
             attestation_type: None,
-            timestamp: chrono::Utc::now().timestamp() as u64,
+            timestamp: u64::try_from(chrono::Utc::now().timestamp())?,
             nonce: None,
             server_hashed_password: None,
         },
diff --git a/crate/server/src/core/operations/rekey.rs b/crate/server/src/core/operations/rekey.rs
index 0e35349cd..9da43b1d4 100644
--- a/crate/server/src/core/operations/rekey.rs
+++ b/crate/server/src/core/operations/rekey.rs
@@ -58,7 +58,7 @@ pub(crate) async fn rekey(
     // there can only be one private key
     let owm = owm_s
         .pop()
-        .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.to_string()))?;
+        .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/rekey_keypair.rs b/crate/server/src/core/operations/rekey_keypair.rs
index 085fbedbe..15b3a81e3 100644
--- a/crate/server/src/core/operations/rekey_keypair.rs
+++ b/crate/server/src/core/operations/rekey_keypair.rs
@@ -71,7 +71,7 @@ pub(crate) async fn rekey_keypair(
     // there can only be one private key
     let owm = owm_s
         .pop()
-        .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.to_string()))?;
+        .ok_or_else(|| KmsError::KmipError(ErrorReason::Item_Not_Found, uid_or_tags.to_owned()))?;
 
     if !owm_s.is_empty() {
         return Err(KmsError::InvalidRequest(format!(
@@ -90,7 +90,7 @@ pub(crate) async fn rekey_keypair(
         kms_bail!(KmsError::InvalidRequest(
             "The cryptographic algorithm must be specified in the private key attributes for key \
              pair creation"
-                .to_string()
+                .to_owned()
         ))
     }
 }
diff --git a/crate/server/src/core/operations/revoke.rs b/crate/server/src/core/operations/revoke.rs
index cb379f94a..88ae29f55 100644
--- a/crate/server/src/core/operations/revoke.rs
+++ b/crate/server/src/core/operations/revoke.rs
@@ -53,7 +53,7 @@ pub(crate) async fn revoke_operation(
     .await?;
 
     Ok(RevokeResponse {
-        unique_identifier: UniqueIdentifier::TextString(uid_or_tags.to_string()),
+        unique_identifier: UniqueIdentifier::TextString(uid_or_tags.to_owned()),
     })
 }
 
diff --git a/crate/server/src/core/operations/validate.rs b/crate/server/src/core/operations/validate.rs
index cacc53b40..8bb4742c7 100644
--- a/crate/server/src/core/operations/validate.rs
+++ b/crate/server/src/core/operations/validate.rs
@@ -81,7 +81,7 @@ pub(crate) async fn validate_operation(
     {
         (None, None) => {
             return Err(KmsError::Certificate(
-                "Empty chain cannot be validated".to_string(),
+                "Empty chain cannot be validated".to_owned(),
             ));
         }
         (None, Some(certificates)) => Ok::<_, KmsError>((certificates.clone(), certificates.len())),
@@ -112,7 +112,7 @@ pub(crate) async fn validate_operation(
         return Err(KmsError::Certificate(
             "Number of certificates found in database and number of certificates in request do \
              not match"
-                .to_string(),
+                .to_owned(),
         ));
     };
 
@@ -249,7 +249,7 @@ fn sort_certificates(certificates: &[X509]) -> KResult<Vec<X509>> {
 
     if sorted_chains.is_empty() {
         return Err(KmsError::Certificate(
-            "No root authority found, cannot proceed full chain validation".to_string(),
+            "No root authority found, cannot proceed full chain validation".to_owned(),
         ));
     }
 
@@ -310,7 +310,7 @@ fn sort_certificates(certificates: &[X509]) -> KResult<Vec<X509>> {
 
     if sorted_chains.len() != certificates.len() {
         return Err(KmsError::Certificate(
-            "Failed to sort the certificates. Certificate chain incomplete?".to_string(),
+            "Failed to sort the certificates. Certificate chain incomplete?".to_owned(),
         ));
     }
 
@@ -343,7 +343,7 @@ fn sort_certificates(certificates: &[X509]) -> KResult<Vec<X509>> {
 fn verify_chain_signature(certificates: &[X509]) -> KResult<ValidityIndicator> {
     if certificates.is_empty() {
         return Err(KmsError::Certificate(
-            "Certificate chain is empty".to_string(),
+            "Certificate chain is empty".to_owned(),
         ));
     }
 
@@ -352,7 +352,7 @@ fn verify_chain_signature(certificates: &[X509]) -> KResult<ValidityIndicator> {
 
     // Get leaf
     let leaf = certificates.last().ok_or_else(|| {
-        KmsError::Certificate("Failed to get last element of the chain".to_string())
+        KmsError::Certificate("Failed to get last element of the chain".to_owned())
     })?;
 
     // Add authorities to the store
@@ -378,7 +378,7 @@ fn verify_chain_signature(certificates: &[X509]) -> KResult<ValidityIndicator> {
 
     if !result {
         return Err(KmsError::Certificate(
-            "Result of the function verify_cert: {result:?}".to_string(),
+            "Result of the function verify_cert: {result:?}".to_owned(),
         ));
     }
 
@@ -386,7 +386,7 @@ fn verify_chain_signature(certificates: &[X509]) -> KResult<ValidityIndicator> {
     let mut issuer_public_key = certificates
         .first()
         .ok_or_else(|| {
-            KmsError::Certificate("Failed to get the first element of the chain".to_string())
+            KmsError::Certificate("Failed to get the first element of the chain".to_owned())
         })?
         .public_key()?;
     for cert in certificates {
@@ -441,10 +441,10 @@ async fn get_crl_bytes(uri_list: Vec<String>) -> KResult<HashMap<String, Vec<u8>
         } else {
             let path_buf = path::Path::new(&uri).canonicalize()?;
             match path_buf.to_str() {
-                Some(s) => Some(UriType::Path(s.to_string())),
+                Some(s) => Some(UriType::Path(s.to_owned())),
                 None => {
                     return Err(KmsError::Certificate(
-                        "The uri provided is invalid".to_string(),
+                        "The uri provided is invalid".to_owned(),
                     ))
                 }
             }
@@ -499,7 +499,7 @@ async fn get_crl_bytes(uri_list: Vec<String>) -> KResult<HashMap<String, Vec<u8>
             }
             _ => {
                 return Err(KmsError::Certificate(
-                    "Error that should not manifest".to_string(),
+                    "Error that should not manifest".to_owned(),
                 ))
             }
         };
@@ -555,7 +555,7 @@ async fn verify_crls(certificates: Vec<X509>) -> KResult<ValidityIndicator> {
                 debug!("Parent CRL verification: revocation status: {res:?}");
                 if res == ValidityIndicator::Invalid {
                     return Err(KmsError::Certificate(
-                        "Certificate is revoked or removed from CRL".to_string(),
+                        "Certificate is revoked or removed from CRL".to_owned(),
                     ));
                 }
             }
@@ -571,8 +571,8 @@ async fn verify_crls(certificates: Vec<X509>) -> KResult<ValidityIndicator> {
                     .and_then(|x| x.get(0))
                     .and_then(GeneralNameRef::uri);
                 if let Some(crl_uri) = crl_uri {
-                    if !uri_list.contains(&crl_uri.to_string()) {
-                        uri_list.push(crl_uri.to_string());
+                    if !uri_list.contains(&crl_uri.to_owned()) {
+                        uri_list.push(crl_uri.to_owned());
                         trace!("Found CRL URI: {crl_uri}");
                     }
                 }
@@ -602,7 +602,7 @@ async fn verify_crls(certificates: Vec<X509>) -> KResult<ValidityIndicator> {
                 debug!("Revocation status: result: {res:?}");
                 if res == ValidityIndicator::Invalid {
                     return Err(KmsError::Certificate(
-                        "Certificate is revoked or removed from CRL".to_string(),
+                        "Certificate is revoked or removed from CRL".to_owned(),
                     ));
                 }
             }
@@ -624,7 +624,7 @@ async fn certificates_by_uid(
     let mut results = Vec::new();
     for unique_identifier in unique_identifiers {
         let unique_identifier = unique_identifier.as_str().ok_or_else(|| {
-            KmsError::Certificate("as_str returned None in certificates_by_uid".to_string())
+            KmsError::Certificate("as_str returned None in certificates_by_uid".to_owned())
         })?;
         let result = certificate_by_uid(unique_identifier, kms, user, params).await?;
         results.push(result);
diff --git a/crate/server/src/core/operations/wrapping/unwrap.rs b/crate/server/src/core/operations/wrapping/unwrap.rs
index 83f72c499..4677cb93c 100644
--- a/crate/server/src/core/operations/wrapping/unwrap.rs
+++ b/crate/server/src/core/operations/wrapping/unwrap.rs
@@ -57,7 +57,7 @@ pub(crate) async fn unwrap_key(
         ObjectType::PublicKey | ObjectType::Certificate => {
             let attributes = match object_type {
                 ObjectType::PublicKey | ObjectType::Certificate => unwrapping_key.attributes,
-                _ => unreachable!("unwrap_key: unsupported object type: {object_type}"),
+                _ => kms_bail!("unwrap_key: unsupported object type: {object_type}"),
             };
             let private_key_uid =
                 attributes
diff --git a/crate/server/src/database/cached_sqlcipher.rs b/crate/server/src/database/cached_sqlcipher.rs
index ca3dc8b99..7b19f39a2 100644
--- a/crate/server/src/database/cached_sqlcipher.rs
+++ b/crate/server/src/database/cached_sqlcipher.rs
@@ -6,6 +6,7 @@ use std::{
 };
 
 use async_trait::async_trait;
+use clap::crate_version;
 use cosmian_kmip::{
     crypto::{secret::Secret, symmetric::AES_256_GCM_KEY_LENGTH},
     kmip::{
@@ -16,9 +17,9 @@ use cosmian_kmip::{
 use cosmian_kms_client::access::{IsWrapped, ObjectOperationType};
 use sqlx::{
     sqlite::{SqliteConnectOptions, SqlitePoolOptions},
-    ConnectOptions, Pool, Sqlite,
+    ConnectOptions, Pool, Row, Sqlite,
 };
-use tracing::trace;
+use tracing::{debug, trace};
 
 use super::{
     cached_sqlite_struct::KMSSqliteCache,
@@ -33,10 +34,11 @@ use crate::{
     core::extra_database_params::ExtraDatabaseParams,
     database::{
         database_trait::AtomicOperation,
-        sqlite::{atomic_, retrieve_tags_},
-        Database, SQLITE_QUERIES,
+        migrate::do_migration,
+        sqlite::{atomic_, is_migration_in_progress_, migrate_, retrieve_tags_},
+        Database, KMS_VERSION_BEFORE_MIGRATION_SUPPORT, SQLITE_QUERIES,
     },
-    kms_bail, kms_error,
+    get_sqlite_query, kms_bail, kms_error,
     result::{KResult, KResultHelper},
 };
 
@@ -88,29 +90,21 @@ impl CachedSqlCipher {
     }
 
     async fn create_tables(pool: &Pool<Sqlite>) -> KResult<()> {
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("create-table-objects")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(pool)
-        .await?;
-
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("create-table-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(pool)
-        .await?;
-
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("create-table-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(pool)
-        .await?;
+        sqlx::query(get_sqlite_query!("create-table-context"))
+            .execute(pool)
+            .await?;
+
+        sqlx::query(get_sqlite_query!("create-table-objects"))
+            .execute(pool)
+            .await?;
+
+        sqlx::query(get_sqlite_query!("create-table-read_access"))
+            .execute(pool)
+            .await?;
+
+        sqlx::query(get_sqlite_query!("create-table-tags"))
+            .execute(pool)
+            .await?;
 
         Ok(())
     }
@@ -124,11 +118,11 @@ impl CachedSqlCipher {
         group_id: u128,
         key: &Secret<AES_256_GCM_KEY_LENGTH>,
     ) -> KResult<Arc<Pool<Sqlite>>> {
-        if !self.cache.exists(group_id) {
+        if !self.cache.exists(group_id)? {
             let pool = self.instantiate_group_database(group_id, key).await?;
             Self::create_tables(&pool).await?;
             self.cache.save(group_id, key, pool).await?;
-        } else if !self.cache.opened(group_id) {
+        } else if !self.cache.opened(group_id)? {
             let pool = self.instantiate_group_database(group_id, key).await?;
             self.cache.save(group_id, key, pool).await?;
         }
@@ -143,6 +137,50 @@ impl Database for CachedSqlCipher {
         Some(self.path.join(format!("{group_id}.sqlite")))
     }
 
+    async fn migrate(&self, params: Option<&ExtraDatabaseParams>) -> KResult<()> {
+        if let Some(params) = params {
+            let pool = self.pre_query(params.group_id, &params.key).await?;
+
+            trace!("Migrate database");
+            // Get the context rows
+            match sqlx::query(get_sqlite_query!("select-context"))
+                .fetch_optional(&*pool)
+                .await?
+            {
+                None => {
+                    trace!("No context row found, migrating from scratch");
+                    return migrate_(
+                        &pool,
+                        KMS_VERSION_BEFORE_MIGRATION_SUPPORT,
+                        "insert-context",
+                    )
+                    .await;
+                }
+                Some(context_row) => {
+                    let last_kms_version_run = context_row.get::<String, _>(0);
+                    let state = context_row.get::<String, _>(1);
+                    trace!(
+                        "Context row found, migrating from version {last_kms_version_run} (state: \
+                         {state})"
+                    );
+                    let current_kms_version = crate_version!();
+                    debug!(
+                        "[state={state}] Last KMS version run: {last_kms_version_run}, Current \
+                         KMS version: {current_kms_version}"
+                    );
+
+                    if do_migration(&last_kms_version_run, current_kms_version, &state)? {
+                        return migrate_(&pool, current_kms_version, "update-context").await;
+                    }
+                }
+            }
+
+            return Ok(());
+        }
+
+        kms_bail!("Missing group_id/key for opening SQLCipher")
+    }
+
     async fn create(
         &self,
         uid: Option<String>,
@@ -154,6 +192,10 @@ impl Database for CachedSqlCipher {
     ) -> KResult<String> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
+
             let mut tx = pool.begin().await?;
             match create_(uid, owner, object, attributes, tags, &mut tx).await {
                 Ok(uid) => {
@@ -174,14 +216,14 @@ impl Database for CachedSqlCipher {
 
     async fn retrieve(
         &self,
-        uid: &str,
+        uid_or_tags: &str,
         user: &str,
-        operation_type: ObjectOperationType,
+        query_access_grant: ObjectOperationType,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashMap<String, ObjectWithMetadata>> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
-            let ret = retrieve_(uid, user, operation_type, &*pool).await;
+            let ret = retrieve_(uid_or_tags, user, query_access_grant, &*pool).await;
             self.post_query(params.group_id)?;
             return ret
         }
@@ -214,6 +256,10 @@ impl Database for CachedSqlCipher {
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
+
             let mut tx = pool.begin().await?;
             match update_object_(uid, object, attributes, tags, &mut tx).await {
                 Ok(()) => {
@@ -240,6 +286,9 @@ impl Database for CachedSqlCipher {
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
             let mut tx = pool.begin().await?;
             match update_state_(uid, state, &mut tx).await {
                 Ok(()) => {
@@ -270,6 +319,9 @@ impl Database for CachedSqlCipher {
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
             let mut tx = pool.begin().await?;
             match upsert_(uid, user, object, attributes, tags, state, &mut tx).await {
                 Ok(()) => {
@@ -291,13 +343,16 @@ impl Database for CachedSqlCipher {
     async fn delete(
         &self,
         uid: &str,
-        owner: &str,
+        user: &str,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
             let mut tx = pool.begin().await?;
-            match delete_(uid, owner, &mut tx).await {
+            match delete_(uid, user, &mut tx).await {
                 Ok(()) => {
                     tx.commit().await?;
                     self.post_query(params.group_id)?;
@@ -316,12 +371,12 @@ impl Database for CachedSqlCipher {
 
     async fn list_user_granted_access_rights(
         &self,
-        owner: &str,
+        user: &str,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashMap<String, (String, StateEnumeration, HashSet<ObjectOperationType>)>> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
-            let ret = list_user_granted_access_rights_(owner, &*pool).await;
+            let ret = list_user_granted_access_rights_(user, &*pool).await;
             self.post_query(params.group_id)?;
             return ret
         }
@@ -347,13 +402,16 @@ impl Database for CachedSqlCipher {
     async fn grant_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
-            let ret = insert_access_(uid, userid, operation_types, &*pool).await;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
+            let ret = insert_access_(uid, user, operation_types, &*pool).await;
             self.post_query(params.group_id)?;
             return ret
         }
@@ -364,13 +422,16 @@ impl Database for CachedSqlCipher {
     async fn remove_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
-            let ret = remove_access_(uid, userid, operation_types, &*pool).await;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
+            let ret = remove_access_(uid, user, operation_types, &*pool).await;
             self.post_query(params.group_id)?;
             return ret
         }
@@ -381,12 +442,12 @@ impl Database for CachedSqlCipher {
     async fn is_object_owned_by(
         &self,
         uid: &str,
-        userid: &str,
+        owner: &str,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<bool> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
-            let ret = is_object_owned_by_(uid, userid, &*pool).await;
+            let ret = is_object_owned_by_(uid, owner, &*pool).await;
             self.post_query(params.group_id)?;
             return ret
         }
@@ -424,7 +485,7 @@ impl Database for CachedSqlCipher {
     async fn list_user_access_rights_on_object(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         no_inherited_access: bool,
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashSet<ObjectOperationType>> {
@@ -433,7 +494,7 @@ impl Database for CachedSqlCipher {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
             let ret =
-                list_user_access_rights_on_object_(uid, userid, no_inherited_access, &*pool).await;
+                list_user_access_rights_on_object_(uid, user, no_inherited_access, &*pool).await;
             self.post_query(params.group_id)?;
             return ret
         }
@@ -443,14 +504,17 @@ impl Database for CachedSqlCipher {
 
     async fn atomic(
         &self,
-        owner: &str,
+        user: &str,
         operations: &[AtomicOperation],
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
         if let Some(params) = params {
             let pool = self.pre_query(params.group_id, &params.key).await?;
+            if is_migration_in_progress_(&*pool).await? {
+                kms_bail!("Migration in progress. Please retry later");
+            }
             let mut tx = pool.begin().await?;
-            return match atomic_(owner, operations, &mut tx).await {
+            return match atomic_(user, operations, &mut tx).await {
                 Ok(()) => {
                     tx.commit().await?;
                     self.post_query(params.group_id)?;
diff --git a/crate/server/src/database/cached_sqlite_struct.rs b/crate/server/src/database/cached_sqlite_struct.rs
index 4c50ddf16..8ea3b5845 100644
--- a/crate/server/src/database/cached_sqlite_struct.rs
+++ b/crate/server/src/database/cached_sqlite_struct.rs
@@ -13,7 +13,7 @@ use cosmian_kmip::crypto::{secret::Secret, symmetric::AES_256_GCM_KEY_LENGTH};
 use sqlx::{Pool, Sqlite};
 use tracing::info;
 
-use crate::{kms_bail, kms_error, result::KResult};
+use crate::{error::KmsError, kms_bail, kms_error, result::KResult};
 
 macro_rules! mac {
     ($res: expr, $key:expr, $($bytes: expr),+) => {
@@ -64,26 +64,31 @@ impl fmt::Debug for KMSSqliteCacheItem {
 }
 
 /// Give the time since EPOCH in secs
-pub(crate) fn _now() -> u64 {
-    SystemTime::now()
+pub(crate) fn _now() -> KResult<u64> {
+    Ok(SystemTime::now()
         .duration_since(SystemTime::UNIX_EPOCH)
-        .expect("Unable to get duration since epoch")
-        .as_secs()
+        .map_err(|e| {
+            KmsError::DatabaseError(format!("Unable to get duration since epoch. Error: {e:?}"))
+        })?
+        .as_secs())
 }
 
 impl KMSSqliteCacheItem {
-    #[must_use]
-    pub(crate) fn new(sqlite: Pool<Sqlite>, mac: Vec<u8>, freeable_cache_index: usize) -> Self {
-        Self {
+    pub(crate) fn new(
+        sqlite: Pool<Sqlite>,
+        mac: Vec<u8>,
+        freeable_cache_index: usize,
+    ) -> KResult<Self> {
+        Ok(Self {
             sqlite: Arc::new(sqlite),
             mac,
-            inserted_at: _now(),
+            inserted_at: _now()?,
             in_used: 0,
             last_used_at: 0,
             closed: false,
             closed_at: 0,
             freeable_cache_index,
-        }
+        })
     }
 }
 
@@ -122,21 +127,24 @@ impl KMSSqliteCache {
     }
 
     /// Test if a sqlite connection is opened for a given id
-    pub(crate) fn opened(&self, id: u128) -> bool {
-        let sqlites = self.sqlites.read().expect("Unable to lock for read");
+    pub(crate) fn opened(&self, id: u128) -> KResult<bool> {
+        let sqlites = self.sqlites.read().map_err(|e| {
+            KmsError::DatabaseError(format!("Unable to lock for read. Error: {e:?}"))
+        })?;
         if !sqlites.contains_key(&id) {
-            return false
+            return Ok(false);
         }
 
-        !sqlites[&id].closed
+        Ok(!sqlites[&id].closed)
     }
 
     /// Test if a sqlite connection exist in the cache
-    pub(crate) fn exists(&self, id: u128) -> bool {
-        self.sqlites
+    pub(crate) fn exists(&self, id: u128) -> KResult<bool> {
+        Ok(self
+            .sqlites
             .read()
-            .expect("Unable to lock for read")
-            .contains_key(&id)
+            .map_err(|e| KmsError::DatabaseError(format!("Unable to lock for read. Error: {e:?}")))?
+            .contains_key(&id))
     }
 
     /// Get the sqlite handler and tag it as "used"
@@ -147,7 +155,9 @@ impl KMSSqliteCache {
         id: u128,
         key: &Secret<AES_256_GCM_KEY_LENGTH>,
     ) -> KResult<Arc<Pool<Sqlite>>> {
-        let mut sqlites = self.sqlites.write().expect("Unable to lock for write");
+        let mut sqlites = self.sqlites.write().map_err(|e| {
+            KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+        })?;
 
         let item = sqlites
             .get_mut(&id)
@@ -160,7 +170,7 @@ impl KMSSqliteCache {
         // We need to check if the key provided by the user is the same that was used to open the database
         // If we do not, we can just send any password: the database is already opened anyway.
         // Do this by checking the macs
-        let mut mac = vec![0u8; 32];
+        let mut mac = vec![0_u8; 32];
         mac!(mac.as_mut_slice(), key, id.to_be_bytes().as_slice());
         if mac != item.mac {
             kms_bail!("Database secret is wrong");
@@ -170,12 +180,14 @@ impl KMSSqliteCache {
         if item.in_used == 0 {
             self.freeable_sqlites
                 .write()
-                .expect("Unable to lock for write")
+                .map_err(|e| {
+                    KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+                })?
                 .uncache(item.freeable_cache_index)?;
         }
 
         item.in_used += 1;
-        item.last_used_at = _now();
+        item.last_used_at = _now()?;
 
         Ok(Arc::clone(&item.sqlite))
     }
@@ -185,7 +197,9 @@ impl KMSSqliteCache {
     ///
     /// The function will return an error if the database is not in the cache or already released
     pub(crate) fn release(&self, id: u128) -> KResult<()> {
-        let mut sqlites = self.sqlites.write().expect("Unable to lock for write");
+        let mut sqlites = self.sqlites.write().map_err(|e| {
+            KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+        })?;
 
         let item = sqlites
             .get_mut(&id)
@@ -201,7 +215,9 @@ impl KMSSqliteCache {
         if item.in_used == 0 {
             self.freeable_sqlites
                 .write()
-                .expect("Unable to lock for write")
+                .map_err(|e| {
+                    KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+                })?
                 .recache(item.freeable_cache_index)?;
         }
 
@@ -219,20 +235,24 @@ impl KMSSqliteCache {
             let id = self
                 .freeable_sqlites
                 .write()
-                .expect("Unable to lock for write")
+                .map_err(|e| {
+                    KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+                })?
                 .pop();
 
             let Ok(id) = id else { break }; // nothing in the cache, just leave
 
             let sq = {
-                let mut sqlites = self.sqlites.write().expect("Unable to lock for write");
+                let mut sqlites = self.sqlites.write().map_err(|e| {
+                    KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+                })?;
 
                 let item = sqlites
                     .get_mut(&id)
                     .ok_or_else(|| kms_error!("Key is not in the cache"))?;
 
                 item.closed = true;
-                item.closed_at = _now();
+                item.closed_at = _now()?;
 
                 info!("CachedSQLCipher: freeing = {item:?}");
 
@@ -266,12 +286,13 @@ impl KMSSqliteCache {
         self.flush().await?;
         // If nothing has been flush, allow to exceed max cache size
 
-        let mut sqlites = self.sqlites.write().expect("Unable to lock for write");
+        let mut sqlites = self.sqlites.write().map_err(|e| {
+            KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+        })?;
 
-        let mut freeable_sqlites = self
-            .freeable_sqlites
-            .write()
-            .expect("Unable to lock for write");
+        let mut freeable_sqlites = self.freeable_sqlites.write().map_err(|e| {
+            KmsError::DatabaseError(format!("Unable to lock for write. Error: {e:?}"))
+        })?;
 
         let item = sqlites.get_mut(&id);
         if let Some(item) = item {
@@ -285,7 +306,7 @@ impl KMSSqliteCache {
             item.sqlite = Arc::new(pool);
             item.closed = false;
             item.in_used = 1;
-            item.last_used_at = _now();
+            item.last_used_at = _now()?;
         } else {
             info!("CachedSQLCipher: new group_id={id}");
 
@@ -294,15 +315,15 @@ impl KMSSqliteCache {
 
             // Add it to the SqliteCache
             // compute the mac
-            let mut mac = vec![0u8; 32];
+            let mut mac = vec![0_u8; 32];
             mac!(mac.as_mut_slice(), key, id.to_be_bytes().as_slice());
-            let mut item = KMSSqliteCacheItem::new(pool, mac, freeable_cache_id);
+            let mut item = KMSSqliteCacheItem::new(pool, mac, freeable_cache_id)?;
 
             freeable_sqlites.uncache(freeable_cache_id)?;
 
             // Make it usable (to avoid direct free after alloc in case of cache overflow)
             item.in_used = 1;
-            item.last_used_at = _now();
+            item.last_used_at = _now()?;
 
             sqlites.insert(id, item);
         };
@@ -484,6 +505,7 @@ impl FreeableSqliteCache {
 
 #[cfg(test)]
 mod tests {
+    #![allow(clippy::unwrap_used, clippy::expect_used)]
     use std::{str::FromStr, sync::atomic::Ordering, time::Duration};
 
     use cosmian_kmip::crypto::{secret::Secret, symmetric::AES_256_GCM_KEY_LENGTH};
@@ -566,7 +588,7 @@ mod tests {
         assert_eq!(fsc.length, 0);
         assert_eq!(fsc.size, 4);
 
-        assert!(fsc.pop().is_err());
+        fsc.pop().unwrap_err();
 
         assert_eq!(fsc.push(5), 4);
 
@@ -591,7 +613,7 @@ mod tests {
 
         assert!(fsc.uncache(4).is_err());
 
-        assert!(fsc.uncache(2).is_ok());
+        fsc.uncache(2).unwrap();
 
         assert_eq!(fsc.head, 0);
         assert_eq!(fsc.tail, 3);
@@ -604,7 +626,7 @@ mod tests {
         assert_eq!(fsc.entries[3].next, FSCNeighborEntry::Nil);
         assert_eq!(fsc.entries[3].prev, FSCNeighborEntry::Chained(1));
 
-        assert!(fsc.uncache(0).is_ok());
+        fsc.uncache(0).unwrap();
 
         assert_eq!(fsc.head, 1);
         assert_eq!(fsc.tail, 3);
@@ -615,7 +637,7 @@ mod tests {
         assert_eq!(fsc.entries[1].next, FSCNeighborEntry::Chained(3));
         assert_eq!(fsc.entries[1].prev, FSCNeighborEntry::Nil);
 
-        assert!(fsc.uncache(3).is_ok());
+        fsc.uncache(3).unwrap();
 
         assert_eq!(fsc.head, 1);
         assert_eq!(fsc.tail, 1);
@@ -626,7 +648,7 @@ mod tests {
         assert_eq!(fsc.entries[1].next, FSCNeighborEntry::Nil);
         assert_eq!(fsc.entries[1].prev, FSCNeighborEntry::Nil);
 
-        assert!(fsc.uncache(1).is_ok());
+        fsc.uncache(1).unwrap();
 
         assert_eq!(fsc.length, 0);
         assert_eq!(fsc.size, 4);
@@ -634,7 +656,7 @@ mod tests {
         assert!(!fsc.entries[1].chained);
 
         assert!(fsc.uncache(1).is_err());
-        assert!(fsc.pop().is_err());
+        fsc.pop().unwrap_err();
 
         assert_eq!(fsc.push(5), 4);
         assert_eq!(fsc.head, 4);
@@ -658,7 +680,7 @@ mod tests {
         assert!(fsc.recache(4).is_err());
         assert!(fsc.recache(3).is_err());
 
-        assert!(fsc.uncache(2).is_ok());
+        fsc.uncache(2).unwrap();
 
         assert_eq!(fsc.head, 0);
         assert_eq!(fsc.tail, 3);
@@ -671,7 +693,7 @@ mod tests {
         assert_eq!(fsc.entries[3].next, FSCNeighborEntry::Nil);
         assert_eq!(fsc.entries[3].prev, FSCNeighborEntry::Chained(1));
 
-        assert!(fsc.recache(2).is_ok());
+        fsc.recache(2).unwrap();
         assert!(fsc.recache(2).is_err());
 
         assert_eq!(fsc.head, 0);
@@ -685,11 +707,11 @@ mod tests {
         assert_eq!(fsc.entries[3].next, FSCNeighborEntry::Chained(2));
         assert_eq!(fsc.entries[3].prev, FSCNeighborEntry::Chained(1));
 
-        assert!(fsc.uncache(0).is_ok());
-        assert!(fsc.uncache(1).is_ok());
-        assert!(fsc.uncache(2).is_ok());
-        assert!(fsc.uncache(3).is_ok());
-        assert!(fsc.recache(3).is_ok());
+        fsc.uncache(0).unwrap();
+        fsc.uncache(1).unwrap();
+        fsc.uncache(2).unwrap();
+        fsc.uncache(3).unwrap();
+        fsc.recache(3).unwrap();
 
         assert_eq!(fsc.head, 3);
         assert_eq!(fsc.tail, 3);
@@ -714,66 +736,64 @@ mod tests {
         let sqlite2 = connect().await.expect("Can't create database");
         let sqlite3 = connect().await.expect("Can't create database");
 
-        assert!(cache.save(1, &password, sqlite).await.is_ok());
+        cache.save(1, &password, sqlite).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 1);
-        assert!(cache.save(2, &password, sqlite2).await.is_ok());
+        cache.save(2, &password, sqlite2).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 2); // flush should do nothing here
-        assert!(cache.opened(1));
-        assert!(cache.opened(2));
+        assert!(cache.opened(1).unwrap());
+        assert!(cache.opened(2).unwrap());
 
-        assert!(cache.exists(1));
+        assert!(cache.exists(1).unwrap());
 
         let sqlite2 = connect().await.expect("Can't create database");
-        assert!(cache.save(2, &password, sqlite2).await.is_ok()); // double saved = ok
+        cache.save(2, &password, sqlite2).await.unwrap(); // double saved = ok
 
-        assert!(cache.release(2).is_ok());
+        cache.release(2).unwrap();
         assert!(cache.release(2).is_err()); // not twice
 
-        assert!(cache.exists(2));
-        assert!(cache.opened(2)); // still opened
+        assert!(cache.exists(2).unwrap());
+        assert!(cache.opened(2).unwrap()); // still opened
 
-        assert!(!cache.exists(3));
-        assert!(cache.save(3, &password, sqlite3).await.is_ok());
+        assert!(!cache.exists(3).unwrap());
+        cache.save(3, &password, sqlite3).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 2); // flush should do nothing here
-        assert!(cache.opened(3)); // still opened
-        assert!(!cache.opened(2)); // not opened anymore
-        assert!(cache.exists(2));
+        assert!(cache.opened(3).unwrap()); // still opened
+        assert!(!cache.opened(2).unwrap()); // not opened anymore
+        assert!(cache.exists(2).unwrap());
 
         let sqlite2 = connect().await.expect("Can't create database");
-        assert!(cache.save(2, &password, sqlite2).await.is_ok());
+        cache.save(2, &password, sqlite2).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 3); // flush should do nothing here
-        assert!(cache.opened(2));
+        assert!(cache.opened(2).unwrap());
 
-        assert!(cache.get(4, &password).is_err());
-        assert!(
-            cache
-                .get(1, &Secret::<AES_256_GCM_KEY_LENGTH>::new_random().unwrap())
-                .is_err()
-        ); // bad &password
-        assert!(cache.get(1, &password).is_ok()); // 2 uses of sqlite1
+        cache.get(4, &password).unwrap_err();
+        cache
+            .get(1, &Secret::<AES_256_GCM_KEY_LENGTH>::new_random().unwrap())
+            .unwrap_err(); // bad &password
+        cache.get(1, &password).unwrap(); // 2 uses of sqlite1
 
         let sqlite4 = connect().await.expect("Can't create database");
-        assert!(cache.save(4, &password, sqlite4).await.is_ok());
+        cache.save(4, &password, sqlite4).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 4); // flush should do nothing here
-        assert!(cache.opened(1));
+        assert!(cache.opened(1).unwrap());
 
-        assert!(cache.release(1).is_ok()); // 1 uses of sqlite1
+        cache.release(1).unwrap(); // 1 uses of sqlite1
 
         let sqlite5 = connect().await.expect("Can't create database");
-        assert!(cache.save(5, &password, sqlite5).await.is_ok());
+        cache.save(5, &password, sqlite5).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 5); // flush should do nothing here
-        assert!(cache.opened(1));
+        assert!(cache.opened(1).unwrap());
 
-        assert!(cache.release(1).is_ok()); // 0 uses of sqlite1
-        assert!(cache.opened(1));
+        cache.release(1).unwrap(); // 0 uses of sqlite1
+        assert!(cache.opened(1).unwrap());
 
         let sqlite6 = connect().await.expect("Can't create database");
-        assert!(cache.save(6, &password, sqlite6).await.is_ok());
+        cache.save(6, &password, sqlite6).await.unwrap();
         assert_eq!(cache.current_size.load(Ordering::Relaxed), 5); // flush should do something here
-        assert!(!cache.opened(1));
-        assert!(cache.exists(1));
+        assert!(!cache.opened(1).unwrap());
+        assert!(cache.exists(1).unwrap());
 
-        assert!(cache.get(1, &password).is_err()); // get after close
+        cache.get(1, &password).unwrap_err(); // get after close
     }
 
     async fn connect() -> std::result::Result<sqlx::Pool<sqlx::Sqlite>, sqlx::Error> {
diff --git a/crate/server/src/database/database_trait.rs b/crate/server/src/database/database_trait.rs
index 430191f9e..e6d5c121e 100644
--- a/crate/server/src/database/database_trait.rs
+++ b/crate/server/src/database/database_trait.rs
@@ -18,6 +18,9 @@ pub(crate) trait Database {
     /// Return the filename of the database or `None` if not supported
     fn filename(&self, group_id: u128) -> Option<PathBuf>;
 
+    /// Migrate the database to the latest version
+    async fn migrate(&self, params: Option<&ExtraDatabaseParams>) -> KResult<()>;
+
     /// Insert the given Object in the database.
     ///
     /// A new UUID will be created if none is supplier.
diff --git a/crate/server/src/database/locate_query.rs b/crate/server/src/database/locate_query.rs
index 686639255..8e85e2517 100644
--- a/crate/server/src/database/locate_query.rs
+++ b/crate/server/src/database/locate_query.rs
@@ -60,7 +60,7 @@ pub(crate) trait PlaceholderTrait {
     /// Get node specifier depending on `object_type` (ie: `PrivateKey` or `Certificate`)
     #[must_use]
     fn extract_text_from_object_type_path() -> String {
-        "object ->> 'object_type'".to_string()
+        "object ->> 'object_type'".to_owned()
     }
 }
 
@@ -73,7 +73,7 @@ impl PlaceholderTrait for MySqlPlaceholder {
     const TYPE_INTEGER: &'static str = "SIGNED";
 
     fn binder(_param_number: usize) -> String {
-        "?".to_string()
+        "?".to_owned()
     }
 
     fn additional_rq_from() -> Option<String> {
diff --git a/crate/server/src/database/migrate.rs b/crate/server/src/database/migrate.rs
new file mode 100644
index 000000000..eef10cf0e
--- /dev/null
+++ b/crate/server/src/database/migrate.rs
@@ -0,0 +1,24 @@
+use tracing::trace;
+use version_compare::{compare, Cmp};
+
+use crate::{error::KmsError, result::KResult};
+
+pub(crate) fn do_migration(
+    last_kms_version_run: &str,
+    current_kms_version: &str,
+    state: &str,
+) -> KResult<bool> {
+    if let Ok(cmp) = compare(last_kms_version_run, current_kms_version) {
+        match (cmp, state) {
+            (Cmp::Eq | Cmp::Ge | Cmp::Gt, "ready") => {
+                trace!("No migration needed");
+                Ok(false)
+            }
+            (Cmp::Eq | Cmp::Ge | Cmp::Gt | Cmp::Ne | Cmp::Lt | Cmp::Le, _) => Ok(true),
+        }
+    } else {
+        Err(KmsError::DatabaseError(
+            "Error comparing versions".to_owned(),
+        ))
+    }
+}
diff --git a/crate/server/src/database/mod.rs b/crate/server/src/database/mod.rs
index d89292ab2..256b5a5cb 100644
--- a/crate/server/src/database/mod.rs
+++ b/crate/server/src/database/mod.rs
@@ -1,3 +1,38 @@
+/// This module contains the database implementation for the KMS server.
+/// It provides functionality for interacting with different types of databases,
+/// such as `SQLite``MySQL``PostgreSQL`eSQL, and Redis.
+///
+/// The module includes the following sub-modules:
+/// - `cached_sqlcipher`: Contains the implementation for caching SQL queries using `SQLCipher`.
+/// - `cached_sqlite_struct`: Contains the implementation for caching `SQLite` structures.
+/// - `database_trait`: Contains the trait definition for a generic database.
+/// - `mysql`: Contains the implementation for `MySQL` database.
+/// - `object_with_metadata`: Contains the implementation for objects with metadata.
+/// - `pgsql`: Contains the implementation for `PostgreSQL` database.
+/// - `redis`: Contains the implementation for Redis database.
+/// - `sqlite`: Contains the implementation for `SQLite` database.
+/// - `locate_query`: Contains utility functions for locating queries.
+/// - `migrate`: Contains functions for database migration.
+/// - `retrieve_object_utils`: Contains utility functions for retrieving objects.
+///
+/// The module also defines the following types and constants:
+/// - `KMSServer`: A type alias for the KMS server.
+/// - `DBObject`: A struct representing a database object.
+/// - `KMS_VERSION_BEFORE_MIGRATION_SUPPORT`: A constant representing the KMS version before migration support.
+/// - `PGSQL_FILE_QUERIES`: A constant representing the `PostgreSQL` file queries.
+/// - `MYSQL_FILE_QUERIES`: A constant representing the `MySQL` file queries.
+/// - `SQLITE_FILE_QUERIES`: A constant representing the `SQLite` file queries.
+///
+/// The module also includes the following functions:
+/// - `state_from_string`: Converts a string to a `StateEnumeration` value.
+///
+/// Finally, the module includes a test module for unit testing.
+///
+/// # Errors
+///
+/// This module does not define any specific errors. However, it may return errors
+/// from the underlying database operations or from the functions defined in the sub-modules.
+/// The specific error types and conditions are documented in the respective functions.
 use cosmian_kmip::kmip::{
     kmip_objects::{Object, ObjectType},
     kmip_types::StateEnumeration,
@@ -20,15 +55,14 @@ pub(crate) mod redis;
 pub(crate) mod sqlite;
 pub(crate) use database_trait::{AtomicOperation, Database};
 mod locate_query;
+mod migrate;
 mod retrieve_object_utils;
 pub(crate) use locate_query::{
     query_from_attributes, MySqlPlaceholder, PgSqlPlaceholder, SqlitePlaceholder,
 };
 pub(crate) use retrieve_object_utils::retrieve_object_for_operation;
 
-#[cfg(test)]
-mod tests;
-
+const KMS_VERSION_BEFORE_MIGRATION_SUPPORT: &str = "4.12.0";
 const PGSQL_FILE_QUERIES: &str = include_str!("query.sql");
 const MYSQL_FILE_QUERIES: &str = include_str!("query_mysql.sql");
 const SQLITE_FILE_QUERIES: &str = include_str!("query.sql");
@@ -55,6 +89,11 @@ pub(crate) struct DBObject {
     pub(crate) object: Object,
 }
 
+/// Converts a string to a `StateEnumeration` value.
+///
+/// # Errors
+///
+/// Returns an error if the input string does not match any valid `StateEnumeration` value.
 pub fn state_from_string(s: &str) -> KResult<StateEnumeration> {
     match s {
         "PreActive" => Ok(StateEnumeration::PreActive),
@@ -66,3 +105,6 @@ pub fn state_from_string(s: &str) -> KResult<StateEnumeration> {
         x => kms_bail!("invalid state in db: {}", x),
     }
 }
+
+#[cfg(test)]
+mod tests;
diff --git a/crate/server/src/database/mysql.rs b/crate/server/src/database/mysql.rs
index 418443bea..db1655162 100644
--- a/crate/server/src/database/mysql.rs
+++ b/crate/server/src/database/mysql.rs
@@ -5,6 +5,7 @@ use std::{
 };
 
 use async_trait::async_trait;
+use clap::crate_version;
 use cosmian_kmip::kmip::{
     kmip_objects::Object,
     kmip_operations::ErrorReason,
@@ -25,11 +26,28 @@ use super::{
 };
 use crate::{
     core::extra_database_params::ExtraDatabaseParams,
-    database::database_trait::AtomicOperation,
+    database::{
+        database_trait::AtomicOperation, migrate::do_migration,
+        KMS_VERSION_BEFORE_MIGRATION_SUPPORT,
+    },
     kms_bail, kms_error,
     result::{KResult, KResultHelper},
 };
 
+#[macro_export]
+macro_rules! get_mysql_query {
+    ($name:literal) => {
+        MYSQL_QUERIES
+            .get($name)
+            .ok_or_else(|| kms_error!("{} SQL query can't be found", $name))?
+    };
+    ($name:expr) => {
+        MYSQL_QUERIES
+            .get($name)
+            .ok_or_else(|| kms_error!("{} SQL query can't be found", $name))?
+    };
+}
+
 /// The `MySQL` connector is also compatible to connect a `MariaDB`
 /// see: <https://mariadb.com/kb/en/mariadb-vs-mysql-compatibility>/
 pub(crate) struct MySqlPool {
@@ -47,35 +65,29 @@ impl MySqlPool {
             .connect_with(options)
             .await?;
 
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("create-table-objects")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_mysql_query!("create-table-context"))
+            .execute(&pool)
+            .await?;
 
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("create-table-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_mysql_query!("create-table-objects"))
+            .execute(&pool)
+            .await?;
 
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("create-table-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_mysql_query!("create-table-read_access"))
+            .execute(&pool)
+            .await?;
+
+        sqlx::query(get_mysql_query!("create-table-tags"))
+            .execute(&pool)
+            .await?;
 
         if clear_database {
             clear_database_(&pool).await?;
         }
 
-        Ok(Self { pool })
+        let mysql_pool = Self { pool };
+        mysql_pool.migrate(None).await?;
+        Ok(mysql_pool)
     }
 }
 
@@ -85,17 +97,59 @@ impl Database for MySqlPool {
         None
     }
 
+    async fn migrate(&self, _params: Option<&ExtraDatabaseParams>) -> KResult<()> {
+        trace!("Migrate database");
+        // Get the context rows
+        match sqlx::query(get_mysql_query!("select-context"))
+            .fetch_optional(&self.pool)
+            .await?
+        {
+            None => {
+                trace!("No context row found, migrating from scratch");
+                return migrate_(
+                    &self.pool,
+                    KMS_VERSION_BEFORE_MIGRATION_SUPPORT,
+                    "insert-context",
+                )
+                .await;
+            }
+            Some(context_row) => {
+                let last_kms_version_run = context_row.get::<String, _>(0);
+                let state = context_row.get::<String, _>(1);
+                trace!(
+                    "Context row found, migrating from version {last_kms_version_run} (state: \
+                     {state})"
+                );
+                let current_kms_version = crate_version!();
+                debug!(
+                    "[state={state}] Last KMS version run: {last_kms_version_run}, Current KMS \
+                     version: {current_kms_version}"
+                );
+
+                if do_migration(&last_kms_version_run, current_kms_version, &state)? {
+                    return migrate_(&self.pool, current_kms_version, "update-context").await;
+                }
+            }
+        }
+
+        Ok(())
+    }
+
     async fn create(
         &self,
         uid: Option<String>,
-        user: &str,
+        owner: &str,
         object: &Object,
         attributes: &Attributes,
         tags: &HashSet<String>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<String> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
-        let uid = match create_(uid, user, object, attributes, tags, &mut tx).await {
+        let uid = match create_(uid, owner, object, attributes, tags, &mut tx).await {
             Ok(uid) => uid,
             Err(e) => {
                 tx.rollback().await.context("transaction failed")?;
@@ -110,10 +164,10 @@ impl Database for MySqlPool {
         &self,
         uid_or_tags: &str,
         user: &str,
-        operation_type: ObjectOperationType,
+        query_access_grant: ObjectOperationType,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashMap<String, ObjectWithMetadata>> {
-        retrieve_(uid_or_tags, user, operation_type, &self.pool).await
+        retrieve_(uid_or_tags, user, query_access_grant, &self.pool).await
     }
 
     async fn retrieve_tags(
@@ -151,6 +205,9 @@ impl Database for MySqlPool {
         state: StateEnumeration,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
         let mut tx = self.pool.begin().await?;
         match update_state_(uid, state, &mut tx).await {
             Ok(()) => {
@@ -174,6 +231,10 @@ impl Database for MySqlPool {
         state: StateEnumeration,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match upsert_(uid, user, object, attributes, tags, state, &mut tx).await {
             Ok(()) => {
@@ -193,6 +254,10 @@ impl Database for MySqlPool {
         user: &str,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match delete_(uid, user, &mut tx).await {
             Ok(()) => {
@@ -225,30 +290,38 @@ impl Database for MySqlPool {
     async fn grant_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
-        insert_access_(uid, userid, operation_types, &self.pool).await
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
+        insert_access_(uid, user, operation_types, &self.pool).await
     }
 
     async fn remove_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
-        remove_access_(uid, userid, operation_types, &self.pool).await
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
+        remove_access_(uid, user, operation_types, &self.pool).await
     }
 
     async fn is_object_owned_by(
         &self,
         uid: &str,
-        userid: &str,
+        owner: &str,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<bool> {
-        is_object_owned_by_(uid, userid, &self.pool).await
+        is_object_owned_by_(uid, owner, &self.pool).await
     }
 
     async fn find(
@@ -272,21 +345,25 @@ impl Database for MySqlPool {
     async fn list_user_access_rights_on_object(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         no_inherited_access: bool,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashSet<ObjectOperationType>> {
-        list_user_access_rights_on_object_(uid, userid, no_inherited_access, &self.pool).await
+        list_user_access_rights_on_object_(uid, user, no_inherited_access, &self.pool).await
     }
 
     async fn atomic(
         &self,
-        owner: &str,
+        user: &str,
         operations: &[AtomicOperation],
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
-        match atomic_(owner, operations, &mut tx).await {
+        match atomic_(user, operations, &mut tx).await {
             Ok(()) => {
                 tx.commit().await?;
                 Ok(())
@@ -321,30 +398,22 @@ pub(crate) async fn create_(
     // If the uid is not provided, generate a new one
     let uid = uid.unwrap_or_else(|| Uuid::new_v4().to_string());
 
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("insert-objects")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid.clone())
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(StateEnumeration::Active.to_string())
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
-
-    // Insert the tags
-    for tag in tags {
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("insert-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
+    sqlx::query(get_mysql_query!("insert-objects"))
         .bind(uid.clone())
-        .bind(tag)
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(StateEnumeration::Active.to_string())
+        .bind(owner)
         .execute(&mut **executor)
         .await?;
+
+    // Insert the tags
+    for tag in tags {
+        sqlx::query(get_mysql_query!("insert-tags"))
+            .bind(uid.clone())
+            .bind(tag)
+            .execute(&mut **executor)
+            .await?;
     }
 
     trace!("Created in DB: {uid} / {owner}");
@@ -371,10 +440,7 @@ where
         let tags_params = tags.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
 
         // Build the raw SQL query
-        let raw_sql = MYSQL_QUERIES
-            .get("select-from-tags")
-            .context("SQL query can't be found")?
-            .replace("@TAGS", &tags_params);
+        let raw_sql = get_mysql_query!("select-from-tags").replace("@TAGS", &tags_params);
 
         // Bind the tags params
         let mut query = sqlx::query::<MySql>(&raw_sql);
@@ -382,21 +448,17 @@ where
             query = query.bind(tag);
         }
         // Bind the tags len and the user
-        query = query.bind(tags.len() as i16).bind(user);
+        query = query.bind(i16::try_from(tags.len())?).bind(user);
 
         // Execute the query
         query.fetch_all(executor).await?
     } else {
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("select-object")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(user)
-        .bind(uid_or_tags)
-        .fetch_optional(executor)
-        .await?
-        .map_or(vec![], |row| vec![row])
+        sqlx::query(get_mysql_query!("select-object"))
+            .bind(user)
+            .bind(uid_or_tags)
+            .fetch_optional(executor)
+            .await?
+            .map_or(vec![], |row| vec![row])
     };
 
     // process the rows and find the tags
@@ -434,14 +496,10 @@ async fn retrieve_tags_<'e, E>(uid: &str, executor: E) -> KResult<HashSet<String
 where
     E: Executor<'e, Database = MySql> + Copy,
 {
-    let rows: Vec<MySqlRow> = sqlx::query(
-        MYSQL_QUERIES
-            .get("select-tags")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .fetch_all(executor)
-    .await?;
+    let rows: Vec<MySqlRow> = sqlx::query(get_mysql_query!("select-tags"))
+        .bind(uid)
+        .fetch_all(executor)
+        .await?;
 
     let tags = rows.iter().map(|r| r.get(0)).collect::<HashSet<String>>();
 
@@ -466,39 +524,27 @@ pub(crate) async fn update_object_(
         .context("failed serializing the attributes to JSON")
         .reason(ErrorReason::Internal_Server_Error)?;
 
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("update-object-with-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
-
-    // Insert the new tags if any
-    if let Some(tags) = tags {
-        // delete the existing tags
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("delete-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
+    sqlx::query(get_mysql_query!("update-object-with-object"))
+        .bind(object_json)
+        .bind(attributes_json)
         .bind(uid)
         .execute(&mut **executor)
         .await?;
 
-        for tag in tags {
-            sqlx::query(
-                MYSQL_QUERIES
-                    .get("insert-tags")
-                    .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-            )
+    // Insert the new tags if any
+    if let Some(tags) = tags {
+        // delete the existing tags
+        sqlx::query(get_mysql_query!("delete-tags"))
             .bind(uid)
-            .bind(tag)
             .execute(&mut **executor)
             .await?;
+
+        for tag in tags {
+            sqlx::query(get_mysql_query!("insert-tags"))
+                .bind(uid)
+                .bind(tag)
+                .execute(&mut **executor)
+                .await?;
         }
     }
 
@@ -511,15 +557,11 @@ pub(crate) async fn update_state_(
     state: StateEnumeration,
     executor: &mut Transaction<'_, MySql>,
 ) -> KResult<()> {
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("update-object-with-state")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(state.to_string())
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_mysql_query!("update-object-with-state"))
+        .bind(state.to_string())
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
     trace!("Updated in DB: {uid}");
     Ok(())
 }
@@ -530,25 +572,17 @@ pub(crate) async fn delete_(
     executor: &mut Transaction<'_, MySql>,
 ) -> KResult<()> {
     // delete the object
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("delete-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_mysql_query!("delete-object"))
+        .bind(uid)
+        .bind(owner)
+        .execute(&mut **executor)
+        .await?;
 
     // delete the tags
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("delete-tags")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_mysql_query!("delete-tags"))
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
 
     trace!("Deleted in DB: {uid}");
     Ok(())
@@ -574,43 +608,31 @@ pub(crate) async fn upsert_(
         .context("failed serializing the attributes to JSON")
         .reason(ErrorReason::Internal_Server_Error)?;
 
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("upsert-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(state.to_string())
-    .bind(owner)
-    .bind(owner)
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_mysql_query!("upsert-object"))
+        .bind(uid)
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(state.to_string())
+        .bind(owner)
+        .bind(owner)
+        .bind(owner)
+        .execute(&mut **executor)
+        .await?;
 
     // Insert the new tags if present
     if let Some(tags) = tags {
         // delete the existing tags
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("delete-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .execute(&mut **executor)
-        .await?;
-        // insert the new ones
-        for tag in tags {
-            sqlx::query(
-                MYSQL_QUERIES
-                    .get("insert-tags")
-                    .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-            )
+        sqlx::query(get_mysql_query!("delete-tags"))
             .bind(uid)
-            .bind(tag)
             .execute(&mut **executor)
             .await?;
+        // insert the new ones
+        for tag in tags {
+            sqlx::query(get_mysql_query!("insert-tags"))
+                .bind(uid)
+                .bind(tag)
+                .execute(&mut **executor)
+                .await?;
         }
     }
 
@@ -627,14 +649,10 @@ where
 {
     debug!("Uid = {}", uid);
 
-    let list = sqlx::query(
-        MYSQL_QUERIES
-            .get("select-rows-read_access-with-object-id")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .fetch_all(executor)
-    .await?;
+    let list = sqlx::query(get_mysql_query!("select-rows-read_access-with-object-id"))
+        .bind(uid)
+        .fetch_all(executor)
+        .await?;
     let mut ids: HashMap<String, HashSet<ObjectOperationType>> = HashMap::with_capacity(list.len());
     for row in list {
         ids.insert(
@@ -656,14 +674,10 @@ where
     E: Executor<'e, Database = MySql> + Copy,
 {
     debug!("Owner = {}", user);
-    let list = sqlx::query(
-        MYSQL_QUERIES
-            .get("select-objects-access-obtained")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(user)
-    .fetch_all(executor)
-    .await?;
+    let list = sqlx::query(get_mysql_query!("select-objects-access-obtained"))
+        .bind(user)
+        .fetch_all(executor)
+        .await?;
     let mut ids: HashMap<String, (String, StateEnumeration, HashSet<ObjectOperationType>)> =
         HashMap::with_capacity(list.len());
     for row in list {
@@ -704,15 +718,11 @@ async fn perms<'e, E>(uid: &str, userid: &str, executor: E) -> KResult<HashSet<O
 where
     E: Executor<'e, Database = MySql> + Copy,
 {
-    let row: Option<MySqlRow> = sqlx::query(
-        MYSQL_QUERIES
-            .get("select-user-accesses-for-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .fetch_optional(executor)
-    .await?;
+    let row: Option<MySqlRow> = sqlx::query(get_mysql_query!("select-user-accesses-for-object"))
+        .bind(uid)
+        .bind(userid)
+        .fetch_optional(executor)
+        .await?;
 
     row.map_or(Ok(HashSet::new()), |row| {
         let perms_raw = row.get::<Vec<u8>, _>(0);
@@ -745,16 +755,12 @@ where
         .reason(ErrorReason::Internal_Server_Error)?;
 
     // Upsert the DB
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("upsert-row-read_access")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .bind(json)
-    .execute(executor)
-    .await?;
+    sqlx::query(get_mysql_query!("upsert-row-read_access"))
+        .bind(uid)
+        .bind(userid)
+        .bind(json)
+        .execute(executor)
+        .await?;
     trace!("Insert read access right in DB: {uid} / {userid}");
     Ok(())
 }
@@ -777,15 +783,11 @@ where
 
     // No remaining permissions, delete the row
     if perms.is_empty() {
-        sqlx::query(
-            MYSQL_QUERIES
-                .get("delete-rows-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .bind(userid)
-        .execute(executor)
-        .await?;
+        sqlx::query(get_mysql_query!("delete-rows-read_access"))
+            .bind(uid)
+            .bind(userid)
+            .execute(executor)
+            .await?;
         return Ok(())
     }
 
@@ -795,16 +797,12 @@ where
         .reason(ErrorReason::Internal_Server_Error)?;
 
     // Update the DB
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("update-rows-read_access-with-permission")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(json)
-    .bind(uid)
-    .bind(userid)
-    .execute(executor)
-    .await?;
+    sqlx::query(get_mysql_query!("update-rows-read_access-with-permission"))
+        .bind(json)
+        .bind(uid)
+        .bind(userid)
+        .execute(executor)
+        .await?;
     Ok(())
 }
 
@@ -812,15 +810,11 @@ pub(crate) async fn is_object_owned_by_<'e, E>(uid: &str, owner: &str, executor:
 where
     E: Executor<'e, Database = MySql> + Copy,
 {
-    let row: Option<MySqlRow> = sqlx::query(
-        MYSQL_QUERIES
-            .get("has-row-objects")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(owner)
-    .fetch_optional(executor)
-    .await?;
+    let row: Option<MySqlRow> = sqlx::query(get_mysql_query!("has-row-objects"))
+        .bind(uid)
+        .bind(owner)
+        .fetch_optional(executor)
+        .await?;
     Ok(row.is_some())
 }
 
@@ -876,30 +870,22 @@ async fn clear_database_<'e, E>(executor: E) -> KResult<()>
 where
     E: Executor<'e, Database = MySql> + Copy,
 {
+    // Erase `context` table
+    sqlx::query(get_mysql_query!("clean-table-context"))
+        .execute(executor)
+        .await?;
     // Erase `objects` table
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("clean-table-objects")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_mysql_query!("clean-table-objects"))
+        .execute(executor)
+        .await?;
     // Erase `read_access` table
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("clean-table-read_access")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_mysql_query!("clean-table-read_access"))
+        .execute(executor)
+        .await?;
     // Erase `tags` table
-    sqlx::query(
-        MYSQL_QUERIES
-            .get("clean-table-tags")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_mysql_query!("clean-table-tags"))
+        .execute(executor)
+        .await?;
     Ok(())
 }
 
@@ -943,3 +929,140 @@ pub(crate) async fn atomic_(
     }
     Ok(())
 }
+
+pub(crate) async fn is_migration_in_progress_<'e, E>(executor: E) -> KResult<bool>
+where
+    E: Executor<'e, Database = MySql> + Copy,
+{
+    match sqlx::query(get_mysql_query!("select-context"))
+        .fetch_optional(executor)
+        .await?
+    {
+        Some(context_row) => {
+            let state = context_row.get::<String, _>(1);
+            Ok(state == "upgrading")
+        }
+        None => Ok(false),
+    }
+}
+
+pub(crate) async fn migrate_(
+    executor: &Pool<MySql>,
+    last_version_run: &str,
+    query_name: &str,
+) -> KResult<()> {
+    trace!("Set status to upgrading and last version run: {last_version_run}");
+    let upsert_context = get_mysql_query!(query_name);
+    trace!("{query_name}: {upsert_context}");
+    match query_name {
+        "insert-context" => {
+            sqlx::query(upsert_context)
+                .bind(last_version_run)
+                .bind("upgrading")
+                .execute(executor)
+                .await
+        }
+        "update-context" => {
+            sqlx::query(upsert_context)
+                .bind(last_version_run)
+                .bind("upgrading")
+                .bind("upgrading")
+                .execute(executor)
+                .await
+        }
+        _ => kms_bail!("Unknown query name: {query_name}"),
+    }?;
+
+    trace!("Migrate data from version {last_version_run}");
+
+    // Process migration for each KMS version
+    let current_kms_version = crate_version!();
+    if last_version_run == KMS_VERSION_BEFORE_MIGRATION_SUPPORT {
+        migrate_from_4_12_0_to_4_13_0(executor).await?;
+    } else {
+        trace!("No migration needed between {last_version_run} and {current_kms_version}");
+    }
+
+    // Set the current running version
+    trace!("Set status to ready and last version run: {current_kms_version}");
+    sqlx::query(get_mysql_query!("update-context"))
+        .bind(current_kms_version)
+        .bind("ready")
+        .bind("upgrading")
+        .execute(executor)
+        .await?;
+
+    Ok(())
+}
+
+/// Before the version 4.13.0, the KMIP attributes were stored in the objects table (via the objects themselves).
+/// The new column attributes allows to store the KMIP attributes in a dedicated column even for KMIP objects that do not have KMIP attributes (such as Certificates).
+pub(crate) async fn migrate_from_4_12_0_to_4_13_0(executor: &Pool<MySql>) -> KResult<()> {
+    trace!("Migrating from 4.12.0 to 4.13.0");
+
+    // Add the column attributes to the objects table
+    if (sqlx::query(get_mysql_query!("has-column-attributes"))
+        .execute(executor)
+        .await)
+        .is_ok()
+    {
+        trace!("Column attributes already exists, nothing to do");
+        return Ok(());
+    }
+
+    trace!("Column attributes does not exist, adding it");
+    sqlx::query(get_mysql_query!("add-column-attributes"))
+        .execute(executor)
+        .await?;
+
+    // Select all objects and extract the KMIP attributes to be stored in the new column
+    let rows = sqlx::query("SELECT * FROM objects")
+        .fetch_all(executor)
+        .await?;
+
+    let mut operations = Vec::with_capacity(rows.len());
+    for row in rows {
+        let uid = row.get::<String, _>(0);
+        let db_object: DBObject = serde_json::from_slice(&row.get::<Vec<u8>, _>(1))
+            .context("migrate: failed deserializing the object")
+            .reason(ErrorReason::Internal_Server_Error)?;
+        let object = Object::post_fix(db_object.object_type, db_object.object);
+        trace!(
+            "migrate_from_4_12_0_to_4_13_0: object (type: {})={:?}",
+            object.object_type(),
+            uid
+        );
+        let attributes = match object.clone().attributes() {
+            Ok(attrs) => attrs.clone(),
+            Err(_error) => {
+                // For example, Certificate object has no KMIP-attribute
+                Attributes::default()
+            }
+        };
+        let tags = retrieve_tags_(&uid, executor).await?;
+        operations.push(AtomicOperation::UpdateObject((
+            uid,
+            object,
+            attributes,
+            Some(tags),
+        )));
+    }
+
+    let mut tx = executor.begin().await?;
+    match atomic_(
+        "this user is not used to update objects",
+        &operations,
+        &mut tx,
+    )
+    .await
+    {
+        Ok(()) => {
+            tx.commit().await?;
+            Ok(())
+        }
+        Err(e) => {
+            tx.rollback().await.context("transaction failed")?;
+            Err(e)
+        }
+    }
+}
diff --git a/crate/server/src/database/object_with_metadata.rs b/crate/server/src/database/object_with_metadata.rs
index fc003463f..7a6535058 100644
--- a/crate/server/src/database/object_with_metadata.rs
+++ b/crate/server/src/database/object_with_metadata.rs
@@ -62,7 +62,8 @@ impl TryFrom<&SqliteRow> for ObjectWithMetadata {
             .context("failed deserializing the object")
             .reason(ErrorReason::Internal_Server_Error)?;
         let object = Object::post_fix(db_object.object_type, db_object.object);
-        let attributes = serde_json::from_str(&row.get::<String, _>(2))?;
+        let raw_attributes = row.get::<Value, _>(2);
+        let attributes = serde_json::from_value(raw_attributes)?;
         let owner = row.get::<String, _>(3);
         let state = state_from_string(&row.get::<String, _>(4))?;
         let raw_permissions = row.get::<Vec<u8>, _>(5);
diff --git a/crate/server/src/database/pgsql.rs b/crate/server/src/database/pgsql.rs
index 79c03d437..0f4d15af9 100644
--- a/crate/server/src/database/pgsql.rs
+++ b/crate/server/src/database/pgsql.rs
@@ -5,6 +5,7 @@ use std::{
 };
 
 use async_trait::async_trait;
+use clap::crate_version;
 use cosmian_kmip::kmip::{
     kmip_objects::Object,
     kmip_operations::ErrorReason,
@@ -22,15 +23,29 @@ use uuid::Uuid;
 use crate::{
     core::extra_database_params::ExtraDatabaseParams,
     database::{
-        database_trait::AtomicOperation, object_with_metadata::ObjectWithMetadata,
-        query_from_attributes, state_from_string, DBObject, Database, PgSqlPlaceholder,
-        PGSQL_QUERIES,
+        database_trait::AtomicOperation, migrate::do_migration,
+        object_with_metadata::ObjectWithMetadata, query_from_attributes, state_from_string,
+        DBObject, Database, PgSqlPlaceholder, KMS_VERSION_BEFORE_MIGRATION_SUPPORT, PGSQL_QUERIES,
     },
     error::KmsError,
     kms_bail, kms_error,
     result::{KResult, KResultHelper},
 };
 
+#[macro_export]
+macro_rules! get_pgsql_query {
+    ($name:literal) => {
+        PGSQL_QUERIES
+            .get($name)
+            .ok_or_else(|| kms_error!("{} SQL query can't be found", $name))?
+    };
+    ($name:expr) => {
+        PGSQL_QUERIES
+            .get($name)
+            .ok_or_else(|| kms_error!("{} SQL query can't be found", $name))?
+    };
+}
+
 pub(crate) struct PgPool {
     pool: Pool<Postgres>,
 }
@@ -48,35 +63,29 @@ impl PgPool {
             .connect_with(options)
             .await?;
 
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("create-table-objects")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_pgsql_query!("create-table-objects"))
+            .execute(&pool)
+            .await?;
 
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("create-table-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_pgsql_query!("create-table-context"))
+            .execute(&pool)
+            .await?;
 
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("create-table-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_pgsql_query!("create-table-read_access"))
+            .execute(&pool)
+            .await?;
+
+        sqlx::query(get_pgsql_query!("create-table-tags"))
+            .execute(&pool)
+            .await?;
 
         if clear_database {
             clear_database_(&pool).await?;
         }
 
-        Ok(Self { pool })
+        let pgsql_pool = Self { pool };
+        pgsql_pool.migrate(None).await?;
+        Ok(pgsql_pool)
     }
 }
 
@@ -86,17 +95,59 @@ impl Database for PgPool {
         None
     }
 
+    async fn migrate(&self, _params: Option<&ExtraDatabaseParams>) -> KResult<()> {
+        trace!("Migrate database");
+        // Get the context rows
+        match sqlx::query(get_pgsql_query!("select-context"))
+            .fetch_optional(&self.pool)
+            .await?
+        {
+            None => {
+                trace!("No context row found, migrating from scratch");
+                return migrate_(
+                    &self.pool,
+                    KMS_VERSION_BEFORE_MIGRATION_SUPPORT,
+                    "insert-context",
+                )
+                .await;
+            }
+            Some(context_row) => {
+                let last_kms_version_run = context_row.get::<String, _>(0);
+                let state = context_row.get::<String, _>(1);
+                trace!(
+                    "Context row found, migrating from version {last_kms_version_run} (state: \
+                     {state})"
+                );
+                let current_kms_version = crate_version!();
+                debug!(
+                    "[state={state}] Last KMS version run: {last_kms_version_run}, Current KMS \
+                     version: {current_kms_version}"
+                );
+
+                if do_migration(&last_kms_version_run, current_kms_version, &state)? {
+                    return migrate_(&self.pool, current_kms_version, "update-context").await;
+                }
+            }
+        }
+
+        Ok(())
+    }
+
     async fn create(
         &self,
         uid: Option<String>,
-        user: &str,
+        owner: &str,
         object: &Object,
         attributes: &Attributes,
         tags: &HashSet<String>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<String> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
-        let uid = match create_(uid, user, object, attributes, tags, &mut tx).await {
+        let uid = match create_(uid, owner, object, attributes, tags, &mut tx).await {
             Ok(uid) => uid,
             Err(e) => {
                 tx.rollback().await.context("transaction failed")?;
@@ -111,10 +162,10 @@ impl Database for PgPool {
         &self,
         uid_or_tags: &str,
         user: &str,
-        operation_type: ObjectOperationType,
+        query_access_grant: ObjectOperationType,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashMap<String, ObjectWithMetadata>> {
-        retrieve_(uid_or_tags, user, operation_type, &self.pool).await
+        retrieve_(uid_or_tags, user, query_access_grant, &self.pool).await
     }
 
     async fn retrieve_tags(
@@ -152,6 +203,9 @@ impl Database for PgPool {
         state: StateEnumeration,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
         let mut tx = self.pool.begin().await?;
         match update_state_(uid, state, &mut tx).await {
             Ok(()) => {
@@ -175,6 +229,10 @@ impl Database for PgPool {
         state: StateEnumeration,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match upsert_(uid, user, object, attributes, tags, state, &mut tx).await {
             Ok(()) => {
@@ -194,6 +252,10 @@ impl Database for PgPool {
         user: &str,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match delete_(uid, user, &mut tx).await {
             Ok(()) => {
@@ -226,30 +288,38 @@ impl Database for PgPool {
     async fn grant_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
-        insert_access_(uid, userid, operation_types, &self.pool).await
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
+        insert_access_(uid, user, operation_types, &self.pool).await
     }
 
     async fn remove_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
-        remove_access_(uid, userid, operation_types, &self.pool).await
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
+        remove_access_(uid, user, operation_types, &self.pool).await
     }
 
     async fn is_object_owned_by(
         &self,
         uid: &str,
-        userid: &str,
+        owner: &str,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<bool> {
-        is_object_owned_by_(uid, userid, &self.pool).await
+        is_object_owned_by_(uid, owner, &self.pool).await
     }
 
     async fn find(
@@ -273,21 +343,25 @@ impl Database for PgPool {
     async fn list_user_access_rights_on_object(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         no_inherited_access: bool,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashSet<ObjectOperationType>> {
-        list_user_access_rights_on_object_(uid, userid, no_inherited_access, &self.pool).await
+        list_user_access_rights_on_object_(uid, user, no_inherited_access, &self.pool).await
     }
 
     async fn atomic(
         &self,
-        owner: &str,
+        user: &str,
         operations: &[AtomicOperation],
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
-        match atomic_(owner, operations, &mut tx).await {
+        match atomic_(user, operations, &mut tx).await {
             Ok(()) => {
                 tx.commit().await?;
                 Ok(())
@@ -322,30 +396,22 @@ pub(crate) async fn create_(
     // If the uid is not provided, generate a new one
     let uid = uid.unwrap_or_else(|| Uuid::new_v4().to_string());
 
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("insert-objects")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid.clone())
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(StateEnumeration::Active.to_string())
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
-
-    // Insert the tags
-    for tag in tags {
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("insert-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
+    sqlx::query(get_pgsql_query!("insert-objects"))
         .bind(uid.clone())
-        .bind(tag)
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(StateEnumeration::Active.to_string())
+        .bind(owner)
         .execute(&mut **executor)
         .await?;
+
+    // Insert the tags
+    for tag in tags {
+        sqlx::query(get_pgsql_query!("insert-tags"))
+            .bind(uid.clone())
+            .bind(tag)
+            .execute(&mut **executor)
+            .await?;
     }
 
     trace!("Created in DB: {uid} / {owner}");
@@ -377,9 +443,7 @@ where
             .join(", ");
 
         // Build the raw SQL query
-        let raw_sql = PGSQL_QUERIES
-            .get("select-from-tags")
-            .context("SQL query can't be found")?
+        let raw_sql = get_pgsql_query!("select-from-tags")
             .replace("@TAGS", &tags_params)
             .replace("@LEN", &format!("${}", tags.len() + 1))
             .replace("@USER", &format!("${}", tags.len() + 2));
@@ -390,21 +454,17 @@ where
             query = query.bind(tag);
         }
         // Bind the tags len and the user
-        query = query.bind(tags.len() as i16).bind(user);
+        query = query.bind(i16::try_from(tags.len())?).bind(user);
 
         // Execute the query
         query.fetch_all(executor).await?
     } else {
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("select-object")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid_or_tags)
-        .bind(user)
-        .fetch_optional(executor)
-        .await?
-        .map_or(vec![], |row| vec![row])
+        sqlx::query(get_pgsql_query!("select-object"))
+            .bind(uid_or_tags)
+            .bind(user)
+            .fetch_optional(executor)
+            .await?
+            .map_or(vec![], |row| vec![row])
     };
 
     // process the rows and find the tags
@@ -442,14 +502,10 @@ async fn retrieve_tags_<'e, E>(uid: &str, executor: E) -> KResult<HashSet<String
 where
     E: Executor<'e, Database = Postgres> + Copy,
 {
-    let rows: Vec<PgRow> = sqlx::query(
-        PGSQL_QUERIES
-            .get("select-tags")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .fetch_all(executor)
-    .await?;
+    let rows: Vec<PgRow> = sqlx::query(get_pgsql_query!("select-tags"))
+        .bind(uid)
+        .fetch_all(executor)
+        .await?;
 
     let tags = rows.iter().map(|r| r.get(0)).collect::<HashSet<String>>();
 
@@ -474,39 +530,27 @@ pub(crate) async fn update_object_(
         .context("failed serializing the attributes to JSON")
         .reason(ErrorReason::Internal_Server_Error)?;
 
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("update-object-with-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
-
-    // Update the tags
-    if let Some(tags) = tags {
-        // delete the existing tags
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("delete-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
+    sqlx::query(get_pgsql_query!("update-object-with-object"))
+        .bind(object_json)
+        .bind(attributes_json)
         .bind(uid)
         .execute(&mut **executor)
         .await?;
 
-        for tag in tags {
-            sqlx::query(
-                PGSQL_QUERIES
-                    .get("insert-tags")
-                    .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-            )
+    // Insert the new tags if any
+    if let Some(tags) = tags {
+        // delete the existing tags
+        sqlx::query(get_pgsql_query!("delete-tags"))
             .bind(uid)
-            .bind(tag)
             .execute(&mut **executor)
             .await?;
+
+        for tag in tags {
+            sqlx::query(get_pgsql_query!("insert-tags"))
+                .bind(uid)
+                .bind(tag)
+                .execute(&mut **executor)
+                .await?;
         }
     }
 
@@ -519,15 +563,11 @@ pub(crate) async fn update_state_(
     state: StateEnumeration,
     executor: &mut Transaction<'_, Postgres>,
 ) -> KResult<()> {
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("update-object-with-state")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(state.to_string())
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("update-object-with-state"))
+        .bind(state.to_string())
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
     trace!("Updated in DB: {uid}");
     Ok(())
 }
@@ -538,25 +578,17 @@ pub(crate) async fn delete_(
     executor: &mut Transaction<'_, Postgres>,
 ) -> KResult<()> {
     // delete the object
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("delete-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("delete-object"))
+        .bind(uid)
+        .bind(owner)
+        .execute(&mut **executor)
+        .await?;
 
     // delete the tags
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("delete-tags")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("delete-tags"))
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
 
     trace!("Deleted in DB: {uid}");
     Ok(())
@@ -582,41 +614,29 @@ pub(crate) async fn upsert_(
         .context("failed serializing the attributes to JSON")
         .reason(ErrorReason::Internal_Server_Error)?;
 
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("upsert-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(state.to_string())
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("upsert-object"))
+        .bind(uid)
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(state.to_string())
+        .bind(owner)
+        .execute(&mut **executor)
+        .await?;
 
     // Insert the new tags if present
     if let Some(tags) = tags {
         // delete the existing tags
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("delete-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .execute(&mut **executor)
-        .await?;
-        // insert the new ones
-        for tag in tags {
-            sqlx::query(
-                PGSQL_QUERIES
-                    .get("insert-tags")
-                    .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-            )
+        sqlx::query(get_pgsql_query!("delete-tags"))
             .bind(uid)
-            .bind(tag)
             .execute(&mut **executor)
             .await?;
+        // insert the new ones
+        for tag in tags {
+            sqlx::query(get_pgsql_query!("insert-tags"))
+                .bind(uid)
+                .bind(tag)
+                .execute(&mut **executor)
+                .await?;
         }
     }
 
@@ -633,14 +653,10 @@ where
 {
     debug!("Uid = {}", uid);
 
-    let list = sqlx::query(
-        PGSQL_QUERIES
-            .get("select-rows-read_access-with-object-id")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .fetch_all(executor)
-    .await?;
+    let list = sqlx::query(get_pgsql_query!("select-rows-read_access-with-object-id"))
+        .bind(uid)
+        .fetch_all(executor)
+        .await?;
     let mut ids: HashMap<String, HashSet<ObjectOperationType>> = HashMap::with_capacity(list.len());
     for row in list {
         ids.insert(
@@ -662,14 +678,10 @@ where
     E: Executor<'e, Database = Postgres> + Copy,
 {
     debug!("Owner = {}", user);
-    let list = sqlx::query(
-        PGSQL_QUERIES
-            .get("select-objects-access-obtained")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(user)
-    .fetch_all(executor)
-    .await?;
+    let list = sqlx::query(get_pgsql_query!("select-objects-access-obtained"))
+        .bind(user)
+        .fetch_all(executor)
+        .await?;
     let mut ids: HashMap<String, (String, StateEnumeration, HashSet<ObjectOperationType>)> =
         HashMap::with_capacity(list.len());
     for row in list {
@@ -710,15 +722,11 @@ async fn perms<'e, E>(uid: &str, userid: &str, executor: E) -> KResult<HashSet<O
 where
     E: Executor<'e, Database = Postgres> + Copy,
 {
-    let row: Option<PgRow> = sqlx::query(
-        PGSQL_QUERIES
-            .get("select-user-accesses-for-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .fetch_optional(executor)
-    .await?;
+    let row: Option<PgRow> = sqlx::query(get_pgsql_query!("select-user-accesses-for-object"))
+        .bind(uid)
+        .bind(userid)
+        .fetch_optional(executor)
+        .await?;
 
     row.map_or(Ok(HashSet::new()), |row| {
         let perms_value = row
@@ -753,16 +761,12 @@ where
         .reason(ErrorReason::Internal_Server_Error)?;
 
     // Upsert the DB
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("upsert-row-read_access")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .bind(json)
-    .execute(executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("upsert-row-read_access"))
+        .bind(uid)
+        .bind(userid)
+        .bind(json)
+        .execute(executor)
+        .await?;
     trace!("Insert read access right in DB: {uid} / {userid}");
     Ok(())
 }
@@ -785,15 +789,11 @@ where
 
     // No remaining permissions, delete the row
     if perms.is_empty() {
-        sqlx::query(
-            PGSQL_QUERIES
-                .get("delete-rows-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .bind(userid)
-        .execute(executor)
-        .await?;
+        sqlx::query(get_pgsql_query!("delete-rows-read_access"))
+            .bind(uid)
+            .bind(userid)
+            .execute(executor)
+            .await?;
         return Ok(())
     }
 
@@ -803,16 +803,12 @@ where
         .reason(ErrorReason::Internal_Server_Error)?;
 
     // Update the DB
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("update-rows-read_access-with-permission")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .bind(json)
-    .execute(executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("update-rows-read_access-with-permission"))
+        .bind(uid)
+        .bind(userid)
+        .bind(json)
+        .execute(executor)
+        .await?;
     trace!("Deleted in DB: {uid} / {userid}");
     Ok(())
 }
@@ -821,15 +817,11 @@ pub(crate) async fn is_object_owned_by_<'e, E>(uid: &str, owner: &str, executor:
 where
     E: Executor<'e, Database = Postgres> + Copy,
 {
-    let row: Option<PgRow> = sqlx::query(
-        PGSQL_QUERIES
-            .get("has-row-objects")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(owner)
-    .fetch_optional(executor)
-    .await?;
+    let row: Option<PgRow> = sqlx::query(get_pgsql_query!("has-row-objects"))
+        .bind(uid)
+        .bind(owner)
+        .fetch_optional(executor)
+        .await?;
     Ok(row.is_some())
 }
 
@@ -863,7 +855,7 @@ fn to_qualified_uids(
     let mut uids = Vec::with_capacity(rows.len());
     for row in rows {
         let attrs: Attributes = match row.try_get::<Value, _>(2) {
-            Err(_) => return Err(KmsError::DatabaseError("no attributes found".to_string())),
+            Err(_) => return Err(KmsError::DatabaseError("no attributes found".to_owned())),
             Ok(v) => serde_json::from_value(v)
                 .context("failed deserializing the attributes")
                 .map_err(|e| KmsError::DatabaseError(e.to_string()))?,
@@ -883,30 +875,22 @@ async fn clear_database_<'e, E>(executor: E) -> KResult<()>
 where
     E: Executor<'e, Database = Postgres> + Copy,
 {
+    // Erase `context` table
+    sqlx::query(get_pgsql_query!("clean-table-context"))
+        .execute(executor)
+        .await?;
     // Erase `objects` table
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("clean-table-objects")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("clean-table-objects"))
+        .execute(executor)
+        .await?;
     // Erase `read_access` table
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("clean-table-read_access")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("clean-table-read_access"))
+        .execute(executor)
+        .await?;
     // Erase `tags` table
-    sqlx::query(
-        PGSQL_QUERIES
-            .get("clean-table-tags")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_pgsql_query!("clean-table-tags"))
+        .execute(executor)
+        .await?;
     Ok(())
 }
 
@@ -950,3 +934,140 @@ pub(crate) async fn atomic_(
     }
     Ok(())
 }
+
+pub(crate) async fn is_migration_in_progress_<'e, E>(executor: E) -> KResult<bool>
+where
+    E: Executor<'e, Database = Postgres> + Copy,
+{
+    match sqlx::query(get_pgsql_query!("select-context"))
+        .fetch_optional(executor)
+        .await?
+    {
+        Some(context_row) => {
+            let state = context_row.get::<String, _>(1);
+            Ok(state == "upgrading")
+        }
+        None => Ok(false),
+    }
+}
+
+pub(crate) async fn migrate_(
+    executor: &Pool<Postgres>,
+    last_version_run: &str,
+    query_name: &str,
+) -> KResult<()> {
+    trace!("Set status to upgrading and last version run: {last_version_run}");
+    let upsert_context = get_pgsql_query!(query_name);
+    trace!("{query_name}: {upsert_context}");
+    match query_name {
+        "insert-context" => {
+            sqlx::query(upsert_context)
+                .bind(last_version_run)
+                .bind("upgrading")
+                .execute(executor)
+                .await
+        }
+        "update-context" => {
+            sqlx::query(upsert_context)
+                .bind(last_version_run)
+                .bind("upgrading")
+                .bind("upgrading")
+                .execute(executor)
+                .await
+        }
+        _ => kms_bail!("Unknown query name: {query_name}"),
+    }?;
+
+    trace!("Migrate data from version {last_version_run}");
+
+    // Process migration for each KMS version
+    let current_kms_version = crate_version!();
+    if last_version_run == KMS_VERSION_BEFORE_MIGRATION_SUPPORT {
+        migrate_from_4_12_0_to_4_13_0(executor).await?;
+    } else {
+        trace!("No migration needed between {last_version_run} and {current_kms_version}");
+    }
+
+    // Set the current running version
+    trace!("Set status to ready and last version run: {current_kms_version}");
+    sqlx::query(get_pgsql_query!("update-context"))
+        .bind(current_kms_version)
+        .bind("ready")
+        .bind("upgrading")
+        .execute(executor)
+        .await?;
+
+    Ok(())
+}
+
+/// Before the version 4.13.0, the KMIP attributes were stored in the objects table (via the objects themselves).
+/// The new column attributes allows to store the KMIP attributes in a dedicated column even for KMIP objects that do not have KMIP attributes (such as Certificates).
+pub(crate) async fn migrate_from_4_12_0_to_4_13_0(executor: &Pool<Postgres>) -> KResult<()> {
+    trace!("Migrating from 4.12.0 to 4.13.0");
+
+    // Add the column attributes to the objects table
+    if (sqlx::query(get_pgsql_query!("has-column-attributes"))
+        .execute(executor)
+        .await)
+        .is_ok()
+    {
+        trace!("Column attributes already exists, nothing to do");
+        return Ok(());
+    }
+
+    trace!("Column attributes does not exist, adding it");
+    sqlx::query(get_pgsql_query!("add-column-attributes"))
+        .execute(executor)
+        .await?;
+
+    // Select all objects and extract the KMIP attributes to be stored in the new column
+    let rows = sqlx::query("SELECT * FROM objects")
+        .fetch_all(executor)
+        .await?;
+
+    let mut operations = Vec::with_capacity(rows.len());
+    for row in rows {
+        let uid = row.get::<String, _>(0);
+        let db_object: DBObject = serde_json::from_slice(&row.get::<Vec<u8>, _>(1))
+            .context("migrate: failed deserializing the object")
+            .reason(ErrorReason::Internal_Server_Error)?;
+        let object = Object::post_fix(db_object.object_type, db_object.object);
+        trace!(
+            "migrate_from_4_12_0_to_4_13_0: object (type: {})={:?}",
+            object.object_type(),
+            uid
+        );
+        let attributes = match object.clone().attributes() {
+            Ok(attrs) => attrs.clone(),
+            Err(_error) => {
+                // For example, Certificate object has no KMIP-attribute
+                Attributes::default()
+            }
+        };
+        let tags = retrieve_tags_(&uid, executor).await?;
+        operations.push(AtomicOperation::UpdateObject((
+            uid,
+            object,
+            attributes,
+            Some(tags),
+        )));
+    }
+
+    let mut tx = executor.begin().await?;
+    match atomic_(
+        "this user is not used to update objects",
+        &operations,
+        &mut tx,
+    )
+    .await
+    {
+        Ok(()) => {
+            tx.commit().await?;
+            Ok(())
+        }
+        Err(e) => {
+            tx.rollback().await.context("transaction failed")?;
+            Err(e)
+        }
+    }
+}
diff --git a/crate/server/src/database/query.sql b/crate/server/src/database/query.sql
index 7257f01ab..6745c78d0 100644
--- a/crate/server/src/database/query.sql
+++ b/crate/server/src/database/query.sql
@@ -1,3 +1,9 @@
+-- name: create-table-context
+CREATE TABLE IF NOT EXISTS context (
+        version VARCHAR(40) PRIMARY KEY,
+        state VARCHAR(40)
+);
+
 -- name: create-table-objects
 CREATE TABLE IF NOT EXISTS objects (
         id VARCHAR(40) PRIMARY KEY,
@@ -6,6 +12,10 @@ CREATE TABLE IF NOT EXISTS objects (
         state VARCHAR(32),
         owner VARCHAR(255)
 );
+-- name: add-column-attributes
+ALTER TABLE objects ADD COLUMN attributes json;
+-- name: has-column-attributes
+SELECT attributes from objects;
 
 -- name: create-table-read_access
 CREATE TABLE IF NOT EXISTS read_access (
@@ -22,6 +32,9 @@ CREATE TABLE IF NOT EXISTS tags (
         UNIQUE (id, tag)
 );
 
+-- name: clean-table-context
+DELETE FROM context;
+
 -- name: clean-table-objects
 DELETE FROM objects;
 
@@ -31,6 +44,18 @@ DELETE FROM read_access;
 -- name: clean-table-tags
 DELETE FROM tags;
 
+-- name: select-context
+SELECT * FROM context ORDER BY version ASC LIMIT 1;
+
+-- name: insert-context
+INSERT INTO context (version, state) VALUES ($1, $2);
+
+-- name: update-context
+UPDATE context SET version=$1, state=$2 WHERE state=$3;
+
+-- name: delete-version
+DELETE FROM context WHERE version=$1;
+
 -- name: insert-objects
 INSERT INTO objects (id, object, attributes, state, owner) VALUES ($1, $2, $3, $4, $5);
 
diff --git a/crate/server/src/database/query_mysql.sql b/crate/server/src/database/query_mysql.sql
index b253d24c6..59df10656 100644
--- a/crate/server/src/database/query_mysql.sql
+++ b/crate/server/src/database/query_mysql.sql
@@ -1,3 +1,9 @@
+-- name: create-table-context
+CREATE TABLE IF NOT EXISTS context (
+        version VARCHAR(40) PRIMARY KEY,
+        state VARCHAR(40)
+);
+
 -- name: create-table-objects
 CREATE TABLE IF NOT EXISTS objects (
         id VARCHAR(40) PRIMARY KEY,
@@ -6,6 +12,10 @@ CREATE TABLE IF NOT EXISTS objects (
         state VARCHAR(32),
         owner VARCHAR(255)
 );
+-- name: add-column-attributes
+ALTER TABLE objects ADD COLUMN attributes json;
+-- name: has-column-attributes
+SHOW COLUMNS FROM objects LIKE 'attributes';
 
 -- name: create-table-read_access
 CREATE TABLE IF NOT EXISTS read_access (
@@ -22,6 +32,9 @@ CREATE TABLE IF NOT EXISTS tags (
         UNIQUE (id, tag)
 );
 
+-- name: clean-table-context
+DELETE FROM context;
+
 -- name: clean-table-objects
 DELETE FROM objects;
 
@@ -31,6 +44,18 @@ DELETE FROM read_access;
 -- name: clean-table-tags
 DELETE FROM tags;
 
+-- name: select-context
+SELECT * FROM context LIMIT 1;
+
+-- name: insert-context
+INSERT INTO context (version, state) VALUES (?, ?);
+
+-- name: update-context
+UPDATE context SET version=?, state=? WHERE state=?;
+
+-- name: delete-version
+DELETE FROM context WHERE version=?;
+
 -- name: insert-objects
 INSERT INTO objects (id, object, attributes, state, owner) VALUES (?, ?, ?, ?, ?);
 
diff --git a/crate/server/src/database/redis/objects_db.rs b/crate/server/src/database/redis/objects_db.rs
index 11f7b52a5..fc242f3b6 100644
--- a/crate/server/src/database/redis/objects_db.rs
+++ b/crate/server/src/database/redis/objects_db.rs
@@ -119,7 +119,9 @@ impl ObjectsDB {
 
     fn encrypt_object(&self, uid: &str, redis_db_object: &RedisDbObject) -> KResult<Vec<u8>> {
         let nonce = {
-            let mut rng = self.rng.lock().expect("failed acquiring a lock on the RNG");
+            let mut rng = self.rng.lock().map_err(|e| {
+                KmsError::DatabaseError(format!("failed acquiring a lock on the RNG. Error: {e:?}"))
+            })?;
             Nonce::new(&mut *rng)
         };
         let ct = self.dem.encrypt(
@@ -136,7 +138,7 @@ impl ObjectsDB {
     fn decrypt_object(&self, uid: &str, ciphertext: &[u8]) -> KResult<RedisDbObject> {
         if ciphertext.len() <= Aes256Gcm::NONCE_LENGTH {
             return Err(KmsError::CryptographicError(
-                "invalid ciphertext".to_string(),
+                "invalid ciphertext".to_owned(),
             ))
         }
         let nonce_bytes = &ciphertext[..Aes256Gcm::NONCE_LENGTH];
diff --git a/crate/server/src/database/redis/permissions.rs b/crate/server/src/database/redis/permissions.rs
index 3f0a730f3..7ea7c46a3 100644
--- a/crate/server/src/database/redis/permissions.rs
+++ b/crate/server/src/database/redis/permissions.rs
@@ -29,8 +29,8 @@ pub(crate) struct Triple {
 impl Triple {
     pub(crate) fn new(obj_uid: &str, user_id: &str, permission: ObjectOperationType) -> Self {
         Self {
-            obj_uid: obj_uid.to_string(),
-            user_id: user_id.to_string(),
+            obj_uid: obj_uid.to_owned(),
+            user_id: user_id.to_owned(),
             permission,
         }
     }
@@ -82,8 +82,8 @@ impl TryFrom<&Location> for Triple {
             KmsError::ConversionError(format!("invalid permissions triple: {parts:?}"))
         })?;
         Ok(Self {
-            obj_uid: uid.to_string(),
-            user_id: user_id.to_string(),
+            obj_uid: uid.to_owned(),
+            user_id: user_id.to_owned(),
             permission: serde_json::from_str(permission)?,
         })
     }
diff --git a/crate/server/src/database/redis/redis_with_findex.rs b/crate/server/src/database/redis/redis_with_findex.rs
index e407f8b55..3f338fdbc 100644
--- a/crate/server/src/database/redis/redis_with_findex.rs
+++ b/crate/server/src/database/redis/redis_with_findex.rs
@@ -128,7 +128,7 @@ impl RedisWithFindex {
         };
         // the database object to index and store
         let db_object =
-            RedisDbObject::new(object.clone(), owner.to_string(), state, Some(tags.clone()));
+            RedisDbObject::new(object.clone(), owner.to_owned(), state, Some(tags.clone()));
         // extract the keywords
         index_additions.insert(
             IndexedValue::Location(Location::from(uid.as_bytes())),
@@ -179,7 +179,7 @@ impl RedisWithFindex {
             .objects_db
             .object_get(uid)
             .await?
-            .ok_or_else(|| KmsError::ItemNotFound(uid.to_string()))?;
+            .ok_or_else(|| KmsError::ItemNotFound(uid.to_owned()))?;
         db_object.object = object.clone();
         if tags.is_some() {
             db_object.tags = tags.cloned();
@@ -215,7 +215,7 @@ impl RedisWithFindex {
             .objects_db
             .object_get(uid)
             .await?
-            .ok_or_else(|| KmsError::ItemNotFound(uid.to_string()))?;
+            .ok_or_else(|| KmsError::ItemNotFound(uid.to_owned()))?;
         db_object.state = state;
         // The state is not indexed, so no updates there
         Ok(db_object)
@@ -228,6 +228,10 @@ impl Database for RedisWithFindex {
         None
     }
 
+    async fn migrate(&self, _params: Option<&ExtraDatabaseParams>) -> KResult<()> {
+        unimplemented!("Redis-with-Findex does not support migrate operation");
+    }
+
     /// Insert the given Object in the database.
     ///
     /// A new UUID will be created if none is supplier.
@@ -283,12 +287,13 @@ impl Database for RedisWithFindex {
             locations
                 .into_iter()
                 .map(|location| {
-                    String::from_utf8(location.to_vec()).map_err(|_| kms_error!("Invalid uid"))
+                    String::from_utf8(location.to_vec())
+                        .map_err(|e| kms_error!(format!("Invalid uid. Error: {e:?}")))
                 })
                 .collect::<KResult<HashSet<String>>>()?
         } else {
             // it is an UID
-            HashSet::from([uid_or_tags.to_string()])
+            HashSet::from([uid_or_tags.to_owned()])
         };
 
         // now retrieve the object
@@ -381,7 +386,7 @@ impl Database for RedisWithFindex {
     async fn upsert(
         &self,
         uid: &str,
-        owner: &str,
+        user: &str,
         object: &Object,
         _attributes: &Attributes,
         tags: Option<&HashSet<String>>,
@@ -389,7 +394,7 @@ impl Database for RedisWithFindex {
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
         let db_object = self
-            .prepare_object_for_upsert(uid, owner, object, tags, state, params)
+            .prepare_object_for_upsert(uid, user, object, tags, state, params)
             .await?;
 
         // upsert the object
@@ -501,7 +506,7 @@ impl Database for RedisWithFindex {
             .objects_db
             .object_get(uid)
             .await?
-            .ok_or_else(|| KmsError::ItemNotFound(uid.to_string()))?;
+            .ok_or_else(|| KmsError::ItemNotFound(uid.to_owned()))?;
         Ok(object.owner == owner)
     }
 
@@ -549,7 +554,8 @@ impl Database for RedisWithFindex {
         let uids = locations
             .into_iter()
             .map(|location| {
-                String::from_utf8(location.to_vec()).map_err(|_| kms_error!("Invalid uid"))
+                String::from_utf8(location.to_vec())
+                    .map_err(|e| kms_error!(format!("Invalid uid. Error: {e:?}")))
             })
             .collect::<KResult<HashSet<String>>>()?;
         trace!("find: uids before permissions: {:?}", uids);
@@ -614,7 +620,7 @@ impl Database for RedisWithFindex {
 
     async fn atomic(
         &self,
-        owner: &str,
+        user: &str,
         operations: &[AtomicOperation],
         params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
@@ -624,20 +630,13 @@ impl Database for RedisWithFindex {
                 AtomicOperation::Upsert((uid, object, _attributes, tags, state)) => {
                     //TODO: this operation contains a non atomic retrieve_tags. It will be hard to make this whole method atomic
                     let db_object = self
-                        .prepare_object_for_upsert(
-                            uid,
-                            owner,
-                            object,
-                            tags.as_ref(),
-                            *state,
-                            params,
-                        )
+                        .prepare_object_for_upsert(uid, user, object, tags.as_ref(), *state, params)
                         .await?;
                     redis_operations.push(RedisOperation::Upsert(uid.clone(), db_object));
                 }
                 AtomicOperation::Create((uid, object, _attributes, tags)) => {
                     let (uid, db_object) = self
-                        .prepare_object_for_create(Some(uid.clone()), owner, object, tags)
+                        .prepare_object_for_create(Some(uid.clone()), user, object, tags)
                         .await?;
                     redis_operations.push(RedisOperation::Create(uid, db_object));
                 }
diff --git a/crate/server/src/database/sqlite.rs b/crate/server/src/database/sqlite.rs
index cd0b603e8..05ddf8555 100644
--- a/crate/server/src/database/sqlite.rs
+++ b/crate/server/src/database/sqlite.rs
@@ -5,6 +5,7 @@ use std::{
 };
 
 use async_trait::async_trait;
+use clap::crate_version;
 use cosmian_kmip::kmip::{
     kmip_objects::Object,
     kmip_operations::ErrorReason,
@@ -23,13 +24,28 @@ use super::object_with_metadata::ObjectWithMetadata;
 use crate::{
     core::extra_database_params::ExtraDatabaseParams,
     database::{
-        database_trait::AtomicOperation, query_from_attributes, state_from_string, DBObject,
-        Database, SqlitePlaceholder, SQLITE_QUERIES,
+        database_trait::AtomicOperation, migrate::do_migration, query_from_attributes,
+        state_from_string, DBObject, Database, SqlitePlaceholder,
+        KMS_VERSION_BEFORE_MIGRATION_SUPPORT, SQLITE_QUERIES,
     },
     kms_bail, kms_error,
     result::{KResult, KResultHelper},
 };
 
+#[macro_export]
+macro_rules! get_sqlite_query {
+    ($name:literal) => {
+        SQLITE_QUERIES
+            .get($name)
+            .ok_or_else(|| kms_error!("{} SQL query can't be found", $name))?
+    };
+    ($name:expr) => {
+        SQLITE_QUERIES
+            .get($name)
+            .ok_or_else(|| kms_error!("{} SQL query can't be found", $name))?
+    };
+}
+
 pub(crate) struct SqlitePool {
     pool: Pool<Sqlite>,
 }
@@ -51,35 +67,29 @@ impl SqlitePool {
             .connect_with(options)
             .await?;
 
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("create-table-objects")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_sqlite_query!("create-table-context"))
+            .execute(&pool)
+            .await?;
 
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("create-table-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_sqlite_query!("create-table-objects"))
+            .execute(&pool)
+            .await?;
 
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("create-table-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .execute(&pool)
-        .await?;
+        sqlx::query(get_sqlite_query!("create-table-read_access"))
+            .execute(&pool)
+            .await?;
+
+        sqlx::query(get_sqlite_query!("create-table-tags"))
+            .execute(&pool)
+            .await?;
 
         if clear_database {
             clear_database_(&pool).await?;
         }
 
-        Ok(Self { pool })
+        let sqlite_pool = Self { pool };
+        sqlite_pool.migrate(None).await?;
+        Ok(sqlite_pool)
     }
 }
 
@@ -89,17 +99,58 @@ impl Database for SqlitePool {
         None
     }
 
+    async fn migrate(&self, _params: Option<&ExtraDatabaseParams>) -> KResult<()> {
+        trace!("Migrate database");
+        // Get the context rows
+        match sqlx::query(get_sqlite_query!("select-context"))
+            .fetch_optional(&self.pool)
+            .await?
+        {
+            None => {
+                trace!("No context row found, migrating from scratch");
+                return migrate_(
+                    &self.pool,
+                    KMS_VERSION_BEFORE_MIGRATION_SUPPORT,
+                    "insert-context",
+                )
+                .await;
+            }
+            Some(context_row) => {
+                let last_kms_version_run = context_row.get::<String, _>(0);
+                let state = context_row.get::<String, _>(1);
+                trace!(
+                    "Context row found, migrating from version {last_kms_version_run} (state: \
+                     {state})"
+                );
+                let current_kms_version = crate_version!();
+                debug!(
+                    "[state={state}] Last KMS version run: {last_kms_version_run}, Current KMS \
+                     version: {current_kms_version}"
+                );
+
+                if do_migration(&last_kms_version_run, current_kms_version, &state)? {
+                    return migrate_(&self.pool, current_kms_version, "update-context").await;
+                }
+            }
+        }
+
+        Ok(())
+    }
+
     async fn create(
         &self,
         uid: Option<String>,
-        user: &str,
+        owner: &str,
         object: &Object,
         attributes: &Attributes,
         tags: &HashSet<String>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<String> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
         let mut tx = self.pool.begin().await?;
-        let uid = match create_(uid, user, object, attributes, tags, &mut tx).await {
+        let uid = match create_(uid, owner, object, attributes, tags, &mut tx).await {
             Ok(uid) => uid,
             Err(e) => {
                 tx.rollback().await.context("transaction failed")?;
@@ -114,10 +165,10 @@ impl Database for SqlitePool {
         &self,
         uid_or_tags: &str,
         user: &str,
-        operation_type: ObjectOperationType,
+        query_access_grant: ObjectOperationType,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashMap<String, ObjectWithMetadata>> {
-        retrieve_(uid_or_tags, user, operation_type, &self.pool).await
+        retrieve_(uid_or_tags, user, query_access_grant, &self.pool).await
     }
 
     async fn retrieve_tags(
@@ -136,6 +187,10 @@ impl Database for SqlitePool {
         tags: Option<&HashSet<String>>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match update_object_(uid, object, attributes, tags, &mut tx).await {
             Ok(()) => {
@@ -155,6 +210,10 @@ impl Database for SqlitePool {
         state: StateEnumeration,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match update_state_(uid, state, &mut tx).await {
             Ok(()) => {
@@ -178,6 +237,10 @@ impl Database for SqlitePool {
         state: StateEnumeration,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match upsert_(uid, user, object, attributes, tags, state, &mut tx).await {
             Ok(()) => {
@@ -197,6 +260,10 @@ impl Database for SqlitePool {
         user: &str,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
         match delete_(uid, user, &mut tx).await {
             Ok(()) => {
@@ -229,30 +296,38 @@ impl Database for SqlitePool {
     async fn grant_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
-        insert_access_(uid, userid, operation_types, &self.pool).await
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
+        insert_access_(uid, user, operation_types, &self.pool).await
     }
 
     async fn remove_access(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         operation_types: HashSet<ObjectOperationType>,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
-        remove_access_(uid, userid, operation_types, &self.pool).await
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
+        remove_access_(uid, user, operation_types, &self.pool).await
     }
 
     async fn is_object_owned_by(
         &self,
         uid: &str,
-        userid: &str,
+        owner: &str,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<bool> {
-        is_object_owned_by_(uid, userid, &self.pool).await
+        is_object_owned_by_(uid, owner, &self.pool).await
     }
 
     async fn find(
@@ -276,21 +351,25 @@ impl Database for SqlitePool {
     async fn list_user_access_rights_on_object(
         &self,
         uid: &str,
-        userid: &str,
+        user: &str,
         no_inherited_access: bool,
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<HashSet<ObjectOperationType>> {
-        list_user_access_rights_on_object_(uid, userid, no_inherited_access, &self.pool).await
+        list_user_access_rights_on_object_(uid, user, no_inherited_access, &self.pool).await
     }
 
     async fn atomic(
         &self,
-        owner: &str,
+        user: &str,
         operations: &[AtomicOperation],
         _params: Option<&ExtraDatabaseParams>,
     ) -> KResult<()> {
+        if is_migration_in_progress_(&self.pool).await? {
+            kms_bail!("Migration in progress. Please retry later");
+        }
+
         let mut tx = self.pool.begin().await?;
-        match atomic_(owner, operations, &mut tx).await {
+        match atomic_(user, operations, &mut tx).await {
             Ok(()) => {
                 tx.commit().await?;
                 Ok(())
@@ -325,30 +404,22 @@ pub(crate) async fn create_(
     // If the uid is not provided, generate a new one
     let uid = uid.unwrap_or_else(|| Uuid::new_v4().to_string());
 
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("insert-objects")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid.clone())
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(StateEnumeration::Active.to_string())
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
-
-    // Insert the tags
-    for tag in tags {
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("insert-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
+    sqlx::query(get_sqlite_query!("insert-objects"))
         .bind(uid.clone())
-        .bind(tag)
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(StateEnumeration::Active.to_string())
+        .bind(owner)
         .execute(&mut **executor)
         .await?;
+
+    // Insert the tags
+    for tag in tags {
+        sqlx::query(get_sqlite_query!("insert-tags"))
+            .bind(uid.clone())
+            .bind(tag)
+            .execute(&mut **executor)
+            .await?;
     }
 
     trace!("Created in DB: {uid} / {owner}");
@@ -380,9 +451,7 @@ where
             .join(", ");
 
         // Build the raw SQL query
-        let raw_sql = SQLITE_QUERIES
-            .get("select-from-tags")
-            .context("SQL query can't be found")?
+        let raw_sql = get_sqlite_query!("select-from-tags")
             .replace("@TAGS", &tags_params)
             .replace("@LEN", &format!("${}", tags.len() + 1))
             .replace("@USER", &format!("${}", tags.len() + 2));
@@ -395,21 +464,17 @@ where
             query = query.bind(tag);
         }
         // Bind the tags len and the user
-        query = query.bind(tags.len() as i16).bind(user);
+        query = query.bind(i16::try_from(tags.len())?).bind(user);
 
         // Execute the query
         query.fetch_all(executor).await?
     } else {
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("select-object")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid_or_tags)
-        .bind(user)
-        .fetch_optional(executor)
-        .await?
-        .map_or(vec![], |row| vec![row])
+        sqlx::query(get_sqlite_query!("select-object"))
+            .bind(uid_or_tags)
+            .bind(user)
+            .fetch_optional(executor)
+            .await?
+            .map_or(vec![], |row| vec![row])
     };
 
     // process the rows and find the tags
@@ -448,14 +513,10 @@ pub(crate) async fn retrieve_tags_<'e, E>(uid: &str, executor: E) -> KResult<Has
 where
     E: Executor<'e, Database = Sqlite> + Copy,
 {
-    let rows: Vec<SqliteRow> = sqlx::query(
-        SQLITE_QUERIES
-            .get("select-tags")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .fetch_all(executor)
-    .await?;
+    let rows: Vec<SqliteRow> = sqlx::query(get_sqlite_query!("select-tags"))
+        .bind(uid)
+        .fetch_all(executor)
+        .await?;
 
     let tags = rows.iter().map(|r| r.get(0)).collect::<HashSet<String>>();
 
@@ -480,38 +541,26 @@ pub(crate) async fn update_object_(
         .context("failed serializing the attributes to JSON")
         .reason(ErrorReason::Internal_Server_Error)?;
 
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("update-object-with-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("update-object-with-object"))
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
 
     // Insert the new tags if any
     if let Some(tags) = tags {
         // delete the existing tags
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("delete-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .execute(&mut **executor)
-        .await?;
-        for tag in tags {
-            sqlx::query(
-                SQLITE_QUERIES
-                    .get("insert-tags")
-                    .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-            )
+        sqlx::query(get_sqlite_query!("delete-tags"))
             .bind(uid)
-            .bind(tag)
             .execute(&mut **executor)
             .await?;
+        for tag in tags {
+            sqlx::query(get_sqlite_query!("insert-tags"))
+                .bind(uid)
+                .bind(tag)
+                .execute(&mut **executor)
+                .await?;
         }
     }
 
@@ -524,15 +573,11 @@ pub(crate) async fn update_state_(
     state: StateEnumeration,
     executor: &mut Transaction<'_, Sqlite>,
 ) -> KResult<()> {
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("update-object-with-state")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(state.to_string())
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("update-object-with-state"))
+        .bind(state.to_string())
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
     trace!("Updated in DB: {uid}");
     Ok(())
 }
@@ -543,25 +588,17 @@ pub(crate) async fn delete_(
     executor: &mut Transaction<'_, Sqlite>,
 ) -> KResult<()> {
     // delete the object
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("delete-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("delete-object"))
+        .bind(uid)
+        .bind(owner)
+        .execute(&mut **executor)
+        .await?;
 
     // delete the tags
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("delete-tags")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("delete-tags"))
+        .bind(uid)
+        .execute(&mut **executor)
+        .await?;
 
     trace!("Deleted in DB: {uid}");
     Ok(())
@@ -587,41 +624,29 @@ pub(crate) async fn upsert_(
         .context("failed serializing the attributes to JSON")
         .reason(ErrorReason::Internal_Server_Error)?;
 
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("upsert-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(object_json)
-    .bind(attributes_json)
-    .bind(state.to_string())
-    .bind(owner)
-    .execute(&mut **executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("upsert-object"))
+        .bind(uid)
+        .bind(object_json)
+        .bind(attributes_json)
+        .bind(state.to_string())
+        .bind(owner)
+        .execute(&mut **executor)
+        .await?;
 
     // Insert the new tags if present
     if let Some(tags) = tags {
         // delete the existing tags
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("delete-tags")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .execute(&mut **executor)
-        .await?;
-        // insert the new ones
-        for tag in tags {
-            sqlx::query(
-                SQLITE_QUERIES
-                    .get("insert-tags")
-                    .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-            )
+        sqlx::query(get_sqlite_query!("delete-tags"))
             .bind(uid)
-            .bind(tag)
             .execute(&mut **executor)
             .await?;
+        // insert the new ones
+        for tag in tags {
+            sqlx::query(get_sqlite_query!("insert-tags"))
+                .bind(uid)
+                .bind(tag)
+                .execute(&mut **executor)
+                .await?;
         }
     }
 
@@ -637,14 +662,10 @@ where
     E: Executor<'e, Database = Sqlite> + Copy,
 {
     debug!("Uid = {}", uid);
-    let list = sqlx::query(
-        SQLITE_QUERIES
-            .get("select-rows-read_access-with-object-id")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .fetch_all(executor)
-    .await?;
+    let list = sqlx::query(get_sqlite_query!("select-rows-read_access-with-object-id"))
+        .bind(uid)
+        .fetch_all(executor)
+        .await?;
     let mut ids: HashMap<String, HashSet<ObjectOperationType>> = HashMap::with_capacity(list.len());
     for row in list {
         ids.insert(
@@ -665,15 +686,11 @@ pub(crate) async fn list_user_granted_access_rights_<'e, E>(
 where
     E: Executor<'e, Database = Sqlite> + Copy,
 {
-    debug!("Owner = {}", user);
-    let list = sqlx::query(
-        SQLITE_QUERIES
-            .get("select-objects-access-obtained")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(user)
-    .fetch_all(executor)
-    .await?;
+    debug!("user = {}", user);
+    let list = sqlx::query(get_sqlite_query!("select-objects-access-obtained"))
+        .bind(user)
+        .fetch_all(executor)
+        .await?;
     let mut ids: HashMap<String, (String, StateEnumeration, HashSet<ObjectOperationType>)> =
         HashMap::with_capacity(list.len());
     for row in list {
@@ -711,15 +728,11 @@ async fn perms<'e, E>(uid: &str, userid: &str, executor: E) -> KResult<HashSet<O
 where
     E: Executor<'e, Database = Sqlite> + Copy,
 {
-    let row: Option<SqliteRow> = sqlx::query(
-        SQLITE_QUERIES
-            .get("select-user-accesses-for-object")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .fetch_optional(executor)
-    .await?;
+    let row: Option<SqliteRow> = sqlx::query(get_sqlite_query!("select-user-accesses-for-object"))
+        .bind(uid)
+        .bind(userid)
+        .fetch_optional(executor)
+        .await?;
 
     row.map_or(Ok(HashSet::<ObjectOperationType>::new()), |row| {
         let perms_raw = row.get::<Vec<u8>, _>(0);
@@ -753,16 +766,12 @@ where
         .reason(ErrorReason::Internal_Server_Error)?;
 
     // Upsert the DB
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("upsert-row-read_access")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .bind(json)
-    .execute(executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("upsert-row-read_access"))
+        .bind(uid)
+        .bind(userid)
+        .bind(json)
+        .execute(executor)
+        .await?;
     trace!("Insert read access right in DB: {uid} / {userid}");
     Ok(())
 }
@@ -785,15 +794,11 @@ where
 
     // No remaining permissions, delete the row
     if perms.is_empty() {
-        sqlx::query(
-            SQLITE_QUERIES
-                .get("delete-rows-read_access")
-                .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-        )
-        .bind(uid)
-        .bind(userid)
-        .execute(executor)
-        .await?;
+        sqlx::query(get_sqlite_query!("delete-rows-read_access"))
+            .bind(uid)
+            .bind(userid)
+            .execute(executor)
+            .await?;
         return Ok(())
     }
 
@@ -803,16 +808,12 @@ where
         .reason(ErrorReason::Internal_Server_Error)?;
 
     // Update the DB
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("update-rows-read_access-with-permission")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(userid)
-    .bind(json)
-    .execute(executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("update-rows-read_access-with-permission"))
+        .bind(uid)
+        .bind(userid)
+        .bind(json)
+        .execute(executor)
+        .await?;
     trace!("Deleted in DB: {uid} / {userid}");
     Ok(())
 }
@@ -821,15 +822,11 @@ pub(crate) async fn is_object_owned_by_<'e, E>(uid: &str, owner: &str, executor:
 where
     E: Executor<'e, Database = Sqlite> + Copy,
 {
-    let row: Option<SqliteRow> = sqlx::query(
-        SQLITE_QUERIES
-            .get("has-row-objects")
-            .ok_or_else(|| kms_error!("SQL query can't be found"))?,
-    )
-    .bind(uid)
-    .bind(owner)
-    .fetch_optional(executor)
-    .await?;
+    let row: Option<SqliteRow> = sqlx::query(get_sqlite_query!("has-row-objects"))
+        .bind(uid)
+        .bind(owner)
+        .fetch_optional(executor)
+        .await?;
     Ok(row.is_some())
 }
 
@@ -885,30 +882,22 @@ pub(crate) async fn clear_database_<'e, E>(executor: E) -> KResult<()>
 where
     E: Executor<'e, Database = Sqlite> + Copy,
 {
+    // Erase `context` table
+    sqlx::query(get_sqlite_query!("clean-table-context"))
+        .execute(executor)
+        .await?;
     // Erase `objects` table
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("clean-table-objects")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("clean-table-objects"))
+        .execute(executor)
+        .await?;
     // Erase `read_access` table
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("clean-table-read_access")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("clean-table-read_access"))
+        .execute(executor)
+        .await?;
     // Erase `tags` table
-    sqlx::query(
-        SQLITE_QUERIES
-            .get("clean-table-tags")
-            .expect("SQL query can't be found"),
-    )
-    .execute(executor)
-    .await?;
+    sqlx::query(get_sqlite_query!("clean-table-tags"))
+        .execute(executor)
+        .await?;
     Ok(())
 }
 
@@ -952,3 +941,140 @@ pub(crate) async fn atomic_(
     }
     Ok(())
 }
+
+pub(crate) async fn is_migration_in_progress_<'e, E>(executor: E) -> KResult<bool>
+where
+    E: Executor<'e, Database = Sqlite> + Copy,
+{
+    match sqlx::query(get_sqlite_query!("select-context"))
+        .fetch_optional(executor)
+        .await?
+    {
+        Some(context_row) => {
+            let state = context_row.get::<String, _>(1);
+            Ok(state == "upgrading")
+        }
+        None => Ok(false),
+    }
+}
+
+pub(crate) async fn migrate_(
+    executor: &Pool<Sqlite>,
+    last_version_run: &str,
+    query_name: &str,
+) -> KResult<()> {
+    trace!("Set status to upgrading and last version run: {last_version_run}");
+    let upsert_context = get_sqlite_query!(query_name);
+    trace!("{query_name}: {upsert_context}");
+    match query_name {
+        "insert-context" => {
+            sqlx::query(upsert_context)
+                .bind(last_version_run)
+                .bind("upgrading")
+                .execute(executor)
+                .await
+        }
+        "update-context" => {
+            sqlx::query(upsert_context)
+                .bind(last_version_run)
+                .bind("upgrading")
+                .bind("upgrading")
+                .execute(executor)
+                .await
+        }
+        _ => kms_bail!("Unknown query name: {query_name}"),
+    }?;
+
+    trace!("Migrate data from version {last_version_run}");
+
+    // Process migration for each KMS version
+    let current_kms_version = crate_version!();
+    if last_version_run == KMS_VERSION_BEFORE_MIGRATION_SUPPORT {
+        migrate_from_4_12_0_to_4_13_0(executor).await?;
+    } else {
+        trace!("No migration needed between {last_version_run} and {current_kms_version}");
+    }
+
+    // Set the current running version
+    trace!("Set status to ready and last version run: {current_kms_version}");
+    sqlx::query(get_sqlite_query!("update-context"))
+        .bind(current_kms_version)
+        .bind("ready")
+        .bind("upgrading")
+        .execute(executor)
+        .await?;
+
+    Ok(())
+}
+
+/// Before the version 4.13.0, the KMIP attributes were stored in the objects table (via the objects themselves).
+/// The new column attributes allows to store the KMIP attributes in a dedicated column even for KMIP objects that do not have KMIP attributes (such as Certificates).
+pub(crate) async fn migrate_from_4_12_0_to_4_13_0(executor: &Pool<Sqlite>) -> KResult<()> {
+    trace!("Migrating from 4.12.0 to 4.13.0");
+
+    // Add the column attributes to the objects table
+    if (sqlx::query("SELECT attributes from objects")
+        .execute(executor)
+        .await)
+        .is_ok()
+    {
+        trace!("Column attributes already exists, nothing to do");
+        return Ok(());
+    }
+
+    trace!("Column attributes does not exist, adding it");
+    sqlx::query(get_sqlite_query!("add-column-attributes"))
+        .execute(executor)
+        .await?;
+
+    // Select all objects and extract the KMIP attributes to be stored in the new column
+    let rows = sqlx::query("SELECT * FROM objects")
+        .fetch_all(executor)
+        .await?;
+
+    let mut operations = Vec::with_capacity(rows.len());
+    for row in rows {
+        let uid = row.get::<String, _>(0);
+        let db_object: DBObject = serde_json::from_slice(&row.get::<Vec<u8>, _>(1))
+            .context("migrate: failed deserializing the object")
+            .reason(ErrorReason::Internal_Server_Error)?;
+        let object = Object::post_fix(db_object.object_type, db_object.object);
+        trace!(
+            "migrate_from_4_12_0_to_4_13_0: object (type: {})={:?}",
+            object.object_type(),
+            uid
+        );
+        let attributes = match object.attributes() {
+            Ok(attrs) => attrs.clone(),
+            Err(_error) => {
+                // For example, Certificate object has no KMIP-attribute
+                Attributes::default()
+            }
+        };
+        let tags = retrieve_tags_(&uid, executor).await?;
+        operations.push(AtomicOperation::UpdateObject((
+            uid,
+            object,
+            attributes,
+            Some(tags),
+        )));
+    }
+
+    let mut tx = executor.begin().await?;
+    match atomic_(
+        "this user is not used to update objects",
+        &operations,
+        &mut tx,
+    )
+    .await
+    {
+        Ok(()) => {
+            tx.commit().await?;
+            Ok(())
+        }
+        Err(e) => {
+            tx.rollback().await.context("transaction failed")?;
+            Err(e)
+        }
+    }
+}
diff --git a/crate/server/src/error.rs b/crate/server/src/error.rs
index f2d982ba2..85ee116bd 100644
--- a/crate/server/src/error.rs
+++ b/crate/server/src/error.rs
@@ -301,6 +301,7 @@ macro_rules! kms_bail {
     };
 }
 
+#[allow(clippy::expect_used)]
 #[cfg(test)]
 mod tests {
     use super::KmsError;
@@ -312,16 +313,10 @@ mod tests {
         assert_eq!("Unexpected server error: interpolate 42", err.to_string());
 
         let err = bail();
-        assert_eq!(
-            "Unexpected server error: interpolate 43",
-            err.unwrap_err().to_string()
-        );
+        err.expect_err("Unexpected server error: interpolate 43");
 
         let err = ensure();
-        assert_eq!(
-            "Unexpected server error: interpolate 44",
-            err.unwrap_err().to_string()
-        );
+        err.expect_err("Unexpected server error: interpolate 44");
     }
 
     fn bail() -> Result<(), KmsError> {
diff --git a/crate/server/src/kms_server.rs b/crate/server/src/kms_server.rs
index 9a943818f..4d397bdac 100644
--- a/crate/server/src/kms_server.rs
+++ b/crate/server/src/kms_server.rs
@@ -171,6 +171,10 @@ async fn start_https_kms_server(
  * Returns a `Result` type that contains a `Server` instance if successful, or an error if
  * something went wrong.
  *
+ * # Errors
+ *
+ * This function can return the following errors:
+ * - `KmsError::ServerError` - If there is an error in the server configuration or preparation.
  */
 pub async fn prepare_kms_server(
     kms_server: Arc<KMS>,
@@ -227,7 +231,7 @@ pub async fn prepare_kms_server(
     let google_cse_jwt_config = if enable_google_cse {
         let Some(jwks_manager) = jwks_manager else {
             return Err(KmsError::ServerError(
-                "No JWKS manager to handle Google CSE JWT authorization".to_string(),
+                "No JWKS manager to handle Google CSE JWT authorization".to_owned(),
             ));
         };
         Some(GoogleCseConfig {
diff --git a/crate/server/src/lib.rs b/crate/server/src/lib.rs
index 269d254d1..5a22a580c 100644
--- a/crate/server/src/lib.rs
+++ b/crate/server/src/lib.rs
@@ -10,29 +10,46 @@
     let_underscore,
     rust_2024_compatibility,
     unreachable_pub,
+    unused,
     clippy::all,
     clippy::suspicious,
     clippy::complexity,
     clippy::perf,
     clippy::style,
     clippy::pedantic,
-    clippy::cargo
+    clippy::cargo,
+
+    // restriction lints
+    clippy::unwrap_used,
+    clippy::get_unwrap,
+    clippy::expect_used,
+    // clippy::indexing_slicing,
+    clippy::unwrap_in_result,
+    clippy::assertions_on_result_states,
+    clippy::panic,
+    clippy::panic_in_result_fn,
+    clippy::renamed_function_params,
+    clippy::verbose_file_reads,
+    clippy::str_to_string,
+    clippy::string_to_string,
+    clippy::unreachable,
+    clippy::as_conversions,
+    clippy::print_stdout,
+    clippy::empty_structs_with_brackets,
+    clippy::unseparated_literal_suffix,
+    clippy::map_err_ignore,
 )]
 #![allow(
     clippy::module_name_repetitions,
     clippy::similar_names,
-    clippy::missing_errors_doc,
-    clippy::missing_panics_doc,
     clippy::too_many_lines,
-    clippy::cast_possible_wrap,
-    clippy::cast_sign_loss,
-    clippy::cast_possible_truncation,
     clippy::cargo_common_metadata,
     clippy::multiple_crate_versions
 )]
 
 pub mod config;
 pub mod core;
+#[allow(clippy::expect_used)]
 pub mod database;
 pub mod error;
 pub mod kms_server;
@@ -43,5 +60,6 @@ pub mod telemetry;
 
 pub use database::KMSServer;
 
+#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
 #[cfg(test)]
 mod tests;
diff --git a/crate/server/src/middlewares/api_token_auth.rs b/crate/server/src/middlewares/api_token_auth.rs
index 9d8fa9eca..0c3cfb022 100644
--- a/crate/server/src/middlewares/api_token_auth.rs
+++ b/crate/server/src/middlewares/api_token_auth.rs
@@ -26,12 +26,8 @@ pub(crate) async fn manage_api_token_request<S, B>(
 where
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
 {
-    trace!("API Token Authentication...");
     match manage_api_token(kms_server, &req).await {
-        Ok(()) => {
-            trace!("API Token Authentication successful");
-            Ok(service.call(req).await?.map_into_left_body())
-        }
+        Ok(()) => Ok(service.call(req).await?.map_into_left_body()),
         Err(e) => {
             error!("{:?} {} 401 unauthorized: {e:?}", req.method(), req.path(),);
             Ok(req
@@ -86,21 +82,15 @@ async fn get_api_token(kms_server: &Arc<KMS>, api_token_id: &str) -> KResult<Str
 }
 
 async fn manage_api_token(kms_server: Arc<KMS>, req: &ServiceRequest) -> KResult<()> {
-    trace!(
-        "Token authentication using this API token ID: {:?}",
-        kms_server.params.api_token_id
-    );
-
     match &kms_server.params.api_token_id {
         Some(api_token_id) => {
+            trace!("Token authentication using this API token ID: {api_token_id}");
             let api_token = get_api_token(&kms_server, api_token_id.as_str()).await?;
 
             let client_token = req
                 .headers()
                 .get(header::AUTHORIZATION)
-                .ok_or_else(|| {
-                    KmsError::InvalidRequest("Missing Authorization header".to_string())
-                })?
+                .ok_or_else(|| KmsError::InvalidRequest("Missing Authorization header".to_owned()))?
                 .to_str()
                 .map_err(|e| {
                     KmsError::InvalidRequest(format!(
@@ -131,7 +121,7 @@ async fn manage_api_token(kms_server: Arc<KMS>, req: &ServiceRequest) -> KResult
                     req.path(),
                 );
                 Err(KmsError::Unauthorized(
-                    "Client and server authentication tokens mismatch".to_string(),
+                    "Client and server authentication tokens mismatch".to_owned(),
                 ))
             }
         }
diff --git a/crate/server/src/middlewares/jwks.rs b/crate/server/src/middlewares/jwks.rs
index a7e9cf737..8d9ea0134 100644
--- a/crate/server/src/middlewares/jwks.rs
+++ b/crate/server/src/middlewares/jwks.rs
@@ -3,7 +3,7 @@ use std::{collections::HashMap, sync::RwLock};
 use alcoholic_jwt::{JWK, JWKS};
 use chrono::{DateTime, Duration, Utc};
 
-use crate::result::KResult;
+use crate::{error::KmsError, result::KResult};
 
 static REFRESH_INTERVAL: i64 = 60; // in secs
 
@@ -27,19 +27,23 @@ impl JwksManager {
     }
 
     /// Lock `jwks` to replace it
-    fn set_jwks(&self, new_jwks: HashMap<String, JWKS>) {
-        let mut jwks = self.jwks.write().expect("cannot lock JWKS for write");
+    fn set_jwks(&self, new_jwks: HashMap<String, JWKS>) -> KResult<()> {
+        let mut jwks = self.jwks.write().map_err(|e| {
+            KmsError::ServerError(format!("cannot lock JWKS for write. Error: {e:?}"))
+        })?;
         *jwks = new_jwks;
+        Ok(())
     }
 
     /// Find the key identifier `kid` in each registered JWKS
-    pub fn find(&self, kid: &str) -> Option<JWK> {
-        self.jwks
+    pub fn find(&self, kid: &str) -> KResult<Option<JWK>> {
+        Ok(self
+            .jwks
             .read()
-            .expect("cannot lock JWKS for read")
+            .map_err(|e| KmsError::ServerError(format!("cannot lock JWKS for read. Error: {e:?}")))?
             .iter()
             .find_map(|(_, jwks)| jwks.find(kid))
-            .cloned()
+            .cloned())
     }
 
     /// Fetch again all JWKS using the `uris`.
@@ -47,10 +51,9 @@ impl JwksManager {
     /// The threshold to refresh JWKS is set to `REFRESH_INTERVAL`.
     pub async fn refresh(&self) -> KResult<()> {
         let refresh_is_allowed = {
-            let mut last_update = self
-                .last_update
-                .write()
-                .expect("cannot lock last_update for write");
+            let mut last_update = self.last_update.write().map_err(|e| {
+                KmsError::ServerError(format!("cannot lock last_update for write. Error: {e:?}"))
+            })?;
 
             let can_be_refreshed = last_update.map_or(true, |lu| {
                 (lu + Duration::seconds(REFRESH_INTERVAL)) < Utc::now()
@@ -65,7 +68,7 @@ impl JwksManager {
         if refresh_is_allowed {
             tracing::info!("Refreshing JWKS");
             let refreshed_jwks = Self::fetch_all(&self.uris).await;
-            self.set_jwks(refreshed_jwks);
+            self.set_jwks(refreshed_jwks)?;
         }
 
         Ok(())
diff --git a/crate/server/src/middlewares/jwt.rs b/crate/server/src/middlewares/jwt.rs
index 4b200591f..e0a0196a4 100644
--- a/crate/server/src/middlewares/jwt.rs
+++ b/crate/server/src/middlewares/jwt.rs
@@ -68,12 +68,12 @@ impl JwtConfig {
         );
         tracing::trace!(
             "validating authentication token, expected JWT issuer: {}",
-            self.jwt_issuer_uri.to_string()
+            self.jwt_issuer_uri
         );
 
         let mut validations = vec![
             #[cfg(not(test))]
-            alcoholic_jwt::Validation::Issuer(self.jwt_issuer_uri.to_string()),
+            alcoholic_jwt::Validation::Issuer(self.jwt_issuer_uri.clone()),
             alcoholic_jwt::Validation::SubjectPresent,
             #[cfg(not(feature = "insecure"))]
             alcoholic_jwt::Validation::NotExpired,
@@ -88,14 +88,14 @@ impl JwtConfig {
         // needs to be fetched from the token headers.
         let kid = token_kid(token)
             .map_err(|e| KmsError::Unauthorized(format!("Failed to decode kid: {e}")))?
-            .ok_or_else(|| KmsError::Unauthorized("No 'kid' claim present in token".to_string()))?;
+            .ok_or_else(|| KmsError::Unauthorized("No 'kid' claim present in token".to_owned()))?;
 
         tracing::trace!("looking for kid `{kid}` JWKS:\n{:?}", self.jwks);
 
         let jwk = self
             .jwks
-            .find(&kid)
-            .ok_or_else(|| KmsError::Unauthorized("Specified key not found in set".to_string()))?;
+            .find(&kid)?
+            .ok_or_else(|| KmsError::Unauthorized("Specified key not found in set".to_owned()))?;
 
         tracing::trace!("JWK has been found:\n{jwk:?}");
 
diff --git a/crate/server/src/middlewares/jwt_token_auth.rs b/crate/server/src/middlewares/jwt_token_auth.rs
index f0b223b44..e8f864cae 100644
--- a/crate/server/src/middlewares/jwt_token_auth.rs
+++ b/crate/server/src/middlewares/jwt_token_auth.rs
@@ -21,7 +21,7 @@ pub(crate) async fn manage_jwt_request<S, B>(
 where
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
 {
-    trace!("JWT Authentication...");
+    trace!("Starting JWT Authentication...");
     match manage_jwt(configs, &req).await {
         Ok(auth_claim) => {
             req.extensions_mut().insert(auth_claim);
diff --git a/crate/server/src/middlewares/main.rs b/crate/server/src/middlewares/main.rs
index b5d2dfef2..fb68944ee 100644
--- a/crate/server/src/middlewares/main.rs
+++ b/crate/server/src/middlewares/main.rs
@@ -51,7 +51,6 @@ where
     type Transform = AuthMiddleware<S>;
 
     fn new_transform(&self, service: S) -> Self::Future {
-        debug!("JWT/Token Authentication enabled");
         ok(AuthMiddleware {
             service: Rc::new(service),
             jwt_configurations: self.jwt_configurations.clone(),
@@ -76,8 +75,8 @@ where
     type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
     type Response = ServiceResponse<EitherBody<B, BoxBody>>;
 
-    fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
-        self.service.poll_ready(cx)
+    fn poll_ready(&self, ctx: &mut Context) -> Poll<Result<(), Self::Error>> {
+        self.service.poll_ready(ctx)
     }
 
     fn call(&self, req: ServiceRequest) -> Self::Future {
diff --git a/crate/server/src/middlewares/ssl_auth.rs b/crate/server/src/middlewares/ssl_auth.rs
index e385fbbe9..8bc2ea4d0 100644
--- a/crate/server/src/middlewares/ssl_auth.rs
+++ b/crate/server/src/middlewares/ssl_auth.rs
@@ -97,9 +97,9 @@ where
     type Response = ServiceResponse<EitherBody<B, BoxBody>>;
 
     /// Poll the `SslAuthMiddleware` for readiness.
-    fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
+    fn poll_ready(&self, ctx: &mut Context) -> Poll<Result<(), Self::Error>> {
         // Poll the underlying service for readiness.
-        self.service.poll_ready(cx)
+        self.service.poll_ready(ctx)
     }
 
     /// Call the `SslAuthMiddleware`.
diff --git a/crate/server/src/result.rs b/crate/server/src/result.rs
index 87b69ca22..d23d31640 100644
--- a/crate/server/src/result.rs
+++ b/crate/server/src/result.rs
@@ -4,9 +4,27 @@ use crate::error::KmsError;
 
 pub type KResult<R> = Result<R, KmsError>;
 
+/// A helper trait for `KResult` that provides additional methods for error handling.
 pub trait KResultHelper<T> {
+    /// Sets the reason for the error.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `KResult` with the specified `ErrorReason` if the original result is an error.
     fn reason(self, reason: ErrorReason) -> KResult<T>;
+
+    /// Sets the context for the error.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `KResult` with the specified context if the original result is an error.
     fn context(self, context: &str) -> KResult<T>;
+
+    /// Sets the context for the error using a closure.
+    ///
+    /// # Errors
+    ///
+    /// Returns a `KResult` with the context returned by the closure if the original result is an error.
     fn with_context<O>(self, op: O) -> KResult<T>
     where
         O: FnOnce() -> String;
@@ -34,7 +52,7 @@ where
 
 impl<T> KResultHelper<T> for Option<T> {
     fn context(self, context: &str) -> KResult<T> {
-        self.ok_or_else(|| KmsError::ServerError(context.to_string()))
+        self.ok_or_else(|| KmsError::ServerError(context.to_owned()))
     }
 
     fn with_context<O>(self, op: O) -> KResult<T>
diff --git a/crate/server/src/routes/google_cse/jwt.rs b/crate/server/src/routes/google_cse/jwt.rs
index 1226e0286..a12c3343a 100644
--- a/crate/server/src/routes/google_cse/jwt.rs
+++ b/crate/server/src/routes/google_cse/jwt.rs
@@ -47,8 +47,7 @@ fn jwt_authorization_config_application(
     ));
 
     let jwt_audience = Some(
-        std::env::var("KMS_GOOGLE_CSE_AUDIENCE")
-            .unwrap_or_else(|_| "cse-authorization".to_string()),
+        std::env::var("KMS_GOOGLE_CSE_AUDIENCE").unwrap_or_else(|_| "cse-authorization".to_owned()),
     );
 
     Arc::new(JwtConfig {
@@ -66,7 +65,7 @@ pub fn jwt_authorization_config(
         .iter()
         .map(|app| {
             (
-                (*app).to_string(),
+                (*app).to_owned(),
                 jwt_authorization_config_application(app, jwks_manager.clone()),
             )
         })
@@ -97,24 +96,26 @@ pub(crate) fn decode_jwt_authorization_token(
                     KmsError::ServerError(
                         "JWT audience should be configured with Google Workspace client-side \
                          encryption"
-                            .to_string(),
+                            .to_owned(),
                     )
                 })?
                 .to_string(),
         ),
-        alcoholic_jwt::Validation::Issuer(jwt_config.jwt_issuer_uri.to_string()),
+        alcoholic_jwt::Validation::Issuer(jwt_config.jwt_issuer_uri.clone()),
     ];
 
     // If a JWKS contains multiple keys, the correct KID first
     // needs to be fetched from the token headers.
     let kid = token_kid(token)
-        .map_err(|_| KmsError::Unauthorized("Failed to decode token headers".to_string()))?
-        .ok_or_else(|| KmsError::Unauthorized("No 'kid' claim present in token".to_string()))?;
+        .map_err(|e| {
+            KmsError::Unauthorized(format!("Failed to decode token headers. Error: {e:?}"))
+        })?
+        .ok_or_else(|| KmsError::Unauthorized("No 'kid' claim present in token".to_owned()))?;
 
     tracing::trace!("looking for kid `{kid}` JWKS:\n{:?}", jwt_config.jwks);
 
-    let jwk = &jwt_config.jwks.find(&kid).ok_or_else(|| {
-        KmsError::Unauthorized("[Google CSE auth] Specified key not found in set".to_string())
+    let jwk = &jwt_config.jwks.find(&kid)?.ok_or_else(|| {
+        KmsError::Unauthorized("[Google CSE auth] Specified key not found in set".to_owned())
     })?;
     tracing::trace!("JWK has been found:\n{jwk:?}");
 
@@ -155,7 +156,7 @@ pub(crate) async fn validate_tokens(
     let cse_config = cse_config.as_ref().ok_or_else(|| {
         KmsError::ServerError(
             "JWT authentication and authorization configurations for Google CSE are not set"
-                .to_string(),
+                .to_owned(),
         )
     })?;
 
@@ -188,26 +189,26 @@ pub(crate) async fn validate_tokens(
 
     // The emails should match (case insensitive)
     let authentication_email = authentication_token.email.ok_or_else(|| {
-        KmsError::Unauthorized("Authentication token should contain an email".to_string())
+        KmsError::Unauthorized("Authentication token should contain an email".to_owned())
     })?;
     let authorization_email = authorization_token.email.ok_or_else(|| {
-        KmsError::Unauthorized("Authorization token should contain an email".to_string())
+        KmsError::Unauthorized("Authorization token should contain an email".to_owned())
     })?;
     kms_ensure!(
         authorization_email == authentication_email,
         KmsError::Unauthorized(
-            "Authentication and authorization emails in tokens do not match".to_string()
+            "Authentication and authorization emails in tokens do not match".to_owned()
         )
     );
 
     if let Some(roles) = roles {
         let role = authorization_token.role.ok_or_else(|| {
-            KmsError::Unauthorized("Authorization token should contain a role".to_string())
+            KmsError::Unauthorized("Authorization token should contain a role".to_owned())
         })?;
         kms_ensure!(
             roles.contains(&role.as_str()),
             KmsError::Unauthorized(
-                "Authorization token should contain a role of writer or owner".to_string()
+                "Authorization token should contain a role of writer or owner".to_owned()
             )
         );
     }
@@ -229,6 +230,7 @@ pub(crate) async fn validate_tokens(
 
 #[cfg(test)]
 mod tests {
+    #![allow(clippy::unwrap_used)]
     use std::sync::Arc;
 
     use tracing::info;
@@ -276,8 +278,8 @@ mod tests {
         let client_id = std::env::var("TEST_GOOGLE_OAUTH_CLIENT_ID").unwrap();
         // Test authentication
         let jwt_authentication_config = JwtAuthConfig {
-            jwt_issuer_uri: Some(vec![JWT_ISSUER_URI.to_string()]),
-            jwks_uri: Some(vec![JWKS_URI.to_string()]),
+            jwt_issuer_uri: Some(vec![JWT_ISSUER_URI.to_owned()]),
+            jwks_uri: Some(vec![JWKS_URI.to_owned()]),
             jwt_audience: Some(vec![client_id]),
         };
         let jwt_authentication_config = JwtConfig {
@@ -292,17 +294,17 @@ mod tests {
         info!("AUTHENTICATION token: {:?}", authentication_token);
         assert_eq!(
             authentication_token.iss,
-            Some("https://accounts.google.com".to_string())
+            Some("https://accounts.google.com".to_owned())
         );
         assert_eq!(
             authentication_token.email,
-            Some("blue@cosmian.com".to_string())
+            Some("blue@cosmian.com".to_owned())
         );
         assert_eq!(
             authentication_token.aud,
             Some(
                 "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com"
-                    .to_string()
+                    .to_owned()
             )
         );
 
@@ -317,7 +319,7 @@ mod tests {
         tracing::trace!("{jwt_authorization_config:#?}");
 
         let (authorization_token, jwt_headers) = decode_jwt_authorization_token(
-            jwt_authorization_config.get("drive").unwrap(),
+            &jwt_authorization_config["drive"],
             &wrap_request.authorization,
         )
         .unwrap();
@@ -326,14 +328,14 @@ mod tests {
 
         assert_eq!(
             authorization_token.email,
-            Some("blue@cosmian.com".to_string())
+            Some("blue@cosmian.com".to_owned())
         );
-        // prev: Some("cse-authorization".to_string())
+        // prev: Some("cse-authorization".to_owned())
         assert_eq!(
             authorization_token.aud,
             Some(
                 "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com"
-                    .to_string()
+                    .to_owned()
             )
         );
     }
diff --git a/crate/server/src/routes/google_cse/mod.rs b/crate/server/src/routes/google_cse/mod.rs
index 4f1e4db4c..dd8851357 100644
--- a/crate/server/src/routes/google_cse/mod.rs
+++ b/crate/server/src/routes/google_cse/mod.rs
@@ -29,7 +29,7 @@ impl CseErrorReply {
     fn from(e: &KmsError) -> Self {
         Self {
             code: e.status_code().as_u16(),
-            message: "A CSE request to the Cosmian KMS failed".to_string(),
+            message: "A CSE request to the Cosmian KMS failed".to_owned(),
             details: e.to_string(),
         }
     }
diff --git a/crate/server/src/routes/google_cse/operations.rs b/crate/server/src/routes/google_cse/operations.rs
index 413e65d29..56e156a5c 100644
--- a/crate/server/src/routes/google_cse/operations.rs
+++ b/crate/server/src/routes/google_cse/operations.rs
@@ -41,26 +41,30 @@ pub struct StatusResponse {
     pub operations_supported: Vec<String>,
 }
 
+/// Returns the status of the server.
+///
+/// # Returns
+/// - `StatusResponse`: The status of the server.
 #[must_use]
 pub fn get_status() -> StatusResponse {
     debug!("get_status");
     StatusResponse {
-        server_type: "KACLS".to_string(),
-        vendor_id: "Cosmian".to_string(),
-        version: crate_version!().to_string(),
-        name: "Cosmian KMS".to_string(),
+        server_type: "KACLS".to_owned(),
+        vendor_id: "Cosmian".to_owned(),
+        version: crate_version!().to_owned(),
+        name: "Cosmian KMS".to_owned(),
         operations_supported: vec![
-            "digest".to_string(),
-            "privatekeydecrypt".to_string(),
-            "privatekeysign".to_string(),
-            "privilegedprivatekeydecrypt".to_string(),
-            "privilegedunwrap".to_string(),
-            "privilegedwrap".to_string(),
-            "rewrap".to_string(),
-            "status".to_string(),
-            "unwrap".to_string(),
-            "wrap".to_string(),
-            "wrapprivatekey".to_string(),
+            "digest".to_owned(),
+            "privatekeydecrypt".to_owned(),
+            "privatekeysign".to_owned(),
+            "privilegedprivatekeydecrypt".to_owned(),
+            "privilegedunwrap".to_owned(),
+            "privilegedwrap".to_owned(),
+            "rewrap".to_owned(),
+            "status".to_owned(),
+            "unwrap".to_owned(),
+            "wrap".to_owned(),
+            "wrapprivatekey".to_owned(),
         ],
     }
 }
@@ -78,10 +82,21 @@ pub struct WrapResponse {
     pub wrapped_key: String,
 }
 
-/// Returns encrypted Data Encryption Key (DEK) and associated data.
+/// Wraps a Data Encryption Key (DEK) using the specified authentication and authorization tokens.
 ///
 /// See [doc](https://developers.google.com/workspace/cse/reference/wrap) and
 /// for more details, see [Encrypt & decrypt data](https://developers.google.com/workspace/cse/guides/encrypt-and-decrypt-data)
+/// # Arguments
+/// - `req_http`: The HTTP request.
+/// - `wrap_request`: The wrap request.
+/// - `cse_config`: The Google CSE configuration.
+/// - `kms`: The KMS server.
+///
+/// # Returns
+/// - `WrapResponse`: The wrapped key.
+///
+/// # Errors
+/// This function can return an error if there is a problem with the encryption process or if the tokens validation fails.
 pub async fn wrap(
     req_http: HttpRequest,
     wrap_request: WrapRequest,
@@ -125,7 +140,7 @@ pub async fn wrap(
             wrapping_method: kmip_types::WrappingMethod::Encrypt,
             encoding_option: Some(EncodingOption::NoEncoding),
             encryption_key_information: Some(kmip_types::EncryptionKeyInformation {
-                unique_identifier: UniqueIdentifier::TextString("[\"google_cse\"]".to_string()),
+                unique_identifier: UniqueIdentifier::TextString("[\"google_cse\"]".to_owned()),
                 cryptographic_parameters: Some(Box::default()),
             }),
             ..Default::default()
@@ -158,10 +173,21 @@ pub struct UnwrapResponse {
     pub key: String,
 }
 
-/// Decrypt the Data Encryption Key (DEK) and associated data.
+/// Unwraps a wrapped Data Encryption Key (DEK) using the specified authentication and authorization tokens.
 ///
 /// See [doc](https://developers.google.com/workspace/cse/reference/wrap) and
 /// for more details, see [Encrypt & decrypt data](https://developers.google.com/workspace/cse/guides/encrypt-and-decrypt-data)
+/// # Arguments
+/// - `req_http`: The HTTP request.
+/// - `unwrap_request`: The unwrap request.
+/// - `cse_config`: The Google CSE configuration.
+/// - `kms`: The KMS server.
+///
+/// # Returns
+/// - `UnwrapResponse`: The unwrapped key.
+///
+/// # Errors
+/// This function can return an error if there is a problem with the decryption process or if the tokens validation fails.
 pub async fn unwrap(
     req_http: HttpRequest,
     unwrap_request: UnwrapRequest,
@@ -201,7 +227,7 @@ pub async fn unwrap(
     wrapped_dek.key_block_mut()?.key_wrapping_data = Some(Box::new(KeyWrappingData {
         wrapping_method: kmip_types::WrappingMethod::Encrypt,
         encryption_key_information: Some(kmip_types::EncryptionKeyInformation {
-            unique_identifier: UniqueIdentifier::TextString("[\"google_cse\"]".to_string()),
+            unique_identifier: UniqueIdentifier::TextString("[\"google_cse\"]".to_owned()),
             cryptographic_parameters: None,
         }),
         encoding_option: Some(EncodingOption::NoEncoding),
@@ -263,6 +289,17 @@ pub struct PrivateKeySignResponse {
 /// See Google documentation:
 /// - Private Key Sign endpoint: <https://developers.google.com/workspace/cse/reference/private-key-sign>
 /// - S/MIME certificate profiles: <https://support.google.com/a/answer/7300887>
+/// # Arguments
+/// - `req_http`: The HTTP request.
+/// - `request`: The private key sign request.
+/// - `cse_config`: The Google CSE configuration.
+/// - `kms`: The KMS server.
+///
+/// # Returns
+/// - `PrivateKeySignResponse`: The signature.
+///
+/// # Errors
+/// This function can return an error if there is a problem with the encryption process or if the tokens validation fails.
 pub async fn private_key_sign(
     req_http: HttpRequest,
     request: PrivateKeySignRequest,
@@ -351,6 +388,18 @@ pub struct PrivateKeyDecryptResponse {
 ///
 /// See Google documentation:
 /// - Private Key Decrypt endpoint: <https://developers.google.com/workspace/cse/reference/private-key-decrypt>
+///
+/// # Arguments
+/// - `req_http`: The HTTP request.
+/// - `request`: The private key decrypt request.
+/// - `cse_config`: The Google CSE configuration.
+/// - `kms`: The KMS server.
+///
+/// # Returns
+/// - `PrivateKeyDecryptResponse`: The decrypted data encryption key.
+///
+/// # Errors
+/// This function can return an error if there is a problem with the decryption process or if the tokens validation fails.
 pub async fn private_key_decrypt(
     req_http: HttpRequest,
     request: PrivateKeyDecryptRequest,
@@ -443,7 +492,7 @@ async fn cse_symmetric_unwrap(
         KeyWrappingData {
             wrapping_method: kmip_types::WrappingMethod::Encrypt,
             encryption_key_information: Some(kmip_types::EncryptionKeyInformation {
-                unique_identifier: UniqueIdentifier::TextString("google_cse".to_string()),
+                unique_identifier: UniqueIdentifier::TextString("google_cse".to_owned()),
                 cryptographic_parameters: None,
             }),
             encoding_option: Some(EncodingOption::TTLVEncoding),
diff --git a/crate/server/src/routes/kmip.rs b/crate/server/src/routes/kmip.rs
index 14d586dbf..dad4234a2 100644
--- a/crate/server/src/routes/kmip.rs
+++ b/crate/server/src/routes/kmip.rs
@@ -17,7 +17,7 @@ use crate::{
     result::KResult,
 };
 
-/// Generate KMIP generic key pair
+/// Generate KMIP JSON TTLV and send it to the KMIP server
 #[post("/kmip/2_1")]
 pub(crate) async fn kmip(
     req_http: HttpRequest,
diff --git a/crate/server/src/routes/mod.rs b/crate/server/src/routes/mod.rs
index 97699c3a8..97a5a145f 100644
--- a/crate/server/src/routes/mod.rs
+++ b/crate/server/src/routes/mod.rs
@@ -77,5 +77,5 @@ pub(crate) async fn get_version(
     kms: Data<Arc<KMSServer>>,
 ) -> KResult<Json<String>> {
     info!("GET /version {}", kms.get_user(&req));
-    Ok(Json(crate_version!().to_string()))
+    Ok(Json(crate_version!().to_owned()))
 }
diff --git a/crate/server/src/routes/ms_dke/mod.rs b/crate/server/src/routes/ms_dke/mod.rs
index 5955067d3..a508b50ce 100644
--- a/crate/server/src/routes/ms_dke/mod.rs
+++ b/crate/server/src/routes/ms_dke/mod.rs
@@ -90,7 +90,7 @@ pub(crate) async fn version(
     kms: Data<Arc<KMSServer>>,
 ) -> KResult<Json<String>> {
     info!("GET /version {}", kms.get_user(&req_http));
-    Ok(Json(crate_version!().to_string()))
+    Ok(Json(crate_version!().to_owned()))
 }
 
 #[get("/{key_name}")]
@@ -101,7 +101,7 @@ pub(crate) async fn get_key(
 ) -> HttpResponse {
     let mut key_name = path.into_inner();
     if key_name.is_empty() {
-        key_name = "dke_key".to_string();
+        "dke_key".clone_into(&mut key_name);
     }
     match _get_key(&key_name, req_http, &kms).await {
         Ok(key_data) => {
@@ -148,7 +148,7 @@ async fn _get_key(key_tag: &str, req_http: HttpRequest, kms: &Arc<KMSServer>) ->
                          is not supported"
                     )
                 })?;
-                let mut existing_path = dke_service_url.path().to_string();
+                let mut existing_path = dke_service_url.path().to_owned();
                 // remove the trailing / if any
                 if existing_path.ends_with('/') {
                     existing_path.pop();
@@ -205,10 +205,7 @@ pub(crate) async fn decrypt(
     kms: Data<Arc<KMSServer>>,
 ) -> HttpResponse {
     let encrypted_data = wrap_request.into_inner();
-    info!(
-        "Encrypted Data : {}",
-        serde_json::to_string(&encrypted_data).unwrap()
-    );
+    info!("Encrypted Data : {encrypted_data:?}",);
     let (key_name, key_id) = path.into_inner();
     // let _key_id = key_id.into_inner();
     trace!("POST /{}/{}/Decrypt {:?}", key_name, key_id, encrypted_data);
@@ -255,13 +252,14 @@ fn big_uint_to_u32(bu: &BigUint) -> u32 {
     let bytes = bu.to_bytes_be();
     let len = bytes.len();
     let min = std::cmp::min(4, len);
-    let mut padded = [0u8; 4];
+    let mut padded = [0_u8; 4];
     padded[4 - min..].copy_from_slice(&bytes[len - min..]);
     u32::from_be_bytes(padded)
 }
 
 #[cfg(test)]
 mod tests {
+    #![allow(clippy::unwrap_used)]
     use chrono::{DateTime, Utc};
     use num_bigint_dig::BigUint;
 
diff --git a/crate/server/src/telemetry/mod.rs b/crate/server/src/telemetry/mod.rs
index 2cc2a5ba3..45673d0f1 100644
--- a/crate/server/src/telemetry/mod.rs
+++ b/crate/server/src/telemetry/mod.rs
@@ -20,6 +20,14 @@ pub struct TelemetryConfig {
 }
 
 /// Initialize the telemetry system
+///
+/// # Arguments
+///
+/// * `clap_config` - The `ClapConfig` object containing the telemetry configuration
+///
+/// # Errors
+///
+/// Returns an error if there is an issue initializing the telemetry system.
 pub fn initialize_telemetry(clap_config: &ClapConfig) -> KResult<()> {
     let config = &clap_config.telemetry;
     let (filter, _reload_handle) =
diff --git a/docker-compose.yml b/docker-compose.yml
index 75459e0c0..54f584644 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ services:
       - POSTGRES_DB=kms
       - POSTGRES_PASSWORD=kms
       - PGDATA=/tmp/postgres2
-  maria:
+  mariadb:
     image: mariadb
     ports:
       - 3306:3306
diff --git a/documentation/docs/cli/main_commands.md b/documentation/docs/cli/main_commands.md
index f33e4d502..85bf4dafd 100644
--- a/documentation/docs/cli/main_commands.md
+++ b/documentation/docs/cli/main_commands.md
@@ -571,7 +571,7 @@ Encrypt a file using Covercrypt
 ### Arguments
 ` <FILE>` The files to encrypt
 
-` <ENCRYPTION_POLICY>` The encryption policy to encrypt the file with Example: "department::marketing && level::confidential"`
+` <ENCRYPTION_POLICY>` The encryption policy to encrypt the file with Example: "`department::marketing` && `level::confidential`"
 
 `--key-id [-k] <KEY_ID>` The public key unique identifier. If not specified, tags should be specified
 
diff --git a/documentation/docs/database.md b/documentation/docs/database.md
new file mode 100644
index 000000000..34830887a
--- /dev/null
+++ b/documentation/docs/database.md
@@ -0,0 +1,120 @@
+# Database
+
+## Selecting the database
+
+The KMS server has support for PostgreSQL, Maria DB, and MySQL, as well as Redis, using the Redis-with-Findex configuration.
+
+Redis with Findex offers the ability to use Redis as a database with application-level encryption: all data is encrypted (using AES 256 GCM) by the KMS servers before being sent to Redis. [Findex](https://github.com/Cosmian/findex/) is a Cosmian cryptographic algorithm used to build encrypted indexes on encrypted data, also stored in Redis. This allows the KMS to perform fast encrypted queries on encrypted data. Redis with Findex offers post-quantum resistance on encrypted data and encrypted indexes.
+
+Redis-with-Findex is most useful when:
+
+- KMS servers are run inside a confidential VM or an enclave. In this case, the secret used to encrypt the Redis data and indexes is protected by the VM or enclave and cannot be recovered at runtime by inspecting the KMS servers' memory.
+- KMS servers are run by a trusted party but the Redis backend is managed by an untrusted third party.
+
+Redis-with-Findex should be selected to [run the Cosmian KMS in the cloud or any other zero-trust environment](./zero_trust.md).
+
+## Configuring the database
+
+The database parameters may be configured either:
+
+- using options on the command line that is used to start the KMS server
+
+### Configuring the database via the command line
+
+For
+
+- PostgreSQL, use:
+
+```sh
+docker run --rm -p 9998:9998 \
+  --name kms ghcr.io/cosmian/kms:4.17.0 \
+  --database-type=postgresql \
+  --database-url=postgres://kms_user:kms_password@pgsql-server:5432/kms
+```
+
+- MySQL or MariaDB, use:
+
+```sh
+docker run --rm -p 9998:9998 \
+  --name kms ghcr.io/cosmian/kms:4.17.0 \
+  --database-type=mysql \
+  --database-url=mysql://kms_user:kms_password@mariadb:3306/kms
+```
+
+- Redis (with-Findex), use:
+
+For Redis with Findex, the `--redis-master-password` and `--redis-findex-label` options must also be specified:
+
+- the `redis-master-password` is the password from which keys will be derived (using Argon 2) to encrypt the Redis data and indexes.
+- the `redis-findex-label` is a public arbitrary label that can be changed to rotate the Findex ciphertexts without changing the password/key.
+
+```sh
+docker run --rm -p 9998:9998 \
+  --name kms ghcr.io/cosmian/kms:4.17.0 \
+  --database-type=redis-findex \
+  --database-url=redis://localhost:6379 \
+  --redis-master-password password \
+  --redis-findex-label label
+```
+
+The `redis-master-password` is the password from which a key will be derived (using Argon 2) to encrypt the Redis data and indexes.
+
+The `redis-findex-label` is a public arbitrary label that can be changed to rotate the Findex ciphertexts without changing the password/key.
+
+!!!info "Setting up a PostgreSQL database"
+    Before running the server, a dedicated database with a dedicated user should be created on the PostgreSQL instance. These sample instructions create a database called `kms` owned by a user `kms_user` with password `kms_password`:
+
+    1. Connect to psql under user `postgres`
+    ```sh
+    sudo -u postgres psql  # or `psql -U postgres`
+    ```
+
+    2. Create user `kms_user` with password `kms_password`
+    ```psql
+    create user kms_user with encrypted password 'kms_password';
+    ```
+
+    3. Create database `kms` under owner `kms_user`
+    ```psql
+    create database kms owner=kms_user;
+    ```
+
+## Using a certificate to authenticate to MySQL or Maria DB
+
+Use a certificate to authenticate to MySQL or Maria DB with the `--mysql-user-cert-file` option on the command line. Specify the certificate file name and mount the file to docker.
+
+Say the certificate is called `cert.p12` and is in a directory called `/certificate` on the host disk.
+
+```sh
+docker run --rm -p 9998:9998 \
+  --name kms ghcr.io/cosmian/kms:4.17.0 \
+  -v /certificate/cert.p12:/root/cosmian-kms/cert.p12 \
+  --database-type=mysql \
+  --database-url=mysql://mysql_server:3306/kms \
+  --mysql-user-cert-file=cert.p12
+```
+
+## Database migration
+
+Depending on the KMS database evolution, a migration can happen between 2 versions of the KMS server. It will be clearly written in the CHANGELOG.md. In that case, a generic database upgrade mechanism is run on startup.
+
+At first, the table `context` is responsible for storing the version of the software run and the state of the database. The state can be one of the following:
+
+- `ready`: the database is ready to be used
+- `upgrading`: the database is being upgraded
+
+On server startup:
+
+- the server checks if the software version is greater than the last version run:
+
+  - if no, it simply starts;
+  - if yes:
+
+    - it looks for all upgrades to apply in order from the last version run to this version;
+    - if there is any to run, it sets an upgrading flag on the db state field in the context table;
+    - it runs all the upgrades in order;
+    - it sets the flag from upgrading to ready;
+
+On every call to the database, a check is performed on the db state field to check if the database is upgrading. If yes, calls fail.
+
+Upgrades resist to being interrupted in the middle and resumed from start if that happens.
diff --git a/documentation/docs/high_availability_mode.md b/documentation/docs/high_availability_mode.md
index d7f23d845..ce2124927 100644
--- a/documentation/docs/high_availability_mode.md
+++ b/documentation/docs/high_availability_mode.md
@@ -13,94 +13,3 @@ When the Cosmian KMS servers are configured to export an HTTPS port (as is the c
 - all the Cosmian KMS servers should expose the same server certificate on their HTTPS port
 - and the load balancer should be configured as an SSL load balancer (HAProxy is a good example of a high-performance SSL load balancer)
 
-### Selecting the database
-
-The KMS server has support for PostgreSQL, Maria DB, and MySQL, as well as Redis, using the Redis-with-Findex configuration.
-
-Redis with Findex offers the ability to use Redis as a database with application-level encryption: all data is encrypted (using AES 256 GCM) by the KMS servers before being sent to Redis. [Findex](https://github.com/Cosmian/findex/) is a Cosmian cryptographic algorithm used to build encrypted indexes on encrypted data, also stored in Redis. This allows the KMS to perform fast encrypted queries on encrypted data. Redis with Findex offers post-quantum resistance on encrypted data and encrypted indexes.
-
-Redis-with-Findex is most useful when:
-
-- KMS servers are run inside a confidential VM or an enclave. In this case, the secret used to encrypt the Redis data and indexes is protected by the VM or enclave and cannot be recovered at runtime by inspecting the KMS servers' memory.
-- KMS servers are run by a trusted party but the Redis backend is managed by an untrusted third party.
-
-Redis-with-Findex should be selected to [run the Cosmian KMS in the cloud or any other zero-trust environment](./zero_trust.md).
-
-### Configuring the database
-
-The database parameters may be configured either:
-
-- using options on the command line that is used to start the KMS server
-
-#### Configuring the database via the command line
-
-For
-
-- PostgreSQL, use `--database-type=postgresql`
-- MySQL or MariaDB, use `--database-type=mysql`
-- Redis (with-Findex), use `--database-type=redis-findex`
-
-and specify the database URL with the `--database-url` option.
-
-e.g.
-
-```sh
-docker run --rm -p 9998:9998 \
-  --name kms ghcr.io/cosmian/kms:4.17.0 \
-  --database-type=postgresql \
-  --database-url=postgres://kms_user:kms_password@pgsql-server:5432/kms
-
-```
-
-For Redis with Findex, the `--redis-master-password` and `--redis-findex-label` options must also be specified:
-
-- the `redis-master-password` is the password from which keys will be derived (using Argon 2) to encrypt the Redis data and indexes.
-- the `redis-findex-label` is a public arbitrary label that can be changed to rotate the Findex ciphertexts without changing the password/key.
-
-Example:
-
-```sh
-docker run --rm -p 9998:9998 \
-  --name kms ghcr.io/cosmian/kms:4.17.0 \
-  --database-type=redis-findex \
-  --database-url=redis://localhost:6379 \
-  --redis-master-password password \
-  --redis-findex-label label
-```
-
-The `redis-master-password` is the password from which a key will be derived (using Argon 2) to encrypt the Redis data and indexes.
-
-The `redis-findex-label` is a public arbitrary label that can be changed to rotate the Findex ciphertexts without changing the password/key.
-
-!!!info "Setting up a PostgreSQL database"
-    Before running the server, a dedicated database with a dedicated user should be created on the PostgreSQL instance. These sample instructions create a database called `kms` owned by a user `kms_user` with password `kms_password`:
-
-    1. Connect to psql under user `postgres`
-    ```sh
-    sudo -u postgres psql  # or `psql -U postgres`
-    ```
-
-    2. Create user `kms_user` with password `kms_password`
-    ```psql
-    create user kms_user with encrypted password 'kms_password';
-    ```
-
-    3. Create database `kms` under owner `kms_user`
-    ```psql
-    create database kms owner=kms_user;
-    ```
-
-### Using a certificate to authenticate to MySQL or Maria DB
-
-Use a certificate to authenticate to MySQL or Maria DB with the `--mysql-user-cert-file` option on the command line. Specify the certificate file name and mount the file to docker.
-
-Say the certificate is called `cert.p12` and is in a directory called `/certificate` on the host disk.
-
-```sh
-docker run --rm -p 9998:9998 \
-  --name kms ghcr.io/cosmian/kms:4.17.0 \
-  -v /certificate/cert.p12:/root/cosmian-kms/cert.p12 \
-  --database-type=mysql \
-  --database-url=mysql://mysql_server:3306/kms \
-  --mysql-user-cert-file=cert.p12
-```
diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml
index 0a6e2fc6e..319c5c0a3 100644
--- a/documentation/mkdocs.yml
+++ b/documentation/mkdocs.yml
@@ -52,6 +52,7 @@ nav:
   - Cryptographic algorithms: algorithms.md
   - Enabling TLS: tls.md
   - Logging and telemetry: logging.md
+  - Configuring database: database.md
   - Deploying in single server mode: single_server_mode.md
   - Deploying for high-availability: high_availability_mode.md
   - Running in the cloud or any zero-trust environment: zero_trust.md


Test patch:
diff --git a/crate/cli/src/tests/certificates/encrypt.rs b/crate/cli/src/tests/certificates/encrypt.rs
index 67458ad5e..1c212596a 100644
--- a/crate/cli/src/tests/certificates/encrypt.rs
+++ b/crate/cli/src/tests/certificates/encrypt.rs
@@ -198,7 +198,7 @@ async fn test_certificate_import_encrypt(
         true,
     )?;
 
-    let _subca_certificate_id = import_certificate(
+    let subca_certificate_id = import_certificate(
         &ctx.owner_client_conf_path,
         "certificates",
         &format!("test_data/certificates/{subca_path}"),
@@ -221,7 +221,7 @@ async fn test_certificate_import_encrypt(
         None,
         None,
         Some(private_key_id.clone()),
-        Some(_subca_certificate_id),
+        Some(subca_certificate_id),
         Some(tags),
         None,
         false,
diff --git a/crate/cli/src/tests/shared/export.rs b/crate/cli/src/tests/shared/export.rs
index 749efb028..7eb0dbec2 100644
--- a/crate/cli/src/tests/shared/export.rs
+++ b/crate/cli/src/tests/shared/export.rs
@@ -178,46 +178,6 @@ pub(crate) async fn test_export_sym_allow_revoked() -> CliResult<()> {
 #[cfg(not(feature = "fips"))]
 #[tokio::test]
 pub(crate) async fn test_export_covercrypt() -> CliResult<()> {
-    // create a temp dir
-    let tmp_dir = TempDir::new()?;
-    let tmp_path = tmp_dir.path();
-    // init the test server
-    let ctx = start_default_test_kms_server().await;
-
-    // generate a new master key pair
-    let (master_private_key_id, _master_public_key_id) = create_cc_master_key_pair(
-        &ctx.owner_client_conf_path,
-        "--policy-specifications",
-        "test_data/policy_specifications.json",
-        &[],
-    )?;
-
-    _export_cc_test(
-        KeyFormatType::CoverCryptSecretKey,
-        &master_private_key_id,
-        tmp_path,
-        ctx,
-    )?;
-    _export_cc_test(
-        KeyFormatType::CoverCryptPublicKey,
-        &_master_public_key_id,
-        tmp_path,
-        ctx,
-    )?;
-
-    let user_key_id = create_user_decryption_key(
-        &ctx.owner_client_conf_path,
-        &master_private_key_id,
-        "(Department::MKG || Department::FIN) && Security Level::Top Secret",
-        &[],
-    )?;
-    _export_cc_test(
-        KeyFormatType::CoverCryptSecretKey,
-        &user_key_id,
-        tmp_path,
-        ctx,
-    )?;
-
     fn _export_cc_test(
         key_format_type: KeyFormatType,
         key_id: &str,
@@ -258,6 +218,46 @@ pub(crate) async fn test_export_covercrypt() -> CliResult<()> {
         Ok(())
     }
 
+    // create a temp dir
+    let tmp_dir = TempDir::new()?;
+    let tmp_path = tmp_dir.path();
+    // init the test server
+    let ctx = start_default_test_kms_server().await;
+
+    // generate a new master key pair
+    let (master_private_key_id, master_public_key_id) = create_cc_master_key_pair(
+        &ctx.owner_client_conf_path,
+        "--policy-specifications",
+        "test_data/policy_specifications.json",
+        &[],
+    )?;
+
+    _export_cc_test(
+        KeyFormatType::CoverCryptSecretKey,
+        &master_private_key_id,
+        tmp_path,
+        ctx,
+    )?;
+    _export_cc_test(
+        KeyFormatType::CoverCryptPublicKey,
+        &master_public_key_id,
+        tmp_path,
+        ctx,
+    )?;
+
+    let user_key_id = create_user_decryption_key(
+        &ctx.owner_client_conf_path,
+        &master_private_key_id,
+        "(Department::MKG || Department::FIN) && Security Level::Top Secret",
+        &[],
+    )?;
+    _export_cc_test(
+        KeyFormatType::CoverCryptSecretKey,
+        &user_key_id,
+        tmp_path,
+        ctx,
+    )?;
+
     Ok(())
 }
 
@@ -348,12 +348,12 @@ pub(crate) async fn test_export_x25519() -> CliResult<()> {
         Some(CryptographicAlgorithm::ECDH)
     );
     let kv = &key_block.key_value;
-    let (d, recommended_curve) = match &kv.key_material {
-        KeyMaterial::TransparentECPrivateKey {
-            d,
-            recommended_curve,
-        } => (d, recommended_curve),
-        _ => panic!("Invalid key value type"),
+    let KeyMaterial::TransparentECPrivateKey {
+        d,
+        recommended_curve,
+    } = &kv.key_material
+    else {
+        panic!("Invalid key value type");
     };
     assert_eq!(recommended_curve, &RecommendedCurve::CURVE25519);
     let mut d_vec = d.to_bytes_be();
@@ -407,12 +407,12 @@ pub(crate) async fn test_export_x25519() -> CliResult<()> {
         Some(CryptographicAlgorithm::ECDH)
     );
     let kv = &key_block.key_value;
-    let (q_string, recommended_curve) = match &kv.key_material {
-        KeyMaterial::TransparentECPublicKey {
-            q_string,
-            recommended_curve,
-        } => (q_string, recommended_curve),
-        _ => panic!("Invalid key value type"),
+    let KeyMaterial::TransparentECPublicKey {
+        q_string,
+        recommended_curve,
+    } = &kv.key_material
+    else {
+        panic!("Invalid key value type")
     };
     assert_eq!(recommended_curve, &RecommendedCurve::CURVE25519);
     let pkey_1 = PKey::public_key_from_raw_bytes(q_string, Id::X25519).unwrap();
diff --git a/crate/cli/src/tests/shared/import_export_wrapping.rs b/crate/cli/src/tests/shared/import_export_wrapping.rs
index 51d0b6bcd..77daa2c17 100644
--- a/crate/cli/src/tests/shared/import_export_wrapping.rs
+++ b/crate/cli/src/tests/shared/import_export_wrapping.rs
@@ -15,6 +15,7 @@ use cosmian_kms_client::{
             },
         },
     },
+    kmip::extra::tagging::EMPTY_TAGS,
     read_object_from_json_ttlv_file, write_kmip_object_to_file,
 };
 use kms_test_server::start_default_test_kms_server;
@@ -96,7 +97,7 @@ pub(crate) async fn test_import_export_wrap_rfc_5649() -> CliResult<()> {
         None,
         None,
         None,
-        &[] as &[&str],
+        &EMPTY_TAGS,
     )?;
     test_import_export_wrap_private_key(
         &ctx.owner_client_conf_path,
@@ -192,7 +193,7 @@ pub(crate) async fn test_import_export_wrap_ecies() -> CliResult<()> {
         None,
         None,
         None,
-        &[] as &[&str],
+        &EMPTY_TAGS,
     )?;
     test_import_export_wrap_private_key(
         &ctx.owner_client_conf_path,
diff --git a/crate/cli/src/tests/symmetric/create_key.rs b/crate/cli/src/tests/symmetric/create_key.rs
index 66f9b2368..981e930f3 100644
--- a/crate/cli/src/tests/symmetric/create_key.rs
+++ b/crate/cli/src/tests/symmetric/create_key.rs
@@ -6,7 +6,7 @@ use cloudproof::reexport::crypto_core::{
     reexport::rand_core::{RngCore, SeedableRng},
     CsRng,
 };
-use cosmian_kms_client::KMS_CLI_CONF_ENV;
+use cosmian_kms_client::{kmip::extra::tagging::EMPTY_TAGS, KMS_CLI_CONF_ENV};
 use kms_test_server::start_default_test_kms_server;
 
 use super::SUB_COMMAND;
@@ -78,7 +78,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             Some(128),
             None,
             None,
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         //  AES 256 bit key from a base64 encoded key
         rng.fill_bytes(&mut key);
@@ -88,7 +88,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             None,
             Some(&key_b64),
             None,
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
     }
 
@@ -100,7 +100,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             None,
             None,
             Some("chacha20"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         // ChaCha20 128 bit key
         create_symmetric_key(
@@ -108,7 +108,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             Some(128),
             None,
             Some("chacha20"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         //  ChaCha20 256 bit key from a base64 encoded key
         let mut rng = CsRng::from_entropy();
@@ -120,7 +120,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             None,
             Some(&key_b64),
             Some("chacha20"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
     }
 
@@ -132,7 +132,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             None,
             None,
             Some("sha3"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         // ChaCha20 salts
         create_symmetric_key(
@@ -140,28 +140,28 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             Some(224),
             None,
             Some("sha3"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         create_symmetric_key(
             &ctx.owner_client_conf_path,
             Some(256),
             None,
             Some("sha3"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         create_symmetric_key(
             &ctx.owner_client_conf_path,
             Some(384),
             None,
             Some("sha3"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         create_symmetric_key(
             &ctx.owner_client_conf_path,
             Some(512),
             None,
             Some("sha3"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
         //  ChaCha20 256 bit salt from a base64 encoded salt
         let mut rng = CsRng::from_entropy();
@@ -173,7 +173,7 @@ pub(crate) async fn test_create_symmetric_key() -> CliResult<()> {
             None,
             Some(&key_b64),
             Some("sha3"),
-            &[] as &[&str],
+            &EMPTY_TAGS,
         )?;
     }
     Ok(())
diff --git a/crate/server/src/database/tests/additional_redis_findex_tests.rs b/crate/server/src/database/tests/additional_redis_findex_tests.rs
index f05ed5641..b62c55ca7 100644
--- a/crate/server/src/database/tests/additional_redis_findex_tests.rs
+++ b/crate/server/src/database/tests/additional_redis_findex_tests.rs
@@ -29,7 +29,7 @@ use crate::{
     result::KResult,
 };
 
-struct DummyDB {}
+struct DummyDB;
 #[async_trait]
 impl RemovedLocationsFinder for DummyDB {
     async fn find_removed_locations(
@@ -73,7 +73,7 @@ pub(crate) async fn test_objects_db() -> KResult<()> {
         uid,
         &RedisDbObject::new(
             object.clone(),
-            "owner".to_string(),
+            "owner".to_owned(),
             StateEnumeration::Active,
             Some(HashSet::new()),
         ),
@@ -127,8 +127,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("O1"));
     assert_eq!(
-        permissions.get("O1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     //find the permission for the object O1
@@ -138,8 +138,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("U1"));
     assert_eq!(
-        permissions.get("U1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // add the permission Decrypt to user U1 for object O1
@@ -160,8 +160,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("O1"));
     assert_eq!(
-        permissions.get("O1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt, ObjectOperationType::Decrypt])
+        permissions["O1"],
+        HashSet::from([ObjectOperationType::Encrypt, ObjectOperationType::Decrypt])
     );
 
     //find the permission for the object O1
@@ -171,8 +171,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("U1"));
     assert_eq!(
-        permissions.get("U1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt, ObjectOperationType::Decrypt])
+        permissions["U1"],
+        HashSet::from([ObjectOperationType::Encrypt, ObjectOperationType::Decrypt])
     );
 
     // the situation now is that we have
@@ -194,8 +194,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("O1"));
     assert_eq!(
-        permissions.get("O1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     //find the permission for the object O1
@@ -205,13 +205,13 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 2);
     assert!(permissions.contains_key("U1"));
     assert_eq!(
-        permissions.get("U1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt, ObjectOperationType::Decrypt])
+        permissions["U1"],
+        HashSet::from([ObjectOperationType::Encrypt, ObjectOperationType::Decrypt])
     );
     assert!(permissions.contains_key("U2"));
     assert_eq!(
-        permissions.get("U2").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U2"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // the situation now is that we have
@@ -234,13 +234,13 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 2);
     assert!(permissions.contains_key("O1"));
     assert_eq!(
-        permissions.get("O1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
     assert!(permissions.contains_key("O2"));
     assert_eq!(
-        permissions.get("O2").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O2"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     //find the permission for the object O2
@@ -250,8 +250,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("U2"));
     assert_eq!(
-        permissions.get("U2").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U2"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // the situation now is that we have
@@ -275,8 +275,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("O1"));
     assert_eq!(
-        permissions.get("O1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     //find the permission for the object O1
@@ -286,13 +286,13 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 2);
     assert!(permissions.contains_key("U1"));
     assert_eq!(
-        permissions.get("U1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
     assert!(permissions.contains_key("U2"));
     assert_eq!(
-        permissions.get("U2").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U2"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // let us remove the permission Encrypt on object O1 for user U1
@@ -316,8 +316,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("U2"));
     assert_eq!(
-        permissions.get("U2").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U2"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // let us remove the permission Encrypt on object O1 for user U2
@@ -335,8 +335,8 @@ pub(crate) async fn test_permissions_db() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("O2"));
     assert_eq!(
-        permissions.get("O2").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O2"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     //find the permission for the object O1
@@ -398,8 +398,8 @@ pub(crate) async fn test_corner_case() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("O1"));
     assert_eq!(
-        permissions.get("O1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["O1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // test there is one permission for object O1
@@ -409,8 +409,8 @@ pub(crate) async fn test_corner_case() -> KResult<()> {
     assert_eq!(permissions.len(), 1);
     assert!(permissions.contains_key("U1"));
     assert_eq!(
-        permissions.get("U1").unwrap(),
-        &HashSet::from([ObjectOperationType::Encrypt])
+        permissions["U1"],
+        HashSet::from([ObjectOperationType::Encrypt])
     );
 
     // remove the permission again
diff --git a/crate/server/src/database/tests/database_tests.rs b/crate/server/src/database/tests/database_tests.rs
index 46d2d6b30..5ca92ac15 100644
--- a/crate/server/src/database/tests/database_tests.rs
+++ b/crate/server/src/database/tests/database_tests.rs
@@ -11,6 +11,7 @@ use cosmian_kmip::{
     },
 };
 use cosmian_kms_client::access::ObjectOperationType;
+use cosmian_logger::log_utils::log_init;
 use uuid::Uuid;
 
 use crate::{
@@ -26,7 +27,7 @@ use crate::{
 pub(crate) async fn tx_and_list<DB: Database>(
     db_and_params: &(DB, Option<ExtraDatabaseParams>),
 ) -> KResult<()> {
-    cosmian_logger::log_utils::log_init(None);
+    log_init(None);
     let db = &db_and_params.0;
     let db_params = db_and_params.1.as_ref();
 
@@ -113,7 +114,7 @@ pub(crate) async fn tx_and_list<DB: Database>(
 pub(crate) async fn atomic<DB: Database>(
     db_and_params: &(DB, Option<ExtraDatabaseParams>),
 ) -> KResult<()> {
-    cosmian_logger::log_utils::log_init(None);
+    log_init(None);
     let db = &db_and_params.0;
     let db_params = db_and_params.1.as_ref();
 
@@ -234,7 +235,7 @@ pub(crate) async fn atomic<DB: Database>(
 pub(crate) async fn upsert<DB: Database>(
     db_and_params: &(DB, Option<ExtraDatabaseParams>),
 ) -> KResult<()> {
-    cosmian_logger::log_utils::log_init(None);
+    log_init(None);
     let db = &db_and_params.0;
     let db_params = db_and_params.1.as_ref();
 
@@ -277,7 +278,7 @@ pub(crate) async fn upsert<DB: Database>(
     let attributes = symmetric_key.attributes_mut()?;
     attributes.link = Some(vec![Link {
         link_type: LinkType::PreviousLink,
-        linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
+        linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_owned()),
     }]);
 
     db.upsert(
@@ -300,11 +301,15 @@ pub(crate) async fn upsert<DB: Database>(
         1 => {
             assert_eq!(StateEnumeration::PreActive, objs_[0].state);
             assert_eq!(
-                objs_[0].object.attributes()?.link.as_ref().ok_or_else(
-                    || KmsError::ServerError("links should not be empty".to_string())
-                )?[0]
-                    .linked_object_identifier,
-                LinkedObjectIdentifier::TextString("foo".to_string())
+                objs_[0]
+                    .object
+                    .attributes()?
+                    .link
+                    .as_ref()
+                    .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?
+                    [0]
+                .linked_object_identifier,
+                LinkedObjectIdentifier::TextString("foo".to_owned())
             );
         }
         _ => kms_bail!("There should be only one object"),
@@ -326,7 +331,7 @@ pub(crate) async fn upsert<DB: Database>(
 pub(crate) async fn crud<DB: Database>(
     db_and_params: &(DB, Option<ExtraDatabaseParams>),
 ) -> KResult<()> {
-    cosmian_logger::log_utils::log_init(None);
+    log_init(None);
     let db = &db_and_params.0;
     let db_params = db_and_params.1.as_ref();
 
@@ -385,7 +390,7 @@ pub(crate) async fn crud<DB: Database>(
     let attributes = symmetric_key.attributes_mut()?;
     attributes.link = Some(vec![Link {
         link_type: LinkType::PreviousLink,
-        linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
+        linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_owned()),
     }]);
 
     db.update_object(
@@ -407,11 +412,15 @@ pub(crate) async fn crud<DB: Database>(
         1 => {
             assert_eq!(StateEnumeration::Active, objs_[0].state);
             assert_eq!(
-                objs_[0].object.attributes()?.link.as_ref().ok_or_else(
-                    || KmsError::ServerError("links should not be empty".to_string())
-                )?[0]
-                    .linked_object_identifier,
-                LinkedObjectIdentifier::TextString("foo".to_string())
+                objs_[0]
+                    .object
+                    .attributes()?
+                    .link
+                    .as_ref()
+                    .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?
+                    [0]
+                .linked_object_identifier,
+                LinkedObjectIdentifier::TextString("foo".to_owned())
             );
         }
         _ => kms_bail!("There should be only one object"),
diff --git a/crate/server/src/database/tests/find_attributes_test.rs b/crate/server/src/database/tests/find_attributes_test.rs
index 9969a1c72..c271a9a0b 100644
--- a/crate/server/src/database/tests/find_attributes_test.rs
+++ b/crate/server/src/database/tests/find_attributes_test.rs
@@ -46,7 +46,7 @@ pub(crate) async fn find_attributes<DB: Database>(
     // Define the link vector
     let link = vec![Link {
         link_type: LinkType::ParentLink,
-        linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_string()),
+        linked_object_identifier: LinkedObjectIdentifier::TextString("foo".to_owned()),
     }];
 
     let attributes = symmetric_key.attributes_mut()?;
@@ -77,7 +77,7 @@ pub(crate) async fn find_attributes<DB: Database>(
             assert_eq!(&symmetric_key, &objs_[0].object);
             assert_eq!(
                 objs_[0].object.attributes()?.link.as_ref().unwrap()[0].linked_object_identifier,
-                LinkedObjectIdentifier::TextString("foo".to_string())
+                LinkedObjectIdentifier::TextString("foo".to_owned())
             );
         }
         _ => kms_bail!("There should be one object"),
@@ -103,7 +103,7 @@ pub(crate) async fn find_attributes<DB: Database>(
     // Define a link vector not present in any database objects
     let link = vec![Link {
         link_type: LinkType::ParentLink,
-        linked_object_identifier: LinkedObjectIdentifier::TextString("bar".to_string()),
+        linked_object_identifier: LinkedObjectIdentifier::TextString("bar".to_owned()),
     }];
 
     let researched_attributes = Some(Attributes {
diff --git a/crate/server/src/database/tests/mod.rs b/crate/server/src/database/tests/mod.rs
index f3a5dad90..04fd68384 100644
--- a/crate/server/src/database/tests/mod.rs
+++ b/crate/server/src/database/tests/mod.rs
@@ -1,6 +1,10 @@
+#![allow(clippy::unwrap_used, clippy::expect_used)]
+
 use std::path::PathBuf;
 
 use cosmian_kmip::crypto::{secret::Secret, symmetric::AES_256_GCM_KEY_LENGTH};
+use cosmian_logger::log_utils::log_init;
+use tempfile::TempDir;
 
 use self::{
     additional_redis_findex_tests::{test_corner_case, test_objects_db, test_permissions_db},
@@ -35,7 +39,7 @@ fn get_redis_url() -> String {
     if let Ok(var_env) = std::env::var("REDIS_HOST") {
         format!("redis://{var_env}:6379")
     } else {
-        "redis://localhost:6379".to_string()
+        "redis://localhost:6379".to_owned()
     }
 }
 
@@ -131,8 +135,8 @@ pub(crate) async fn test_sql_cipher() -> KResult<()> {
 
 #[tokio::test]
 pub(crate) async fn test_sqlite() -> KResult<()> {
-    find_attributes(&get_sqlite().await?).await?;
     json_access(&get_sqlite().await?).await?;
+    find_attributes(&get_sqlite().await?).await?;
     owner(&get_sqlite().await?).await?;
     permissions(&get_sqlite().await?).await?;
     tags(&get_sqlite().await?, true).await?;
@@ -159,14 +163,35 @@ pub(crate) async fn test_pgsql() -> KResult<()> {
 
 #[tokio::test]
 pub(crate) async fn test_mysql() -> KResult<()> {
-    crud(&get_mysql().await?).await?;
-    upsert(&get_mysql().await?).await?;
-    tx_and_list(&get_mysql().await?).await?;
-    atomic(&get_mysql().await?).await?;
+    log_init(None);
     json_access(&get_mysql().await?).await?;
     find_attributes(&get_mysql().await?).await?;
     owner(&get_mysql().await?).await?;
     permissions(&get_mysql().await?).await?;
     tags(&get_mysql().await?, true).await?;
+    tx_and_list(&get_mysql().await?).await?;
+    atomic(&get_mysql().await?).await?;
+    upsert(&get_mysql().await?).await?;
+    crud(&get_mysql().await?).await?;
+    Ok(())
+}
+
+#[tokio::test]
+pub(crate) async fn test_migrate_sqlite() -> KResult<()> {
+    log_init(None);
+    for sqlite_path in [
+        "src/tests/migrate/kms_4.12.0.sqlite",
+        "src/tests/migrate/kms_4.16.0.sqlite",
+        "src/tests/migrate/kms_4.17.0.sqlite",
+    ] {
+        let tmp_dir = TempDir::new().unwrap();
+        let tmp_path = tmp_dir.path();
+        let tmp_file_path = tmp_path.join("kms.db");
+        if tmp_file_path.exists() {
+            std::fs::remove_file(&tmp_file_path)?;
+        }
+        std::fs::copy(sqlite_path, &tmp_file_path)?;
+        SqlitePool::instantiate(&tmp_file_path, false).await?;
+    }
     Ok(())
 }
diff --git a/crate/server/src/database/tests/owner_test.rs b/crate/server/src/database/tests/owner_test.rs
index 852f2a838..762802e60 100644
--- a/crate/server/src/database/tests/owner_test.rs
+++ b/crate/server/src/database/tests/owner_test.rs
@@ -144,8 +144,8 @@ pub(crate) async fn owner<DB: Database>(
         .list_user_granted_access_rights(user_id_2, db_params)
         .await?;
     assert_eq!(
-        objects.get(&uid).unwrap(),
-        &(
+        objects[&uid],
+        (
             String::from(owner),
             StateEnumeration::Active,
             vec![ObjectOperationType::Get].into_iter().collect(),
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 ce44fbc52..667f93c50 100644
--- a/crate/server/src/tests/cover_crypt_tests/integration_tests.rs
+++ b/crate/server/src/tests/cover_crypt_tests/integration_tests.rs
@@ -76,7 +76,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         Some(header_metadata.clone()),
         Some(authentication_data.clone()),
@@ -135,7 +135,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -224,7 +224,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         &app,
         &Revoke {
             unique_identifier: Some(UniqueIdentifier::TextString(
-                user_decryption_key_identifier_1.to_string(),
+                user_decryption_key_identifier_1.to_owned(),
             )),
             revocation_reason: RevocationReason::TextString("Revocation test".to_owned()),
             compromise_occurrence_date: None,
@@ -234,7 +234,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     //
     // Rekey all key pairs with matching access policy
-    let ap_to_edit = "Department::MKG".to_string();
+    let ap_to_edit = "Department::MKG".to_owned();
     let request = build_rekey_keypair_request(
         private_key_unique_identifier,
         RekeyEditAction::RekeyAccessPolicy(ap_to_edit.clone()),
@@ -262,7 +262,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -286,7 +286,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         None,
     );
     let post_ttlv_decrypt: KResult<DecryptResponse> = test_utils::post(&app, &request).await;
-    assert!(post_ttlv_decrypt.is_err());
+    post_ttlv_decrypt.unwrap_err();
 
     // decrypt
     let request = build_decryption_request(
@@ -316,7 +316,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
     )?;
     let rekey_keypair_response: KResult<ReKeyKeyPairResponse> =
         test_utils::post(&app, &request).await;
-    assert!(rekey_keypair_response.is_ok());
+    rekey_keypair_response.unwrap();
 
     // test user2 can no longer decrypt old message
     let request = build_decryption_request(
@@ -328,7 +328,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         None,
     );
     let post_ttlv_decrypt: KResult<DecryptResponse> = test_utils::post(&app, &request).await;
-    assert!(post_ttlv_decrypt.is_err());
+    post_ttlv_decrypt.unwrap_err();
 
     //
     // Add new Attributes
@@ -348,7 +348,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
     )?;
     let rekey_keypair_response: KResult<ReKeyKeyPairResponse> =
         test_utils::post(&app, &request).await;
-    assert!(rekey_keypair_response.is_ok());
+    rekey_keypair_response.unwrap();
 
     // Encrypt for new attribute
     let data = "New tech research data".as_bytes();
@@ -356,7 +356,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -366,13 +366,13 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         }),
     )?;
     let encrypt_response: KResult<EncryptResponse> = test_utils::post(&app, &request).await;
-    assert!(encrypt_response.is_ok());
+    encrypt_response.unwrap();
 
     //
     // Rename Attributes
     let rename_policy_attributes_pair = vec![(
         Attribute::from(("Department", "HR")),
-        "HumanResources".to_string(),
+        "HumanResources".to_owned(),
     )];
     let request = build_rekey_keypair_request(
         private_key_unique_identifier,
@@ -380,7 +380,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
     )?;
     let rekey_keypair_response: KResult<ReKeyKeyPairResponse> =
         test_utils::post(&app, &request).await;
-    assert!(rekey_keypair_response.is_ok());
+    rekey_keypair_response.unwrap();
 
     // Encrypt for renamed attribute
     let data = "hr data".as_bytes();
@@ -388,7 +388,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -398,7 +398,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         }),
     )?;
     let encrypt_response: KResult<EncryptResponse> = test_utils::post(&app, &request).await;
-    assert!(encrypt_response.is_ok());
+    encrypt_response.unwrap();
 
     //
     // Disable ABE Attribute
@@ -409,7 +409,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
     )?;
     let rekey_keypair_response: KResult<ReKeyKeyPairResponse> =
         test_utils::post(&app, &request).await;
-    assert!(rekey_keypair_response.is_ok());
+    rekey_keypair_response.unwrap();
 
     // Encrypt with disabled ABE attribute will fail
     let authentication_data = b"cc the uid".to_vec();
@@ -418,7 +418,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -428,7 +428,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         }),
     )?;
     let encrypt_response: KResult<EncryptResponse> = test_utils::post(&app, &request).await;
-    assert!(encrypt_response.is_err());
+    encrypt_response.unwrap_err();
 
     //
     // Delete attribute
@@ -439,7 +439,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
     )?;
     let rekey_keypair_response: KResult<ReKeyKeyPairResponse> =
         test_utils::post(&app, &request).await;
-    assert!(rekey_keypair_response.is_ok());
+    rekey_keypair_response.unwrap();
 
     // Encrypt for removed attribute will fail
     let data = "New hr data".as_bytes();
@@ -447,7 +447,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         public_key_unique_identifier,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -457,7 +457,7 @@ async fn integration_tests_use_ids_no_tags() -> KResult<()> {
         }),
     )?;
     let encrypt_response: KResult<EncryptResponse> = test_utils::post(&app, &request).await;
-    assert!(encrypt_response.is_err());
+    encrypt_response.unwrap_err();
 
     //
     // Destroy user decryption key
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 a201bbf26..ee21cc87a 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
@@ -47,7 +47,7 @@ async fn test_re_key_with_tags() -> KResult<()> {
     // Re_key all key pairs with matching access policy
     let request = build_rekey_keypair_request(
         &mkp_json_tag,
-        RekeyEditAction::RekeyAccessPolicy("Department::MKG".to_string()),
+        RekeyEditAction::RekeyAccessPolicy("Department::MKG".to_owned()),
     )?;
     let rekey_keypair_response: ReKeyKeyPairResponse = test_utils::post(&app, &request).await?;
     assert_eq!(
@@ -65,7 +65,7 @@ async fn test_re_key_with_tags() -> KResult<()> {
     let encryption_policy = "Level::Confidential && Department::MKG";
     let request = build_encryption_request(
         &mkp_json_tag,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -127,7 +127,7 @@ async fn integration_tests_with_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         &mkp_json_tag,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         Some(header_metadata.clone()),
         Some(authentication_data.clone()),
@@ -178,7 +178,7 @@ async fn integration_tests_with_tags() -> KResult<()> {
 
     let request = build_encryption_request(
         &mkp_json_tag,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -253,7 +253,7 @@ async fn integration_tests_with_tags() -> KResult<()> {
     let _revoke_response: RevokeResponse = test_utils::post(
         &app,
         &Revoke {
-            unique_identifier: Some(UniqueIdentifier::TextString(udk1_json_tag.to_string())),
+            unique_identifier: Some(UniqueIdentifier::TextString(udk1_json_tag.clone())),
             revocation_reason: RevocationReason::TextString("Revocation test".to_owned()),
             compromise_occurrence_date: None,
         },
@@ -264,7 +264,7 @@ async fn integration_tests_with_tags() -> KResult<()> {
     // Rekey all key pairs with matching access policy
     let request = build_rekey_keypair_request(
         &mkp_json_tag,
-        RekeyEditAction::RekeyAccessPolicy("Department::MKG".to_string()),
+        RekeyEditAction::RekeyAccessPolicy("Department::MKG".to_owned()),
     )?;
     let rekey_keypair_response: ReKeyKeyPairResponse = test_utils::post(&app, &request).await?;
     assert_eq!(
@@ -282,7 +282,7 @@ async fn integration_tests_with_tags() -> KResult<()> {
     let encryption_policy = "Level::Confidential && Department::MKG";
     let request = build_encryption_request(
         &mkp_json_tag,
-        Some(encryption_policy.to_string()),
+        Some(encryption_policy.to_owned()),
         data.to_vec(),
         None,
         Some(authentication_data.clone()),
@@ -303,7 +303,7 @@ async fn integration_tests_with_tags() -> KResult<()> {
         None,
     );
     let post_ttlv_decrypt: KResult<DecryptResponse> = test_utils::post(&app, &request).await;
-    assert!(post_ttlv_decrypt.is_err());
+    post_ttlv_decrypt.unwrap_err();
 
     // decrypt
     let request = build_decryption_request(
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 0d939122a..39314b930 100644
--- a/crate/server/src/tests/cover_crypt_tests/unit_tests.rs
+++ b/crate/server/src/tests/cover_crypt_tests/unit_tests.rs
@@ -138,7 +138,7 @@ async fn test_cover_crypt_keys() -> KResult<()> {
         },
         object: pk.clone(),
     };
-    assert!(kms.import(request, owner, None).await.is_err());
+    kms.import(request, owner, None).await.unwrap_err();
 
     // re-import public key - should succeed
     let request = Import {
@@ -312,7 +312,7 @@ async fn test_abe_encrypt_decrypt() -> KResult<()> {
             None,
         )
         .await;
-    assert!(er.is_err());
+    er.unwrap_err();
 
     // encrypt a resource FIN + Secret
     let secret_authentication_data = b"cc the uid secret".to_vec();
@@ -355,7 +355,7 @@ async fn test_abe_encrypt_decrypt() -> KResult<()> {
             None,
         )
         .await;
-    assert!(er.is_err());
+    er.unwrap_err();
 
     // Create a user decryption key MKG | FIN + secret
     let secret_mkg_fin_access_policy = "(Department::MKG || Department::FIN) && Level::secret";
@@ -416,7 +416,7 @@ async fn test_abe_encrypt_decrypt() -> KResult<()> {
             None,
         )
         .await;
-    assert!(dr.is_err());
+    dr.unwrap_err();
 
     // decrypt resource FIN + Secret
     let dr = kms
@@ -459,7 +459,7 @@ async fn test_abe_encrypt_decrypt() -> KResult<()> {
             None,
         )
         .await;
-    assert!(dr.is_err());
+    dr.unwrap_err();
 
     Ok(())
 }
diff --git a/crate/server/src/tests/curve_25519_tests.rs b/crate/server/src/tests/curve_25519_tests.rs
index 461e01f8e..d1d5ddfdd 100644
--- a/crate/server/src/tests/curve_25519_tests.rs
+++ b/crate/server/src/tests/curve_25519_tests.rs
@@ -10,6 +10,7 @@ use cosmian_kmip::{
         CURVE_25519_Q_LENGTH_BITS,
     },
     kmip::{
+        extra::tagging::EMPTY_TAGS,
         kmip_messages::{Message, MessageBatchItem, MessageHeader},
         kmip_objects::{Object, ObjectType},
         kmip_operations::{ErrorReason, Import, Operation},
@@ -37,7 +38,7 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
     let owner = "eyJhbGciOiJSUzI1Ni";
 
     // request key pair creation
-    let request = create_ec_key_pair_request(&[] as &[&str], RecommendedCurve::CURVE25519)?;
+    let request = create_ec_key_pair_request(EMPTY_TAGS, RecommendedCurve::CURVE25519)?;
     let response = kms.create_key_pair(request, owner, None).await?;
     // check that the private and public key exist
     // check secret key
@@ -84,14 +85,14 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
         attributes
             .link
             .as_ref()
-            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?
+            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?
             .len(),
         1
     );
     let link = &attributes
         .link
         .as_ref()
-        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?[0];
+        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?[0];
     assert_eq!(link.link_type, LinkType::PublicKeyLink);
     assert_eq!(
         link.linked_object_identifier,
@@ -138,14 +139,14 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
         attributes
             .link
             .as_ref()
-            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?
+            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?
             .len(),
         1
     );
     let link = &attributes
         .link
         .as_ref()
-        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?[0];
+        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?[0];
     assert_eq!(link.link_type, LinkType::PrivateKeyLink);
     assert_eq!(
         link.linked_object_identifier,
@@ -156,7 +157,7 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
     assert_eq!(pk_bytes.len(), X25519_PUBLIC_KEY_LENGTH);
     let pk = to_ec_public_key(
         &pk_bytes,
-        CURVE_25519_Q_LENGTH_BITS as u32,
+        u32::try_from(CURVE_25519_Q_LENGTH_BITS)?,
         sk_uid,
         RecommendedCurve::CURVE25519,
         Some(CryptographicAlgorithm::ECDH),
@@ -210,7 +211,7 @@ async fn test_curve_25519_multiple() -> KResult<()> {
         },
         items: vec![
             MessageBatchItem::new(Operation::CreateKeyPair(create_ec_key_pair_request(
-                &[] as &[&str],
+                EMPTY_TAGS,
                 RecommendedCurve::CURVE25519,
             )?)),
             MessageBatchItem::new(Operation::Locate(
@@ -234,19 +235,19 @@ async fn test_curve_25519_multiple() -> KResult<()> {
         },
         items: vec![
             MessageBatchItem::new(Operation::CreateKeyPair(create_ec_key_pair_request(
-                &[] as &[&str],
+                EMPTY_TAGS,
                 RecommendedCurve::CURVE25519,
             )?)),
             MessageBatchItem::new(Operation::CreateKeyPair(create_ec_key_pair_request(
-                &[] as &[&str],
+                EMPTY_TAGS,
                 RecommendedCurve::CURVEED25519,
             )?)),
             MessageBatchItem::new(Operation::CreateKeyPair(create_ec_key_pair_request(
-                &[] as &[&str],
+                EMPTY_TAGS,
                 RecommendedCurve::SECP256K1,
             )?)),
             MessageBatchItem::new(Operation::CreateKeyPair(create_ec_key_pair_request(
-                &[] as &[&str],
+                EMPTY_TAGS,
                 RecommendedCurve::CURVEED25519,
             )?)),
         ],
@@ -294,7 +295,7 @@ async fn test_curve_25519_multiple() -> KResult<()> {
         response.items[2].result_message,
         Some(
             "Not Supported: Generation of Key Pair for curve: SECP256K1, is not supported"
-                .to_string()
+                .to_owned()
         )
     );
 
diff --git a/crate/server/src/tests/google_cse/mod.rs b/crate/server/src/tests/google_cse/mod.rs
index 66a75217e..2d0d61cbe 100644
--- a/crate/server/src/tests/google_cse/mod.rs
+++ b/crate/server/src/tests/google_cse/mod.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::unwrap_used, clippy::print_stdout, clippy::panic_in_result_fn)]
+
 use std::{
     fs::File,
     io::Read,
@@ -70,7 +72,7 @@ fn import_google_cse_symmetric_key() -> Import {
     let object = read_object_from_json_ttlv_bytes(&symmetric_key).unwrap();
 
     let request = Import {
-        unique_identifier: UniqueIdentifier::TextString("google_cse".to_string()),
+        unique_identifier: UniqueIdentifier::TextString("google_cse".to_owned()),
         object_type: object.object_type(),
         replace_existing: Some(false),
         key_wrap_type: None,
@@ -140,7 +142,7 @@ async fn test_cse_private_key_sign() -> KResult<()> {
 
     let jwt = generate_google_jwt().await;
 
-    let app = test_utils::test_app(Some("http://127.0.0.1/".to_string())).await;
+    let app = test_utils::test_app(Some("http://127.0.0.1/".to_owned())).await;
 
     let import_request = import_google_cse_symmetric_key();
     tracing::debug!("import_request created");
@@ -150,8 +152,8 @@ async fn test_cse_private_key_sign() -> KResult<()> {
 
     tracing::debug!("grant post");
     let access = Access {
-        unique_identifier: Some(UniqueIdentifier::TextString("google_cse".to_string())),
-        user_id: "*".to_string(),
+        unique_identifier: Some(UniqueIdentifier::TextString("google_cse".to_owned())),
+        user_id: "*".to_owned(),
         operation_types: vec![
             ObjectOperationType::Create,
             ObjectOperationType::Destroy,
@@ -173,11 +175,11 @@ async fn test_cse_private_key_sign() -> KResult<()> {
     let pksr = PrivateKeySignRequest {
         authentication: jwt.clone(),
         authorization: jwt,
-        algorithm: "SHA256withRSA".to_string(),
-        digest: digest.to_string(),
+        algorithm: "SHA256withRSA".to_owned(),
+        digest: digest.to_owned(),
         rsa_pss_salt_length: None,
-        reason: "Gmail".to_string(),
-        wrapped_private_key: wrapped_private_key.to_string(),
+        reason: "Gmail".to_owned(),
+        wrapped_private_key: wrapped_private_key.to_owned(),
     };
 
     tracing::debug!("private key sign request post");
@@ -215,7 +217,7 @@ async fn test_cse_private_key_decrypt() -> KResult<()> {
 
     let jwt = generate_google_jwt().await;
 
-    let app = test_utils::test_app(Some("http://127.0.0.1/".to_string())).await;
+    let app = test_utils::test_app(Some("http://127.0.0.1/".to_owned())).await;
 
     let path = std::env::current_dir()?;
     println!("The current directory is {}", path.display());
@@ -251,8 +253,8 @@ async fn test_cse_private_key_decrypt() -> KResult<()> {
 
     tracing::debug!("grant post");
     let access = Access {
-        unique_identifier: Some(UniqueIdentifier::TextString("google_cse".to_string())),
-        user_id: "*".to_string(),
+        unique_identifier: Some(UniqueIdentifier::TextString("google_cse".to_owned())),
+        user_id: "*".to_owned(),
         operation_types: vec![
             ObjectOperationType::Create,
             ObjectOperationType::Destroy,
@@ -273,12 +275,12 @@ async fn test_cse_private_key_decrypt() -> KResult<()> {
     let request = PrivateKeyDecryptRequest {
         authentication: jwt.clone(),
         authorization: jwt,
-        algorithm: "RSA/ECB/PKCS1Padding".to_string(),
+        algorithm: "RSA/ECB/PKCS1Padding".to_owned(),
         encrypted_data_encryption_key: general_purpose::STANDARD
             .encode(encrypted_data_encryption_key),
         rsa_oaep_label: None,
-        reason: "Gmail".to_string(),
-        wrapped_private_key: wrapped_private_key.to_string(),
+        reason: "Gmail".to_owned(),
+        wrapped_private_key: wrapped_private_key.to_owned(),
     };
 
     tracing::debug!("private key decrypt request post");
diff --git a/crate/server/src/tests/google_cse/utils.rs b/crate/server/src/tests/google_cse/utils.rs
index 853986ca0..bbadfa1c2 100644
--- a/crate/server/src/tests/google_cse/utils.rs
+++ b/crate/server/src/tests/google_cse/utils.rs
@@ -57,7 +57,7 @@ pub(crate) async fn google_cse_auth() -> KResult<GoogleCseConfig> {
     let jwks_manager = Arc::new(JwksManager::new(uris).await?);
 
     let jwt_config = JwtConfig {
-        jwt_issuer_uri: GOOGLE_JWT_ISSUER_URI.to_string(),
+        jwt_issuer_uri: GOOGLE_JWT_ISSUER_URI.to_owned(),
         jwks: jwks_manager.clone(),
         jwt_audience: None,
     };
@@ -65,6 +65,6 @@ pub(crate) async fn google_cse_auth() -> KResult<GoogleCseConfig> {
     Ok(GoogleCseConfig {
         authentication: vec![jwt_config].into(),
         authorization: google_cse::jwt_authorization_config(&jwks_manager),
-        kacls_url: "http://0.0.0.0:9998/google_cse".to_string(),
+        kacls_url: "http://0.0.0.0:9998/google_cse".to_owned(),
     })
 }
diff --git a/crate/server/src/tests/kmip_messages.rs b/crate/server/src/tests/kmip_messages.rs
index 02c77797e..22b89baf6 100644
--- a/crate/server/src/tests/kmip_messages.rs
+++ b/crate/server/src/tests/kmip_messages.rs
@@ -3,6 +3,7 @@ use std::sync::Arc;
 use cosmian_kmip::{
     crypto::elliptic_curves::kmip_requests::create_ec_key_pair_request,
     kmip::{
+        extra::tagging::EMPTY_TAGS,
         kmip_messages::{Message, MessageBatchItem, MessageHeader},
         kmip_operations::{Decrypt, ErrorReason, Locate, Operation},
         kmip_types::{
@@ -26,15 +27,14 @@ async fn test_kmip_messages() -> KResult<()> {
     let owner = "eyJhbGciOiJSUzI1Ni";
 
     // request key pair creation
-    let ec_create_request =
-        create_ec_key_pair_request(&[] as &[&str], RecommendedCurve::CURVE25519)?;
+    let ec_create_request = create_ec_key_pair_request(EMPTY_TAGS, RecommendedCurve::CURVE25519)?;
 
     // prepare and send the single message
     let items = vec![
         MessageBatchItem::new(Operation::CreateKeyPair(ec_create_request)),
         MessageBatchItem::new(Operation::Locate(Locate::default())),
         MessageBatchItem::new(Operation::Decrypt(Decrypt {
-            unique_identifier: Some(UniqueIdentifier::TextString("id_12345".to_string())),
+            unique_identifier: Some(UniqueIdentifier::TextString("id_12345".to_owned())),
             data: Some(b"decrypted_data".to_vec()),
             ..Default::default()
         })),
@@ -112,7 +112,7 @@ async fn test_kmip_messages() -> KResult<()> {
         Some(
             "Get Key: no available key found (must be an active symmetric key or private key) for \
              object identifier id_12345"
-                .to_string()
+                .to_owned()
         )
     );
     assert!(response.items[2].response_payload.is_none());
diff --git a/crate/server/src/tests/kmip_server_tests.rs b/crate/server/src/tests/kmip_server_tests.rs
index cc27d746c..9cd0938da 100644
--- a/crate/server/src/tests/kmip_server_tests.rs
+++ b/crate/server/src/tests/kmip_server_tests.rs
@@ -13,6 +13,7 @@ use cosmian_kmip::{
         symmetric::symmetric_key_create_request,
     },
     kmip::{
+        extra::tagging::EMPTY_TAGS,
         kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue, KeyWrappingData},
         kmip_objects::{Object, ObjectType},
         kmip_operations::{Get, Import},
@@ -42,7 +43,7 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
     let owner = "eyJhbGciOiJSUzI1Ni";
 
     // request key pair creation
-    let request = create_ec_key_pair_request(&[] as &[&str], RecommendedCurve::CURVE25519)?;
+    let request = create_ec_key_pair_request(EMPTY_TAGS, RecommendedCurve::CURVE25519)?;
     let response = kms.create_key_pair(request, owner, None).await?;
     // check that the private and public key exist
     // check secret key
@@ -88,14 +89,14 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
     assert_eq!(
         attr.link
             .as_ref()
-            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?
+            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?
             .len(),
         1
     );
     let link = &attr
         .link
         .as_ref()
-        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?[0];
+        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?[0];
     assert_eq!(link.link_type, LinkType::PublicKeyLink);
     assert_eq!(
         link.linked_object_identifier,
@@ -141,14 +142,14 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
     assert_eq!(
         attr.link
             .as_ref()
-            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?
+            .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?
             .len(),
         1
     );
     let link = &attr
         .link
         .as_ref()
-        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_string()))?[0];
+        .ok_or_else(|| KmsError::ServerError("links should not be empty".to_owned()))?[0];
     assert_eq!(link.link_type, LinkType::PrivateKeyLink);
     assert_eq!(
         link.linked_object_identifier,
@@ -159,7 +160,7 @@ async fn test_curve_25519_key_pair() -> KResult<()> {
     assert_eq!(pk_bytes.len(), X25519_PUBLIC_KEY_LENGTH);
     let pk = to_ec_public_key(
         &pk_bytes,
-        CURVE_25519_Q_LENGTH_BITS as u32,
+        u32::try_from(CURVE_25519_Q_LENGTH_BITS)?,
         sk_uid,
         RecommendedCurve::CURVE25519,
         Some(CryptographicAlgorithm::ECDH),
@@ -218,7 +219,7 @@ async fn test_import_wrapped_symmetric_key() -> KResult<()> {
                 attributes: None,
             },
             cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
-            cryptographic_length: Some(wrapped_symmetric_key.len() as i32 * 8),
+            cryptographic_length: Some(i32::try_from(wrapped_symmetric_key.len())? * 8),
             key_wrapping_data: Some(Box::new(KeyWrappingData {
                 wrapping_method: WrappingMethod::Encrypt,
                 iv_counter_nonce: Some(aesgcm_nonce.to_vec()),
@@ -237,7 +238,7 @@ async fn test_import_wrapped_symmetric_key() -> KResult<()> {
         attributes: Attributes {
             object_type: Some(ObjectType::SymmetricKey),
             cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
-            cryptographic_length: Some(wrapped_symmetric_key.len() as i32),
+            cryptographic_length: Some(i32::try_from(wrapped_symmetric_key.len())?),
             key_format_type: Some(KeyFormatType::TransparentSymmetricKey),
             ..Attributes::default()
         },
@@ -261,7 +262,7 @@ async fn test_create_transparent_symmetric_key() -> KResult<()> {
     let owner = "eyJhbGciOiJSUzI1Ni";
 
     let request =
-        symmetric_key_create_request(256, CryptographicAlgorithm::AES, &[] as &[&str]).unwrap();
+        symmetric_key_create_request(256, CryptographicAlgorithm::AES, EMPTY_TAGS).unwrap();
 
     trace!("request: {:?}", request);
     let response = kms.create(request, owner, None).await?;
@@ -305,7 +306,7 @@ async fn test_database_user_tenant() -> KResult<()> {
     let owner = "eyJhbGciOiJSUzI1Ni";
 
     // request key pair creation
-    let request = create_ec_key_pair_request(&[] as &[&str], RecommendedCurve::CURVE25519)?;
+    let request = create_ec_key_pair_request(EMPTY_TAGS, RecommendedCurve::CURVE25519)?;
     let response = kms.create_key_pair(request, owner, None).await?;
 
     // check that we can get the private and public key
@@ -350,7 +351,7 @@ async fn test_database_user_tenant() -> KResult<()> {
             None,
         )
         .await;
-    assert!(sk_response.is_err());
+    sk_response.unwrap_err();
 
     let pk_response = kms
         .get(
@@ -364,7 +365,7 @@ async fn test_database_user_tenant() -> KResult<()> {
             None,
         )
         .await;
-    assert!(pk_response.is_err());
+    pk_response.unwrap_err();
 
     Ok(())
 }
diff --git a/crate/server/src/tests/migrate/kms_4.12.0.sqlite b/crate/server/src/tests/migrate/kms_4.12.0.sqlite
new file mode 100644
index 000000000..e2819f8fc
Binary files /dev/null and b/crate/server/src/tests/migrate/kms_4.12.0.sqlite differ
diff --git a/crate/server/src/tests/migrate/kms_4.16.0.sqlite b/crate/server/src/tests/migrate/kms_4.16.0.sqlite
new file mode 100644
index 000000000..3e92b7e98
Binary files /dev/null and b/crate/server/src/tests/migrate/kms_4.16.0.sqlite differ
diff --git a/crate/server/src/tests/migrate/kms_4.17.0.sqlite b/crate/server/src/tests/migrate/kms_4.17.0.sqlite
new file mode 100644
index 000000000..60138905d
Binary files /dev/null and b/crate/server/src/tests/migrate/kms_4.17.0.sqlite differ
diff --git a/crate/server/src/tests/ms_dke/mod.rs b/crate/server/src/tests/ms_dke/mod.rs
index e29ffd2b5..f50fbcddf 100644
--- a/crate/server/src/tests/ms_dke/mod.rs
+++ b/crate/server/src/tests/ms_dke/mod.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::unwrap_used, clippy::print_stdout)]
+
 use base64::{engine::general_purpose::STANDARD, Engine};
 use cosmian_kmip::kmip::{
     kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue},
diff --git a/crate/server/src/tests/test_utils.rs b/crate/server/src/tests/test_utils.rs
index f243cdfe4..9f72fb7e9 100644
--- a/crate/server/src/tests/test_utils.rs
+++ b/crate/server/src/tests/test_utils.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::unwrap_used, clippy::print_stdout, clippy::expect_used)]
+
 use std::{env::temp_dir, path::PathBuf, sync::Arc};
 
 use actix_http::Request;
@@ -37,11 +39,11 @@ pub(crate) fn https_clap_config_opts(google_cse_kacls_url: Option<String>) -> Cl
     ClapConfig {
         http: HttpConfig {
             https_p12_file: Some(PathBuf::from("src/tests/kmserver.acme.com.p12")),
-            https_p12_password: Some("password".to_string()),
+            https_p12_password: Some("password".to_owned()),
             ..Default::default()
         },
         db: DBConfig {
-            database_type: Some("sqlite".to_string()),
+            database_type: Some("sqlite".to_owned()),
             database_url: None,
             sqlite_path,
             clear_database: true,
@@ -104,7 +106,7 @@ where
     if res.status() != StatusCode::OK {
         kms_bail!(
             "{}",
-            String::from_utf8(read_body(res).await.to_vec()).unwrap_or_else(|_| "[N/A".to_string())
+            String::from_utf8(read_body(res).await.to_vec()).unwrap_or_else(|_| "[N/A".to_owned())
         );
     }
     let body = read_body(res).await;
@@ -130,7 +132,7 @@ where
     if res.status() != StatusCode::OK {
         kms_bail!(
             "{}",
-            String::from_utf8(read_body(res).await.to_vec()).unwrap_or_else(|_| "[N/A".to_string())
+            String::from_utf8(read_body(res).await.to_vec()).unwrap_or_else(|_| "[N/A".to_owned())
         );
     }
     println!("OK before bytes");
diff --git a/crate/server/src/tests/test_validate.rs b/crate/server/src/tests/test_validate.rs
index 8d0177e3f..cdf65c4e4 100644
--- a/crate/server/src/tests/test_validate.rs
+++ b/crate/server/src/tests/test_validate.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::unwrap_used)]
+
 use std::{fs, path, sync::Arc};
 
 use cosmian_kmip::kmip::{
@@ -55,7 +57,7 @@ pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsErr
         validity_time: None,
     };
     let res = kms.validate(request, owner, None).await;
-    assert!(res.is_err());
+    res.unwrap_err();
     debug!("OK: Validate root/intermediate/leaf1 certificates - invalid (revoked)");
     let request = Validate {
         certificate: Some(
@@ -82,18 +84,18 @@ pub(crate) async fn test_validate_with_certificates_bytes() -> Result<(), KmsErr
             .to_vec(),
         ),
         unique_identifier: None,
-        validity_time: //Some(Asn1Time::days_from_now(3651).unwrap().to_string()), // this is supposed to work but it does not.
-        Some("4804152030Z".to_string())
+        validity_time: //Some(Asn1Time::days_from_now(3651).unwrap().to_owned()), // this is supposed to work but it does not.
+        Some("4804152030Z".to_owned())
     };
     let res = kms.validate(request, owner, None).await;
-    assert!(res.is_err());
+    res.unwrap_err();
     debug!("OK: Validate root/intermediate/leaf2 certificates - invalid");
     let request = Validate {
         certificate: Some([leaf2_cert.clone(), root_cert.clone()].to_vec()),
         unique_identifier: None,
         validity_time: None,
     };
-    assert!(kms.validate(request, owner, None).await.is_err());
+    kms.validate(request, owner, None).await.unwrap_err();
     debug!("OK: Validate root/leaf2 certificates - missing intermediate");
 
     Result::Ok(())
@@ -198,7 +200,7 @@ pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError
         validity_time: None,
     };
     let res = kms.validate(request, owner, None).await;
-    assert!(res.is_err());
+    res.unwrap_err();
     debug!("OK: Validate root/intermediate/leaf1 certificates - invalid (revoked)");
 
     // No certificate in chain
@@ -208,7 +210,7 @@ pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError
         validity_time: None,
     };
     let res = kms.validate(request, owner, None).await;
-    assert!(res.is_err());
+    res.unwrap_err();
 
     // Root and intermediate valid certificates. Leaf valid. Test returns valid.
     let request = Validate {
@@ -247,11 +249,11 @@ pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError
                 res_root.unique_identifier.clone(),
             ],
         ),
-        validity_time: //Some(Asn1Time::days_from_now(3651).unwrap().to_string()), // this is supposed to work but it does not.
-        Some("4804152030Z".to_string())
+        validity_time: //Some(Asn1Time::days_from_now(3651).unwrap().to_owned()), // this is supposed to work but it does not.
+        Some("4804152030Z".to_owned())
     };
     let res = kms.validate(request, owner, None).await;
-    assert!(res.is_err());
+    res.unwrap_err();
     debug!(
         "OK: Validate root/intermediate/leaf2 certificates - invalid (won't be valid in the \
          future)"
@@ -263,7 +265,7 @@ pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError
         unique_identifier: Some(vec![res_root.unique_identifier.clone()]),
         validity_time: None,
     };
-    assert!(kms.validate(request, owner, None).await.is_err());
+    kms.validate(request, owner, None).await.unwrap_err();
 
     debug!("OK: Validate root/leaf2 certificates - invalid (missing intermediate)");
     // Root certificate not provided. Intermediate and leaf are valid certificates. Return is Invalid.
@@ -272,7 +274,7 @@ pub(crate) async fn test_validate_with_certificates_ids() -> Result<(), KmsError
         unique_identifier: Some([res_intermediate.unique_identifier.clone()].to_vec()),
         validity_time: None,
     };
-    assert!(kms.validate(request, owner, None).await.is_err());
+    kms.validate(request, owner, None).await.unwrap_err();
     debug!("OK: Validate root/leaf2 certificates - invalid (missing root)");
 
     Result::Ok(())