// Copyright 2026-Present Datadog, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Certificate signer request generation.
//!
//! A typical workflow for certifying a [`PrivateKey`] as part of a trust chain
//! is shown below:
//!
//! ```text
//!        ┌──────────┐           ┌──────┐
//!        │Key Holder│           │Issuer│
//!        └─────┬────┘           └───┬──┘
//!              │────┐               │
//!              │    │ Generate key  │
//!              │<───┘               │
//!              │                    │
//!              │────┐               │
//!              │    │ Generate CSR  │
//!              │<───┘               │
//!              │                    │
//!              │     Send CSR       │
//!              │───────────────────>│
//!              │                    │
//!              │                    │────┐
//!              │                    │    │ Generate certificate from CSR
//!              │                    │<───┘
//!              │                    │
//!              │    Certificate     │
//!              │<───────────────────│
//!        ┌─────┴────┐           ┌───┴──┐
//!        │Key Holder│           │Issuer│
//!        └──────────┘           └──────┘
//! ```
//!
//! Where:
//!
//!    * "Key Holder" is has a [`PrivateKey`].
//!    * "Issuer" has a CA certificate that chains to a trusted root.
//!
//! The certificate returned from the "Issuer" certifies the [`PrivateKey`] can
//! be trusted as part of the trust chain from Issuer's root.

use rcgen::{
    CertificateParams, DistinguishedName, DnType, DnValue, ExtendedKeyUsagePurpose, IsCa,
    KeyUsagePurpose, SanType, string::Ia5String,
};
use thiserror::Error;

use crate::keys::PrivateKey;

// CSR DN field values.
const CSR_ON: &str = "Datadog, Inc.";
const CSR_OU: &str = "RC Attestation Certificate";

/// Failures when generating a new [`CertificateSigningRequest`].
#[derive(Debug, Error)]
pub enum CsrError {
    /// The SAN provided is invalid.
    #[error("invalid SAN provided: {0}")]
    San(rcgen::Error),

    /// The CSR was populated, but was invalid / could not be serialised.
    #[error("failed to serialise CSR: {0}")]
    Serialise(rcgen::Error),

    /// The provided CA or SAN value was an empty string.
    #[error("an empty CN or SAN was provided")]
    EmptyIdent,
}

/// A [`CertificateSigningRequest`] ("CSR") contains requested certificate
/// parameters that are provided to a Certificate Authority for issuance,
/// notably:
///
///   * The public key to certify.
///   * The key usage purposes (and EKUs) to be certified for.
///   * The common name / SAN fields ("certificate name").
///
/// The resulting [`Certificate`] issued by a CA contains values provided in
/// this [`CertificateSigningRequest`] in addition to values specified by the
/// CA's issuance policy. The CA is free to override (or reject) any field
/// provided in a CSR.
///
/// [`Certificate`]: crate::certificate::Certificate
#[derive(Debug, PartialEq)]
pub struct CertificateSigningRequest {
    /// The certificate profile used when generating the "serialised" request.
    ///
    /// This is kept for informative purposes, allowing callers to inspect
    /// (read-only) properties of the generated CSR (i.e. for logging).
    profile: CertificateParams,

    /// A pre-serialised form of "profile".
    serialised: rcgen::CertificateSigningRequest,
}

