//! Manage self-signed x509 certificate containing enclave remote attestation
//! endorsements.

use std::{borrow::Cow, fmt};

use lexe_crypto::ed25519;
use lexe_enclave::enclave;
use lexe_hex::hex;
use yasna::models::ObjectIdentifier;

use crate::attest_client::quote::ReportData;

/// Packages a remote attestation quote generated by
/// `lexe_tls_attest_server::quote::quote_enclave` into a x509 cert extension
/// with all the evidence a client needs to verify it.
///
/// ```asn.1
/// SgxAttestationExtension ::= SEQUENCE {
///     QUOTE      OCTET STRING
/// }
/// ```
#[derive(Eq, PartialEq)]
pub struct SgxAttestationExtension<'quote> {
    pub quote: Cow<'quote, [u8]>,
    // We don't verify the QE_REPORT because it requires calling an Intel JSON
    // API for marginal security against old SGX versions. Also, the MIN CPUSVN
    // already handles the case where the platform enclaves are too old.
    // pub qe_report: Cow<'b, [u8]>,
}

// -- impl SgxAttestationExtension -- //

impl SgxAttestationExtension<'_> {
    /// This is the Intel SGX OID prefix + 1337.7
    /// gramine uses the same but 1337.6 to embed the quote.
    pub const OID: &'static [u64] = &[1, 2, 840, 113741, 1337, 7];

    /// DER-encoded OID
    pub const OID_DER: &'static [u8] = &[
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF8, 0x4D, 0x8A, 0x39, 0x07,
    ];

    pub fn oid_yasna() -> ObjectIdentifier {
        ObjectIdentifier::from_slice(Self::OID)
    }

    #[rustfmt::skip]
    pub const fn oid_asn1_rs() -> asn1_rs::Oid<'static> {
        // TODO(phlip9): won't parse OID_DER...
        asn1_rs::oid!(1.2.840.113741.1337.7)
    }

    /// Clients that don't understand a critical extension will immediately
    /// reject the cert. Unfortunately, setting this to true seems to break
    /// clients...
    pub const fn is_critical() -> bool {
        false
    }

    /// Serialize the attestation to DER.
    pub fn to_der_bytes(&self) -> Vec<u8> {
        yasna::construct_der(|writer| {
            writer.write_sequence(|writer| {
                writer.next().write_bytes(&self.quote);
            })
        })
    }

    pub fn to_cert_extension(&self) -> rcgen::CustomExtension {
        let mut ext = rcgen::CustomExtension::from_oid_content(
            Self::OID,
            self.to_der_bytes(),
        );
        ext.set_criticality(Self::is_critical());
        ext
    }
}

impl SgxAttestationExtension<'static> {
    /// Build a dummy attestation for testing on non-SGX platforms.
    pub fn dummy(cert_pk: &ed25519::PublicKey) -> Self {
        // Use a dummy report as the 'quote'.
        let mut report = enclave::report().clone();

        // Insert the cert pk into the first 32 bytes of the `reportdata` field,
        // See `ReportData::from_cert_pk` in `attestation::quote::quote_enclave`
        let report_data = ReportData::from_cert_pk(cert_pk);
        report.reportdata = report_data.into_inner();

        Self {
            quote: Cow::Owned(AsRef::<[u8]>::as_ref(&report).to_owned()),
        }
    }

    /// Deserialize the attestation from DER bytes.
    pub fn from_der_bytes(buf: &[u8]) -> yasna::ASN1Result<Self> {
        yasna::parse_der(buf, |reader| {
            reader.read_sequence(|reader| {
                let quote = reader.next().read_bytes()?;
                Ok(Self {
                    quote: Cow::Owned(quote),
                })
            })
        })
    }
}

impl fmt::Debug for SgxAttestationExtension<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("SgxAttestationExtension")
            .field("quote", &hex::display(&self.quote))
            .finish()
    }
}

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

    #[test]
    fn test_sgx_attestation_ext_oid_der_bytes() {
        let oid = SgxAttestationExtension::oid_yasna();
        let oid_der = yasna::encode_der(&oid);
        assert_eq!(SgxAttestationExtension::OID_DER, &oid_der);
    }

    #[test]
    fn test_sgx_attestation_ext_serde() {
        let ext = SgxAttestationExtension {
            quote: b"test".as_slice().into(),
        };

        let der_bytes = ext.to_der_bytes();
        let ext2 = SgxAttestationExtension::from_der_bytes(&der_bytes).unwrap();
        assert_eq!(ext, ext2);

        let der_bytes2 = ext2.to_der_bytes();
        assert_eq!(der_bytes, der_bytes2);

        assert_eq!(b"test".as_slice(), ext2.quote.as_ref());
    }
}
