//! This module contains cryptographic setup code, a long with what policy
//! and ciphers we accept.

use crate::config::TlsConfiguration;
use crypto_glue::{
    ec::EcPrivateKey,
    ecdsa_p256::{EcdsaP256SigningKey, EcdsaP256VerifyingKey},
    ecdsa_p384::{EcdsaP384DerSignature, EcdsaP384SigningKey, EcdsaP384VerifyingKey},
    pkcs8::PrivateKeyInfo,
    rand,
    rsa::RS256PrivateKey,
    traits::{
        DecodeDer, DecodePem, EncodePem, Pkcs1DecodeRsaPrivateKey, Pkcs8DecodePrivateKey,
        Pkcs8EncodePrivateKey, PublicKeyParts,
    },
    x509::{
        oiddb::{rfc5280, rfc5912},
        uuid_to_serial, Builder, Certificate, CertificateBuilder, ExtendedKeyUsage, GeneralName,
        Ia5String, Name, Profile, SubjectAltName, SubjectPublicKeyInfoOwned, Time, Validity,
    },
};
use rustls::{
    pki_types::{pem::PemObject, CertificateDer, CertificateRevocationListDer, PrivateKeyDer},
    server::{ServerConfig, WebPkiClientVerifier},
    RootCertStore,
};
use std::fs;
use std::io::{Read, Write};
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio_rustls::TlsAcceptor;
use uuid::Uuid;

const CA_VALID_DAYS: u64 = 30;
const CERT_VALID_DAYS: u64 = 5;

// Basing minimums off https://www.keylength.com setting "year" to 2030 - tested as at 2023-09-25
//
// |Method           |Date     |Symmetric| FM      |DL Key| DL Group|Elliptic Curve|Hash|
// |   ---           |   ---   |   ---   |   ---   | ---  |   ---   |  ---         | ---|
// |Lenstra / Verheul|2030     |  93     |2493^2016|165   | 2493    |  176         | 186|
// |Lenstra Updated  |2030     |  88     |1698^2063|176   | 1698    |  176         | 176|
// |ECRYPT           |2029-2068|  256    |15360    |512   | 15360   |  512         | 512|
// |NIST             |2019-2030|  112    |2048     |224   | 2048    |  224         | 224|
// |ANSSI            |> 2030   |  128    |3072     |200   | 3072    |  256         | 256|
// |NSA              |-        |  256    |3072     |-     | -       |  384         | 384|
// |RFC3766          |-        |  -      |   -     | -    |   -     |   -          |  - |
// |BSI              |-        |  -      |   -     | -    |   -     |   -          |  - |
// DL - Discrete Logarithm
// FM - Factoring Modulus

const RSA_MIN_KEY_SIZE_BITS: u64 = 2048;