impl CertificateSigningRequest {
    /// Create a new certificate signing request for a leaf certificate profile.
    ///
    /// Certificates SHOULD use unique CN strings.
    ///
    /// Issuers MUST apply the following best practices when issuing the CA
    /// certificate:
    ///
    ///   * Set CA: FALSE as a basic constraint, and mark it as critical.
    ///
    pub fn new_leaf(private_key: &PrivateKey, cn: &str, san: &str) -> Result<Self, CsrError> {
        if cn.trim().is_empty() || san.trim().is_empty() {
            return Err(CsrError::EmptyIdent);
        }

        //
        // The following code configures the certificate profile to be signed by
        // an issuer, and the various fields and their consequences are defined
        // in RFC5280:
        //
        // 		https://datatracker.ietf.org/doc/html/rfc5280
        //

        let mut profile = CertificateParams::new([]).map_err(CsrError::San)?;

        // Explicitly mark this as not a CA (therefore end-entity / leaf)
        // certificate.
        profile.is_ca = IsCa::NoCa; // Issuer will insert CA: FALSE critical.

        // Explicitly opt of of the following:
        profile.serial_number = None; // Generated by issuer
        profile.name_constraints = None; // Only applies to CAs.
        profile.crl_distribution_points = vec![]; // CRLs are distributed via delivery protocol.
        profile.custom_extensions = vec![]; // N/A

        // Build the DN, which specifies the identity of the owner.
        let mut distinguished_name = DistinguishedName::new();
        distinguished_name.push(DnType::CommonName, cn);
        distinguished_name.push(DnType::OrganizationName, CSR_ON);
        distinguished_name.push(DnType::OrganizationalUnitName, CSR_OU);
        profile.distinguished_name = distinguished_name;

        // Specify the SAN DNS name.
        profile.subject_alt_names = vec![SanType::DnsName(
            Ia5String::try_from(san).map_err(CsrError::San)?,
        )];

        // Configure the key profile.
        //
        // Key Usage, § 4.2.1.3:
        //
        //   The digitalSignature bit is asserted when the subject public key is
        //   used for verifying digital signatures, other than signatures on
        //   certificates (bit 5) and CRLs (bit 6), such as those used in an
        //   entity authentication service, a data origin authentication
        //   service, and/or an integrity service.
        //
        // Extended Key Usage, § 4.2.1.12 for "codeSigning":
        //
        //   * Signing of downloadable executable code
        //   * Key usage bits that may be consistent: digitalSignature
        //
        profile.key_usages = vec![KeyUsagePurpose::DigitalSignature];
        profile.extended_key_usages = vec![ExtendedKeyUsagePurpose::CodeSigning];

        // Provide the Subject Key Identifier hash, which in this implementation
        // is a SHA256 hash over the X509 SubjectPublicKeyInfo defined in § 4.1.
        //
        // § 4.2.1.2:
        //
        //   To assist applications in identifying the appropriate end entity
        //   certificate, this extension SHOULD be included in all end entity
        //   certificates.
        //
        profile.key_identifier_method =
            rcgen::KeyIdMethod::PreSpecified(private_key.public_key().key_id().to_vec());

        let serialised = profile
            .serialize_request(private_key)
            .map_err(CsrError::Serialise)?;

        Ok(Self {
            profile,
            serialised,
        })
    }

