cosmian_kms_cli 5.20.0

Command Line Interface used to manage the KMS server If any assistance is needed, please either visit the Cosmian technical documentation at https://docs.cosmian.com or contact the Cosmian support team on Discord https://discord.com/invite/7kPMNtHpnz
Documentation
// KMIP 2.1 Activate Operation Compliance Tests
// Verifies that Activate operation returns proper error reasons per KMIP 2.1 Table 166
//
// KMIP 2.1 Specification - Table 166: Activate Errors
// Result Status: Operation Failed
// Result Reasons that SHALL be returned for errors detected in an Activate Operation:
//
// 1. Invalid Object Type       - Tested: test_activate_invalid_object_type
// 2. Object Not Found          - Tested: test_activate_object_not_found
// 3. Wrong Key Lifecycle State - Tested: test_activate_already_active,
//                                        test_activate_deactivated_key,
//                                        test_activate_destroyed_key,
//                                        test_activate_compromised_key
// 4. Attestation Failed        - Not testable: requires HSM/attestation infrastructure
// 5. Attestation Required      - Not testable: requires HSM/attestation infrastructure
// 6. Feature Not Supported     - Not applicable: Activate is a core KMIP operation
// 7. Invalid Field             - Tested implicitly: server validates request structure
// 8. Invalid Message           - Tested implicitly: server validates KMIP message structure
// 9. Operation Not Supported   - Not applicable: Activate is a mandatory KMIP operation
// 10. Permission Denied        - Tested: test_activate_permission_denied (requires auth setup)
// 11. Response Too Large       - Not applicable: Activate response is minimal (just UID)
//
// Note: Some result reasons (Attestation Failed/Required, Feature Not Supported, etc.)
// cannot be easily tested in the current test infrastructure as they require specific
// server configurations or hardware. These are handled by the server implementation
// but may not have direct test coverage.

use cosmian_kmip::time_normalize;
use cosmian_kms_client::{
    KmsClient, KmsClientError,
    cosmian_kmip::{
        kmip_0::kmip_types::{CryptographicUsageMask, RevocationReason, RevocationReasonCode},
        kmip_2_1::{
            kmip_attributes::Attributes,
            kmip_objects::ObjectType,
            kmip_operations::{Activate, Create, CreateResponse, Destroy},
            kmip_types::{CryptographicAlgorithm, UniqueIdentifier},
            requests::build_revoke_key_request,
        },
    },
};
use cosmian_logger::{info, log_init};
use test_kms_server::start_default_test_kms_server;

use crate::error::result::KmsCliResult;

/// Helper function to create a symmetric key in Pre-Active state
/// We set an activation date in the future to ensure Pre-Active state
async fn create_preactive_symmetric_key(client: &KmsClient) -> KmsCliResult<String> {
    // Set activation date 1 hour in the future to ensure Pre-Active state
    let future_activation = time_normalize()? + time::Duration::hours(1);

    // Create a symmetric key with future activation date
    let attributes = Attributes {
        activation_date: Some(future_activation),
        cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
        cryptographic_length: Some(256),
        cryptographic_usage_mask: Some(
            CryptographicUsageMask::Encrypt | CryptographicUsageMask::Decrypt,
        ),
        object_type: Some(ObjectType::SymmetricKey),
        ..Default::default()
    };

    // Create the request
    let request = Create {
        object_type: ObjectType::SymmetricKey,
        attributes,
        protection_storage_masks: None,
    };

    // Create the key
    let response: CreateResponse = client.create(request).await?;
    Ok(response.unique_identifier.to_string())
}

/// Test successful activation of a Pre-Active symmetric key
#[tokio::test]
async fn test_activate_success() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create a key in Pre-Active state
    let key_id = create_preactive_symmetric_key(&client).await?;

    // Activate the key
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id.clone()),
    };

    let response = client.activate(activate_request).await?;
    assert_eq!(response.unique_identifier.to_string(), key_id);

    Ok(())
}