/// Ensure we're enforcing safe minimums for TLS keys
pub fn check_privkey_minimums(privkey: &PrivateKeyDer<'_>) -> Result<(), String> {
    match privkey {
        PrivateKeyDer::Pkcs8(pkcs8_der) => {
            let private_key_info = PrivateKeyInfo::try_from(pkcs8_der.secret_pkcs8_der())
                .map_err(|_err| "Invalid pkcs8 der".to_string())?;

            let oids = private_key_info
                .algorithm
                .oids()
                .map_err(|_err| "Invalid pkcs8 key oids".to_string())?;

            match oids {
                (rfc5912::ID_EC_PUBLIC_KEY, Some(rfc5912::SECP_256_R_1)) => {
                    debug!("The EC private key size is: 256 bits, that's OK!");
                    Ok(())
                }
                (rfc5912::ID_EC_PUBLIC_KEY, Some(rfc5912::SECP_384_R_1)) => {
                    debug!("The EC private key size is: 384 bits, that's OK!");
                    Ok(())
                }
                (rfc5912::RSA_ENCRYPTION, None) => {
                    let priv_key = RS256PrivateKey::try_from(private_key_info)
                        .map_err(|_err| "Invalid rsa key".to_string())?;

                    let priv_key_bits = priv_key.size() * 8;

                    if priv_key_bits < RSA_MIN_KEY_SIZE_BITS as usize {
                        Err(format!(
                            "TLS RSA key is less than {RSA_MIN_KEY_SIZE_BITS} bits!"
                        ))
                    } else {
                        debug!(
                            "The RSA private key size is: {} bits, that's OK!",
                            priv_key_bits
                        );
                        Ok(())
                    }
                }
                _ => Err("TLS Private Key Oids not understood".into()),
            }
        }
        PrivateKeyDer::Sec1(sec1_der) => {
            // ECDSA only
            let priv_key = EcPrivateKey::from_der(sec1_der.secret_sec1_der())
                .map_err(|_err| "Invalid sec1 der".to_string())?;

            match priv_key.parameters.and_then(|params| params.named_curve()) {
                Some(rfc5912::SECP_256_R_1) => {
                    debug!("The EC private key size is: 256 bits, that's OK!");
                    Ok(())
                }
                Some(rfc5912::SECP_384_R_1) => {
                    debug!("The EC private key size is: 384 bits, that's OK!");
                    Ok(())
                }
                Some(_) => Err("TLS Private Key Oids not understood".into()),
                None => Err("TLS Private Key has no oids".into()),
            }
        }
        PrivateKeyDer::Pkcs1(pkcs1_der) => {
            // RSA only
            let priv_key = RS256PrivateKey::from_pkcs1_der(pkcs1_der.secret_pkcs1_der())
                .map_err(|_err| "Invalid pkcs1 der".to_string())?;

            let priv_key_bits = priv_key.size() * 8;

            if priv_key_bits < RSA_MIN_KEY_SIZE_BITS as usize {
                Err(format!(
                    "TLS RSA key is less than {RSA_MIN_KEY_SIZE_BITS} bits!"
                ))
            } else {
                debug!(
                    "The RSA private key size is: {} bits, that's OK!",
                    priv_key_bits
                );
                Ok(())
            }
        }
        _ => Err("TLS Private Key Format not understood".into()),
    }
}