    /// Create a new certificate signing request for an intermediate certificate
    /// profile.
    ///
    /// Issuers SHOULD apply the following best practices when issuing the CA
    /// certificate:
    ///
    ///   * Apply name constraints such that the provisioned CA can issue
    ///     certificates only under the subdomain for which it is intended to be
    ///     used (DC isolation).
    ///
    ///   * Constrain the pathLen of the CA to prevent further unintended CA
    ///     issuance.
    ///
    pub fn new_intermediate(private_key: &PrivateKey, cn: &str) -> Result<Self, CsrError> {
        if cn.trim().is_empty() {
            return Err(CsrError::EmptyIdent);
        }

        //
        // The following code configures the certificate profile to be signed by
        // an issuer, and the various fields and their consequences are defined
        // in RFC5280:
        //
        // 		https://datatracker.ietf.org/doc/html/rfc5280
        //

        let mut profile = CertificateParams::new([]).map_err(CsrError::San)?;

        // The issuer MUST mark this as not a CA (therefore end-entity / leaf)
        // certificate with an appropriate pathLen.
        //
        // Basic Constraints, § 4.2.1.9:
        //
        //   The basic constraints extension identifies whether the subject of
        //   the certificate is a CA and the maximum depth of valid
        //   certification paths that include this certificate.
        //
        //   A pathLenConstraint of zero indicates that no non- self-issued
        //   intermediate CA certificates may follow in a valid certification
        //   path.
        //
        // This parameter is not supported in the CSR.
        profile.name_constraints = None;

        // Explicitly opt out of the following:
        profile.serial_number = None; // Generated by issuer
        profile.name_constraints = None; // Only applies to CAs.
        profile.crl_distribution_points = vec![]; // CRLs are distributed via delivery protocol.
        profile.custom_extensions = vec![]; // N/A

        // Build the DN, which specifies the identity of the owner.
        let mut distinguished_name = DistinguishedName::new();
        distinguished_name.push(DnType::CommonName, cn);
        distinguished_name.push(DnType::OrganizationName, CSR_ON);
        distinguished_name.push(DnType::OrganizationalUnitName, CSR_OU);
        profile.distinguished_name = distinguished_name;

        // The issuer MUST constrain this intermediate using Name Constraints
        // such that it MAY issue certificates only under the subdomain it is
        // responsible for.
        //
        // This parameter is not supported in the CSR.
        profile.is_ca = IsCa::NoCa;

        // Configure the key profile.
        //
        // Key Usage, § 4.2.1.3:
        //
        //   The keyCertSign bit is asserted when the subject public key is used
        //   for verifying signatures on public key certificates. If the
        //   keyCertSign bit is asserted, then the cA bit in the basic
        //   constraints extension (Section 4.2.1.9) MUST also be asserted.
        //
        //   The cRLSign bit is asserted when the subject public key is used for
        //   verifying signatures on certificate revocation lists (e.g., CRLs,
        //   delta CRLs, or ARLs).
        //
        profile.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];
        profile.extended_key_usages = vec![];

        // Provide the Subject Key Identifier hash, which in this implementation
        // is a SHA256 hash over the X509 SubjectPublicKeyInfo defined in § 4.1.
        //
        // § 4.2.1.2:
        //
        //   To assist applications in identifying the appropriate end entity
        //   certificate, this extension SHOULD be included in all end entity
        //   certificates.
        //
        profile.key_identifier_method =
            rcgen::KeyIdMethod::PreSpecified(private_key.public_key().key_id().to_vec());

        let serialised = profile
            .serialize_request(private_key)
            .map_err(CsrError::Serialise)?;

