// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2026 ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG, Cologne
// SPDX-FileCopyrightText: Author: Pierre Gronau <Pierre.Gronau@ndaal.eu>

//! TLS 1.3 helpers: a self-signed localhost certificate and a TLS 1.3-only
//! server configuration.
//!
//! The crypto provider is **aws-lc-rs** with rustls `prefer-post-quantum`, so
//! the X25519MLKEM768 post-quantum hybrid key-exchange group is offered first
//! (carried over from the ndaal OSCAL viewer / vulnerability-lookup-rs design).
//! The same certificate backs both listeners — HTTPS (HTTP/1.1 + HTTP/2) over
//! TCP and HTTP/3 over QUIC — so they present one identity. It is regenerated
//! at every start-up with a 45-day validity (the Let's Encrypt short-lived
//! style); browsers warn once on a self-signed localhost cert.

use std::net::{Ipv4Addr, Ipv6Addr};

use rustls::pki_types::{CertificateDer, PrivateKeyDer};

/// Install the process-wide rustls crypto provider (aws-lc-rs).
///
/// With the `prefer-post-quantum` feature this provider offers the
/// X25519MLKEM768 post-quantum hybrid key-exchange group first.
///
/// # Errors
///
/// Returns an error only if a provider is already installed — impossible at
/// the top of `main`, but surfaced rather than panicking.
pub fn install_crypto_provider() -> anyhow::Result<()> {
    rustls::crypto::aws_lc_rs::default_provider()
        .install_default()
        .map_err(|_| anyhow::anyhow!("rustls crypto provider was already installed"))
}

/// Generate a self-signed TLS certificate for `localhost` valid for 45 days.
///
/// # Errors
///
/// Returns an error if certificate or key generation fails.
pub fn generate_self_signed_cert(
) -> anyhow::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
    use rcgen::{CertificateParams, SanType};

    let mut params = CertificateParams::new(vec!["localhost".to_owned()])?;
    params.subject_alt_names = vec![
        SanType::DnsName("localhost".try_into()?),
        SanType::IpAddress(Ipv4Addr::LOCALHOST.into()),
        SanType::IpAddress(Ipv6Addr::LOCALHOST.into()),
    ];

    let now = time::OffsetDateTime::now_utc();
    params.not_before = now;
    params.not_after = now.saturating_add(time::Duration::days(45));

    let key_pair = rcgen::KeyPair::generate()?;
    let cert = params.self_signed(&key_pair)?;
    let cert_der = CertificateDer::from(cert);
    let key_der = PrivateKeyDer::try_from(key_pair.serialize_der())
        .map_err(|e| anyhow::anyhow!("failed to parse private key: {e}"))?;

    Ok((vec![cert_der], key_der))
}

/// Build a TLS 1.3-only server configuration with the given ALPN protocols.
///
/// # Errors
///
/// Returns an error if the certificate/key pair is rejected by rustls.
pub fn build_tls13_server_config(
    cert_chain: Vec<CertificateDer<'static>>,
    key_der: PrivateKeyDer<'static>,
    alpn: Vec<Vec<u8>>,
) -> anyhow::Result<rustls::ServerConfig> {
    let mut config = rustls::ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
        .with_no_client_auth()
        .with_single_cert(cert_chain, key_der)?;
    config.alpn_protocols = alpn;
    Ok(config)
}