/// From the server configuration, generate a TLS acceptor that we can use
/// to build our sockets for HTTPS/LDAPS.
pub fn setup_tls(
    tls_config: &Option<TlsConfiguration>,
) -> Result<Option<TlsAcceptor>, std::io::Error> {
    let Some(tls_param) = tls_config.as_ref() else {
        return Ok(None);
    };

    let cert_iter = CertificateDer::pem_file_iter(&tls_param.chain).map_err(|err| {
        std::io::Error::other(format!(
            "Failed to create TLS listener. The Certificate Chain could not be parsed: {err:?}"
        ))
    })?;

    let cert_chain_der = cert_iter
        .collect::<Result<Vec<_>, _>>()
        .map_err(|err| std::io::Error::other(format!("Failed to create TLS listener. The Certificate Chain could not be transformed from PEM to DER: {err:?}")))?;

    let private_key_der = PrivateKeyDer::from_pem_file(&tls_param.key).map_err(|err| {
        std::io::Error::other(format!(
            "Failed to create TLS listener. The Private Key could not be parsed {err:?}"
        ))
    })?;

    check_privkey_minimums(&private_key_der).map_err(|err| {
        std::io::Error::other(format!(
            "Private Key does not meet cryptographic minimum standards: {err:?}"
        ))
    })?;

    // Configure the rustls cryptoprovider. We currently use aws-lc-rc as the
    // default. We may swap to rustcrypto in future.
    let provider: Arc<_> = rustls::crypto::aws_lc_rs::default_provider().into();

    let client_cert_verifier = if let Some(client_ca) = tls_param.client_ca.as_ref() {
        info!(
            "Loading client CA certificates from {}",
            client_ca.display()
        );

        let read_dir = fs::read_dir(client_ca).map_err(|err| {
            std::io::Error::other(format!(
                "Failed to create TLS listener. An error occurred while loading a client ca from {}: {:?}",
                client_ca.display(),
                err
            ))
        })?;

        let dirents: Vec<_> = read_dir.filter_map(|item| item.ok()).collect();

        let mut client_cert_roots = RootCertStore::empty();

        for cert_dir_ent in dirents.iter().filter(|item| {
            item.file_name()
                .to_str()
                // Hashed certs end in .0
                // Hashed crls are .r0
                .map(|fname| fname.ends_with(".0"))
                .unwrap_or_default()
        }) {
            let cert_pem = CertificateDer::from_pem_file(cert_dir_ent.path()).map_err(|err| {
                std::io::Error::other(format!("Failed to create TLS listener. A Client CA Certificate could not be parsed: {} {err:?}", cert_dir_ent.path().display()))
            })?;

            client_cert_roots.add(cert_pem).map_err(|err| {
                std::io::Error::other(format!("Failed to create TLS listener. A Client CA Certificate failed to be added to the trusted store: {} {err:?}", cert_dir_ent.path().display()))
            })?;
        }

        let mut client_cert_crls = Vec::new();

        for cert_dir_ent in dirents.iter().filter(|item| {
            item.file_name()
                .to_str()
                // Hashed certs end in .0
                // Hashed crls are .r0
                .map(|fname| fname.ends_with(".r0"))
                .unwrap_or_default()
        }) {
            let cert_pem = CertificateRevocationListDer::from_pem_file(cert_dir_ent.path())
                .map_err(|err| {
                    std::io::Error::other(format!("Failed to create TLS listener. A Certificate Revocation List could not be parsed: {} {err:?}", cert_dir_ent.path().display()))
                })?;

            client_cert_crls.push(cert_pem);
        }

        WebPkiClientVerifier::builder_with_provider(client_cert_roots.into(), provider.clone())
            .with_crls(client_cert_crls)
            .allow_unauthenticated()
            .build()
            .map_err(|err| {
                std::io::Error::other(format!("Failed to create TLS listener. The Client Certificate Verifier could not be built: {err:?}"))
            })?
    } else {
        WebPkiClientVerifier::no_client_auth()
    };

    let tls_server_config = ServerConfig::builder_with_provider(provider)
        .with_safe_default_protocol_versions()
        .and_then(|builder| {
            builder
                .with_client_cert_verifier(client_cert_verifier)
                .with_single_cert(cert_chain_der, private_key_der)
        })
        .map_err(|err| std::io::Error::other(format!("Failed to create TLS listener. The TLS Server Configuration could not be built: {err:?}")))?;

    let tls_acceptor = TlsAcceptor::from(Arc::new(tls_server_config));

    Ok(Some(tls_acceptor))
}

#[derive(Debug)]
pub(crate) struct CaHandle {
    key: EcdsaP384SigningKey,
    cert: Certificate,
}

pub(crate) fn write_ca(
    key_ar: impl AsRef<Path>,
    cert_ar: impl AsRef<Path>,
    handle: &CaHandle,
) -> Result<(), ()> {
    let key_path: &Path = key_ar.as_ref();
    let cert_path: &Path = cert_ar.as_ref();

    let key_pem = handle.key.to_pkcs8_pem(Default::default()).map_err(|e| {
        error!(err = ?e, "Failed to convert key to PEM");
    })?;

    let cert_pem = handle.cert.to_pem(Default::default()).map_err(|e| {
        error!(err = ?e, "Failed to convert cert to PEM");
    })?;

    fs::File::create(key_path)
        .and_then(|mut file| file.write_all(key_pem.as_bytes()))
        .map_err(|e| {
            error!(err = ?e, "Failed to create {:?}", key_path);
        })?;

    fs::File::create(cert_path)
        .and_then(|mut file| file.write_all(cert_pem.as_bytes()))
        .map_err(|e| {
            error!(err = ?e, "Failed to create {:?}", cert_path);
        })
}