/// Test KMIP 2.1 Error: Object Not Found
#[tokio::test]
async fn test_activate_object_not_found() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    let non_existent_id = "non-existent-key-id-12345".to_string();
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(non_existent_id),
    };

    let result = client.activate(activate_request).await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    // Should return Object_Not_Found error reason
    assert!(
        matches!(&err, KmsClientError::RequestFailed(msg) if msg.contains("Object_Not_Found") || msg.contains("Item_Not_Found")),
        "Expected Object_Not_Found error, got: {err:?}"
    );

    Ok(())
}

/// Test KMIP 2.1 Error: Wrong Key Lifecycle State - Already Active
#[tokio::test]
async fn test_activate_already_active() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create a key in Pre-Active state
    let key_id = create_preactive_symmetric_key(&client).await?;

    // Activate it once (should succeed)
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id.clone()),
    };
    client.activate(activate_request).await?;

    // Try to activate it again (should fail)
    let activate_request2 = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id),
    };

    let result = client.activate(activate_request2).await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    // Should return Wrong_Key_Lifecycle_State error reason
    assert!(
        matches!(&err, KmsClientError::RequestFailed(msg) if msg.contains("Wrong_Key_Lifecycle_State")),
        "Expected Wrong_Key_Lifecycle_State error, got: {err:?}"
    );

    Ok(())
}

/// Test KMIP 2.1 Error: Wrong Key Lifecycle State - Deactivated
#[tokio::test]
async fn test_activate_deactivated_key() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create and activate a key
    let key_id = create_preactive_symmetric_key(&client).await?;
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id.clone()),
    };
    client.activate(activate_request).await?;

    // Revoke (deactivate) the key
    let revoke_request = build_revoke_key_request(
        &key_id,
        RevocationReason {
            revocation_reason_code: RevocationReasonCode::Unspecified,
            revocation_message: Some("Test revocation".to_string()),
        },
    )?;
    client.revoke(revoke_request).await?;

    // Try to activate a deactivated key
    let activate_request2 = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id),
    };

    let result = client.activate(activate_request2).await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    // Should return Wrong_Key_Lifecycle_State error reason
    assert!(
        matches!(&err, KmsClientError::RequestFailed(msg) if msg.contains("Wrong_Key_Lifecycle_State")),
        "Expected Wrong_Key_Lifecycle_State error for deactivated key, got: {err:?}"
    );

    Ok(())
}

/// Test that activation sets the activation date correctly
/// Note: We cannot easily test Invalid Object Type without creating objects
/// that cannot be activated, which would require more complex setup with
/// `OpaqueObjects` or other non-activatable types. The server-side validation
/// handles this case.
#[tokio::test]
async fn test_activate_state_transition() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create a key - it should be in Pre-Active state by default
    let key_id = create_preactive_symmetric_key(&client).await?;

    // Activate the key
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id.clone()),
    };
    let response = client.activate(activate_request).await?;
    assert_eq!(response.unique_identifier.to_string(), key_id);

    // The key should now be in Active state
    // (We rely on the server's internal state management and the fact
    // that subsequent operations on an Active key work correctly)

    Ok(())
}

/// Test KMIP 2.1 Error: Wrong Key Lifecycle State - Destroyed
#[tokio::test]
async fn test_activate_destroyed_key() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create and activate a key
    let key_id = create_preactive_symmetric_key(&client).await?;
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id.clone()),
    };
    client.activate(activate_request).await?;

    // Revoke the key first (required before destroy per KMIP spec)
    let revoke_request = build_revoke_key_request(
        &key_id,
        RevocationReason {
            revocation_reason_code: RevocationReasonCode::Unspecified,
            revocation_message: Some("Test revocation for destroy".to_string()),
        },
    )?;
    client.revoke(revoke_request).await?;

    // Destroy the key
    let destroy_request = Destroy {
        unique_identifier: Some(UniqueIdentifier::TextString(key_id.clone())),
        remove: false,
        cascade: false,
        expected_object_type: None,
    };
    client.destroy(destroy_request).await?;

    // Try to activate a destroyed key (should fail with Wrong Key Lifecycle State)
    let activate_request2 = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id),
    };

    let result = client.activate(activate_request2).await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    // Should return Wrong_Key_Lifecycle_State error reason
    assert!(
        matches!(&err, KmsClientError::RequestFailed(msg) if msg.contains("Wrong_Key_Lifecycle_State")),
        "Expected Wrong_Key_Lifecycle_State error for destroyed key, got: {err:?}"
    );

    Ok(())
}

