//! Decrypt
//! ---------------------
//! This API is used by KMS to decrypt data using a key which resides within an external key manager
use std::sync::Arc;

use actix_web::{
    HttpRequest, HttpResponse, post,
    web::{Data, Json, Path},
};
use base64::{Engine, engine::general_purpose::STANDARD};
use cosmian_kms_server_database::reexport::cosmian_kmip::{
    kmip_0::kmip_types::ErrorReason,
    kmip_1_4::kmip_types::ResultReason,
    kmip_2_1::{
        kmip_operations::Decrypt,
        kmip_types::{CryptographicAlgorithm, CryptographicParameters, UniqueIdentifier},
    },
};
use serde::{Deserialize, Serialize};
use tracing::{debug, info};

use crate::{
    core::KMS,
    error::KmsError,
    result::KResult,
    routes::aws_xks::{
        encrypt_decrypt::{EncryptionAlgorithm, RequestMetadata},
        error::{XksErrorName, XksErrorReply},
    },
};

/// KMS uses this API to encrypt data using a key in an external key manager.
///
/// Example:
/// ```json
/// {
///     "requestMetadata": {
///     "awsPrincipalArn": "arn:aws:iam::123456789012:user/Alice",
///     "kmsKeyArn": "arn:aws:kms:us-east-2:123456789012:/key/1234abcd-12ab-34cd-56ef-1234567890ab",
///     "kmsOperation": "Decrypt",
///     "kmsRequestId": "5112f4d6-db54-4af4-ae30-c55a22a8dfae",
///     "kmsViaService": "ebs"
///     },
///     "additionalAuthenticatedData": "cHJvamVjdD1uaWxlLGRlcGFydG1lbnQ9bWFya2V0aW5n",
///     "encryptionAlgorithm": "AES_GCM",
///     "ciphertext": "ghxkK1txeDNn3q8Y",
///     "ciphertextMetadata": "a2V5X3ZlcnNpb249MQ==",
///     "initializationVector": "HMrlRw85cAJUd5Ax",
///     "authenticationTag": "vBxN2ncH1oEkR8WVXpmyYQ=="
/// }
/// ```
#[derive(Deserialize, Debug, Serialize)]
#[allow(non_snake_case)]
pub(crate) struct DecryptRequest {
    /// The HTTP body of the request contains requestMetadata fields
    /// that provide additional context on the request being made.
    /// This information is helpful for auditing and for implementing
    /// an optional secondary layer of authorization at the XKS Proxy
    /// (see a later section on Authorization).
    /// There is no expectation for the XKS Proxy to validate any information
    /// included in the requestMetadata beyond validating the signature
    /// that covers the entire request payload.
    pub requestMetadata: RequestMetadata,
    /// Base64 encoded ciphertext provided to an external key manager for decryption.
    /// At a minimum, the proxy MUST support the ability to process 4300 bytes of ciphertext.
    /// Note the Base64 encoded string corresponding to 4300 bytes of binary data
    /// will be 5736 bytes long.
    /// This field is REQUIRED.
    pub ciphertext: String,
    /// Base64 encoded ciphertextMetadata that was included with the ciphertext
    /// in the output of the encrypt call that produced the ciphertext being decrypted.
    /// This is an OPTIONAL, vendor-specific field.
    /// When present, the size of the field MUST NOT exceed 20 bytes (before Base64 encoding).
    /// The XKS Proxy MUST detect when the ciphertextMetadata passed to decrypt
    /// has been modified relative to the ciphertextMetadata generated
    /// during the corresponding encrypt.
    /// Appending the ciphertextMetadata to the additionalAuthenticatedData
    /// and using that as the AAD for the external key manager, as described in the Encrypt API,
    /// will automatically accomplish this.
    pub ciphertextMetadata: Option<String>,
    /// Specifies the algorithm that will be used for encryption.
    /// For the v1 specification, this MUST be `AES_GCM`.
    /// This field is REQUIRED.
    pub encryptionAlgorithm: EncryptionAlgorithm,
    /// AES-GCM is an example of an AEAD (Authenticated Decryption with Additional Data) cipher
    /// for which the encrypt operation produces an authenticationTag in addition to the ciphertext.
    /// The authenticationTag can be used to ensure the integrity of the ciphertext
    /// and additional data passed as AAD.
    /// For a decrypt call to succeed, the same AAD that was used to create the ciphertext
    /// must be supplied to the decrypt operation.
    /// This field is OPTIONAL.
    /// When present, this field MUST be specified as a Base64 encoded string
    /// and used as the Additional Authenticated Data (AAD) input to the AES-GCM operation
    /// inside the external key manager.
    /// The XKS Proxy MUST be able to handle AAD values up to 8192 bytes in length
    /// (the Base64 encoding of 8192 bytes will be 10924 bytes).
    pub additionalAuthenticatedData: Option<String>,
    /// Base64 encoded initialization vector generated by the external key manager
    /// that was used during encrypt operation.
    /// For a decrypt call to succeed, this must be the same IV that was generated
    /// when the ciphertext was created.
    /// This field is REQUIRED.
    /// For `AES_GCM`, the length of the initializationVector MUST be 12 bytes or 16 bytes
    /// (the Base64 encoding will have 16 bytes or 24 bytes).
    pub initializationVector: String,
    /// Base64 encoded message authentication code.
    /// Authentication tag size MUST be 16 bytes (the Base64 encoding will have 24 bytes).
    /// For a decrypt call to succeed, this must be the same tag
    /// that was generated by the encrypt call when the ciphertext was created.
    /// This field is REQUIRED.
    pub authenticationTag: String,
}