/// build up a CA certificate and key.
pub(crate) fn build_ca() -> Result<CaHandle, ()> {
    let mut rng = rand::thread_rng();

    let root_serial_uuid = Uuid::new_v4();
    let serial_number = uuid_to_serial(root_serial_uuid);

    let now = SystemTime::now();
    let not_before = Time::try_from(now).map_err(|err| {
        error!(?err, "Unable to convert system time");
    })?;
    let not_after =
        Time::try_from(now + Duration::from_secs(CA_VALID_DAYS * 86400)).map_err(|err| {
            error!(?err, "Unable to convert system time");
        })?;

    let validity = Validity {
        not_before,
        not_after,
    };

    let profile = Profile::Root;
    let root_subject = Name::from_str("C=AU,ST=QLD,O=Kanidm,CN=Kanidm Generated CA,OU=Development and Evaluation - NOT FOR PRODUCTION")
        .map_err(|err| {
            error!(?err, "Invalid root subject DN - THIS IS A BUG.");
        })?;

    let signing_key = EcdsaP384SigningKey::random(&mut rng);
    let verifying_key = EcdsaP384VerifyingKey::from(&signing_key);
    let pub_key = SubjectPublicKeyInfoOwned::from_key(verifying_key).map_err(|err| {
        error!(?err, "Unable to access subject public key information");
    })?;

    let builder = CertificateBuilder::new(
        profile,
        serial_number,
        validity,
        root_subject.clone(),
        pub_key.clone(),
        &signing_key,
    )
    .map_err(|err| {
        error!(?err, "Unable to create certificate builder");
    })?;

    let cert = builder
        .build_with_rng::<EcdsaP384DerSignature>(&mut rng)
        .map_err(|err| {
            error!(?err, "Unable to sign certificate request");
        })?;

    Ok(CaHandle {
        key: signing_key,
        cert,
    })
}

pub(crate) fn load_ca(
    ca_key_ar: impl AsRef<Path>,
    ca_cert_ar: impl AsRef<Path>,
) -> Result<CaHandle, ()> {
    let ca_key_path: &Path = ca_key_ar.as_ref();
    let ca_cert_path: &Path = ca_cert_ar.as_ref();

    let mut ca_key_pem = String::new();
    fs::File::open(ca_key_path)
        .and_then(|mut file| file.read_to_string(&mut ca_key_pem))
        .map_err(|e| {
            error!(err = ?e, "Failed to read {:?}", ca_key_path);
        })?;

    let mut ca_cert_pem = String::new();
    fs::File::open(ca_cert_path)
        .and_then(|mut file| file.read_to_string(&mut ca_cert_pem))
        .map_err(|e| {
            error!(err = ?e, "Failed to read {:?}", ca_cert_path);
        })?;

    let ca_key = EcdsaP384SigningKey::from_pkcs8_pem(&ca_key_pem).map_err(|e| {
        error!(err = ?e, "Failed to convert PEM to key");
    })?;

    let ca_cert = Certificate::from_pem(&ca_cert_pem).map_err(|e| {
        error!(err = ?e, "Failed to convert PEM to cert");
    })?;

    Ok(CaHandle {
        key: ca_key,
        cert: ca_cert,
    })
}

pub(crate) struct CertHandle {
    key: EcdsaP256SigningKey,
    cert: Certificate,
    chain: Vec<Certificate>,
}

pub(crate) fn write_cert(
    key_ar: impl AsRef<Path>,
    chain_ar: impl AsRef<Path>,
    cert_ar: impl AsRef<Path>,
    handle: &CertHandle,
) -> Result<(), ()> {
    let key_path: &Path = key_ar.as_ref();
    let chain_path: &Path = chain_ar.as_ref();
    let cert_path: &Path = cert_ar.as_ref();

    let key_pem = handle.key.to_pkcs8_pem(Default::default()).map_err(|e| {
        error!(err = ?e, "Failed to convert key to PEM");
    })?;

    let cert_pem = handle.cert.to_pem(Default::default()).map_err(|e| {
        error!(err = ?e, "Failed to convert cert to PEM");
    })?;

    let mut chain_pem = cert_pem.clone();

    // Build the chain PEM.
    for ca_cert in &handle.chain {
        match ca_cert.to_pem(Default::default()) {
            Ok(c) => {
                chain_pem.push_str(&c);
            }
            Err(e) => {
                error!(err = ?e, "Failed to convert cert to PEM");
                return Err(());
            }
        }
    }

    fs::File::create(key_path)
        .and_then(|mut file| file.write_all(key_pem.as_bytes()))
        .map_err(|e| {
            error!(err = ?e, "Failed to create {:?}", key_path);
        })?;

    fs::File::create(chain_path)
        .and_then(|mut file| file.write_all(chain_pem.as_bytes()))
        .map_err(|e| {
            error!(err = ?e, "Failed to create {:?}", chain_path);
        })?;

    fs::File::create(cert_path)
        .and_then(|mut file| file.write_all(cert_pem.as_bytes()))
        .map_err(|e| {
            error!(err = ?e, "Failed to create {:?}", cert_path);
        })
}

