intel-dcap-api 0.6.0

Intel DCAP API Client
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 Matter Labs

use intel_dcap_api::{ApiClient, ApiVersion, CaType, IntelApiError, UpdateType};
use mockito::Server;
use reqwest::Client;

// Create a test client without TLS requirements
async fn create_test_client(base_url: &str) -> ApiClient {
    // Create a custom client without TLS requirements for testing
    ApiClient::new_with_base_url(base_url).expect("Failed to create client")
}

#[tokio::test]
async fn test_simple_request() {
    let mut server = Server::new_async().await;

    // First, test with plain reqwest to ensure mock works
    let _m = server
        .mock("GET", "/test")
        .with_status(200)
        .with_body("test")
        .create_async()
        .await;

    let client = Client::new();
    let resp = client
        .get(format!("{}/test", server.url()))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);
    assert_eq!(resp.text().await.unwrap(), "test");
}

#[tokio::test]
async fn test_tdx_tcb_minimal() {
    let mut server = Server::new_async().await;

    // Use minimal response
    let _m = server
        .mock("GET", "/tdx/certification/v4/tcb")
        .match_query(mockito::Matcher::UrlEncoded(
            "fmspc".into(),
            "test123".into(),
        ))
        .with_status(200)
        .with_header("TCB-Info-Issuer-Chain", "test-cert")
        .with_body("{}")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_tdx_tcb_info("test123", None, None).await;

    match &result {
        Ok(resp) => {
            assert_eq!(resp.tcb_info_json, "{}");
            assert_eq!(resp.issuer_chain, "test-cert");
        }
        Err(e) => {
            eprintln!("Error: {:?}", e);
            panic!("Request failed");
        }
    }
}

#[tokio::test]
async fn test_sgx_qe_identity_minimal() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/sgx/certification/v4/qe/identity")
        .with_status(200)
        .with_header("SGX-Enclave-Identity-Issuer-Chain", "test-cert")
        .with_body("{}")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_sgx_qe_identity(None, None).await;

    assert!(result.is_ok());
    let resp = result.unwrap();
    assert_eq!(resp.enclave_identity_json, "{}");
    assert_eq!(resp.issuer_chain, "test-cert");
}

#[tokio::test]
async fn test_pck_crl_minimal() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/sgx/certification/v4/pckcrl")
        .match_query(mockito::Matcher::UrlEncoded(
            "ca".into(),
            "processor".into(),
        ))
        .with_status(200)
        .with_header("SGX-PCK-CRL-Issuer-Chain", "test-cert")
        .with_body("test-crl")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_pck_crl(CaType::Processor, None).await;

    assert!(result.is_ok());
    let resp = result.unwrap();
    assert_eq!(String::from_utf8_lossy(&resp.crl_data), "test-crl");
    assert_eq!(resp.issuer_chain, "test-cert");
}

#[tokio::test]
async fn test_error_handling() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/sgx/certification/v4/tcb")
        .match_query(mockito::Matcher::UrlEncoded("fmspc".into(), "bad".into()))
        .with_status(404)
        .with_header("Request-ID", "test-123")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_sgx_tcb_info("bad", None, None).await;

    assert!(result.is_err());
    match result.unwrap_err() {
        IntelApiError::ApiError {
            status, request_id, ..
        } => {
            assert_eq!(status.as_u16(), 404);
            assert_eq!(request_id, "test-123");
        }
        _ => panic!("Wrong error type"),
    }
}

#[tokio::test]
async fn test_update_types() {
    let mut server = Server::new_async().await;

    // Test Early update type
    let _m1 = server
        .mock("GET", "/tdx/certification/v4/tcb")
        .match_query(mockito::Matcher::AllOf(vec![
            mockito::Matcher::UrlEncoded("fmspc".into(), "test".into()),
            mockito::Matcher::UrlEncoded("update".into(), "early".into()),
        ]))
        .with_status(200)
        .with_header("TCB-Info-Issuer-Chain", "cert")
        .with_body("{\"early\":true}")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client
        .get_tdx_tcb_info("test", Some(UpdateType::Early), None)
        .await;
    assert!(result.is_ok());
    assert_eq!(result.unwrap().tcb_info_json, "{\"early\":true}");

    // Test Standard update type
    let _m2 = server
        .mock("GET", "/tdx/certification/v4/tcb")
        .match_query(mockito::Matcher::AllOf(vec![
            mockito::Matcher::UrlEncoded("fmspc".into(), "test".into()),
            mockito::Matcher::UrlEncoded("update".into(), "standard".into()),
        ]))
        .with_status(200)
        .with_header("TCB-Info-Issuer-Chain", "cert")
        .with_body("{\"standard\":true}")
        .create_async()
        .await;

    let result2 = client
        .get_tdx_tcb_info("test", Some(UpdateType::Standard), None)
        .await;
    assert!(result2.is_ok());
    assert_eq!(result2.unwrap().tcb_info_json, "{\"standard\":true}");
}