/// The HTTP response body contains the keySpec, keyUsage, and keyStatus fields.
/// ```json
/// {
//     "plaintext": "SGVsbG8gV29ybGQh"
/// }
/// ```
#[derive(Serialize, Default, Deserialize)]
#[allow(non_snake_case)]
pub(crate) struct DecryptResponse {
    ///  Base64 encoded plaintext generated by an external key manager
    /// from decrypting the provided ciphertext.
    /// The size of the plaintext MUST be the same as the size of the ciphertext.
    /// Plaintext returned by the decrypt API MUST NOT be logged at XKS Proxy
    /// or the external key manager.
    pub plaintext: String,
}

#[post("/kms/xks/v1/keys/{key_id}/decrypt")]
pub(crate) async fn decrypt(
    req_http: HttpRequest,
    key_id: Path<String>,
    request: Json<DecryptRequest>,
    kms: Data<Arc<KMS>>,
) -> HttpResponse {
    let request = request.into_inner();
    let key_id = key_id.into_inner();
    info!(
        "POST /kms/xks/v1/keys/{key_id}/decrypt - operation: {} - id: {} - user: {}",
        request.requestMetadata.kmsOperation,
        request.requestMetadata.kmsRequestId,
        request.requestMetadata.awsPrincipalArn
    );
    debug!("decrypt request: {:?}", request.requestMetadata);
    let kms = kms.into_inner();
    match decrypt_inner(req_http, request, key_id, &kms)
        .await
        .map(Json)
    {
        Ok(wrap_response) => HttpResponse::Ok().json(wrap_response),
        Err(e) => match e {
            KmsError::Unauthorized(msg) => XksErrorReply {
                errorName: XksErrorName::InvalidKeyUsageException,
                errorMessage: Some(msg),
            }
            .into(),
            KmsError::ItemNotFound(msg) => XksErrorReply {
                errorName: XksErrorName::KeyNotFoundException,
                errorMessage: Some(msg),
            }
            .into(),
            KmsError::CryptographicError(msg)
            | KmsError::Kmip21Error(ErrorReason::Cryptographic_Failure, msg)
            | KmsError::Kmip14Error(ResultReason::CryptographicFailure, msg) => XksErrorReply {
                errorName: XksErrorName::InvalidCiphertextException,
                errorMessage: Some(msg),
            }
            .into(),
            _ => {
                info!("Decrypt error: {:?}", e);
                HttpResponse::from_error(e)
            }
        },
    }
}

async fn decrypt_inner(
    _req_http: HttpRequest,
    request: DecryptRequest,
    key_id_or_tags: String,
    kms: &Arc<KMS>,
) -> KResult<DecryptResponse> {
    let user = request.requestMetadata.awsPrincipalArn;
    let cryptographic_parameters = match request.encryptionAlgorithm {
        EncryptionAlgorithm::AES_GCM => CryptographicParameters {
            cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
            ..Default::default()
        },
    };
    let data = STANDARD.decode(&request.ciphertext)?;
    // Supplied Nonce or new one.
    let nonce = STANDARD.decode(&request.initializationVector)?;
    let aead = match request.additionalAuthenticatedData {
        None => None,
        Some(b64) => Some(STANDARD.decode(&b64)?),
    };
    let tag = STANDARD.decode(request.authenticationTag)?;
    // override the key_id if one is supplied as part of the metadata
    let key_id = if let Some(key_id) = request.ciphertextMetadata {
        String::from_utf8(STANDARD.decode(key_id)?)?
    } else {
        key_id_or_tags
    };

    let response = kms
        .decrypt(
            Decrypt {
                unique_identifier: Some(UniqueIdentifier::TextString(key_id)),
                cryptographic_parameters: Some(cryptographic_parameters),
                data: Some(data),
                i_v_counter_nonce: Some(nonce.clone()),
                correlation_value: None,
                init_indicator: None,
                final_indicator: None,
                authenticated_encryption_additional_data: aead.clone(),
                authenticated_encryption_tag: Some(tag),
            },
            &user,
        )
        .await?;
    let plaintext = response
        .data
        .ok_or_else(|| KmsError::ServerError("Missing AES GCM ciphertext".to_owned()))?;
    Ok(DecryptResponse {
        plaintext: STANDARD.encode(&plaintext),
    })
}