pub(crate) fn build_cert(domain_name: &str, ca_handle: &CaHandle) -> Result<CertHandle, ()> {
    let mut rng = rand::thread_rng();

    let root_serial_uuid = Uuid::new_v4();
    let serial_number = uuid_to_serial(root_serial_uuid);

    let now = SystemTime::now();
    let not_before = Time::try_from(now).map_err(|err| {
        error!(?err, "Unable to convert system time");
    })?;
    let not_after =
        Time::try_from(now + Duration::from_secs(CERT_VALID_DAYS * 86400)).map_err(|err| {
            error!(?err, "Unable to convert system time");
        })?;

    let validity = Validity {
        not_before,
        not_after,
    };

    let profile = Profile::Leaf {
        issuer: ca_handle.cert.tbs_certificate.subject.clone(),
        enable_key_agreement: true,
        enable_key_encipherment: true,
        include_subject_key_identifier: true,
    };
    let root_subject = Name::from_str("C=AU,ST=QLD,O=Kanidm,CN=Kanidm Generated Server Certificate,OU=Development and Evaluation - NOT FOR PRODUCTION")
        .map_err(|err| {
            error!(?err, "Invalid cert subject DN - THIS IS A BUG.");
        })?;

    let signing_key = EcdsaP256SigningKey::random(&mut rng);
    let verifying_key = EcdsaP256VerifyingKey::from(&signing_key);
    let pub_key = SubjectPublicKeyInfoOwned::from_key(verifying_key).map_err(|err| {
        error!(?err, "Unable to access subject public key information");
    })?;

    let mut builder = CertificateBuilder::new(
        profile,
        serial_number,
        validity,
        root_subject.clone(),
        pub_key.clone(),
        &ca_handle.key,
    )
    .map_err(|err| {
        error!(?err, "Unable to create certificate builder");
    })?;

    let eku_extension = ExtendedKeyUsage(vec![rfc5280::ID_KP_SERVER_AUTH]);

    builder.add_extension(&eku_extension).map_err(|err| {
        error!(?err, "Unable to add extended key usage extension");
    })?;

    let alt_name = Ia5String::new(domain_name).map_err(|err| {
        error!(?err, "Invalid subject alternative name");
    })?;

    let san = SubjectAltName(vec![GeneralName::DnsName(alt_name)]);

    builder.add_extension(&san).map_err(|err| {
        error!(?err, "Unable to add subject alternative name extension");
    })?;

    let cert = builder
        .build_with_rng::<EcdsaP384DerSignature>(&mut rng)
        .map_err(|err| {
            error!(?err, "Unable to sign certificate request");
        })?;

    Ok(CertHandle {
        key: signing_key,
        cert,
        chain: vec![ca_handle.cert.clone()],
    })
}

#[cfg(test)]
mod tests {
    use crate::crypto::{build_ca, load_ca, write_ca};

    #[test]
    fn test_ca_loader() {
        let ca_key_tempfile = tempfile::NamedTempFile::new().unwrap();
        let ca_cert_tempfile = tempfile::NamedTempFile::new().unwrap();
        // let's test the defaults first

        if let Ok(ca) = build_ca() {
            write_ca(ca_key_tempfile.path(), ca_cert_tempfile.path(), &ca).unwrap();
            assert!(load_ca(ca_key_tempfile.path(), ca_cert_tempfile.path()).is_ok());
        };
    }
}