#[tokio::test]
async fn test_v3_api_headers() {
    let mut server = Server::new_async().await;

    // V3 uses different header names for CRL
    let _m = server
        .mock("GET", "/sgx/certification/v3/pckcrl")
        .match_query(mockito::Matcher::UrlEncoded("ca".into(), "platform".into()))
        .with_status(200)
        .with_header("SGX-PCK-CRL-Issuer-Chain", "v3-cert")
        .with_body("v3-crl-data")
        .create_async()
        .await;

    let client = ApiClient::new_with_options(server.url(), ApiVersion::V3).unwrap();
    let result = client.get_pck_crl(CaType::Platform, None).await;

    assert!(result.is_ok());
    let resp = result.unwrap();
    assert_eq!(String::from_utf8_lossy(&resp.crl_data), "v3-crl-data");
    assert_eq!(resp.issuer_chain, "v3-cert");
}

#[tokio::test]
async fn test_sgx_qve_identity() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/sgx/certification/v4/qve/identity")
        .with_status(200)
        .with_header("SGX-Enclave-Identity-Issuer-Chain", "qve-cert")
        .with_body("{\"id\":\"QVE\"}")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_sgx_qve_identity(None, None).await;

    assert!(result.is_ok());
    let resp = result.unwrap();
    assert_eq!(resp.enclave_identity_json, "{\"id\":\"QVE\"}");
    assert_eq!(resp.issuer_chain, "qve-cert");
}

#[tokio::test]
async fn test_tdx_qe_identity() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/tdx/certification/v4/qe/identity")
        .with_status(200)
        .with_header("SGX-Enclave-Identity-Issuer-Chain", "tdx-qe-cert")
        .with_body("{\"id\":\"TDX-QE\"}")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_tdx_qe_identity(None, None).await;

    assert!(result.is_ok());
    let resp = result.unwrap();
    assert_eq!(resp.enclave_identity_json, "{\"id\":\"TDX-QE\"}");
    assert_eq!(resp.issuer_chain, "tdx-qe-cert");
}

#[tokio::test]
async fn test_error_with_details() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/sgx/certification/v4/pckcert")
        .match_query(mockito::Matcher::Any)
        .with_status(400)
        .with_header("Request-ID", "error-req-123")
        .with_header("Error-Code", "InvalidParameter")
        .with_header("Error-Message", "PPID format is invalid")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client
        .get_pck_certificate_by_ppid("bad", "bad", "bad", "bad", None, None)
        .await;

    assert!(result.is_err());
    match result.unwrap_err() {
        IntelApiError::ApiError {
            status,
            request_id,
            error_code,
            error_message,
        } => {
            assert_eq!(status.as_u16(), 400);
            assert_eq!(request_id, "error-req-123");
            assert_eq!(error_code.as_deref(), Some("InvalidParameter"));
            assert_eq!(error_message.as_deref(), Some("PPID format is invalid"));
        }
        _ => panic!("Wrong error type"),
    }
}

#[tokio::test]
async fn test_sgx_tcb_info() {
    let mut server = Server::new_async().await;

    let _m = server
        .mock("GET", "/sgx/certification/v4/tcb")
        .match_query(mockito::Matcher::UrlEncoded(
            "fmspc".into(),
            "00606A6A0000".into(),
        ))
        .with_status(200)
        .with_header("TCB-Info-Issuer-Chain", "sgx-tcb-cert")
        .with_body("{\"tcbInfo\":{\"fmspc\":\"00606A6A0000\"}}")
        .create_async()
        .await;

    let client = create_test_client(&server.url()).await;
    let result = client.get_sgx_tcb_info("00606A6A0000", None, None).await;

    assert!(result.is_ok());
    let resp = result.unwrap();
    assert_eq!(
        resp.tcb_info_json,
        "{\"tcbInfo\":{\"fmspc\":\"00606A6A0000\"}}"
    );
    assert_eq!(resp.issuer_chain, "sgx-tcb-cert");
}