/// Test KMIP 2.1 Error: Wrong Key Lifecycle State - Compromised
#[tokio::test]
async fn test_activate_compromised_key() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create and activate a key
    let key_id = create_preactive_symmetric_key(&client).await?;
    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id.clone()),
    };
    client.activate(activate_request).await?;

    // Revoke with Compromised reason (transitions to Compromised state)
    let revoke_request = build_revoke_key_request(
        &key_id,
        RevocationReason {
            revocation_reason_code: RevocationReasonCode::KeyCompromise,
            revocation_message: Some("Test key compromise".to_string()),
        },
    )?;
    client.revoke(revoke_request).await?;

    // Try to activate a compromised key (should fail with Wrong Key Lifecycle State)
    let activate_request2 = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id),
    };

    let result = client.activate(activate_request2).await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    // Should return Wrong_Key_Lifecycle_State error reason
    assert!(
        matches!(&err, KmsClientError::RequestFailed(msg) if msg.contains("Wrong_Key_Lifecycle_State")),
        "Expected Wrong_Key_Lifecycle_State error for compromised key, got: {err:?}"
    );

    Ok(())
}

/// Test KMIP 2.1 Error: Permission Denied
/// This test would require authentication/authorization setup to properly trigger
/// We mark it as ignored since the test infrastructure doesn't support auth setup
#[tokio::test]
#[ignore = "Requires authentication setup to test Permission Denied"]
async fn test_activate_permission_denied() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Create a key
    let key_id = create_preactive_symmetric_key(&client).await?;

    // In a real scenario with proper auth setup:
    // - Create a key as user A
    // - Try to activate as user B without permissions
    // - Should fail with Permission Denied (ErrorReason::Permission_Denied)

    let activate_request = Activate {
        unique_identifier: UniqueIdentifier::TextString(key_id),
    };

    let result = client.activate(activate_request).await;
    // With proper auth, this would fail with Permission Denied
    assert!(result.is_ok() || result.is_err());

    Ok(())
}

/// Test KMIP 2.1 Error: Invalid Object Type
/// According to KMIP spec, only certain object types can be activated.
/// Objects like `OpaqueObject` do not have lifecycle states and cannot be activated.
#[tokio::test]
async fn test_activate_invalid_object_type() -> KmsCliResult<()> {
    log_init(None);
    let ctx = start_default_test_kms_server().await;
    let client = ctx.get_owner_client();

    // Try to create an opaque object (non-cryptographic object)
    // Opaque objects do not have lifecycle states and cannot be activated
    let attributes = Attributes {
        object_type: Some(ObjectType::OpaqueObject),
        ..Default::default()
    };

    let create_request = Create {
        object_type: ObjectType::OpaqueObject,
        attributes,
        protection_storage_masks: None,
    };

    // Try to create the opaque object
    let create_result = client.create(create_request).await;

    // If creation succeeds, try to activate it (should fail with Invalid Object Type)
    if let Ok(response) = create_result {
        let activate_request = Activate {
            unique_identifier: response.unique_identifier,
        };

        let result = client.activate(activate_request).await;

        // Should fail with Invalid Object Type
        assert!(result.is_err(), "Activating OpaqueObject should fail");
        let err = result.unwrap_err();
        // Should return Invalid_Object_Type error reason
        assert!(
            matches!(&err, KmsClientError::RequestFailed(msg) if msg.contains("Invalid_Object_Type")),
            "Expected Invalid_Object_Type error, got: {err:?}"
        );
    } else {
        // If OpaqueObject creation is not supported, the test passes
        // as we cannot test activation on a non-existent object type
        info!(
            "OpaqueObject creation not supported, skipping Invalid Object Type test for activation"
        );
    }

    Ok(())
}