        Ok(Self {
            profile,
            serialised,
        })
    }

    /// Return the pre-serialised CSR as DER bytes.
    ///
    /// This call returns pre-cached content in O(1) time.
    pub fn as_der_bytes(&self) -> &[u8] {
        self.serialised.der()
    }

    /// Serialise the CSR into a PEM block.
    pub fn as_pem_string(&self) -> String {
        self.serialised.pem().expect("failed to generate CSR PEM")
    }

    /// Get the CommonName (CN) from the Distinguished Name in the CSR.
    ///
    /// Returns the CN value that was provided when creating the CSR in `new_leaf()`.
    ///
    /// Panics if the CSR doesn't have a CommonName (which should never happen for CSRs
    /// created via `new_leaf()`), or if the CommonName is in an unexpected encoding.
    pub fn common_name(&self) -> &str {
        let dn_value = self
            .profile
            .distinguished_name
            .get(&DnType::CommonName)
            .expect("CSR should always have a CommonName");

        // Extract the string from the DnValue enum
        // Since new_leaf() pushes CN as &str, it becomes DnValue::Utf8String
        match dn_value {
            DnValue::Utf8String(s) => s.as_str(),
            DnValue::PrintableString(s) => s.as_str(),
            DnValue::Ia5String(s) => s.as_str(),
            DnValue::TeletexString(s) => s.as_str(),
            // BmpString and UniversalString don't implement as_str(),
            // but they shouldn't be used for CN in our CSRs
            _ => panic!("Unexpected DnValue type for CommonName"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use assert_matches::assert_matches;
    use proptest::{prelude::*, strategy::LazyJust};
    use rcgen::{CertificateSigningRequestParams, PublicKeyData, SanType};

    fn arb_string() -> impl Strategy<Value = String> {
        prop_oneof![
            // Any random string, including incompatible characters.
            10 => any::<String>(),
            // ASCII only (DN compatible).
            10 => prop::collection::vec(0_u8..=127, 1..1025).prop_map(|v| String::from_utf8(v).unwrap()),
            // An IP address, which the rcgen crate would have helpfully
            // inferred into a IPSan - no magic please.
            1 => LazyJust::new(|| "127.0.0.42".to_string()),
        ]
    }

    fn is_bad_dn(s: &str) -> bool {
        Ia5String::try_from(s).is_err()
    }

    proptest! {
        #[test]
        fn prop_leaf_csr_generation(
            cn in arb_string(),
            san in arb_string(),
        ) {
            let key = PrivateKey::new();

            let csr = match CertificateSigningRequest::new_leaf(&key, &cn, &san) {
                Ok(v) => v,
                Err(CsrError::EmptyIdent) => {
                    assert!(cn.trim().is_empty() || san.trim().is_empty());
                    return Ok(());
                }
                Err(e) => {
                    assert!(is_bad_dn(&cn) || is_bad_dn(&san), "{e}");
                    return Ok(());
                }
            };

            // Invariant: the provided SAN is the only SAN present, and it is a
            // DNS name.
            match csr.profile.subject_alt_names.as_slice() {
                [SanType::DnsName(v)] => {
                    assert_eq!(v.to_string(), san);
                }
                _ => panic!("invalid san config"),
            }

            // Invariant: the DN is composed of the hard-coded Datadog
            // identifiers, plus the variable CN.
            let mut want_dn = DistinguishedName::new();
            want_dn.push(DnType::CommonName, cn);
            want_dn.push(DnType::OrganizationName, "Datadog, Inc.");
            want_dn.push(DnType::OrganizationalUnitName, "RC Attestation Certificate");
            assert_eq!(csr.profile.distinguished_name, want_dn);

            // Invariant: a leaf should never request to be a CA cert.
            assert_eq!(csr.profile.is_ca, rcgen::IsCa::NoCa);

            // Invariant: KU & EKU must be suitable for code signing.
            assert_eq!(csr.profile.key_usages, vec![KeyUsagePurpose::DigitalSignature]);
            assert_eq!(csr.profile.extended_key_usages, vec![ExtendedKeyUsagePurpose::CodeSigning]);

            // Invariant: no serial, name constraints, CRL points, or
            // extensions are set.
            assert_eq!(csr.profile.serial_number, None);
            assert_eq!(csr.profile.name_constraints, None);
            assert_eq!(csr.profile.crl_distribution_points, vec![]);
            assert_eq!(csr.profile.custom_extensions, vec![]);

            // Invariant: the cert profile and the serialised bytes are
            // consistent (meaning the serialised bytes accurately represent the
            // contents of the profile kept for informative purposes) and
            // deterministic.
            let read = CertificateSigningRequestParams::from_pem(&csr.as_pem_string()).unwrap();
            assert_eq!(read.params.serial_number, csr.profile.serial_number);
            assert_eq!(read.params.subject_alt_names, csr.profile.subject_alt_names);
            assert_eq!(read.params.distinguished_name, csr.profile.distinguished_name);
            assert_eq!(read.params.is_ca, csr.profile.is_ca);
            assert_eq!(read.params.key_usages, csr.profile.key_usages);
            assert_eq!(read.params.extended_key_usages, csr.profile.extended_key_usages);
            assert_eq!(read.params.name_constraints, csr.profile.name_constraints);
            assert_eq!(read.params.crl_distribution_points, csr.profile.crl_distribution_points);
            assert_eq!(read.params.custom_extensions, csr.profile.custom_extensions);
            assert_eq!(read.params.use_authority_key_identifier_extension, csr.profile.use_authority_key_identifier_extension);
            // assert_matches!(read.params.key_identifier_method, rcgen::KeyIdMethod::PreSpecified(_)); // No content

            // Invariant: the SubjectPublicKeyInfo must have been propagated (it
            // for whatever reason does not make it into the deserialised
            // profile, but rather into the attached public key).
            assert_eq!(read.public_key.subject_public_key_info(), key.public_key().subject_public_key_info());

            // Invariant: the public key is correctly included in the serialised
            // form.
            assert_eq!(read.public_key.der_bytes(), key.public_key().der_bytes());
        }

        #[test]
        fn prop_intermediate_csr_generation(
            cn in arb_string(),
        ) {
            let key = PrivateKey::new();

            let csr = match CertificateSigningRequest::new_intermediate(&key, &cn) {
                Ok(v) => v,
                Err(CsrError::EmptyIdent) => {
                    assert!(cn.trim().is_empty());
                    return Ok(());
                }
                Err(e) => {
                    assert!(is_bad_dn(&cn), "{e}");
                    return Ok(());
                }
            };

            // Invariant: no SANs are provided.
            assert!(csr.profile.subject_alt_names.is_empty());

            // Invariant: the DN is composed of the hard-coded Datadog
            // identifiers, plus the variable CN.
            let mut want_dn = DistinguishedName::new();
            want_dn.push(DnType::CommonName, cn);
            want_dn.push(DnType::OrganizationName, "Datadog, Inc.");
            want_dn.push(DnType::OrganizationalUnitName, "RC Attestation Certificate");
            assert_eq!(csr.profile.distinguished_name, want_dn);

            // Invariant: not set in CSR - provided by issuer.
            assert_eq!(csr.profile.is_ca, IsCa::NoCa);

            // Invariant: name constraints are not set in CSR - provided by
            // issuer.
            assert_matches!(csr.profile.name_constraints, None);

            // Invariant: KU & EKU must be suitable for code signing.
            assert_eq!(csr.profile.key_usages, vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign]);
            assert_eq!(csr.profile.extended_key_usages, vec![]);

            // Invariant: no serial, CRL points, or extensions are set.
            assert_eq!(csr.profile.serial_number, None);
            assert_eq!(csr.profile.crl_distribution_points, vec![]);
            assert_eq!(csr.profile.custom_extensions, vec![]);

            // Invariant: the cert profile and the serialised bytes are
            // consistent (meaning the serialised bytes accurately represent the
            // contents of the profile kept for informative purposes) and
            // deterministic.
            let read = CertificateSigningRequestParams::from_pem(&csr.as_pem_string()).unwrap();
            assert_eq!(read.params.serial_number, csr.profile.serial_number);
            assert_eq!(read.params.subject_alt_names, csr.profile.subject_alt_names);
            assert_eq!(read.params.distinguished_name, csr.profile.distinguished_name);
            assert_eq!(read.params.is_ca, csr.profile.is_ca);
            assert_eq!(read.params.key_usages, csr.profile.key_usages);
            assert_eq!(read.params.extended_key_usages, csr.profile.extended_key_usages);
            assert_eq!(read.params.name_constraints, csr.profile.name_constraints);
            assert_eq!(read.params.crl_distribution_points, csr.profile.crl_distribution_points);
            assert_eq!(read.params.custom_extensions, csr.profile.custom_extensions);
            assert_eq!(read.params.use_authority_key_identifier_extension, csr.profile.use_authority_key_identifier_extension);
            // assert_matches!(read.params.key_identifier_method, rcgen::KeyIdMethod::PreSpecified(_)); // No content

            // Invariant: the SubjectPublicKeyInfo must have been propagated (it
            // for whatever reason does not make it into the deserialised
            // profile, but rather into the attached public key).
            assert_eq!(read.public_key.subject_public_key_info(), key.public_key().subject_public_key_info());

            // Invariant: the public key is correctly included in the serialised
            // form.
            assert_eq!(read.public_key.der_bytes(), key.public_key().der_bytes());
        }
    }
}
